Skip to content

Commit 58258a7

Browse files
committed
feat: do taproot checks for addOutput()
1 parent d7e24cb commit 58258a7

File tree

9 files changed

+360
-39
lines changed

9 files changed

+360
-39
lines changed

src/psbt.d.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="node" />
22
import { Psbt as PsbtBase } from 'bip174';
3-
import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate } from 'bip174/src/lib/interfaces';
3+
import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate, TapInternalKey, TapTree } from 'bip174/src/lib/interfaces';
44
import { Network } from './networks';
55
import { Transaction } from './transaction';
66
export interface TransactionInput {
@@ -125,7 +125,7 @@ interface PsbtOptsOptional {
125125
}
126126
interface PsbtInputExtended extends PsbtInput, TransactionInput {
127127
}
128-
declare type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript;
128+
declare type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript | PsbtOutputExtendedTaproot;
129129
interface PsbtOutputExtendedAddress extends PsbtOutput {
130130
address: string;
131131
value: number;
@@ -134,6 +134,12 @@ interface PsbtOutputExtendedScript extends PsbtOutput {
134134
script: Buffer;
135135
value: number;
136136
}
137+
interface PsbtOutputExtendedTaproot extends PsbtOutput {
138+
tapInternalKey: TapInternalKey;
139+
tapTree?: TapTree;
140+
script?: Buffer;
141+
value: number;
142+
}
137143
interface HDSignerBase {
138144
/**
139145
* DER format compressed publicKey buffer

src/psbt.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,13 @@ class Psbt {
226226
arguments.length > 1 ||
227227
!outputData ||
228228
outputData.value === undefined ||
229-
(outputData.address === undefined && outputData.script === undefined)
229+
(outputData.address === undefined &&
230+
outputData.script === undefined &&
231+
outputData.tapInternalKey === undefined)
230232
) {
231233
throw new Error(
232234
`Invalid arguments for Psbt.addOutput. ` +
233-
`Requires single object with at least [script or address] and [value]`,
235+
`Requires single object with at least [script, address or tapInternalKey] and [value]`,
234236
);
235237
}
236238
checkInputsForPartialSig(this.data.inputs, 'addOutput');
@@ -240,6 +242,20 @@ class Psbt {
240242
const script = (0, address_1.toOutputScript)(address, network);
241243
outputData = Object.assign(outputData, { script });
242244
}
245+
if ((0, bip371_1.isTaprootOutput)(outputData)) {
246+
(0, bip371_1.checkTaprootOutputFields)(
247+
outputData,
248+
outputData,
249+
'addOutput',
250+
);
251+
const scriptAndAddress = (0, bip371_1.getNewTaprootScriptAndAddress)(
252+
outputData,
253+
outputData,
254+
this.opts.network,
255+
);
256+
if (scriptAndAddress)
257+
outputData = Object.assign(outputData, scriptAndAddress);
258+
}
243259
const c = this.__CACHE;
244260
this.data.addOutput(outputData);
245261
c.__FEE = undefined;
@@ -1028,6 +1044,7 @@ function checkFees(psbt, cache, opts) {
10281044
}
10291045
}
10301046
function checkInputsForPartialSig(inputs, action) {
1047+
// todo: add for taproot
10311048
inputs.forEach(input => {
10321049
let throws = false;
10331050
let pSigs = [];

src/psbt/bip371.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/// <reference types="node" />
22
import { Taptree } from '../types';
3-
import { PsbtInput, TapLeaf } from 'bip174/src/lib/interfaces';
3+
import { PsbtInput, PsbtOutput, TapLeaf } from 'bip174/src/lib/interfaces';
4+
import { Network } from '../networks';
45
export declare const toXOnly: (pubKey: Buffer) => Buffer;
56
/**
67
* Default tapscript finalizer. It searches for the `tapLeafHashToFinalize` if provided.
@@ -16,7 +17,13 @@ export declare function tapScriptFinalizer(inputIndex: number, input: PsbtInput,
1617
};
1718
export declare function serializeTaprootSignature(sig: Buffer, sighashType?: number): Buffer;
1819
export declare function isTaprootInput(input: PsbtInput): boolean;
20+
export declare function isTaprootOutput(output: PsbtOutput, script?: Buffer): boolean;
1921
export declare function checkTaprootInputFields(inputData: PsbtInput, newInputData: PsbtInput, action: string): void;
22+
export declare function checkTaprootOutputFields(outputData: PsbtOutput, newOutputData: PsbtOutput, action: string): void;
23+
export declare function getNewTaprootScriptAndAddress(outputData: PsbtOutput, newOutputData: PsbtOutput, network?: Network): {
24+
script: Buffer;
25+
address: string;
26+
} | undefined;
2027
export declare function tweakInternalPubKey(inputIndex: number, input: PsbtInput): Buffer;
2128
/**
2229
* Convert a binary tree to a BIP371 type list. Each element of the list is (according to BIP371):

src/psbt/bip371.js

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
'use strict';
22
Object.defineProperty(exports, '__esModule', { value: true });
3-
exports.tapTreeFromList = exports.tapTreeToList = exports.tweakInternalPubKey = exports.checkTaprootInputFields = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0;
3+
exports.tapTreeFromList = exports.tapTreeToList = exports.tweakInternalPubKey = exports.getNewTaprootScriptAndAddress = exports.checkTaprootOutputFields = exports.checkTaprootInputFields = exports.isTaprootOutput = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0;
44
const types_1 = require('../types');
55
const psbtutils_1 = require('./psbtutils');
66
const taprootutils_1 = require('../payments/taprootutils');
7+
const payments_1 = require('../payments');
78
const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33));
89
exports.toXOnly = toXOnly;
910
/**
@@ -52,11 +53,54 @@ function isTaprootInput(input) {
5253
);
5354
}
5455
exports.isTaprootInput = isTaprootInput;
56+
function isTaprootOutput(output, script) {
57+
return (
58+
output &&
59+
!!(
60+
output.tapInternalKey ||
61+
output.tapTree ||
62+
(output.tapBip32Derivation && output.tapBip32Derivation.length) ||
63+
(script && (0, psbtutils_1.isP2TR)(script))
64+
)
65+
);
66+
}
67+
exports.isTaprootOutput = isTaprootOutput;
5568
function checkTaprootInputFields(inputData, newInputData, action) {
56-
checkMixedTaprootAndNonTaprootFields(inputData, newInputData, action);
69+
checkMixedTaprootAndNonTaprootInputFields(inputData, newInputData, action);
5770
checkIfTapLeafInTree(inputData, newInputData, action);
5871
}
5972
exports.checkTaprootInputFields = checkTaprootInputFields;
73+
function checkTaprootOutputFields(outputData, newOutputData, action) {
74+
checkMixedTaprootAndNonTaprootOutputFields(outputData, newOutputData, action);
75+
}
76+
exports.checkTaprootOutputFields = checkTaprootOutputFields;
77+
function getNewTaprootScriptAndAddress(outputData, newOutputData, network) {
78+
if (!newOutputData.tapTree && !newOutputData.tapInternalKey) return;
79+
const tapInternalKey =
80+
newOutputData.tapInternalKey || outputData.tapInternalKey;
81+
const tapTree = newOutputData.tapTree || outputData.tapTree;
82+
if (tapInternalKey) {
83+
const { script, address } = getTaprootScriptAndAddress(
84+
tapInternalKey,
85+
tapTree,
86+
network,
87+
);
88+
const { script: newScript } = newOutputData;
89+
if (newScript && !newScript.equals(script))
90+
throw new Error('Error adding output. Script or address missmatch.');
91+
return { script, address };
92+
}
93+
}
94+
exports.getNewTaprootScriptAndAddress = getNewTaprootScriptAndAddress;
95+
function getTaprootScriptAndAddress(tapInternalKey, tapTree, network) {
96+
const scriptTree = tapTree && tapTreeFromList(tapTree.leaves);
97+
const { output, address } = (0, payments_1.p2tr)({
98+
internalPubkey: tapInternalKey,
99+
scriptTree,
100+
network,
101+
});
102+
return { script: output, address: address };
103+
}
60104
function tweakInternalPubKey(inputIndex, input) {
61105
const tapInternalKey = input.tapInternalKey;
62106
const outputKey =
@@ -144,14 +188,36 @@ function instertLeafInTree(leaf, tree, depth = 0) {
144188
const rightSide = instertLeafInTree(leaf, tree && tree[1], depth + 1);
145189
if (rightSide) return [tree && tree[0], rightSide];
146190
}
147-
function checkMixedTaprootAndNonTaprootFields(inputData, newInputData, action) {
191+
function checkMixedTaprootAndNonTaprootInputFields(
192+
inputData,
193+
newInputData,
194+
action,
195+
) {
148196
const isBadTaprootUpdate =
149-
isTaprootInput(inputData) && hasNonTaprootInputFields(newInputData);
197+
isTaprootInput(inputData) && hasNonTaprootFields(newInputData);
150198
const isBadNonTaprootUpdate =
151-
hasNonTaprootInputFields(inputData) && isTaprootInput(newInputData);
199+
hasNonTaprootFields(inputData) && isTaprootInput(newInputData);
152200
const hasMixedFields =
153201
inputData === newInputData &&
154-
(isTaprootInput(newInputData) && hasNonTaprootInputFields(newInputData));
202+
(isTaprootInput(newInputData) && hasNonTaprootFields(newInputData)); // todo: bad? use !===
203+
if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields)
204+
throw new Error(
205+
`Invalid arguments for Psbt.${action}. ` +
206+
`Cannot use both taproot and non-taproot fields.`,
207+
);
208+
}
209+
function checkMixedTaprootAndNonTaprootOutputFields(
210+
inputData,
211+
newInputData,
212+
action,
213+
) {
214+
const isBadTaprootUpdate =
215+
isTaprootOutput(inputData) && hasNonTaprootFields(newInputData);
216+
const isBadNonTaprootUpdate =
217+
hasNonTaprootFields(inputData) && isTaprootOutput(newInputData);
218+
const hasMixedFields =
219+
inputData === newInputData &&
220+
(isTaprootOutput(newInputData) && hasNonTaprootFields(newInputData));
155221
if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields)
156222
throw new Error(
157223
`Invalid arguments for Psbt.${action}. ` +
@@ -244,13 +310,13 @@ function canFinalizeLeaf(leaf, tapScriptSig, hash) {
244310
tapScriptSig.find(tss => tss.leafHash.equals(leafHash)) !== undefined
245311
);
246312
}
247-
function hasNonTaprootInputFields(input) {
313+
function hasNonTaprootFields(io) {
248314
return (
249-
input &&
315+
io &&
250316
!!(
251-
input.redeemScript ||
252-
input.witnessScript ||
253-
(input.bip32Derivation && input.bip32Derivation.length)
317+
io.redeemScript ||
318+
io.witnessScript ||
319+
(io.bip32Derivation && io.bip32Derivation.length)
254320
)
255321
);
256322
}

0 commit comments

Comments
 (0)