Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions modules/utxo-core/src/bip322/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,47 @@ export function getBip322ProofMessageAtIndex(psbt: utxolib.Psbt, inputIndex: num
}
return Buffer.from(proprietaryKeyVals[0].value);
}

/**
* checks if the transaction contains a BIP322 proof
* does not check if the proof is valid
* Source: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#user-content-Full
* @params tx The transaction or the PSBT to check
* @returns boolean
*/
export function isBip322ProofCheck(tx: utxolib.bitgo.UtxoPsbt | utxolib.bitgo.UtxoTransaction<bigint>): boolean {
if (tx instanceof utxolib.bitgo.UtxoPsbt) {
if (tx.version !== 0 || tx.locktime !== 0 || tx.data.outputs.length !== 1) {
return false;
}
const output = tx.txOutputs[0];
if (output.script.toString('hex') !== '6a' || output.value !== 0n) {
return false;
}

// Return true if there is a message encoded in every input in the proprietary field
return tx.data.inputs.every((input, index) => getBip322ProofMessageAtIndex(tx, index) !== undefined);
} else if (tx instanceof utxolib.bitgo.UtxoTransaction) {
if (tx.version !== 0 || tx.locktime !== 0 || tx.outs.length !== 1) {
return false;
}
if (tx.outs.length !== 1) {
return false;
}
const output = tx.outs[0];
// check that the only output is an OP_RETURN with 0 value
if (output.script.toString('hex') !== '6a' || output.value !== 0n) {
return false;
}

for (const input of tx.ins) {
if (input.index !== 0 || input.sequence !== 0) {
return false;
}
}

return true;
} else {
throw new Error('Unsupported transaction type for BIP322 proof check');
}
}
48 changes: 47 additions & 1 deletion modules/utxo-core/test/bip322/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import assert from 'assert';

import * as utxolib from '@bitgo/utxo-lib';

import { getBip322ProofMessageAtIndex } from '../../src/bip322';
import { getBip322ProofMessageAtIndex, isBip322ProofCheck } from '../../src/bip322';

import { BIP322_FIXTURE_HELLO_WORLD_TOSIGN_PSBT } from './bip322.utils';

Expand Down Expand Up @@ -35,4 +35,50 @@ describe('BIP322 Proof utils', function () {
assert.ok(messageBuffer, 'Message buffer should not be undefined');
assert.deepStrictEqual(messageBuffer.toString('utf-8'), 'Hello World', 'Message does not match expected value');
});

describe('isBip322ProofCheck', function () {
it('should work for PSBTs', function () {
const psbt = utxolib.bitgo.createPsbtFromBuffer(
BIP322_FIXTURE_HELLO_WORLD_TOSIGN_PSBT.toBuffer(),
utxolib.networks.bitcoin
);
assert.ok(isBip322ProofCheck(psbt), 'Expected PSBT to be a valid BIP322 proof');
assert.deepEqual(
isBip322ProofCheck(
utxolib.testutil.constructPsbt(
[{ scriptType: 'taprootKeyPathSpend', value: BigInt(1000) }],
[{ scriptType: 'p2sh', value: BigInt(900) }],
utxolib.networks.bitcoin,
utxolib.testutil.getDefaultWalletKeys(),
'unsigned'
)
),
false
);
});

it('should work for Transactions', function () {
const psbt = utxolib.bitgo.createPsbtFromBuffer(
BIP322_FIXTURE_HELLO_WORLD_TOSIGN_PSBT.toBuffer(),
utxolib.networks.bitcoin
);
// Cannot extract the transaction because it has no signatures
const tx = psbt.getUnsignedTx();
assert.ok(isBip322ProofCheck(tx), 'Expected Transaction to be a valid BIP322 proof');
assert.deepEqual(
isBip322ProofCheck(
utxolib.testutil
.constructPsbt(
[{ scriptType: 'taprootKeyPathSpend', value: BigInt(1000) }],
[{ scriptType: 'p2sh', value: BigInt(900) }],
utxolib.networks.bitcoin,
utxolib.testutil.getDefaultWalletKeys(),
'unsigned'
)
.getUnsignedTx()
),
false
);
});
});
});