Skip to content

Commit 278fa8c

Browse files
authored
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
1 parent f246c3d commit 278fa8c

File tree

6 files changed

+110
-15
lines changed

6 files changed

+110
-15
lines changed

.idea/dictionaries/fuxing.xml

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/jellyfish-transaction/__tests__/tx_signature/sign_input.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,24 @@ describe('sign single input', () => {
149149
.rejects.toThrow('witnessScript required, only P2WPKH can be guessed')
150150
})
151151

152+
it('should fail as isV0P2WPKH cannot match provided prevout ', async () => {
153+
return await expect(TransactionSigner.signInput(transaction, 1, {
154+
prevout: {
155+
script: {
156+
stack: [
157+
OP_CODES.OP_0,
158+
// hash is invalid
159+
OP_CODES.OP_PUSHDATA(Buffer.from('1d0f172a0ecb48aee1be1f2687d2963ae33f71a0', 'hex'), 'little')
160+
]
161+
},
162+
value: new BigNumber('6'),
163+
dct_id: 0x00
164+
},
165+
ellipticPair: keyPair
166+
}, SIGHASH.ALL))
167+
.rejects.toThrow('invalid input option - attempting to sign a mismatch vout and elliptic pair is not allowed')
168+
})
169+
152170
describe('SIGHASH for consistency should err-out as they are not implemented', () => {
153171
it('should err SIGHASH.NONE', async () => {
154172
return await expect(TransactionSigner.signInput(transaction, 1, {

packages/jellyfish-transaction/__tests__/tx_signature/sign_transaction.test.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,37 @@ describe('sign transaction', () => {
6868
expect(signed.lockTime).toBe(0x00000000)
6969
})
7070

71-
describe('validate', () => {
71+
describe('validation', () => {
72+
it('should fail as vin.length == 0', async () => {
73+
const txn: Transaction = {
74+
...transaction,
75+
vin: []
76+
}
77+
return await expect(TransactionSigner.sign(txn, []))
78+
.rejects.toThrow('vin.length = 0 - attempting to sign transaction without vin is not allowed')
79+
})
80+
81+
it('should fail as provided prevout and ellipticPair is mismatched', async () => {
82+
const input = {
83+
prevout: {
84+
script: {
85+
stack: [
86+
OP_CODES.OP_0,
87+
OP_CODES.OP_PUSHDATA(Buffer.from('1d0f172a0ecb48aee1be1f2687d2963ae33f71a0', 'hex'), 'little')
88+
]
89+
},
90+
value: new BigNumber('1000'),
91+
dct_id: 0x00
92+
},
93+
ellipticPair: keyPair
94+
}
95+
return await expect(TransactionSigner.sign(transaction, [input]))
96+
.rejects.toThrow('invalid input option - attempting to sign a mismatch vout and elliptic pair is not allowed')
97+
})
98+
7299
it('should fail as vin.length != inputOptions.length', async () => {
73-
return await expect(TransactionSigner.sign(transaction, [inputOption, inputOption], {
74-
sigHashType: SIGHASH.NONE
75-
})).rejects.toThrow('vin.length and inputOptions.length must match')
100+
return await expect(TransactionSigner.sign(transaction, [inputOption, inputOption]))
101+
.rejects.toThrow('vin.length and inputOptions.length must match')
76102
})
77103

78104
it('should fail if version is different from DeFiTransactionConstants.Version', async () => {

packages/jellyfish-transaction/src/tx_signature.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,44 @@ function hashOutputs (transaction: Transaction, sigHashType: SIGHASH): string {
8181
return dSHA256(buffer.toBuffer()).toString('hex')
8282
}
8383

84+
/**
85+
*
86+
* The witness must consist of exactly 2 items.
87+
* The '0' in scriptPubKey indicates the following push is a version 0 witness program.
88+
* The length of 20 indicates that it is a P2WPKH type.
89+
*
90+
* @param {SignInputOption} signInputOption to check is is V0 P2WPKH
91+
*/
92+
async function isV0P2WPKH (signInputOption: SignInputOption): Promise<boolean> {
93+
const stack = signInputOption.prevout.script.stack
94+
95+
if (stack.length === 2 && stack[1] instanceof OP_PUSHDATA && (stack[1] as OP_PUSHDATA).length() === 20) {
96+
const pubkey: Buffer = await signInputOption.ellipticPair.publicKey()
97+
const pubkeyHashHex = HASH160(pubkey).toString('hex')
98+
const pushDataHex = (stack[1] as OP_PUSHDATA).hex
99+
100+
if (pubkeyHashHex === pushDataHex) {
101+
return true
102+
}
103+
104+
throw new Error('invalid input option - attempting to sign a mismatch vout and elliptic pair is not allowed')
105+
}
106+
107+
return false
108+
}
109+
84110
/**
85111
* If script is not provided, it needs to be guessed
86112
*
87113
* @param {Vin} vin of the script
88114
* @param {SignInputOption} signInputOption to sign the vin
89115
*/
90116
async function getScriptCode (vin: Vin, signInputOption: SignInputOption): Promise<Script> {
91-
const stack = signInputOption.prevout.script.stack
92-
93117
if (signInputOption.witnessScript !== undefined) {
94118
return signInputOption.witnessScript
95119
}
96120

97-
// The witness must consist of exactly 2 items.
98-
// The '0' in scriptPubKey indicates the following push is a version 0 witness program.
99-
// The length of 20 indicates that it is a P2WPKH type.
100-
if (stack.length === 2 && stack[1] instanceof OP_PUSHDATA && (stack[1] as OP_PUSHDATA).length() === 20) {
121+
if (await isV0P2WPKH(signInputOption)) {
101122
const pubkey: Buffer = await signInputOption.ellipticPair.publicKey()
102123
const pubkeyHash = HASH160(pubkey)
103124

@@ -196,6 +217,10 @@ export const TransactionSigner = {
196217
validate (transaction: Transaction, inputOptions: SignInputOption[], option: SignOption) {
197218
const { version = true, lockTime = true } = (option.validate !== undefined) ? option.validate : {}
198219

220+
if (transaction.vin.length === 0) {
221+
throw new Error('vin.length = 0 - attempting to sign transaction without vin is not allowed')
222+
}
223+
199224
if (transaction.vin.length !== inputOptions.length) {
200225
throw new Error('vin.length and inputOptions.length must match')
201226
}

packages/jellyfish-wallet-mnemonic/__tests__/mnemonic/bip32.test.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { MnemonicHdNode, MnemonicHdNodeProvider, mnemonicToSeed, generateMnemoni
22
import BigNumber from 'bignumber.js'
33
import { Transaction, Vout } from '@defichain/jellyfish-transaction'
44
import { OP_CODES } from '@defichain/jellyfish-transaction/dist/script'
5+
import { HASH160 } from '@defichain/jellyfish-crypto'
56

67
const regTestBip32Options = {
78
bip32: {
@@ -80,7 +81,15 @@ describe('24 words: random', () => {
8081
})
8182

8283
it('should sign tx', async () => {
83-
const signed = await node.signTx(transaction, [prevout])
84+
const signed = await node.signTx(transaction, [{
85+
...prevout,
86+
script: {
87+
stack: [
88+
OP_CODES.OP_0,
89+
OP_CODES.OP_PUSHDATA(HASH160(await node.publicKey()), 'little')
90+
]
91+
}
92+
}])
8493

8594
expect(signed.witness.length).toBe(1)
8695
expect(signed.witness[0].scripts.length).toBe(2)
@@ -130,7 +139,15 @@ describe('24 words: abandon x23 art', () => {
130139
})
131140

132141
it('should sign tx', async () => {
133-
const signed = await node.signTx(transaction, [prevout])
142+
const signed = await node.signTx(transaction, [{
143+
...prevout,
144+
script: {
145+
stack: [
146+
OP_CODES.OP_0,
147+
OP_CODES.OP_PUSHDATA(HASH160(await node.publicKey()), 'little')
148+
]
149+
}
150+
}])
134151

135152
expect(signed.witness.length).toBe(1)
136153
expect(signed.witness[0].scripts.length).toBe(2)
@@ -169,7 +186,15 @@ describe('24 words: abandon x23 art', () => {
169186
})
170187

171188
it('should sign tx', async () => {
172-
const signed = await node.signTx(transaction, [prevout])
189+
const signed = await node.signTx(transaction, [{
190+
...prevout,
191+
script: {
192+
stack: [
193+
OP_CODES.OP_0,
194+
OP_CODES.OP_PUSHDATA(HASH160(await node.publicKey()), 'little')
195+
]
196+
}
197+
}])
173198

174199
expect(signed.witness.length).toBe(1)
175200
expect(signed.witness[0].scripts.length).toBe(2)

packages/jellyfish-wallet/__tests__/wallet_hd_node.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@ describe("WalletHdNode: 44'/1129'/0'", () => {
4545
vout: [],
4646
lockTime: 0
4747
}, [])
48-
).rejects.toThrow('option.validate.version = true - trying to sign a txn 0 different from 4 is not supported')
48+
).rejects.toThrow()
4949
})
5050
})

0 commit comments

Comments
 (0)