Skip to content

Commit

Permalink
feat: implement addSignature in VersionedTransaction (#27945)
Browse files Browse the repository at this point in the history
* Implement addSignature in VersionedTransaction

* Update asserts

* VersionedTransaction.addSignature test

* Update VersionedTransaction tests
  • Loading branch information
vsakos committed Sep 22, 2022
1 parent e1a49f7 commit e15a5c0
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 20 deletions.
17 changes: 17 additions & 0 deletions web3.js/src/transaction/versioned.ts
Expand Up @@ -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;

Expand Down Expand Up @@ -106,4 +107,20 @@ export class VersionedTransaction {
this.signatures[signerIndex] = sign(messageData, signer.secretKey);
}
}

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,
);
const signerIndex = signerPubkeys.findIndex(pubkey =>
pubkey.equals(publicKey),
);
assert(
signerIndex >= 0,
`Can not add signature; \`${publicKey.toBase58()}\` is not required to sign this transaction`,
);
this.signatures[signerIndex] = signature;
}
}
115 changes: 95 additions & 20 deletions web3.js/test/transaction.test.ts
Expand Up @@ -8,6 +8,7 @@ import {PublicKey} from '../src/publickey';
import {
Transaction,
TransactionInstruction,
TransactionMessage,
VersionedTransaction,
} from '../src/transaction';
import {StakeProgram, SystemProgram} from '../src/programs';
Expand Down Expand Up @@ -860,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();
Expand Down Expand Up @@ -963,3 +944,97 @@ describe('Transaction', () => {
t1.serialize();
});
});

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: signer1.publicKey,
instructions: [
new TransactionInstruction({
data: Buffer.from('Hello!'),
keys: [
{
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',
),
}),
],
recentBlockhash: bs58.encode(recentBlockhash),
});

const transaction = new VersionedTransaction(message.compileToV0Message());

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(signer2.publicKey, signature2);
transaction.addSignature(signer1.publicKey, signature1);

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`,
);
});
});
});

0 comments on commit e15a5c0

Please sign in to comment.