From d9a271742f7c6c5607c0e21fdb545161781615c4 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Tue, 3 Mar 2020 13:39:00 -0700 Subject: [PATCH] feat: add transaction signature verification --- web3.js/flow-typed/tweetnacl.js | 10 ++- web3.js/src/transaction.js | 16 +++++ web3.js/test/get-confirmed-block.test.js | 81 ++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 web3.js/test/get-confirmed-block.test.js diff --git a/web3.js/flow-typed/tweetnacl.js b/web3.js/flow-typed/tweetnacl.js index a797309b5c..552b47a8a6 100644 --- a/web3.js/flow-typed/tweetnacl.js +++ b/web3.js/flow-typed/tweetnacl.js @@ -8,13 +8,17 @@ declare module "tweetnacl" { (): KeyPair, fromSecretKey(secretKey: Buffer): KeyPair, fromSeed(seed: Uint8Array): KeyPair, - }; + + declare type DetachedFunc = { + (text: Buffer, secretKey: Buffer): Buffer, + verify(message: Buffer, signature: Buffer|null, publicKey: Buffer): bool, + }; + declare module.exports: { sign: { keyPair: KeypairFunc; - detached(text: Buffer, secretKey: Buffer): Buffer; + detached: DetachedFunc; }; }; } - diff --git a/web3.js/src/transaction.js b/web3.js/src/transaction.js index 7821ec2372..b63b8012a9 100644 --- a/web3.js/src/transaction.js +++ b/web3.js/src/transaction.js @@ -411,6 +411,22 @@ export class Transaction { this.signatures[index].signature = Buffer.from(signature); } + /** + * Verify signatures of a complete, signed Transaction + */ + verifySignatures(): boolean { + let verified = true; + const signData = this._getSignData(); + for (const {signature, publicKey} of this.signatures) { + if ( + !nacl.sign.detached.verify(signData, signature, publicKey.toBuffer()) + ) { + verified = false; + } + } + return verified; + } + /** * Serialize the Transaction in the wire format. * diff --git a/web3.js/test/get-confirmed-block.test.js b/web3.js/test/get-confirmed-block.test.js new file mode 100644 index 0000000000..55be8e5e38 --- /dev/null +++ b/web3.js/test/get-confirmed-block.test.js @@ -0,0 +1,81 @@ +// @flow +import {Account} from '../src/account'; +import {SystemProgram} from '../src/system-program'; +import {Transaction} from '../src/transaction'; + +test('verify getConfirmedBlock', () => { + const account0 = new Account(); + const account1 = new Account(); + const account2 = new Account(); + const account3 = new Account(); + const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash + + // Create a couple signed transactions + const transfer0 = SystemProgram.transfer( + account0.publicKey, + account1.publicKey, + 123, + ); + + const transaction0 = new Transaction({recentBlockhash}).add(transfer0); + transaction0.sign(account0); + const transfer1 = SystemProgram.transfer( + account2.publicKey, + account3.publicKey, + 456, + ); + + let transaction1 = new Transaction({recentBlockhash}).add(transfer1); + transaction1.sign(account2); + + // Build ConfirmedBlock, with dummy data for blockhashes, balances + const confirmedBlock = { + blockhash: recentBlockhash, + previousBlockhash: recentBlockhash, + transactions: [ + { + transaction: transaction0, + meta: { + fee: 0, + preBalances: [100000, 100000, 1, 1, 1], + postBalances: [99877, 100123, 1, 1, 1], + status: {Ok: 'null'}, + }, + }, + { + transaction: transaction1, + meta: { + fee: 0, + preBalances: [100000, 100000, 1, 1, 1], + postBalances: [99544, 100456, 1, 1, 1], + status: {Ok: 'null'}, + }, + }, + ], + rewards: [], + }; + + // Verify signatures in ConfirmedBlock + for (const transactionWithMeta of confirmedBlock.transactions) { + expect(transactionWithMeta.transaction.verifySignatures()).toBe(true); + } + + const bogusSignature = { + signature: Buffer.alloc(64, 9), + publicKey: account2.publicKey, + }; + transaction1.signatures[0] = bogusSignature; + + let badConfirmedBlock = confirmedBlock; + badConfirmedBlock.transactions[1].transaction = transaction1; + + // Verify signatures in ConfirmedBlock + const verifications = badConfirmedBlock.transactions.map( + transactionWithMeta => transactionWithMeta.transaction.verifySignatures(), + ); + expect( + verifications.reduce( + (accumulator, currentValue) => accumulator && currentValue, + ), + ).toBe(false); +});