diff --git a/modules/utxo-core/src/bip322/index.ts b/modules/utxo-core/src/bip322/index.ts index 111eb7bf12..2f0b915bb3 100644 --- a/modules/utxo-core/src/bip322/index.ts +++ b/modules/utxo-core/src/bip322/index.ts @@ -1,2 +1,3 @@ export * from './toSpend'; export * from './toSign'; +export * from './utils'; diff --git a/modules/utxo-core/src/bip322/utils.ts b/modules/utxo-core/src/bip322/utils.ts new file mode 100644 index 0000000000..06409aa065 --- /dev/null +++ b/modules/utxo-core/src/bip322/utils.ts @@ -0,0 +1,30 @@ +import * as utxolib from '@bitgo/utxo-lib'; + +export function addBip322ProofMessage(psbt: utxolib.bitgo.UtxoPsbt, inputIndex: number, message: Buffer): void { + utxolib.bitgo.addProprietaryKeyValuesFromUnknownKeyValues(psbt, 'input', inputIndex, { + key: { + identifier: utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER, + subtype: utxolib.bitgo.ProprietaryKeySubtype.BIP322_MESSAGE, + keydata: Buffer.alloc(0), + }, + value: message, + }); +} + +export function getBip322ProofInputIndex(psbt: utxolib.Psbt): number | undefined { + const res = psbt.data.inputs.flatMap((input, inputIndex) => { + const proprietaryKeyVals = utxolib.bitgo.getPsbtInputProprietaryKeyVals(input, { + identifier: utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER, + subtype: utxolib.bitgo.ProprietaryKeySubtype.BIP322_MESSAGE, + }); + if (proprietaryKeyVals.length > 1) { + throw new Error(`Multiple BIP322 messages found at input index ${inputIndex}`); + } + return proprietaryKeyVals.length === 0 ? [] : [inputIndex]; + }); + return res.length === 0 ? undefined : res[0]; +} + +export function psbtIsBip322Proof(psbt: utxolib.Psbt): boolean { + return getBip322ProofInputIndex(psbt) !== undefined; +} diff --git a/modules/utxo-core/test/bip322/bip322.utils.ts b/modules/utxo-core/test/bip322/bip322.utils.ts new file mode 100644 index 0000000000..97d9b0c1a4 --- /dev/null +++ b/modules/utxo-core/test/bip322/bip322.utils.ts @@ -0,0 +1,18 @@ +import { payments, ECPair } from '@bitgo/utxo-lib'; + +import { buildToSignPsbt, buildToSpendTransaction } from '../../src/bip322'; + +export const BIP322_WIF_FIXTURE = 'L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k'; +export const BIP322_PRV_FIXTURE = ECPair.fromWIF(BIP322_WIF_FIXTURE); +export const BIP322_PAYMENT_P2WPKH_FIXTURE = payments.p2wpkh({ + address: 'bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l', +}); + +export const BIP322_FIXTURE_HELLO_WORLD_TOSPEND_TX = buildToSpendTransaction( + BIP322_PAYMENT_P2WPKH_FIXTURE.output as Buffer, + Buffer.from('Hello World') +); + +export const BIP322_FIXTURE_HELLOW_WORLD_TOSIGN_PSBT = buildToSignPsbt(BIP322_FIXTURE_HELLO_WORLD_TOSPEND_TX, { + witnessScript: BIP322_PAYMENT_P2WPKH_FIXTURE.output as Buffer, +}); diff --git a/modules/utxo-core/test/bip322/toSign.ts b/modules/utxo-core/test/bip322/toSign.ts index 10d13fbf15..2998271313 100644 --- a/modules/utxo-core/test/bip322/toSign.ts +++ b/modules/utxo-core/test/bip322/toSign.ts @@ -1,17 +1,13 @@ import assert from 'assert'; -import { payments, ECPair, Transaction } from '@bitgo/utxo-lib'; +import { Transaction } from '@bitgo/utxo-lib'; import * as bip322 from '../../src/bip322'; +import { BIP322_PAYMENT_P2WPKH_FIXTURE, BIP322_PRV_FIXTURE as prv } from './bip322.utils'; describe('BIP322 toSign', function () { describe('buildToSignPsbt', function () { - const WIF = 'L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k'; - const prv = ECPair.fromWIF(WIF); - const scriptPubKey = payments.p2wpkh({ - address: 'bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l', - }).output as Buffer; - + const scriptPubKey = BIP322_PAYMENT_P2WPKH_FIXTURE.output as Buffer; // Source: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#transaction-hashes const fixtures = [ { diff --git a/modules/utxo-core/test/bip322/toSpend.ts b/modules/utxo-core/test/bip322/toSpend.ts index f6fc030458..3ccd3b03e2 100644 --- a/modules/utxo-core/test/bip322/toSpend.ts +++ b/modules/utxo-core/test/bip322/toSpend.ts @@ -1,9 +1,9 @@ import assert from 'assert'; -import { payments } from '@bitgo/utxo-lib'; - import { buildToSpendTransaction, hashMessageWithTag } from '../../src/bip322'; +import { BIP322_PAYMENT_P2WPKH_FIXTURE } from './bip322.utils'; + describe('to_spend', function () { describe('Message hashing', function () { // Test vectors from BIP322 @@ -31,9 +31,7 @@ describe('to_spend', function () { }); describe('build to_spend transaction', function () { - const scriptPubKey = payments.p2wpkh({ - address: 'bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l', - }).output as Buffer; + const scriptPubKey = BIP322_PAYMENT_P2WPKH_FIXTURE.output as Buffer; // Source: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#transaction-hashes const fixtures = [ diff --git a/modules/utxo-core/test/bip322/utils.ts b/modules/utxo-core/test/bip322/utils.ts new file mode 100644 index 0000000000..190d7a9bc7 --- /dev/null +++ b/modules/utxo-core/test/bip322/utils.ts @@ -0,0 +1,46 @@ +import assert from 'assert'; + +import * as utxolib from '@bitgo/utxo-lib'; + +import { addBip322ProofMessage, getBip322ProofInputIndex, psbtIsBip322Proof } from '../../src/bip322'; + +import { BIP322_FIXTURE_HELLOW_WORLD_TOSIGN_PSBT } from './bip322.utils'; + +describe('BIP322 Proof utils', function () { + it('should add a BIP322 proof message to a PSBT input', function () { + const psbt = utxolib.bitgo.createPsbtFromBuffer( + BIP322_FIXTURE_HELLOW_WORLD_TOSIGN_PSBT.toBuffer(), + utxolib.networks.bitcoin + ); + const inputIndex = 0; + const message = Buffer.from('Hello World'); + addBip322ProofMessage(psbt, inputIndex, message); + + const proprietaryKeyVals = utxolib.bitgo.getPsbtInputProprietaryKeyVals(psbt.data.inputs[inputIndex], { + identifier: utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER, + subtype: utxolib.bitgo.ProprietaryKeySubtype.BIP322_MESSAGE, + }); + + assert.ok(proprietaryKeyVals.length === 1); + assert.ok(proprietaryKeyVals[0].value.equals(message)); + assert.ok(proprietaryKeyVals[0].key.keydata.length === 0); // keydata should be empty + assert.ok(proprietaryKeyVals[0].key.identifier === utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER); + assert.ok(proprietaryKeyVals[0].key.subtype === utxolib.bitgo.ProprietaryKeySubtype.BIP322_MESSAGE); + }); + + it('should return the input index of a BIP322 proof message', function () { + const psbt = utxolib.bitgo.createPsbtFromBuffer( + BIP322_FIXTURE_HELLOW_WORLD_TOSIGN_PSBT.toBuffer(), + utxolib.networks.bitcoin + ); + assert.ok(!psbtIsBip322Proof(psbt)); // initially should not be a BIP322 proof + + const inputIndex = 0; + const message = Buffer.from('Hello World'); + addBip322ProofMessage(psbt, inputIndex, message); + + const resultIndex = getBip322ProofInputIndex(psbt); + assert.strictEqual(resultIndex, inputIndex); + assert.ok(psbtIsBip322Proof(psbt)); + }); +});