import { describe, it, expect } from 'vitest' import { solanaMessageOffset, isVersionedTransactionBytes, assertSameMessage } from './solana-message' /** * Build a fake serialized tx: [sigCount (single shortvec byte, <128)] * + sigCount × 64 signature bytes + message bytes. */ function makeTx( sigCount: number, message: number[], sigFill: (slot: number) => number[] = () => new Array(64).fill(0) ): Uint8Array { const sigs: number[] = [] for (let s = 0; s < sigCount; s++) sigs.push(...sigFill(s)) return Uint8Array.from([sigCount, ...sigs, ...message]) } describe('solanaMessageOffset', () => { it('points just past the signature array', () => { expect(solanaMessageOffset(makeTx(1, [0x01, 0x02]))).toBe(1 + 64) expect(solanaMessageOffset(makeTx(2, [0x01]))).toBe(1 + 128) }) it('returns -1 when the shortvec never terminates', () => { expect(solanaMessageOffset(Uint8Array.from([0x80, 0x80, 0x80]))).toBe(-1) }) it('returns -1 when the signatures overrun the buffer', () => { // Claims 2 sigs (128 bytes) but only 10 bytes follow. expect( solanaMessageOffset(Uint8Array.from([0x02, ...new Array(10).fill(0)])) ).toBe(-1) }) it('returns -1 when there is no message after the signatures', () => { // 1 sig (64 bytes), nothing after → offset lands exactly at the end. expect( solanaMessageOffset(Uint8Array.from([0x01, ...new Array(64).fill(0)])) ).toBe(-1) }) }) describe('isVersionedTransactionBytes', () => { it('true when the first message byte sets the version high bit', () => { expect(isVersionedTransactionBytes(makeTx(1, [0x80, 0x00]))).toBe(true) }) it('false for a legacy message', () => { expect(isVersionedTransactionBytes(makeTx(1, [0x01, 0x00]))).toBe(false) }) it('false for malformed bytes', () => { expect(isVersionedTransactionBytes(Uint8Array.from([0x80, 0x80]))).toBe( false ) }) }) describe('assertSameMessage', () => { const message = [0x09, 0x08, 0x07, 0x06] it('passes when only the signature differs (freshly signed tx)', () => { // Relay fee-payer in slot 0 (unchanged), user signs slot 1. const sent = makeTx(2, message, () => new Array(64).fill(0)) const signed = makeTx(2, message, slot => new Array(64).fill(slot === 1 ? 0xff : 0) ) expect(() => assertSameMessage(sent, signed)).not.toThrow() }) it('throws when the signature count changes, even if message bytes match', () => { // returned drops a signature slot (2 → 1) but keeps the same message bytes; // the offset shifts, so a naive message-only compare would wrongly pass. const sent = makeTx(2, message) const returnedFewerSlots = makeTx(1, message) expect(() => assertSameMessage(sent, returnedFewerSlots)).toThrow( 'signature layout changed' ) }) it('throws when message bytes change (tampered recipient/amount)', () => { const sent = makeTx(1, message) const tampered = makeTx(1, [0x09, 0x08, 0x07, 0x05]) // last byte flipped expect(() => assertSameMessage(sent, tampered)).toThrow( 'Signed transaction does not match the request' ) }) it('throws when the message length changes', () => { const sent = makeTx(1, message) const longer = makeTx(1, [...message, 0x00]) expect(() => assertSameMessage(sent, longer)).toThrow( 'Signed transaction does not match the request' ) }) it('throws on malformed input (cannot locate message)', () => { const sent = makeTx(1, message) const malformed = Uint8Array.from([0x80, 0x80]) expect(() => assertSameMessage(sent, malformed)).toThrow( 'Malformed Solana transaction' ) expect(() => assertSameMessage(malformed, sent)).toThrow( 'Malformed Solana transaction' ) }) })