0

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.

2
  • It seems like the error is in transaction creation, can you give more detail about how does the tx generate in the backend? Also, there is no need to extract the signature from the signed transaction and then append it to the transaction in the backend. signMessage method returns a signed transaction so if the user is your sole signer then directly send this transaction to the network.
    – 0xShuk
    Mar 27 at 22:06
  • Hey @0xShuk Thanks for the answer. The user is not the only signer and I need to register that the transaction was successful so I cannot trust the front end to send it. What do you think could cause that in the transaction? I have updated the question. Thanks!
    – Jinspe
    Mar 27 at 23:21

2 Answers 2

2

In this flow, is it possible that when the user is signing the transaction, their wallet is automatically adding priority fee instructions and signing that version of the transaction? Therefore the user's signature cannot be used afterwards on the serialized transaction previously saved in the database?

4
  • Omg, yes that could be it, do you know how to avoid that? Thanks for the answer
    – Jinspe
    Mar 28 at 16:13
  • quote: '@phantom will automatically calculate and add priority fees to ALL transactions, so long as: - the TX does not have an existing Priority Fee IX - the TX does not already have a signature present - after enhancing the TX, it is still < the byte size limit (1232 bytes)'. So I am adding a priority transaction fee on top, following this guide quicknode.com/guides/solana-development/transactions/… Will see if it does the trick.
    – Jinspe
    Mar 28 at 16:46
  • Update: it worked, fixing the priority fee avoided the wallet to change the transaction by itself. Guess we need to keep a look for news from Solana.
    – Jinspe
    Mar 30 at 13:29
  • Awesome, glad to hear that! Thanks for following up. Could also consider modifying the previously saved serialized transaction to add the priority fee instructions from the user's transaction if your flow allows it.
    – Blocklord
    Mar 31 at 0:33
3

Since your user first signs the transaction and then you sign with a private key in the backend, you may need to use partialSign. You may be getting the "Signature verification failed" error because using sign in the backend might have invalidated the user's original signature.

The answers from this question provides additional context: What is the difference between signing and partial signing a transaction?

I did a bunch more digging, and I think the real difference is that sign updates this.signers on the transaction, while partialSign does not. In practice that means that calling sign invalidates previous signatures, while calling partialSign does not.

1
  • Hey @john.Thank you for your answer. I use tweet Nacl to create the signature in the backend, similar to how it is shown in the Solana cookbook. solanacookbook.com/references/… Then add it to the transaction using addSignature, not the sign method. I can verify that the signature from the user is not valid before adding the backend one. Thank you again for looking at it.
    – Jinspe
    Mar 28 at 8:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.