From 278fa8c1a1a73b385626ad33e9909fb74c2d2162 Mon Sep 17 00:00:00 2001 From: Fuxing Loh <4266087+fuxingloh@users.noreply.github.com> Date: Thu, 6 May 2021 17:03:40 +0800 Subject: [PATCH] txn input validation (length, mismatch prevout) (#190) * added vin.length = 0 - attempting to sign transaction without vin is not allowed * added invalid input option - attempting to sign a mismatch vout and elliptic pair is not allowed * fixed bip32.test.ts to use correct prevout --- .idea/dictionaries/fuxing.xml | 3 +- .../__tests__/tx_signature/sign_input.test.ts | 18 +++++++++ .../tx_signature/sign_transaction.test.ts | 34 +++++++++++++++-- .../jellyfish-transaction/src/tx_signature.ts | 37 ++++++++++++++++--- .../__tests__/mnemonic/bip32.test.ts | 31 ++++++++++++++-- .../__tests__/wallet_hd_node.test.ts | 2 +- 6 files changed, 110 insertions(+), 15 deletions(-) diff --git a/.idea/dictionaries/fuxing.xml b/.idea/dictionaries/fuxing.xml index 2b5e0706a..070bc95cf 100644 --- a/.idea/dictionaries/fuxing.xml +++ b/.idea/dictionaries/fuxing.xml @@ -85,6 +85,7 @@ isvalid iswatchonly iswitness + jingyi jsonrpc keyhash keypoololdest @@ -188,4 +189,4 @@ wtxid - + \ No newline at end of file diff --git a/packages/jellyfish-transaction/__tests__/tx_signature/sign_input.test.ts b/packages/jellyfish-transaction/__tests__/tx_signature/sign_input.test.ts index 70ea6c5b9..b0bd769f0 100644 --- a/packages/jellyfish-transaction/__tests__/tx_signature/sign_input.test.ts +++ b/packages/jellyfish-transaction/__tests__/tx_signature/sign_input.test.ts @@ -149,6 +149,24 @@ describe('sign single input', () => { .rejects.toThrow('witnessScript required, only P2WPKH can be guessed') }) + it('should fail as isV0P2WPKH cannot match provided prevout ', async () => { + return await expect(TransactionSigner.signInput(transaction, 1, { + prevout: { + script: { + stack: [ + OP_CODES.OP_0, + // hash is invalid + OP_CODES.OP_PUSHDATA(Buffer.from('1d0f172a0ecb48aee1be1f2687d2963ae33f71a0', 'hex'), 'little') + ] + }, + value: new BigNumber('6'), + dct_id: 0x00 + }, + ellipticPair: keyPair + }, SIGHASH.ALL)) + .rejects.toThrow('invalid input option - attempting to sign a mismatch vout and elliptic pair is not allowed') + }) + describe('SIGHASH for consistency should err-out as they are not implemented', () => { it('should err SIGHASH.NONE', async () => { return await expect(TransactionSigner.signInput(transaction, 1, { diff --git a/packages/jellyfish-transaction/__tests__/tx_signature/sign_transaction.test.ts b/packages/jellyfish-transaction/__tests__/tx_signature/sign_transaction.test.ts index e745736c4..00adba9ee 100644 --- a/packages/jellyfish-transaction/__tests__/tx_signature/sign_transaction.test.ts +++ b/packages/jellyfish-transaction/__tests__/tx_signature/sign_transaction.test.ts @@ -68,11 +68,37 @@ describe('sign transaction', () => { expect(signed.lockTime).toBe(0x00000000) }) - describe('validate', () => { + describe('validation', () => { + it('should fail as vin.length == 0', async () => { + const txn: Transaction = { + ...transaction, + vin: [] + } + return await expect(TransactionSigner.sign(txn, [])) + .rejects.toThrow('vin.length = 0 - attempting to sign transaction without vin is not allowed') + }) + + it('should fail as provided prevout and ellipticPair is mismatched', async () => { + const input = { + prevout: { + script: { + stack: [ + OP_CODES.OP_0, + OP_CODES.OP_PUSHDATA(Buffer.from('1d0f172a0ecb48aee1be1f2687d2963ae33f71a0', 'hex'), 'little') + ] + }, + value: new BigNumber('1000'), + dct_id: 0x00 + }, + ellipticPair: keyPair + } + return await expect(TransactionSigner.sign(transaction, [input])) + .rejects.toThrow('invalid input option - attempting to sign a mismatch vout and elliptic pair is not allowed') + }) + it('should fail as vin.length != inputOptions.length', async () => { - return await expect(TransactionSigner.sign(transaction, [inputOption, inputOption], { - sigHashType: SIGHASH.NONE - })).rejects.toThrow('vin.length and inputOptions.length must match') + return await expect(TransactionSigner.sign(transaction, [inputOption, inputOption])) + .rejects.toThrow('vin.length and inputOptions.length must match') }) it('should fail if version is different from DeFiTransactionConstants.Version', async () => { diff --git a/packages/jellyfish-transaction/src/tx_signature.ts b/packages/jellyfish-transaction/src/tx_signature.ts index 544ba8426..2e6b47cc3 100644 --- a/packages/jellyfish-transaction/src/tx_signature.ts +++ b/packages/jellyfish-transaction/src/tx_signature.ts @@ -81,6 +81,32 @@ function hashOutputs (transaction: Transaction, sigHashType: SIGHASH): string { return dSHA256(buffer.toBuffer()).toString('hex') } +/** + * + * The witness must consist of exactly 2 items. + * The '0' in scriptPubKey indicates the following push is a version 0 witness program. + * The length of 20 indicates that it is a P2WPKH type. + * + * @param {SignInputOption} signInputOption to check is is V0 P2WPKH + */ +async function isV0P2WPKH (signInputOption: SignInputOption): Promise { + const stack = signInputOption.prevout.script.stack + + if (stack.length === 2 && stack[1] instanceof OP_PUSHDATA && (stack[1] as OP_PUSHDATA).length() === 20) { + const pubkey: Buffer = await signInputOption.ellipticPair.publicKey() + const pubkeyHashHex = HASH160(pubkey).toString('hex') + const pushDataHex = (stack[1] as OP_PUSHDATA).hex + + if (pubkeyHashHex === pushDataHex) { + return true + } + + throw new Error('invalid input option - attempting to sign a mismatch vout and elliptic pair is not allowed') + } + + return false +} + /** * If script is not provided, it needs to be guessed * @@ -88,16 +114,11 @@ function hashOutputs (transaction: Transaction, sigHashType: SIGHASH): string { * @param {SignInputOption} signInputOption to sign the vin */ async function getScriptCode (vin: Vin, signInputOption: SignInputOption): Promise