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