Skip to content

Commit 2641f25

Browse files
feat(utxo-core): simplify signature process with direct message param
Refactor buildToSignPsbt to accept message directly instead of a toSpendTx parameter. This simplifies the API by internally generating the toSpend transaction and storing the message in the PSBT for later verification. Also adds message to the PSBT as proprietary data so it can be verified during the validation process. Issue: BTC-2375
1 parent 496f98c commit 2641f25

File tree

4 files changed

+25
-40
lines changed

4 files changed

+25
-40
lines changed

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { Psbt, Transaction, bitgo } from '@bitgo/utxo-lib';
1+
import { Psbt, bitgo } from '@bitgo/utxo-lib';
22

3-
import { isTaprootChain } from './utils';
3+
import { addBip322ProofMessage, isTaprootChain } from './utils';
4+
import { BIP322_TAG, buildToSpendTransaction } from './toSpend';
45

56
export type AddressDetails = {
67
redeemScript?: Buffer;
@@ -13,11 +14,14 @@ export type AddressDetails = {
1314
* Source implementation:
1415
* https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#full
1516
*
16-
* @param {string} toSpendTxHex - The hex representation of the `toSpend` transaction.
17+
* @param {string} message - The message that is hashed into the `to_spend` transaction.
1718
* @param {AddressDetails} addressDetails - The details of the address, including redeemScript and/or witnessScript.
18-
* @returns {string} - The hex representation of the constructed PSBT.
19+
* @param {string} [tag=BIP322_TAG] - The tag to use for hashing, defaults to BIP322_TAG.
20+
* @returns {Psbt} - The hex representation of the constructed PSBT.
1921
*/
20-
export function buildToSignPsbt(toSpendTx: Transaction<bigint>, addressDetails: AddressDetails): Psbt {
22+
export function buildToSignPsbt(message: string, addressDetails: AddressDetails, tag = BIP322_TAG): Psbt {
23+
const toSpendTx = buildToSpendTransaction(addressDetails.scriptPubKey, message, tag);
24+
2125
// Create PSBT object for constructing the transaction
2226
const psbt = new Psbt();
2327
// Set default value for nVersion and nLockTime
@@ -41,6 +45,9 @@ export function buildToSignPsbt(toSpendTx: Transaction<bigint>, addressDetails:
4145
psbt.updateInput(0, { witnessScript: addressDetails.witnessScript });
4246
}
4347

48+
// Add the message as a proprietary key value to the PSBT so we can verify it later
49+
addBip322ProofMessage(psbt as bitgo.UtxoPsbt, 0, Buffer.from(message));
50+
4451
// Set the output
4552
psbt.addOutput({
4653
value: BigInt(0), // vout[0].nValue = 0
@@ -50,7 +57,7 @@ export function buildToSignPsbt(toSpendTx: Transaction<bigint>, addressDetails:
5057
}
5158

5259
export function buildToSignPsbtForChainAndIndex(
53-
toSpendTx: Transaction<bigint>,
60+
message: string,
5461
rootWalletKeys: bitgo.RootWalletKeys,
5562
chain: bitgo.ChainCode,
5663
index: number
@@ -63,12 +70,7 @@ export function buildToSignPsbtForChainAndIndex(
6370
bitgo.scriptTypeForChain(chain)
6471
);
6572

66-
const toSpendScriptPubKey = toSpendTx.outs[0].script;
67-
if (!toSpendScriptPubKey.equals(output.scriptPubKey)) {
68-
throw new Error('Output scriptPubKey does not match the expected output script for the chain and index.');
69-
}
70-
71-
return buildToSignPsbt(toSpendTx, {
73+
return buildToSignPsbt(message, {
7274
scriptPubKey: output.scriptPubKey,
7375
redeemScript: output.redeemScript,
7476
witnessScript: output.witnessScript,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ export const BIP322_FIXTURE_HELLO_WORLD_TOSPEND_TX = buildToSpendTransaction(
1313
Buffer.from('Hello World')
1414
);
1515

16-
export const BIP322_FIXTURE_HELLOW_WORLD_TOSIGN_PSBT = buildToSignPsbt(BIP322_FIXTURE_HELLO_WORLD_TOSPEND_TX, {
16+
export const BIP322_FIXTURE_HELLO_WORLD_TOSIGN_PSBT = buildToSignPsbt('Hello World', {
1717
scriptPubKey: BIP322_PAYMENT_P2WPKH_FIXTURE.output as Buffer,
1818
});

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

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,9 @@ describe('BIP322 toSign', function () {
2323

2424
fixtures.forEach(({ message, txid }) => {
2525
it(`should build a to_sign PSBT for message "${message}"`, function () {
26-
const toSpendTx = bip322.buildToSpendTransaction(scriptPubKey, Buffer.from(message));
27-
const addressDetails = {
26+
const result = bip322.buildToSignPsbt(message, {
2827
scriptPubKey,
29-
};
30-
const result = bip322.buildToSignPsbt(toSpendTx, addressDetails);
28+
});
3129
const computedTxid = result
3230
.signAllInputs(prv, [utxolib.Transaction.SIGHASH_ALL])
3331
.finalizeAllInputs()
@@ -41,13 +39,6 @@ describe('BIP322 toSign', function () {
4139
describe('buildToSignPsbtForChainAndIndex', function () {
4240
const rootWalletKeys = utxolib.testutil.getDefaultWalletKeys();
4341

44-
it('should fail when scriptPubKey of to_spend is different than to_sign', function () {
45-
const toSpendTx = bip322.buildToSpendTransaction(BIP322_PAYMENT_P2WPKH_FIXTURE.output as Buffer, 'Hello World');
46-
assert.throws(() => {
47-
bip322.buildToSignPsbtForChainAndIndex(toSpendTx, rootWalletKeys, 0, 0);
48-
}, /Output scriptPubKey does not match the expected output script for the chain and index./);
49-
});
50-
5142
function run(chain: utxolib.bitgo.ChainCode, shouldFail: boolean, index: number) {
5243
it(`should${
5344
shouldFail ? ' fail to' : ''
@@ -60,7 +51,7 @@ describe('BIP322 toSign', function () {
6051
return;
6152
}
6253
const toSpendTx = bip322.buildToSpendTransactionFromChainAndIndex(rootWalletKeys, chain, index, message);
63-
const toSignPsbt = bip322.buildToSignPsbtForChainAndIndex(toSpendTx, rootWalletKeys, chain, index);
54+
const toSignPsbt = bip322.buildToSignPsbtForChainAndIndex(message, rootWalletKeys, chain, index);
6455

6556
const derivedKeys = rootWalletKeys.deriveForChainAndIndex(chain, index);
6657
const prv1 = derivedKeys.triple[0];

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

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,37 @@ import assert from 'assert';
22

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

5-
import { addBip322ProofMessage, getBip322ProofInputIndex, psbtIsBip322Proof } from '../../src/bip322';
5+
import { getBip322ProofInputIndex, psbtIsBip322Proof } from '../../src/bip322';
66

7-
import { BIP322_FIXTURE_HELLOW_WORLD_TOSIGN_PSBT } from './bip322.utils';
7+
import { BIP322_FIXTURE_HELLO_WORLD_TOSIGN_PSBT } from './bip322.utils';
88

99
describe('BIP322 Proof utils', function () {
1010
it('should add a BIP322 proof message to a PSBT input', function () {
1111
const psbt = utxolib.bitgo.createPsbtFromBuffer(
12-
BIP322_FIXTURE_HELLOW_WORLD_TOSIGN_PSBT.toBuffer(),
12+
BIP322_FIXTURE_HELLO_WORLD_TOSIGN_PSBT.toBuffer(),
1313
utxolib.networks.bitcoin
1414
);
15-
const inputIndex = 0;
16-
const message = Buffer.from('Hello World');
17-
addBip322ProofMessage(psbt, inputIndex, message);
1815

19-
const proprietaryKeyVals = utxolib.bitgo.getPsbtInputProprietaryKeyVals(psbt.data.inputs[inputIndex], {
16+
const proprietaryKeyVals = utxolib.bitgo.getPsbtInputProprietaryKeyVals(psbt.data.inputs[0], {
2017
identifier: utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER,
2118
subtype: utxolib.bitgo.ProprietaryKeySubtype.BIP322_MESSAGE,
2219
});
2320

2421
assert.ok(proprietaryKeyVals.length === 1);
25-
assert.ok(proprietaryKeyVals[0].value.equals(message));
22+
assert.ok(proprietaryKeyVals[0].value.equals(Buffer.from('Hello World')));
2623
assert.ok(proprietaryKeyVals[0].key.keydata.length === 0); // keydata should be empty
2724
assert.ok(proprietaryKeyVals[0].key.identifier === utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER);
2825
assert.ok(proprietaryKeyVals[0].key.subtype === utxolib.bitgo.ProprietaryKeySubtype.BIP322_MESSAGE);
2926
});
3027

3128
it('should return the input index of a BIP322 proof message', function () {
3229
const psbt = utxolib.bitgo.createPsbtFromBuffer(
33-
BIP322_FIXTURE_HELLOW_WORLD_TOSIGN_PSBT.toBuffer(),
30+
BIP322_FIXTURE_HELLO_WORLD_TOSIGN_PSBT.toBuffer(),
3431
utxolib.networks.bitcoin
3532
);
36-
assert.ok(!psbtIsBip322Proof(psbt)); // initially should not be a BIP322 proof
37-
38-
const inputIndex = 0;
39-
const message = Buffer.from('Hello World');
40-
addBip322ProofMessage(psbt, inputIndex, message);
4133

4234
const resultIndex = getBip322ProofInputIndex(psbt);
43-
assert.strictEqual(resultIndex, inputIndex);
35+
assert.strictEqual(resultIndex, 0);
4436
assert.ok(psbtIsBip322Proof(psbt));
4537
});
4638
});

0 commit comments

Comments
 (0)