/** * Solana tx wire-format helpers (pure byte ops, no @solana/web3.js). * Wire format: [shortvec sigCount] [sigCount × 64-byte sigs] [message bytes]. * Used to confirm a wallet only ADDED a signature and didn't change the message. */ /** Offset where the message begins, or -1 if the tx bytes are malformed. */ export function solanaMessageOffset(serializedTx: Uint8Array): number { let offset = 0 let sigCount = 0 let shift = 0 let terminated = false // shortvec: little-endian base-128, continuation bit 0x80. while (offset < serializedTx.length) { const byte = serializedTx[offset]! sigCount |= (byte & 0x7f) << shift offset++ if ((byte & 0x80) === 0) { terminated = true break } shift += 7 if (shift >= 32) return -1 } if (!terminated) return -1 const messageStart = offset + sigCount * 64 return messageStart >= serializedTx.length ? -1 : messageStart } /** V0 messages set the high bit (0x80) on the first message byte; legacy don't. */ export function isVersionedTransactionBytes(serializedTx: Uint8Array): boolean { const messageStart = solanaMessageOffset(serializedTx) if (messageStart < 0) return false return (serializedTx[messageStart]! & 0x80) !== 0 } /** * Throw unless `returned` keeps the byte-identical message of `sent` — signing * may only add a signature. Compares the message region only (sigs differ). */ export function assertSameMessage( sent: Uint8Array, returned: Uint8Array ): void { const sentStart = solanaMessageOffset(sent) const returnedStart = solanaMessageOffset(returned) if (sentStart < 0 || returnedStart < 0) { throw new Error('Malformed Solana transaction: cannot locate message bytes') } // Signing fills a pre-allocated slot, so the sig count (hence the offset) // can't change. A different offset = added/dropped signer → reject. if (sentStart !== returnedStart) { throw new Error( 'Signed transaction does not match the request — signature layout changed' ) } const sentMsg = sent.subarray(sentStart) const returnedMsg = returned.subarray(returnedStart) const tampered = sentMsg.length !== returnedMsg.length || sentMsg.some((byte, i) => byte !== returnedMsg[i]) if (tampered) { throw new Error( 'Signed transaction does not match the request — wallet returned a different transaction' ) } }