Skip to content

Commit

Permalink
feat: Allow for verifying the sigs of partially signed txs in web3.js (
Browse files Browse the repository at this point in the history
…solana-labs#29249)

* feat: allow for verifying the sigs of partially signed txs

* fix: make comment ab verifying sigs more specific

Co-authored-by: Steven Luscher <[email protected]>

* feat: add tests for partial signed tx verification

* fix: revert lockfile changes

* fix: make tests more modular

* fix: run linter

Co-authored-by: Steven Luscher <[email protected]>
  • Loading branch information
2 people authored and nickfrosty committed Jan 4, 2023
1 parent 17e413b commit 04e0aaf
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 4 deletions.
13 changes: 9 additions & 4 deletions web3.js/src/transaction/legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -726,10 +726,15 @@ export class Transaction {
}

/**
* Verify signatures of a complete, signed Transaction
*/
verifySignatures(): boolean {
return this._verifySignatures(this.serializeMessage(), true);
* Verify signatures of a Transaction
* Optional parameter specifies if we're expecting a fully signed Transaction or a partially signed one.
* If no boolean is provided, we expect a fully signed Transaction by default.
*/
verifySignatures(requireAllSignatures?: boolean): boolean {
return this._verifySignatures(
this.serializeMessage(),
requireAllSignatures === undefined ? true : requireAllSignatures,
);
}

/**
Expand Down
121 changes: 121 additions & 0 deletions web3.js/test/transaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,127 @@ describe('Transaction', () => {
expect(expectedTransaction.signatures).to.have.length(1);
});

describe('partially signed transaction signature verification tests', () => {
const sender = Keypair.fromSeed(Uint8Array.from(Array(32).fill(8))); // Arbitrary known account
const feePayer = Keypair.fromSeed(Uint8Array.from(Array(32).fill(9))); // Arbitrary known account
const fakeKey = Keypair.fromSeed(Uint8Array.from(Array(32).fill(10))); // Arbitrary known account
const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash
const recipient = new PublicKey(
'J3dxNj7nDRRqRRXuEMynDG57DkZK4jYRuv3Garmb1i99',
); // Arbitrary known public key
const transfer = SystemProgram.transfer({
fromPubkey: sender.publicKey,
toPubkey: recipient,
lamports: 49,
});
let expectedTransaction: Transaction;
beforeEach(() => {
expectedTransaction = new Transaction({
blockhash: recentBlockhash,
lastValidBlockHeight: 9999,
}).add(transfer);
// To have 2 required signers we add a feepayer
expectedTransaction.feePayer = feePayer.publicKey;
});

it('verifies for no sigs', () => {
expect(expectedTransaction.signatures).to.have.length(0);

// No extra param should require all sigs, should be false for no sigs
expect(expectedTransaction.verifySignatures()).to.be.false;

// True should require all sigs, should be false for no sigs
expect(expectedTransaction.verifySignatures(true)).to.be.false;

// False should verify only the available sigs, should be true for no sigs
expect(expectedTransaction.verifySignatures(false)).to.be.true;
});

it('verifies for one sig', () => {
// Add one required sig
expectedTransaction.partialSign(sender);

expect(
expectedTransaction.signatures.filter(sig => sig.signature !== null),
).to.have.length(1);

// No extra param should require all sigs, should be false for one missing sig
expect(expectedTransaction.verifySignatures()).to.be.false;

// True should require all sigs, should be false one missing sigs
expect(expectedTransaction.verifySignatures(true)).to.be.false;

// False should verify only the available sigs, should be true one valid sig
expect(expectedTransaction.verifySignatures(false)).to.be.true;
});

it('verifies for all sigs', () => {
// Add all required sigs
expectedTransaction.partialSign(sender);
expectedTransaction.partialSign(feePayer);

expect(
expectedTransaction.signatures.filter(sig => sig.signature !== null),
).to.have.length(2);

// No extra param should require all sigs, should be true for no missing sig
expect(expectedTransaction.verifySignatures()).to.be.true;

// True should require all sigs, should be true for no missing sig
expect(expectedTransaction.verifySignatures(true)).to.be.true;

// False should verify only the available sigs, should be true for no missing sig
expect(expectedTransaction.verifySignatures(false)).to.be.true;
});

it('throws for wrong sig with only one sig present', () => {
// Add one required sigs
expectedTransaction.partialSign(feePayer);

// Add a wrong signature
expectedTransaction.signatures[0].publicKey = fakeKey.publicKey;

// No extra param should require all sigs, should throw for wrong sig
expect(() => expectedTransaction.verifySignatures()).to.throw(
'unknown signer: ' + fakeKey.publicKey.toBase58(),
);

// True should require all sigs, should throw for wrong sig
expect(() => expectedTransaction.verifySignatures(true)).to.throw(
'unknown signer: ' + fakeKey.publicKey.toBase58(),
);

// False should verify only the available sigs, should throw for wrong sig
expect(() => expectedTransaction.verifySignatures(false)).to.throw(
'unknown signer: ' + fakeKey.publicKey.toBase58(),
);
});

it('throws for wrong sig with all sigs present', () => {
// Add all required sigs
expectedTransaction.partialSign(sender);
expectedTransaction.partialSign(feePayer);

// Add a wrong signature
expectedTransaction.signatures[0].publicKey = fakeKey.publicKey;

// No extra param should require all sigs, should throw for wrong sig
expect(() => expectedTransaction.verifySignatures()).to.throw(
'unknown signer: ' + fakeKey.publicKey.toBase58(),
);

// True should require all sigs, should throw for wrong sig
expect(() => expectedTransaction.verifySignatures(true)).to.throw(
'unknown signer: ' + fakeKey.publicKey.toBase58(),
);

// False should verify only the available sigs, should throw for wrong sig
expect(() => expectedTransaction.verifySignatures(false)).to.throw(
'unknown signer: ' + fakeKey.publicKey.toBase58(),
);
});
});

it('deprecated - externally signed stake delegate', () => {
const authority = Keypair.fromSeed(Uint8Array.from(Array(32).fill(1)));
const stake = new PublicKey(2);
Expand Down

0 comments on commit 04e0aaf

Please sign in to comment.