From 9bba4b5c535f55bd0d88803952b18a02b5a7740c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Varga-Somogyi=20=C3=81kos?= Date: Tue, 20 Sep 2022 20:57:11 +0200 Subject: [PATCH 1/4] Implement addSignature in VersionedTransaction --- web3.js/src/transaction/versioned.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/web3.js/src/transaction/versioned.ts b/web3.js/src/transaction/versioned.ts index 0eac6dd0989a06..0c11b41e7b0abc 100644 --- a/web3.js/src/transaction/versioned.ts +++ b/web3.js/src/transaction/versioned.ts @@ -7,6 +7,7 @@ import {SIGNATURE_LENGTH_IN_BYTES} from './constants'; import * as shortvec from '../utils/shortvec-encoding'; import * as Layout from '../layout'; import {sign} from '../utils/ed25519'; +import {PublicKey} from '../publickey'; export type TransactionVersion = 'legacy' | 0; @@ -106,4 +107,19 @@ export class VersionedTransaction { this.signatures[signerIndex] = sign(messageData, signer.secretKey); } } + + addSignature(publicKey: PublicKey, signature: Uint8Array) { + const signerPubkeys = this.message.staticAccountKeys.slice( + 0, + this.message.header.numRequiredSignatures, + ); + const signerIndex = signerPubkeys.findIndex(pubkey => + pubkey.equals(publicKey), + ); + assert( + signerIndex >= 0, + `Cannot add signature with non signer key ${publicKey.toBase58()}`, + ); + this.signatures[signerIndex] = signature; + } } From 9669533282bd2720f4c551d2733c833ee2f93217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Varga-Somogyi=20=C3=81kos?= Date: Tue, 20 Sep 2022 23:15:57 +0200 Subject: [PATCH 2/4] Update asserts --- web3.js/src/transaction/versioned.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web3.js/src/transaction/versioned.ts b/web3.js/src/transaction/versioned.ts index 0c11b41e7b0abc..53e78ade1fff09 100644 --- a/web3.js/src/transaction/versioned.ts +++ b/web3.js/src/transaction/versioned.ts @@ -109,6 +109,7 @@ export class VersionedTransaction { } addSignature(publicKey: PublicKey, signature: Uint8Array) { + assert(signature.byteLength === 64, 'Signature must be 64 bytes long'); const signerPubkeys = this.message.staticAccountKeys.slice( 0, this.message.header.numRequiredSignatures, @@ -118,7 +119,7 @@ export class VersionedTransaction { ); assert( signerIndex >= 0, - `Cannot add signature with non signer key ${publicKey.toBase58()}`, + `Can not add signature; \`${publicKey.toBase58()}\` is not required to sign this transaction`, ); this.signatures[signerIndex] = signature; } From c413b3bfdcfe44acf2e705075ca5ca8ae55f1cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Varga-Somogyi=20=C3=81kos?= Date: Tue, 20 Sep 2022 23:16:31 +0200 Subject: [PATCH 3/4] VersionedTransaction.addSignature test --- web3.js/test/transaction.test.ts | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/web3.js/test/transaction.test.ts b/web3.js/test/transaction.test.ts index 9786384a2c2783..28231edd6a7ae9 100644 --- a/web3.js/test/transaction.test.ts +++ b/web3.js/test/transaction.test.ts @@ -8,6 +8,7 @@ import {PublicKey} from '../src/publickey'; import { Transaction, TransactionInstruction, + TransactionMessage, VersionedTransaction, } from '../src/transaction'; import {StakeProgram, SystemProgram} from '../src/programs'; @@ -962,4 +963,48 @@ describe('Transaction', () => { t1.partialSign(signer); t1.serialize(); }); + + it('externally signed versioned transaction', () => { + const signer = Keypair.generate(); + const invalidSigner = Keypair.generate(); + + const recentBlockhash = new PublicKey(3).toBuffer(); + + const message = new TransactionMessage({ + payerKey: signer.publicKey, + instructions: [ + new TransactionInstruction({ + data: Buffer.from('Hello!'), + keys: [ + { + pubkey: signer.publicKey, + isSigner: true, + isWritable: true, + }, + ], + programId: new PublicKey( + 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr', + ), + }), + ], + recentBlockhash: bs58.encode(recentBlockhash), + }); + + const transaction = new VersionedTransaction(message.compileToV0Message()); + + const signature = sign(transaction.message.serialize(), signer.secretKey); + + transaction.addSignature(signer.publicKey, signature); + + expect(transaction.signatures).to.have.length(1); + expect(transaction.signatures[0]).to.eq(signature); + expect(() => { + transaction.addSignature(signer.publicKey, new Uint8Array(32)); + }).to.throw('Signature must be 64 bytes long'); + expect(() => { + transaction.addSignature(invalidSigner.publicKey, new Uint8Array(64)); + }).to.throw( + `Can not add signature; \`${invalidSigner.publicKey.toBase58()}\` is not required to sign this transaction`, + ); + }); }); From 76012075f4a4d331bc1e2503649111b8b9c1f880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Varga-Somogyi=20=C3=81kos?= Date: Tue, 20 Sep 2022 23:54:36 +0200 Subject: [PATCH 4/4] Update VersionedTransaction tests --- web3.js/test/transaction.test.ts | 104 ++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 37 deletions(-) diff --git a/web3.js/test/transaction.test.ts b/web3.js/test/transaction.test.ts index 28231edd6a7ae9..6fec2cd8e14af8 100644 --- a/web3.js/test/transaction.test.ts +++ b/web3.js/test/transaction.test.ts @@ -861,26 +861,6 @@ describe('Transaction', () => { expect(tx.verifySignatures()).to.be.true; }); - it('deserializes versioned transactions', () => { - const serializedVersionedTx = Buffer.from( - 'AdTIDASR42TgVuXKkd7mJKk373J3LPVp85eyKMVcrboo9KTY8/vm6N/Cv0NiHqk2I8iYw6VX5ZaBKG8z' + - '9l1XjwiAAQACA+6qNbqfjaIENwt9GzEK/ENiB/ijGwluzBUmQ9xlTAMcCaS0ctnyxTcXXlJr7u2qtnaM' + - 'gIAO2/c7RBD0ipHWUcEDBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAJbI7VNs6MzREUlnzRaJ' + - 'pBKP8QQoDn2dWQvD0KIgHFDiAwIACQAgoQcAAAAAAAIABQEAAAQAATYPBwAKBDIBAyQWIw0oCxIdCA4i' + - 'JzQRKwUZHxceHCohMBUJJiwpMxAaGC0TLhQxGyAMBiU2NS8VDgAAAADuAgAAAAAAAAIAAAAAAAAAAdGCT' + - 'Qiq5yw3+3m1sPoRNj0GtUNNs0FIMocxzt3zuoSZHQABAwQFBwgLDA8RFBcYGhwdHh8iIyUnKiwtLi8yF' + - 'wIGCQoNDhASExUWGRsgISQmKCkrMDEz', - 'base64', - ); - - expect(() => Transaction.from(serializedVersionedTx)).to.throw( - 'Versioned messages must be deserialized with VersionedMessage.deserialize()', - ); - - const versionedTx = VersionedTransaction.deserialize(serializedVersionedTx); - expect(versionedTx.message.version).to.eq(0); - }); - it('can serialize, deserialize, and reserialize with a partial signer', () => { const signer = Keypair.generate(); const acc0Writable = Keypair.generate(); @@ -963,24 +943,57 @@ describe('Transaction', () => { t1.partialSign(signer); t1.serialize(); }); +}); - it('externally signed versioned transaction', () => { - const signer = Keypair.generate(); - const invalidSigner = Keypair.generate(); +describe('VersionedTransaction', () => { + it('deserializes versioned transactions', () => { + const serializedVersionedTx = Buffer.from( + 'AdTIDASR42TgVuXKkd7mJKk373J3LPVp85eyKMVcrboo9KTY8/vm6N/Cv0NiHqk2I8iYw6VX5ZaBKG8z' + + '9l1XjwiAAQACA+6qNbqfjaIENwt9GzEK/ENiB/ijGwluzBUmQ9xlTAMcCaS0ctnyxTcXXlJr7u2qtnaM' + + 'gIAO2/c7RBD0ipHWUcEDBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAJbI7VNs6MzREUlnzRaJ' + + 'pBKP8QQoDn2dWQvD0KIgHFDiAwIACQAgoQcAAAAAAAIABQEAAAQAATYPBwAKBDIBAyQWIw0oCxIdCA4i' + + 'JzQRKwUZHxceHCohMBUJJiwpMxAaGC0TLhQxGyAMBiU2NS8VDgAAAADuAgAAAAAAAAIAAAAAAAAAAdGCT' + + 'Qiq5yw3+3m1sPoRNj0GtUNNs0FIMocxzt3zuoSZHQABAwQFBwgLDA8RFBcYGhwdHh8iIyUnKiwtLi8yF' + + 'wIGCQoNDhASExUWGRsgISQmKCkrMDEz', + 'base64', + ); + + expect(() => Transaction.from(serializedVersionedTx)).to.throw( + 'Versioned messages must be deserialized with VersionedMessage.deserialize()', + ); + + const versionedTx = VersionedTransaction.deserialize(serializedVersionedTx); + expect(versionedTx.message.version).to.eq(0); + }); + + describe('addSignature', () => { + const signer1 = Keypair.generate(); + const signer2 = Keypair.generate(); + const signer3 = Keypair.generate(); const recentBlockhash = new PublicKey(3).toBuffer(); const message = new TransactionMessage({ - payerKey: signer.publicKey, + payerKey: signer1.publicKey, instructions: [ new TransactionInstruction({ data: Buffer.from('Hello!'), keys: [ { - pubkey: signer.publicKey, + pubkey: signer1.publicKey, + isSigner: true, + isWritable: true, + }, + { + pubkey: signer2.publicKey, isSigner: true, isWritable: true, }, + { + pubkey: signer3.publicKey, + isSigner: false, + isWritable: false, + }, ], programId: new PublicKey( 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr', @@ -992,19 +1005,36 @@ describe('Transaction', () => { const transaction = new VersionedTransaction(message.compileToV0Message()); - const signature = sign(transaction.message.serialize(), signer.secretKey); + it('appends externally generated signatures at correct indexes', () => { + const signature1 = sign( + transaction.message.serialize(), + signer1.secretKey, + ); + const signature2 = sign( + transaction.message.serialize(), + signer2.secretKey, + ); - transaction.addSignature(signer.publicKey, signature); + transaction.addSignature(signer2.publicKey, signature2); + transaction.addSignature(signer1.publicKey, signature1); - expect(transaction.signatures).to.have.length(1); - expect(transaction.signatures[0]).to.eq(signature); - expect(() => { - transaction.addSignature(signer.publicKey, new Uint8Array(32)); - }).to.throw('Signature must be 64 bytes long'); - expect(() => { - transaction.addSignature(invalidSigner.publicKey, new Uint8Array(64)); - }).to.throw( - `Can not add signature; \`${invalidSigner.publicKey.toBase58()}\` is not required to sign this transaction`, - ); + expect(transaction.signatures).to.have.length(2); + expect(transaction.signatures[0]).to.eq(signature1); + expect(transaction.signatures[1]).to.eq(signature2); + }); + + it('fatals when the signature is the wrong length', () => { + expect(() => { + transaction.addSignature(signer1.publicKey, new Uint8Array(32)); + }).to.throw('Signature must be 64 bytes long'); + }); + + it('fatals when adding a signature for a public key that has not been marked as a signer', () => { + expect(() => { + transaction.addSignature(signer3.publicKey, new Uint8Array(64)); + }).to.throw( + `Can not add signature; \`${signer3.publicKey.toBase58()}\` is not required to sign this transaction`, + ); + }); }); });