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
40 changes: 40 additions & 0 deletions modules/utxo-core/src/bip322/toSpend.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Hash } from 'fast-sha256';
import { Psbt } from '@bitgo/utxo-lib';

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

Expand All @@ -22,3 +23,42 @@ export function hashMessageWithTag(message: string | Buffer, tag = BIP322_TAG):
const messageHash = messageHasher.digest();
return Buffer.from(messageHash);
}

/**
* Build a BIP322 "to spend" transaction
* Source: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#full
*
* @param {Buffer} scriptPubKey - The scriptPubKey to use for the output
* @param {string | Buffer} message - The message to include in the transaction
* @param {Buffer} [tag=BIP322_TAG] - The tag to use for hashing, defaults to BIP322_TAG.
* @returns {string} - The hex representation of the constructed transaction
*/
export function buildToSpendTransaction(scriptPubKey: Buffer, message: string | Buffer, tag = BIP322_TAG): string {
// Create PSBT object for constructing the transaction
const psbt = new Psbt();
// Set default value for nVersion and nLockTime
psbt.setVersion(0); // nVersion = 0
psbt.setLocktime(0); // nLockTime = 0
// Compute the message hash - SHA256(SHA256(tag) || SHA256(tag) || message)
const messageHash = hashMessageWithTag(message, tag);
// Construct the scriptSig - OP_0 PUSH32[ message_hash ]
const scriptSigPartOne = new Uint8Array([0x00, 0x20]); // OP_0 PUSH32
const scriptSig = new Uint8Array(scriptSigPartOne.length + messageHash.length);
scriptSig.set(scriptSigPartOne);
scriptSig.set(messageHash, scriptSigPartOne.length);
// Set the input
psbt.addInput({
hash: '0'.repeat(64), // vin[0].prevout.hash = 0000...000
index: 0xffffffff, // vin[0].prevout.n = 0xFFFFFFFF
sequence: 0, // vin[0].nSequence = 0
finalScriptSig: Buffer.from(scriptSig), // vin[0].scriptSig = OP_0 PUSH32[ message_hash ]
witnessScript: Buffer.from([]), // vin[0].scriptWitness = []
});
// Set the output
psbt.addOutput({
value: BigInt(0), // vout[0].nValue = 0
script: scriptPubKey, // vout[0].scriptPubKey = message_challenge
});
// Return transaction
return psbt.extractTransaction().toHex();
}
30 changes: 29 additions & 1 deletion modules/utxo-core/test/bip322/toSpend.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import assert from 'assert';

import { hashMessageWithTag } from '../../src/bip322';
import { payments, Transaction } from '@bitgo/utxo-lib';

import { buildToSpendTransaction, hashMessageWithTag } from '../../src/bip322';

describe('to_spend', function () {
describe('Message hashing', function () {
Expand All @@ -27,4 +29,30 @@ describe('to_spend', function () {
});
});
});

describe('build to_spend transaction', function () {
const scriptPubKey = payments.p2wpkh({
address: 'bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l',
}).output as Buffer;

// Source: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#transaction-hashes
const fixtures = [
{
message: '',
txid: 'c5680aa69bb8d860bf82d4e9cd3504b55dde018de765a91bb566283c545a99a7',
},
{
message: 'Hello World',
txid: 'b79d196740ad5217771c1098fc4a4b51e0535c32236c71f1ea4d61a2d603352b',
},
];

fixtures.forEach(({ message, txid }) => {
it(`should build a to_spend transaction for message "${message}"`, function () {
const result = buildToSpendTransaction(scriptPubKey, Buffer.from(message));
const computedTxid = Transaction.fromHex(result).getId();
assert.strictEqual(computedTxid, txid, `Transaction ID for message "${message}" does not match expected value`);
});
});
});
});