we are recently been having some issues with our transactions on Solana. We serialize the transaction, send it to the user, deserialize it, make the user sign, extract the signature, send back the signature, assemble the transaction with the signature, and submit it to the network. It was working well but in the past 4 days most transactions failed, the error is:
Signature verification failed
at Transaction.serialize
We use the offline transaction sign method.
Here is the flow: a. User sends a request to the backend to generate a transaction for which he is the fee payer. We generate the transaction and do
transaction.serializeMessage().toString('base64');
Saving the serialized transaction in DB and sending it to the user
b. The user gets the serialized transaction then we do:
const recoveredTx = Transaction.populate(Message.from( Buffer.from(encodedB64Transaction, 'base64')));
// signTransaction from Solana wallet adapter
const signedTx = await wallet.signTransaction(recoveredTx );
const userSignatureBuffer = signedTx.signatures.find(
(sign) => sign.publicKey.toString() === userPubKey && sign.signature
);
const encodedSignature = bs58.encode(userSignatureBuffer)
c. We send the encodeSignature to the backend and get the serialized transaction previously saved in the database
const decodedSignature = base58.decode(encodedSignature);
const recoveredTx = Transaction.populate(
Message.from(Buffer.from(encodedB64Transaction, 'base64'))
);
recoveredTx.addSignature(signerPublickey, Buffer.from(decodedSignature));
d. Send to Solana network:
await solanaConnection.sendRawTransaction(
transaction.serialize(),
{ skipPreflight: true }
);
This is where it fails, I have looked, logged the signatures, and verified them using tweetnacl, they are indeed incorrect.
My suspicion is that:
transaction.serializeMessage().toString('base64');
Transaction.populate(Message.from( Buffer.from(encodedB64Transaction, 'base64')));
is not consistent, making the user sign a different transaction.
Does anyone know what is the issue? Is the serialization/deserialization the issue? The odd part is that this process works sometimes and used to work all the time. Thanks
Edit:
The user is not the only signer we also sign with a private key in the backend.
const privateKeySignature = tweetnacl.sign.detached(
serializedTransaction,
privateKey
);
recoveredTx.addSignature(publicKey, Buffer.from(privateKeySignature ));
We can see that this signature is correct using the following:
const isSignatureCorrect = tweetnacl.sign.detached.verify(
Buffer.from(encodedB64Transaction, 'base64'),
base58.decode(signature),
publicKey.toBytes() );
but the signature from the user is not correct.
For the transaction generation, those are basic spl token transfers. We get the token addresses of the user using:
solanaConnection.getParsedProgramAccounts(
TOKEN_PROGRAM_ID,
{
filters: [
{
dataSize: 165, // number of bytes
},
{
memcmp: {
offset: 32,
bytes: walletPublicKey.toString(),
},
},
],
}
);
then use @solana/spl-token
to createAssociatedTokenAccountInstruction
if the user doesn't have a token account then createTransferInstruction
Maybe the token account is to blame? Thanks again.