Skip to content

Commit b9af0c1

Browse files
Merge pull request #6603 from BitGo/BTC-2360
feat(utxo-core): implement BIP322 to_spend transaction builder
2 parents 6e2bb2c + 1d989a5 commit b9af0c1

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

modules/utxo-core/src/bip322/toSpend.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Hash } from 'fast-sha256';
2+
import { Psbt } from '@bitgo/utxo-lib';
23

34
export const BIP322_TAG = 'BIP0322-signed-message';
45

@@ -22,3 +23,42 @@ export function hashMessageWithTag(message: string | Buffer, tag = BIP322_TAG):
2223
const messageHash = messageHasher.digest();
2324
return Buffer.from(messageHash);
2425
}
26+
27+
/**
28+
* Build a BIP322 "to spend" transaction
29+
* Source: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#full
30+
*
31+
* @param {Buffer} scriptPubKey - The scriptPubKey to use for the output
32+
* @param {string | Buffer} message - The message to include in the transaction
33+
* @param {Buffer} [tag=BIP322_TAG] - The tag to use for hashing, defaults to BIP322_TAG.
34+
* @returns {string} - The hex representation of the constructed transaction
35+
*/
36+
export function buildToSpendTransaction(scriptPubKey: Buffer, message: string | Buffer, tag = BIP322_TAG): string {
37+
// Create PSBT object for constructing the transaction
38+
const psbt = new Psbt();
39+
// Set default value for nVersion and nLockTime
40+
psbt.setVersion(0); // nVersion = 0
41+
psbt.setLocktime(0); // nLockTime = 0
42+
// Compute the message hash - SHA256(SHA256(tag) || SHA256(tag) || message)
43+
const messageHash = hashMessageWithTag(message, tag);
44+
// Construct the scriptSig - OP_0 PUSH32[ message_hash ]
45+
const scriptSigPartOne = new Uint8Array([0x00, 0x20]); // OP_0 PUSH32
46+
const scriptSig = new Uint8Array(scriptSigPartOne.length + messageHash.length);
47+
scriptSig.set(scriptSigPartOne);
48+
scriptSig.set(messageHash, scriptSigPartOne.length);
49+
// Set the input
50+
psbt.addInput({
51+
hash: '0'.repeat(64), // vin[0].prevout.hash = 0000...000
52+
index: 0xffffffff, // vin[0].prevout.n = 0xFFFFFFFF
53+
sequence: 0, // vin[0].nSequence = 0
54+
finalScriptSig: Buffer.from(scriptSig), // vin[0].scriptSig = OP_0 PUSH32[ message_hash ]
55+
witnessScript: Buffer.from([]), // vin[0].scriptWitness = []
56+
});
57+
// Set the output
58+
psbt.addOutput({
59+
value: BigInt(0), // vout[0].nValue = 0
60+
script: scriptPubKey, // vout[0].scriptPubKey = message_challenge
61+
});
62+
// Return transaction
63+
return psbt.extractTransaction().toHex();
64+
}

modules/utxo-core/test/bip322/toSpend.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import assert from 'assert';
22

3-
import { hashMessageWithTag } from '../../src/bip322';
3+
import { payments, Transaction } from '@bitgo/utxo-lib';
4+
5+
import { buildToSpendTransaction, hashMessageWithTag } from '../../src/bip322';
46

57
describe('to_spend', function () {
68
describe('Message hashing', function () {
@@ -27,4 +29,30 @@ describe('to_spend', function () {
2729
});
2830
});
2931
});
32+
33+
describe('build to_spend transaction', function () {
34+
const scriptPubKey = payments.p2wpkh({
35+
address: 'bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l',
36+
}).output as Buffer;
37+
38+
// Source: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#transaction-hashes
39+
const fixtures = [
40+
{
41+
message: '',
42+
txid: 'c5680aa69bb8d860bf82d4e9cd3504b55dde018de765a91bb566283c545a99a7',
43+
},
44+
{
45+
message: 'Hello World',
46+
txid: 'b79d196740ad5217771c1098fc4a4b51e0535c32236c71f1ea4d61a2d603352b',
47+
},
48+
];
49+
50+
fixtures.forEach(({ message, txid }) => {
51+
it(`should build a to_spend transaction for message "${message}"`, function () {
52+
const result = buildToSpendTransaction(scriptPubKey, Buffer.from(message));
53+
const computedTxid = Transaction.fromHex(result).getId();
54+
assert.strictEqual(computedTxid, txid, `Transaction ID for message "${message}" does not match expected value`);
55+
});
56+
});
57+
});
3058
});

0 commit comments

Comments
 (0)