Skip to content

Commit

Permalink
txn input validation (length, mismatch prevout) (#190)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
fuxingloh authored May 6, 2021
1 parent f246c3d commit 278fa8c
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 15 deletions.
3 changes: 2 additions & 1 deletion .idea/dictionaries/fuxing.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
37 changes: 31 additions & 6 deletions packages/jellyfish-transaction/src/tx_signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,23 +81,44 @@ 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<boolean> {
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
*
* @param {Vin} vin of the script
* @param {SignInputOption} signInputOption to sign the vin
*/
async function getScriptCode (vin: Vin, signInputOption: SignInputOption): Promise<Script> {
const stack = signInputOption.prevout.script.stack

if (signInputOption.witnessScript !== undefined) {
return signInputOption.witnessScript
}

// 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.
if (stack.length === 2 && stack[1] instanceof OP_PUSHDATA && (stack[1] as OP_PUSHDATA).length() === 20) {
if (await isV0P2WPKH(signInputOption)) {
const pubkey: Buffer = await signInputOption.ellipticPair.publicKey()
const pubkeyHash = HASH160(pubkey)

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

if (transaction.vin.length === 0) {
throw new Error('vin.length = 0 - attempting to sign transaction without vin is not allowed')
}

if (transaction.vin.length !== inputOptions.length) {
throw new Error('vin.length and inputOptions.length must match')
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { MnemonicHdNode, MnemonicHdNodeProvider, mnemonicToSeed, generateMnemoni
import BigNumber from 'bignumber.js'
import { Transaction, Vout } from '@defichain/jellyfish-transaction'
import { OP_CODES } from '@defichain/jellyfish-transaction/dist/script'
import { HASH160 } from '@defichain/jellyfish-crypto'

const regTestBip32Options = {
bip32: {
Expand Down Expand Up @@ -80,7 +81,15 @@ describe('24 words: random', () => {
})

it('should sign tx', async () => {
const signed = await node.signTx(transaction, [prevout])
const signed = await node.signTx(transaction, [{
...prevout,
script: {
stack: [
OP_CODES.OP_0,
OP_CODES.OP_PUSHDATA(HASH160(await node.publicKey()), 'little')
]
}
}])

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

it('should sign tx', async () => {
const signed = await node.signTx(transaction, [prevout])
const signed = await node.signTx(transaction, [{
...prevout,
script: {
stack: [
OP_CODES.OP_0,
OP_CODES.OP_PUSHDATA(HASH160(await node.publicKey()), 'little')
]
}
}])

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

it('should sign tx', async () => {
const signed = await node.signTx(transaction, [prevout])
const signed = await node.signTx(transaction, [{
...prevout,
script: {
stack: [
OP_CODES.OP_0,
OP_CODES.OP_PUSHDATA(HASH160(await node.publicKey()), 'little')
]
}
}])

expect(signed.witness.length).toBe(1)
expect(signed.witness[0].scripts.length).toBe(2)
Expand Down
2 changes: 1 addition & 1 deletion packages/jellyfish-wallet/__tests__/wallet_hd_node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ describe("WalletHdNode: 44'/1129'/0'", () => {
vout: [],
lockTime: 0
}, [])
).rejects.toThrow('option.validate.version = true - trying to sign a txn 0 different from 4 is not supported')
).rejects.toThrow()
})
})

0 comments on commit 278fa8c

Please sign in to comment.