@scure/btc-signer 1.0.0 → 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 +43 -23
- package/index.d.ts +26 -6
- package/index.d.ts.map +1 -1
- package/index.js +87 -48
- package/index.js.map +1 -1
- package/index.ts +105 -65
- package/package.json +10 -13
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,19 +1504,17 @@ 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
|
-
const data =
|
|
1487
|
-
if (data.length !==
|
|
1509
|
+
const data = base58check.decode(address);
|
|
1510
|
+
if (data.length !== 21) throw new Error('Invalid base58 address');
|
|
1488
1511
|
// Pay To Public Key Hash
|
|
1489
1512
|
if (data[0] === network.pubKeyHash) {
|
|
1490
|
-
|
|
1491
|
-
return { type: 'pkh', hash: bytes.slice(1, bytes.length - 4) };
|
|
1513
|
+
return { type: 'pkh', hash: data.slice(1) };
|
|
1492
1514
|
} else if (data[0] === network.scriptHash) {
|
|
1493
|
-
const bytes = base58.decode(address);
|
|
1494
1515
|
return {
|
|
1495
1516
|
type: 'sh',
|
|
1496
|
-
hash:
|
|
1517
|
+
hash: data.slice(1),
|
|
1497
1518
|
};
|
|
1498
1519
|
}
|
|
1499
1520
|
throw new Error(`Invalid address prefix=${data[0]}`);
|
|
@@ -1502,6 +1523,10 @@ export function Address(network = NETWORK) {
|
|
|
1502
1523
|
}
|
|
1503
1524
|
// /Address
|
|
1504
1525
|
|
|
1526
|
+
/**
|
|
1527
|
+
* Internal, exported only for backwards-compat. Use `SigHash` instead.
|
|
1528
|
+
* @deprecated
|
|
1529
|
+
*/
|
|
1505
1530
|
export enum SignatureHash {
|
|
1506
1531
|
DEFAULT,
|
|
1507
1532
|
ALL,
|
|
@@ -1509,7 +1534,23 @@ export enum SignatureHash {
|
|
|
1509
1534
|
SINGLE,
|
|
1510
1535
|
ANYONECANPAY = 0x80,
|
|
1511
1536
|
}
|
|
1512
|
-
|
|
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
|
+
}
|
|
1513
1554
|
|
|
1514
1555
|
function unpackSighash(hashType: number) {
|
|
1515
1556
|
const masked = hashType & 0b0011111;
|
|
@@ -1605,9 +1646,13 @@ export type TxOpts = {
|
|
|
1605
1646
|
PSBTVersion?: number;
|
|
1606
1647
|
// Flags
|
|
1607
1648
|
// Allow output scripts to be unknown scripts (probably unspendable)
|
|
1649
|
+
/** @deprecated Use `allowUnknownOutputs` */
|
|
1608
1650
|
allowUnknowOutput?: boolean;
|
|
1651
|
+
allowUnknownOutputs?: boolean;
|
|
1609
1652
|
// Try to sign/finalize unknown input. All bets are off, but there is chance that it will work
|
|
1653
|
+
/** @deprecated Use `allowUnknownInputs` */
|
|
1610
1654
|
allowUnknowInput?: boolean;
|
|
1655
|
+
allowUnknownInputs?: boolean;
|
|
1611
1656
|
// Check input/output scripts for sanity
|
|
1612
1657
|
disableScriptCheck?: boolean;
|
|
1613
1658
|
// There is strange behaviour where tx without outputs encoded with empty output in the end,
|
|
@@ -1619,18 +1664,22 @@ export type TxOpts = {
|
|
|
1619
1664
|
lowR?: boolean; // Use lowR signatures
|
|
1620
1665
|
};
|
|
1621
1666
|
|
|
1622
|
-
|
|
1623
|
-
const isPlainObject = (obj: any) =>
|
|
1624
|
-
Object.prototype.toString.call(obj) === '[object Object]' && obj.constructor === Object;
|
|
1625
|
-
|
|
1667
|
+
const toStr = {}.toString;
|
|
1626
1668
|
function validateOpts(opts: TxOpts) {
|
|
1627
|
-
if (
|
|
1669
|
+
if (opts !== undefined && toStr.call(opts) !== '[object Object]')
|
|
1670
|
+
throw new Error(`Wrong object type for transaction options: ${opts}`);
|
|
1671
|
+
|
|
1628
1672
|
const _opts = {
|
|
1629
1673
|
...opts,
|
|
1674
|
+
// Defaults
|
|
1630
1675
|
version: def(opts.version, DEFAULT_VERSION),
|
|
1631
1676
|
lockTime: def(opts.lockTime, 0),
|
|
1632
1677
|
PSBTVersion: def(opts.PSBTVersion, 0),
|
|
1633
|
-
};
|
|
1678
|
+
};
|
|
1679
|
+
if (typeof _opts.allowUnknowInput !== 'undefined')
|
|
1680
|
+
opts.allowUnknownInputs = _opts.allowUnknowInput;
|
|
1681
|
+
if (typeof _opts.allowUnknowOutput !== 'undefined')
|
|
1682
|
+
opts.allowUnknownOutputs = _opts.allowUnknowOutput;
|
|
1634
1683
|
// 0 and -1 happens in tests
|
|
1635
1684
|
if (![-1, 0, 1, 2].includes(_opts.version)) throw new Error(`Unknown version: ${_opts.version}`);
|
|
1636
1685
|
if (typeof _opts.lockTime !== 'number') throw new Error('Transaction lock time should be number');
|
|
@@ -1641,8 +1690,8 @@ function validateOpts(opts: TxOpts) {
|
|
|
1641
1690
|
throw new Error(`Unknown PSBT version ${_opts.PSBTVersion}`);
|
|
1642
1691
|
// Flags
|
|
1643
1692
|
for (const k of [
|
|
1644
|
-
'
|
|
1645
|
-
'
|
|
1693
|
+
'allowUnknownOutputs',
|
|
1694
|
+
'allowUnknownInputs',
|
|
1646
1695
|
'disableScriptCheck',
|
|
1647
1696
|
'bip174jsCompat',
|
|
1648
1697
|
'allowLegacyWitnessUtxo',
|
|
@@ -1856,7 +1905,7 @@ export class Transaction {
|
|
|
1856
1905
|
out += 4 * CompactSizeLen.encode(this.inputs.length).length;
|
|
1857
1906
|
out += 4 * CompactSizeLen.encode(this.outputs.length).length;
|
|
1858
1907
|
for (const i of this.inputs)
|
|
1859
|
-
|
|
1908
|
+
out += 160 + 4 * VarBytes.encode(i.finalScriptSig || P.EMPTY).length;
|
|
1860
1909
|
for (const o of outputs) out += 32 + 4 * VarBytes.encode(o.script).length;
|
|
1861
1910
|
if (this.hasWitnesses) {
|
|
1862
1911
|
for (const i of this.inputs)
|
|
@@ -1980,11 +2029,11 @@ export class Transaction {
|
|
|
1980
2029
|
PSBTOutputCoder.encode(res);
|
|
1981
2030
|
if (
|
|
1982
2031
|
res.script &&
|
|
1983
|
-
!this.opts.
|
|
2032
|
+
!this.opts.allowUnknownOutputs &&
|
|
1984
2033
|
OutScript.decode(res.script).type === 'unknown'
|
|
1985
2034
|
) {
|
|
1986
2035
|
throw new Error(
|
|
1987
|
-
'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'
|
|
1988
2037
|
);
|
|
1989
2038
|
}
|
|
1990
2039
|
if (!this.opts.disableScriptCheck) checkScript(res.script, res.redeemScript, res.witnessScript);
|
|
@@ -2148,10 +2197,10 @@ export class Transaction {
|
|
|
2148
2197
|
// Used pretty often, should be fast
|
|
2149
2198
|
private prevOut(input: TransactionInput): P.UnwrapCoder<typeof RawOutput> {
|
|
2150
2199
|
if (input.nonWitnessUtxo) {
|
|
2151
|
-
if (input.index === undefined) throw new Error('
|
|
2200
|
+
if (input.index === undefined) throw new Error('Unknown input index');
|
|
2152
2201
|
return input.nonWitnessUtxo.outputs[input.index];
|
|
2153
2202
|
} else if (input.witnessUtxo) return input.witnessUtxo;
|
|
2154
|
-
else throw new Error('Cannot find previous output info
|
|
2203
|
+
else throw new Error('Cannot find previous output info');
|
|
2155
2204
|
}
|
|
2156
2205
|
private inputType(input: TransactionInput) {
|
|
2157
2206
|
let txType = 'legacy';
|
|
@@ -2212,12 +2261,7 @@ export class Transaction {
|
|
|
2212
2261
|
}
|
|
2213
2262
|
|
|
2214
2263
|
// Signer can be privateKey OR instance of bip32 HD stuff
|
|
2215
|
-
signIdx(
|
|
2216
|
-
privateKey: Signer,
|
|
2217
|
-
idx: number,
|
|
2218
|
-
allowedSighash?: SignatureHash[],
|
|
2219
|
-
_auxRand?: Bytes
|
|
2220
|
-
): boolean {
|
|
2264
|
+
signIdx(privateKey: Signer, idx: number, allowedSighash?: SigHash[], _auxRand?: Bytes): boolean {
|
|
2221
2265
|
this.checkInputIdx(idx);
|
|
2222
2266
|
const input = this.inputs[idx];
|
|
2223
2267
|
const inputType = this.inputType(input);
|
|
@@ -2242,7 +2286,8 @@ export class Transaction {
|
|
|
2242
2286
|
}
|
|
2243
2287
|
// Sighash checks
|
|
2244
2288
|
// Just for compat with bitcoinjs-lib, so users won't face unexpected behaviour.
|
|
2245
|
-
if (!allowedSighash) allowedSighash = [inputType.defaultSighash];
|
|
2289
|
+
if (!allowedSighash) allowedSighash = [inputType.defaultSighash as unknown as SigHash];
|
|
2290
|
+
else allowedSighash.forEach(validateSigHash);
|
|
2246
2291
|
const sighash = inputType.sighash;
|
|
2247
2292
|
if (!allowedSighash.includes(sighash)) {
|
|
2248
2293
|
throw new Error(
|
|
@@ -2253,7 +2298,7 @@ export class Transaction {
|
|
|
2253
2298
|
// however this was because of bug in bitcoin-core, which remains here because of consensus.
|
|
2254
2299
|
// If this is absolutely neccessary for your case, please open issue.
|
|
2255
2300
|
// We disable it to avoid complicated workflow where SINGLE will block adding new outputs
|
|
2256
|
-
const {
|
|
2301
|
+
const { sigOutputs } = this.inputSighash(idx);
|
|
2257
2302
|
if (sigOutputs === SignatureHash.SINGLE && idx >= this.outputs.length) {
|
|
2258
2303
|
throw new Error(
|
|
2259
2304
|
`Input with sighash SINGLE, but there is no output with corresponding index=${idx}`
|
|
@@ -2282,7 +2327,7 @@ export class Transaction {
|
|
|
2282
2327
|
input.tapInternalKey,
|
|
2283
2328
|
merkleRoot
|
|
2284
2329
|
);
|
|
2285
|
-
const [taprootPubKey,
|
|
2330
|
+
const [taprootPubKey, _] = taprootTweakPubkey(input.tapInternalKey, merkleRoot);
|
|
2286
2331
|
if (P.equalBytes(taprootPubKey, pubKey)) {
|
|
2287
2332
|
const hash = this.preimageWitnessV1(idx, prevOutScript, sighash, amount);
|
|
2288
2333
|
const sig = concat(
|
|
@@ -2295,18 +2340,13 @@ export class Transaction {
|
|
|
2295
2340
|
}
|
|
2296
2341
|
if (input.tapLeafScript) {
|
|
2297
2342
|
input.tapScriptSig = input.tapScriptSig || [];
|
|
2298
|
-
for (const [
|
|
2343
|
+
for (const [_, _script] of input.tapLeafScript) {
|
|
2299
2344
|
const script = _script.subarray(0, -1);
|
|
2300
2345
|
const scriptDecoded = Script.decode(script);
|
|
2301
2346
|
const ver = _script[_script.length - 1];
|
|
2302
2347
|
const hash = tapLeafHash(script, ver);
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
schnorrPub,
|
|
2306
|
-
cb.internalKey,
|
|
2307
|
-
P.EMPTY // Because we cannot have nested taproot tree
|
|
2308
|
-
);
|
|
2309
|
-
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));
|
|
2310
2350
|
// Skip if there is no public key in tapLeafScript
|
|
2311
2351
|
if (pos === -1) continue;
|
|
2312
2352
|
const msg = this.preimageWitnessV1(
|
|
@@ -2319,12 +2359,12 @@ export class Transaction {
|
|
|
2319
2359
|
ver
|
|
2320
2360
|
);
|
|
2321
2361
|
const sig = concat(
|
|
2322
|
-
schnorr.sign(msg,
|
|
2362
|
+
schnorr.sign(msg, privateKey, _auxRand),
|
|
2323
2363
|
sighash !== SignatureHash.DEFAULT ? new Uint8Array([sighash]) : P.EMPTY
|
|
2324
2364
|
);
|
|
2325
2365
|
this.updateInput(
|
|
2326
2366
|
idx,
|
|
2327
|
-
{ tapScriptSig: [[{ pubKey:
|
|
2367
|
+
{ tapScriptSig: [[{ pubKey: schnorrPub, leafHash: hash }, sig]] },
|
|
2328
2368
|
true
|
|
2329
2369
|
);
|
|
2330
2370
|
signed = true;
|
|
@@ -2428,7 +2468,7 @@ export class Transaction {
|
|
|
2428
2468
|
signatures.push(scriptSig[sigIdx][1]);
|
|
2429
2469
|
}
|
|
2430
2470
|
if (signatures.length !== outScript.pubkeys.length) continue;
|
|
2431
|
-
} else if (outScript.type === 'unknown' && this.opts.
|
|
2471
|
+
} else if (outScript.type === 'unknown' && this.opts.allowUnknownInputs) {
|
|
2432
2472
|
// Trying our best to sign what we can
|
|
2433
2473
|
const scriptDecoded = Script.decode(script);
|
|
2434
2474
|
signatures = scriptSig
|
|
@@ -2485,7 +2525,7 @@ export class Transaction {
|
|
|
2485
2525
|
} else if (inputType.last.type === 'wpkh') {
|
|
2486
2526
|
inputScript = P.EMPTY;
|
|
2487
2527
|
witness = [input.partialSig[0][1], input.partialSig[0][0]];
|
|
2488
|
-
} else if (inputType.last.type === 'unknown' && !this.opts.
|
|
2528
|
+
} else if (inputType.last.type === 'unknown' && !this.opts.allowUnknownInputs)
|
|
2489
2529
|
throw new Error('Unknown inputs not allowed');
|
|
2490
2530
|
|
|
2491
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.
|
|
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",
|
|
@@ -55,10 +57,5 @@
|
|
|
55
57
|
"p2wsh",
|
|
56
58
|
"p2tr"
|
|
57
59
|
],
|
|
58
|
-
"funding":
|
|
59
|
-
|
|
60
|
-
"type": "individual",
|
|
61
|
-
"url": "https://paulmillr.com/funding/"
|
|
62
|
-
}
|
|
63
|
-
]
|
|
64
|
-
}
|
|
60
|
+
"funding": "https://paulmillr.com/funding/"
|
|
61
|
+
}
|