@scure/btc-signer 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -20
- package/index.d.ts +26 -6
- package/index.d.ts.map +1 -1
- package/index.js +81 -39
- package/index.js.map +1 -1
- package/index.ts +100 -58
- package/package.json +9 -7
package/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { secp256k1 as _secp, schnorr } from '@noble/curves/secp256k1';
|
|
3
3
|
import { sha256 } from '@noble/hashes/sha256';
|
|
4
4
|
import { ripemd160 } from '@noble/hashes/ripemd160';
|
|
5
|
-
import { hex,
|
|
5
|
+
import { hex, base58check as _b58, bech32, bech32m } from '@scure/base';
|
|
6
6
|
import type { Coder } from '@scure/base';
|
|
7
7
|
import * as P from 'micro-packed';
|
|
8
8
|
|
|
@@ -327,6 +327,7 @@ export function OpToNum(op: ScriptOP, bytesLimit = 4, forceMinimal = true) {
|
|
|
327
327
|
return;
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
|
+
return;
|
|
330
331
|
}
|
|
331
332
|
|
|
332
333
|
// BTC specific variable length integer encoding
|
|
@@ -680,8 +681,8 @@ const PSBTInputCoder = P.validate(PSBTKeyMap(PSBTInput), (i) => {
|
|
|
680
681
|
throw new Error('validateInput: wmpty finalScriptWitness');
|
|
681
682
|
//if (i.finalScriptSig && !i.finalScriptSig.length) throw new Error('validateInput: empty finalScriptSig');
|
|
682
683
|
if (i.partialSig && !i.partialSig.length) throw new Error('Empty partialSig');
|
|
683
|
-
if (i.partialSig) for (const [k
|
|
684
|
-
if (i.bip32Derivation) for (const [k
|
|
684
|
+
if (i.partialSig) for (const [k] of i.partialSig) validatePubkey(k, PubT.ecdsa);
|
|
685
|
+
if (i.bip32Derivation) for (const [k] of i.bip32Derivation) validatePubkey(k, PubT.ecdsa);
|
|
685
686
|
// Locktime = unsigned little endian integer greater than or equal to 500000000 representing
|
|
686
687
|
if (i.requiredTimeLocktime !== undefined && i.requiredTimeLocktime < 500000000)
|
|
687
688
|
throw new Error(`validateInput: wrong timeLocktime=${i.requiredTimeLocktime}`);
|
|
@@ -724,7 +725,7 @@ const PSBTInputCoder = P.validate(PSBTKeyMap(PSBTInput), (i) => {
|
|
|
724
725
|
});
|
|
725
726
|
|
|
726
727
|
const PSBTOutputCoder = P.validate(PSBTKeyMap(PSBTOutput), (o) => {
|
|
727
|
-
if (o.bip32Derivation) for (const [k
|
|
728
|
+
if (o.bip32Derivation) for (const [k] of o.bip32Derivation) validatePubkey(k, PubT.ecdsa);
|
|
728
729
|
return o;
|
|
729
730
|
});
|
|
730
731
|
|
|
@@ -906,7 +907,7 @@ const TxHashIdx = P.struct({ txid: P.bytes(32, true), index: P.U32LE });
|
|
|
906
907
|
// - generate input script
|
|
907
908
|
// - generate address/output/redeem from user input
|
|
908
909
|
// P2ret represents generic interface for all p2* methods
|
|
909
|
-
type P2Ret = {
|
|
910
|
+
export type P2Ret = {
|
|
910
911
|
type: string;
|
|
911
912
|
script: Bytes;
|
|
912
913
|
address?: string;
|
|
@@ -929,7 +930,9 @@ const OutPK: Coder<OptScript, OutPKType | undefined> = {
|
|
|
929
930
|
},
|
|
930
931
|
decode: (to: OutPKType): OptScript => (to.type === 'pk' ? [to.pubkey, 'CHECKSIG'] : undefined),
|
|
931
932
|
};
|
|
933
|
+
// @ts-ignore
|
|
932
934
|
export const p2pk = (pubkey: Bytes, network = NETWORK): P2Ret => {
|
|
935
|
+
// network is unused
|
|
933
936
|
if (!isValidPubkey(pubkey, PubT.ecdsa)) throw new Error('P2PK: invalid publicKey');
|
|
934
937
|
return {
|
|
935
938
|
type: 'pk',
|
|
@@ -937,7 +940,7 @@ export const p2pk = (pubkey: Bytes, network = NETWORK): P2Ret => {
|
|
|
937
940
|
};
|
|
938
941
|
};
|
|
939
942
|
|
|
940
|
-
//
|
|
943
|
+
// Public Key Hash (P2PKH)
|
|
941
944
|
type OutPKHType = { type: 'pkh'; hash: Bytes };
|
|
942
945
|
const OutPKH: Coder<OptScript, OutPKHType | undefined> = {
|
|
943
946
|
encode(from: ScriptType): OutPKHType | undefined {
|
|
@@ -1094,32 +1097,51 @@ export function taprootListToTree(taprootList: TaprootScriptList): TaprootScript
|
|
|
1094
1097
|
return (last?.childs || last) as TaprootScriptTree;
|
|
1095
1098
|
}
|
|
1096
1099
|
type HashedTree =
|
|
1097
|
-
| { type: 'leaf'; version?: number; script: Bytes; hash: Bytes
|
|
1100
|
+
| { type: 'leaf'; version?: number; script: Bytes; hash: Bytes }
|
|
1098
1101
|
| { type: 'branch'; left: HashedTree; right: HashedTree; hash: Bytes };
|
|
1099
|
-
function checkTaprootScript(script: Bytes,
|
|
1102
|
+
function checkTaprootScript(script: Bytes, internalPubKey: Bytes, allowUnknownOutputs = false) {
|
|
1100
1103
|
const out = OutScript.decode(script);
|
|
1101
|
-
if (out.type === 'unknown' &&
|
|
1104
|
+
if (out.type === 'unknown' && allowUnknownOutputs) return;
|
|
1102
1105
|
if (!['tr_ns', 'tr_ms'].includes(out.type))
|
|
1103
1106
|
throw new Error(`P2TR: invalid leaf script=${out.type}`);
|
|
1107
|
+
const outms = out as OutTRNSType | OutTRMSType;
|
|
1108
|
+
if (!allowUnknownOutputs && outms.pubkeys) {
|
|
1109
|
+
for (const p of outms.pubkeys) {
|
|
1110
|
+
if (P.equalBytes(p, TAPROOT_UNSPENDABLE_KEY))
|
|
1111
|
+
throw new Error('Unspendable taproot key in leaf script');
|
|
1112
|
+
// It's likely a mistake at this point:
|
|
1113
|
+
// 1. p2tr(A, p2tr_ns(2, [A, B])) == p2tr(A, p2tr_pk(B)) (A or B key)
|
|
1114
|
+
// but will take more space and fees.
|
|
1115
|
+
// 2. For multi-sig p2tr(A, p2tr_ns(2, [A, B, C])) it's probably a security issue:
|
|
1116
|
+
// User creates 2 of 3 multisig of keys [A, B, C],
|
|
1117
|
+
// but key A always can spend whole output without signatures from other keys.
|
|
1118
|
+
// p2tr(A, p2tr_ns(2, [B, C, D])) is ok: A or (B and C) or (B and D) or (C and D)
|
|
1119
|
+
if (P.equalBytes(p, internalPubKey)) {
|
|
1120
|
+
throw new Error(
|
|
1121
|
+
'Using P2TR with leaf script with same key as internal key is not supported'
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1104
1126
|
}
|
|
1105
|
-
function taprootHashTree(
|
|
1127
|
+
function taprootHashTree(
|
|
1128
|
+
tree: TaprootScriptTree,
|
|
1129
|
+
internalPubKey: Bytes,
|
|
1130
|
+
allowUnknownOutputs = false
|
|
1131
|
+
): HashedTree {
|
|
1106
1132
|
if (!tree) throw new Error('taprootHashTree: empty tree');
|
|
1107
1133
|
if (Array.isArray(tree) && tree.length === 1) tree = tree[0];
|
|
1108
1134
|
// Terminal node (leaf)
|
|
1109
1135
|
if (!Array.isArray(tree)) {
|
|
1110
|
-
const { leafVersion: version, script: leafScript
|
|
1136
|
+
const { leafVersion: version, script: leafScript } = tree;
|
|
1111
1137
|
// Earliest tree walk where we can validate tapScripts
|
|
1112
1138
|
if (tree.tapLeafScript || (tree.tapMerkleRoot && !P.equalBytes(tree.tapMerkleRoot, P.EMPTY)))
|
|
1113
1139
|
throw new Error('P2TR: tapRoot leafScript cannot have tree');
|
|
1114
|
-
// Just to be sure that it is spendable
|
|
1115
|
-
if (tapInternalKey && P.equalBytes(tapInternalKey, TAPROOT_UNSPENDABLE_KEY))
|
|
1116
|
-
throw new Error('P2TR: tapRoot leafScript cannot have unspendble key');
|
|
1117
1140
|
const script = typeof leafScript === 'string' ? hex.decode(leafScript) : leafScript;
|
|
1118
1141
|
if (!isBytes(script)) throw new Error(`checkScript: wrong script type=${script}`);
|
|
1119
|
-
checkTaprootScript(script,
|
|
1142
|
+
checkTaprootScript(script, internalPubKey, allowUnknownOutputs);
|
|
1120
1143
|
return {
|
|
1121
1144
|
type: 'leaf',
|
|
1122
|
-
tapInternalKey,
|
|
1123
1145
|
version,
|
|
1124
1146
|
script,
|
|
1125
1147
|
hash: tapLeafHash(script, version),
|
|
@@ -1130,8 +1152,8 @@ function taprootHashTree(tree: TaprootScriptTree, allowUnknowOutput = false): Ha
|
|
|
1130
1152
|
if (tree.length !== 2) throw new Error('hashTree: non binary tree!');
|
|
1131
1153
|
// branch
|
|
1132
1154
|
// Both nodes should exist
|
|
1133
|
-
const left = taprootHashTree(tree[0],
|
|
1134
|
-
const right = taprootHashTree(tree[1],
|
|
1155
|
+
const left = taprootHashTree(tree[0], internalPubKey, allowUnknownOutputs);
|
|
1156
|
+
const right = taprootHashTree(tree[1], internalPubKey, allowUnknownOutputs);
|
|
1135
1157
|
// We cannot swap left/right here, since it will change structure of tree
|
|
1136
1158
|
let [lH, rH] = [left.hash, right.hash];
|
|
1137
1159
|
if (_cmpBytes(rH, lH) === -1) [lH, rH] = [rH, lH];
|
|
@@ -1143,7 +1165,6 @@ type TaprootLeaf = {
|
|
|
1143
1165
|
script: Bytes;
|
|
1144
1166
|
hash: Bytes;
|
|
1145
1167
|
path: Bytes[];
|
|
1146
|
-
tapInternalKey?: Bytes;
|
|
1147
1168
|
};
|
|
1148
1169
|
|
|
1149
1170
|
type HashedTreeWithPath =
|
|
@@ -1197,7 +1218,7 @@ export function p2tr(
|
|
|
1197
1218
|
internalPubKey?: Bytes | string,
|
|
1198
1219
|
tree?: TaprootScriptTree,
|
|
1199
1220
|
network = NETWORK,
|
|
1200
|
-
|
|
1221
|
+
allowUnknownOutputs = false
|
|
1201
1222
|
): P2TROut {
|
|
1202
1223
|
// Unspendable
|
|
1203
1224
|
if (!internalPubKey && !tree) throw new Error('p2tr: should have pubKey or scriptTree (or both)');
|
|
@@ -1206,7 +1227,9 @@ export function p2tr(
|
|
|
1206
1227
|
? hex.decode(internalPubKey)
|
|
1207
1228
|
: internalPubKey || TAPROOT_UNSPENDABLE_KEY;
|
|
1208
1229
|
if (!isValidPubkey(pubKey, PubT.schnorr)) throw new Error('p2tr: non-schnorr pubkey');
|
|
1209
|
-
let hashedTree = tree
|
|
1230
|
+
let hashedTree = tree
|
|
1231
|
+
? taprootAddPath(taprootHashTree(tree, pubKey, allowUnknownOutputs))
|
|
1232
|
+
: undefined;
|
|
1210
1233
|
const tapMerkleRoot = hashedTree ? hashedTree.hash : undefined;
|
|
1211
1234
|
const [tweakedPubkey, parity] = taprootTweakPubkey(pubKey, tapMerkleRoot || P.EMPTY);
|
|
1212
1235
|
let leaves;
|
|
@@ -1215,7 +1238,7 @@ export function p2tr(
|
|
|
1215
1238
|
...l,
|
|
1216
1239
|
controlBlock: TaprootControlBlock.encode({
|
|
1217
1240
|
version: (l.version || TAP_LEAF_VERSION) + parity,
|
|
1218
|
-
internalKey:
|
|
1241
|
+
internalKey: pubKey,
|
|
1219
1242
|
merklePath: l.path,
|
|
1220
1243
|
}),
|
|
1221
1244
|
}));
|
|
@@ -1351,7 +1374,7 @@ export function p2tr_ms(m: number, pubkeys: Bytes[], allowSamePubkeys = false) {
|
|
|
1351
1374
|
script: OutScript.encode({ type: 'tr_ms', pubkeys, m }),
|
|
1352
1375
|
};
|
|
1353
1376
|
}
|
|
1354
|
-
//
|
|
1377
|
+
// Unknown output type
|
|
1355
1378
|
type OutUnknownType = { type: 'unknown'; script: Bytes };
|
|
1356
1379
|
const OutUnknown: Coder<OptScript, OutUnknownType | undefined> = {
|
|
1357
1380
|
encode(from: ScriptType): OutUnknownType | undefined {
|
|
@@ -1481,7 +1504,7 @@ export function Address(network = NETWORK) {
|
|
|
1481
1504
|
if (version === 0 && data.length === 32) return { type: 'wsh', hash: data };
|
|
1482
1505
|
else if (version === 0 && data.length === 20) return { type: 'wpkh', hash: data };
|
|
1483
1506
|
else if (version === 1 && data.length === 32) return { type: 'tr', pubkey: data };
|
|
1484
|
-
else throw new Error('
|
|
1507
|
+
else throw new Error('Unknown witness program');
|
|
1485
1508
|
}
|
|
1486
1509
|
const data = base58check.decode(address);
|
|
1487
1510
|
if (data.length !== 21) throw new Error('Invalid base58 address');
|
|
@@ -1500,6 +1523,10 @@ export function Address(network = NETWORK) {
|
|
|
1500
1523
|
}
|
|
1501
1524
|
// /Address
|
|
1502
1525
|
|
|
1526
|
+
/**
|
|
1527
|
+
* Internal, exported only for backwards-compat. Use `SigHash` instead.
|
|
1528
|
+
* @deprecated
|
|
1529
|
+
*/
|
|
1503
1530
|
export enum SignatureHash {
|
|
1504
1531
|
DEFAULT,
|
|
1505
1532
|
ALL,
|
|
@@ -1507,7 +1534,23 @@ export enum SignatureHash {
|
|
|
1507
1534
|
SINGLE,
|
|
1508
1535
|
ANYONECANPAY = 0x80,
|
|
1509
1536
|
}
|
|
1510
|
-
|
|
1537
|
+
|
|
1538
|
+
export enum SigHash {
|
|
1539
|
+
DEFAULT = SignatureHash.DEFAULT,
|
|
1540
|
+
ALL = SignatureHash.ALL,
|
|
1541
|
+
NONE = SignatureHash.NONE,
|
|
1542
|
+
SINGLE = SignatureHash.SINGLE,
|
|
1543
|
+
DEFAULT_ANYONECANPAY = SignatureHash.DEFAULT | SignatureHash.ANYONECANPAY,
|
|
1544
|
+
ALL_ANYONECANPAY = SignatureHash.ALL | SignatureHash.ANYONECANPAY,
|
|
1545
|
+
NONE_ANYONECANPAY = SignatureHash.NONE | SignatureHash.ANYONECANPAY,
|
|
1546
|
+
SINGLE_ANYONECANPAY = SignatureHash.SINGLE | SignatureHash.ANYONECANPAY,
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
function validateSigHash(s: SigHash) {
|
|
1550
|
+
if (typeof s !== 'number' || typeof SigHash[s] !== 'string')
|
|
1551
|
+
throw new Error(`Invalid SigHash=${s}`);
|
|
1552
|
+
return s;
|
|
1553
|
+
}
|
|
1511
1554
|
|
|
1512
1555
|
function unpackSighash(hashType: number) {
|
|
1513
1556
|
const masked = hashType & 0b0011111;
|
|
@@ -1603,9 +1646,13 @@ export type TxOpts = {
|
|
|
1603
1646
|
PSBTVersion?: number;
|
|
1604
1647
|
// Flags
|
|
1605
1648
|
// Allow output scripts to be unknown scripts (probably unspendable)
|
|
1649
|
+
/** @deprecated Use `allowUnknownOutputs` */
|
|
1606
1650
|
allowUnknowOutput?: boolean;
|
|
1651
|
+
allowUnknownOutputs?: boolean;
|
|
1607
1652
|
// Try to sign/finalize unknown input. All bets are off, but there is chance that it will work
|
|
1653
|
+
/** @deprecated Use `allowUnknownInputs` */
|
|
1608
1654
|
allowUnknowInput?: boolean;
|
|
1655
|
+
allowUnknownInputs?: boolean;
|
|
1609
1656
|
// Check input/output scripts for sanity
|
|
1610
1657
|
disableScriptCheck?: boolean;
|
|
1611
1658
|
// There is strange behaviour where tx without outputs encoded with empty output in the end,
|
|
@@ -1617,18 +1664,22 @@ export type TxOpts = {
|
|
|
1617
1664
|
lowR?: boolean; // Use lowR signatures
|
|
1618
1665
|
};
|
|
1619
1666
|
|
|
1620
|
-
|
|
1621
|
-
const isPlainObject = (obj: any) =>
|
|
1622
|
-
Object.prototype.toString.call(obj) === '[object Object]' && obj.constructor === Object;
|
|
1623
|
-
|
|
1667
|
+
const toStr = {}.toString;
|
|
1624
1668
|
function validateOpts(opts: TxOpts) {
|
|
1625
|
-
if (
|
|
1669
|
+
if (opts !== undefined && toStr.call(opts) !== '[object Object]')
|
|
1670
|
+
throw new Error(`Wrong object type for transaction options: ${opts}`);
|
|
1671
|
+
|
|
1626
1672
|
const _opts = {
|
|
1627
1673
|
...opts,
|
|
1674
|
+
// Defaults
|
|
1628
1675
|
version: def(opts.version, DEFAULT_VERSION),
|
|
1629
1676
|
lockTime: def(opts.lockTime, 0),
|
|
1630
1677
|
PSBTVersion: def(opts.PSBTVersion, 0),
|
|
1631
|
-
};
|
|
1678
|
+
};
|
|
1679
|
+
if (typeof _opts.allowUnknowInput !== 'undefined')
|
|
1680
|
+
opts.allowUnknownInputs = _opts.allowUnknowInput;
|
|
1681
|
+
if (typeof _opts.allowUnknowOutput !== 'undefined')
|
|
1682
|
+
opts.allowUnknownOutputs = _opts.allowUnknowOutput;
|
|
1632
1683
|
// 0 and -1 happens in tests
|
|
1633
1684
|
if (![-1, 0, 1, 2].includes(_opts.version)) throw new Error(`Unknown version: ${_opts.version}`);
|
|
1634
1685
|
if (typeof _opts.lockTime !== 'number') throw new Error('Transaction lock time should be number');
|
|
@@ -1639,8 +1690,8 @@ function validateOpts(opts: TxOpts) {
|
|
|
1639
1690
|
throw new Error(`Unknown PSBT version ${_opts.PSBTVersion}`);
|
|
1640
1691
|
// Flags
|
|
1641
1692
|
for (const k of [
|
|
1642
|
-
'
|
|
1643
|
-
'
|
|
1693
|
+
'allowUnknownOutputs',
|
|
1694
|
+
'allowUnknownInputs',
|
|
1644
1695
|
'disableScriptCheck',
|
|
1645
1696
|
'bip174jsCompat',
|
|
1646
1697
|
'allowLegacyWitnessUtxo',
|
|
@@ -1978,11 +2029,11 @@ export class Transaction {
|
|
|
1978
2029
|
PSBTOutputCoder.encode(res);
|
|
1979
2030
|
if (
|
|
1980
2031
|
res.script &&
|
|
1981
|
-
!this.opts.
|
|
2032
|
+
!this.opts.allowUnknownOutputs &&
|
|
1982
2033
|
OutScript.decode(res.script).type === 'unknown'
|
|
1983
2034
|
) {
|
|
1984
2035
|
throw new Error(
|
|
1985
|
-
'Transaction/output: unknown output script type, there is a chance that input is unspendable. Pass
|
|
2036
|
+
'Transaction/output: unknown output script type, there is a chance that input is unspendable. Pass allowUnknownScript=true, if you sure'
|
|
1986
2037
|
);
|
|
1987
2038
|
}
|
|
1988
2039
|
if (!this.opts.disableScriptCheck) checkScript(res.script, res.redeemScript, res.witnessScript);
|
|
@@ -2146,10 +2197,10 @@ export class Transaction {
|
|
|
2146
2197
|
// Used pretty often, should be fast
|
|
2147
2198
|
private prevOut(input: TransactionInput): P.UnwrapCoder<typeof RawOutput> {
|
|
2148
2199
|
if (input.nonWitnessUtxo) {
|
|
2149
|
-
if (input.index === undefined) throw new Error('
|
|
2200
|
+
if (input.index === undefined) throw new Error('Unknown input index');
|
|
2150
2201
|
return input.nonWitnessUtxo.outputs[input.index];
|
|
2151
2202
|
} else if (input.witnessUtxo) return input.witnessUtxo;
|
|
2152
|
-
else throw new Error('Cannot find previous output info
|
|
2203
|
+
else throw new Error('Cannot find previous output info');
|
|
2153
2204
|
}
|
|
2154
2205
|
private inputType(input: TransactionInput) {
|
|
2155
2206
|
let txType = 'legacy';
|
|
@@ -2210,12 +2261,7 @@ export class Transaction {
|
|
|
2210
2261
|
}
|
|
2211
2262
|
|
|
2212
2263
|
// Signer can be privateKey OR instance of bip32 HD stuff
|
|
2213
|
-
signIdx(
|
|
2214
|
-
privateKey: Signer,
|
|
2215
|
-
idx: number,
|
|
2216
|
-
allowedSighash?: SignatureHash[],
|
|
2217
|
-
_auxRand?: Bytes
|
|
2218
|
-
): boolean {
|
|
2264
|
+
signIdx(privateKey: Signer, idx: number, allowedSighash?: SigHash[], _auxRand?: Bytes): boolean {
|
|
2219
2265
|
this.checkInputIdx(idx);
|
|
2220
2266
|
const input = this.inputs[idx];
|
|
2221
2267
|
const inputType = this.inputType(input);
|
|
@@ -2240,7 +2286,8 @@ export class Transaction {
|
|
|
2240
2286
|
}
|
|
2241
2287
|
// Sighash checks
|
|
2242
2288
|
// Just for compat with bitcoinjs-lib, so users won't face unexpected behaviour.
|
|
2243
|
-
if (!allowedSighash) allowedSighash = [inputType.defaultSighash];
|
|
2289
|
+
if (!allowedSighash) allowedSighash = [inputType.defaultSighash as unknown as SigHash];
|
|
2290
|
+
else allowedSighash.forEach(validateSigHash);
|
|
2244
2291
|
const sighash = inputType.sighash;
|
|
2245
2292
|
if (!allowedSighash.includes(sighash)) {
|
|
2246
2293
|
throw new Error(
|
|
@@ -2251,7 +2298,7 @@ export class Transaction {
|
|
|
2251
2298
|
// however this was because of bug in bitcoin-core, which remains here because of consensus.
|
|
2252
2299
|
// If this is absolutely neccessary for your case, please open issue.
|
|
2253
2300
|
// We disable it to avoid complicated workflow where SINGLE will block adding new outputs
|
|
2254
|
-
const {
|
|
2301
|
+
const { sigOutputs } = this.inputSighash(idx);
|
|
2255
2302
|
if (sigOutputs === SignatureHash.SINGLE && idx >= this.outputs.length) {
|
|
2256
2303
|
throw new Error(
|
|
2257
2304
|
`Input with sighash SINGLE, but there is no output with corresponding index=${idx}`
|
|
@@ -2280,7 +2327,7 @@ export class Transaction {
|
|
|
2280
2327
|
input.tapInternalKey,
|
|
2281
2328
|
merkleRoot
|
|
2282
2329
|
);
|
|
2283
|
-
const [taprootPubKey,
|
|
2330
|
+
const [taprootPubKey, _] = taprootTweakPubkey(input.tapInternalKey, merkleRoot);
|
|
2284
2331
|
if (P.equalBytes(taprootPubKey, pubKey)) {
|
|
2285
2332
|
const hash = this.preimageWitnessV1(idx, prevOutScript, sighash, amount);
|
|
2286
2333
|
const sig = concat(
|
|
@@ -2293,18 +2340,13 @@ export class Transaction {
|
|
|
2293
2340
|
}
|
|
2294
2341
|
if (input.tapLeafScript) {
|
|
2295
2342
|
input.tapScriptSig = input.tapScriptSig || [];
|
|
2296
|
-
for (const [
|
|
2343
|
+
for (const [_, _script] of input.tapLeafScript) {
|
|
2297
2344
|
const script = _script.subarray(0, -1);
|
|
2298
2345
|
const scriptDecoded = Script.decode(script);
|
|
2299
2346
|
const ver = _script[_script.length - 1];
|
|
2300
2347
|
const hash = tapLeafHash(script, ver);
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
schnorrPub,
|
|
2304
|
-
cb.internalKey,
|
|
2305
|
-
P.EMPTY // Because we cannot have nested taproot tree
|
|
2306
|
-
);
|
|
2307
|
-
const pos = scriptDecoded.findIndex((i) => isBytes(i) && P.equalBytes(i, pubKey));
|
|
2348
|
+
// NOTE: no need to tweak internal key here, since we don't support nested p2tr
|
|
2349
|
+
const pos = scriptDecoded.findIndex((i) => isBytes(i) && P.equalBytes(i, schnorrPub));
|
|
2308
2350
|
// Skip if there is no public key in tapLeafScript
|
|
2309
2351
|
if (pos === -1) continue;
|
|
2310
2352
|
const msg = this.preimageWitnessV1(
|
|
@@ -2317,12 +2359,12 @@ export class Transaction {
|
|
|
2317
2359
|
ver
|
|
2318
2360
|
);
|
|
2319
2361
|
const sig = concat(
|
|
2320
|
-
schnorr.sign(msg,
|
|
2362
|
+
schnorr.sign(msg, privateKey, _auxRand),
|
|
2321
2363
|
sighash !== SignatureHash.DEFAULT ? new Uint8Array([sighash]) : P.EMPTY
|
|
2322
2364
|
);
|
|
2323
2365
|
this.updateInput(
|
|
2324
2366
|
idx,
|
|
2325
|
-
{ tapScriptSig: [[{ pubKey:
|
|
2367
|
+
{ tapScriptSig: [[{ pubKey: schnorrPub, leafHash: hash }, sig]] },
|
|
2326
2368
|
true
|
|
2327
2369
|
);
|
|
2328
2370
|
signed = true;
|
|
@@ -2426,7 +2468,7 @@ export class Transaction {
|
|
|
2426
2468
|
signatures.push(scriptSig[sigIdx][1]);
|
|
2427
2469
|
}
|
|
2428
2470
|
if (signatures.length !== outScript.pubkeys.length) continue;
|
|
2429
|
-
} else if (outScript.type === 'unknown' && this.opts.
|
|
2471
|
+
} else if (outScript.type === 'unknown' && this.opts.allowUnknownInputs) {
|
|
2430
2472
|
// Trying our best to sign what we can
|
|
2431
2473
|
const scriptDecoded = Script.decode(script);
|
|
2432
2474
|
signatures = scriptSig
|
|
@@ -2483,7 +2525,7 @@ export class Transaction {
|
|
|
2483
2525
|
} else if (inputType.last.type === 'wpkh') {
|
|
2484
2526
|
inputScript = P.EMPTY;
|
|
2485
2527
|
witness = [input.partialSig[0][1], input.partialSig[0][0]];
|
|
2486
|
-
} else if (inputType.last.type === 'unknown' && !this.opts.
|
|
2528
|
+
} else if (inputType.last.type === 'unknown' && !this.opts.allowUnknownInputs)
|
|
2487
2529
|
throw new Error('Unknown inputs not allowed');
|
|
2488
2530
|
|
|
2489
2531
|
// Create final scripts (generic part)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scure/btc-signer",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Audited & minimal library for creating, signing & decoding Bitcoin transactions: with Schnorr, Taproot, UTXO & PSBT",
|
|
5
5
|
"files": [
|
|
6
6
|
"index.js",
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
"module": "index.js",
|
|
15
15
|
"types": "index.d.ts",
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@noble/curves": "~1.
|
|
18
|
-
"@noble/hashes": "~1.3.
|
|
19
|
-
"@scure/base": "~1.1.
|
|
17
|
+
"@noble/curves": "~1.2.0",
|
|
18
|
+
"@noble/hashes": "~1.3.1",
|
|
19
|
+
"@scure/base": "~1.1.3",
|
|
20
20
|
"micro-packed": "~0.3.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@scure/bip32": "~1.3.
|
|
23
|
+
"@scure/bip32": "~1.3.2",
|
|
24
24
|
"micro-packed-debugger": "0.3.1",
|
|
25
25
|
"micro-should": "0.4.0",
|
|
26
26
|
"prettier": "2.8.4",
|
|
@@ -36,8 +36,10 @@
|
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsc",
|
|
38
38
|
"lint": "prettier --check index.ts",
|
|
39
|
+
"format": "prettier --write index.ts",
|
|
39
40
|
"test": "node test/index.test.js && node test/psbt-test/bip174-psbt-extended.test.js",
|
|
40
|
-
"test:extended": "node --experimental-loader ./test/bitcoinjs-test/esm-loader.js ./test/bitcoinjs-test/index.test.js"
|
|
41
|
+
"test:extended": "node --experimental-loader ./test/bitcoinjs-test/esm-loader.js ./test/bitcoinjs-test/index.test.js",
|
|
42
|
+
"test:slow": "node test/index.test.js && node test/slow.test.js"
|
|
41
43
|
},
|
|
42
44
|
"keywords": [
|
|
43
45
|
"bitcoin",
|
|
@@ -56,4 +58,4 @@
|
|
|
56
58
|
"p2tr"
|
|
57
59
|
],
|
|
58
60
|
"funding": "https://paulmillr.com/funding/"
|
|
59
|
-
}
|
|
61
|
+
}
|