@scure/btc-signer 1.0.1 → 1.1.1
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 +44 -23
- package/index.d.ts +477 -597
- package/index.d.ts.map +1 -1
- package/index.js +92 -50
- package/index.js.map +1 -1
- package/index.ts +105 -62
- package/package.json +13 -11
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, createBase58check, bech32, bech32m } from '@scure/base';
|
|
6
6
|
import type { Coder } from '@scure/base';
|
|
7
7
|
import * as P from 'micro-packed';
|
|
8
8
|
|
|
@@ -24,7 +24,7 @@ const hash160 = (msg: Bytes) => ripemd160(sha256(msg));
|
|
|
24
24
|
const sha256x2 = (...msgs: Bytes[]) => sha256(sha256(concat(...msgs)));
|
|
25
25
|
const concat = P.concatBytes;
|
|
26
26
|
// Make base58check work
|
|
27
|
-
export const base58check =
|
|
27
|
+
export const base58check = createBase58check(sha256);
|
|
28
28
|
|
|
29
29
|
export function cloneDeep<T>(obj: T): T {
|
|
30
30
|
if (Array.isArray(obj)) return obj.map((i) => cloneDeep(i)) as unknown as T;
|
|
@@ -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',
|
|
@@ -1916,11 +1967,12 @@ export class Transaction {
|
|
|
1916
1967
|
// however in case of nonWitnessUtxo it is better to expect string, since constructing this complex object will be difficult for user
|
|
1917
1968
|
if (typeof nonWitnessUtxo === 'string') nonWitnessUtxo = hex.decode(nonWitnessUtxo);
|
|
1918
1969
|
if (isBytes(nonWitnessUtxo)) nonWitnessUtxo = RawTx.decode(nonWitnessUtxo);
|
|
1919
|
-
if (nonWitnessUtxo
|
|
1970
|
+
if (!('nonWitnessUtxo' in i) && nonWitnessUtxo === undefined)
|
|
1971
|
+
nonWitnessUtxo = cur?.nonWitnessUtxo;
|
|
1920
1972
|
if (typeof txid === 'string') txid = hex.decode(txid);
|
|
1921
1973
|
if (txid === undefined) txid = cur?.txid;
|
|
1922
1974
|
let res: PSBTKeyMapKeys<typeof PSBTInput> = { ...cur, ...i, nonWitnessUtxo, txid };
|
|
1923
|
-
if (res.nonWitnessUtxo === undefined) delete res.nonWitnessUtxo;
|
|
1975
|
+
if (!('nonWitnessUtxo' in i) && res.nonWitnessUtxo === undefined) delete res.nonWitnessUtxo;
|
|
1924
1976
|
if (res.sequence === undefined) res.sequence = DEFAULT_SEQUENCE;
|
|
1925
1977
|
if (res.tapMerkleRoot === null) delete res.tapMerkleRoot;
|
|
1926
1978
|
res = mergeKeyMap(PSBTInput, res, cur, allowedFields);
|
|
@@ -1978,11 +2030,11 @@ export class Transaction {
|
|
|
1978
2030
|
PSBTOutputCoder.encode(res);
|
|
1979
2031
|
if (
|
|
1980
2032
|
res.script &&
|
|
1981
|
-
!this.opts.
|
|
2033
|
+
!this.opts.allowUnknownOutputs &&
|
|
1982
2034
|
OutScript.decode(res.script).type === 'unknown'
|
|
1983
2035
|
) {
|
|
1984
2036
|
throw new Error(
|
|
1985
|
-
'Transaction/output: unknown output script type, there is a chance that input is unspendable. Pass
|
|
2037
|
+
'Transaction/output: unknown output script type, there is a chance that input is unspendable. Pass allowUnknownScript=true, if you sure'
|
|
1986
2038
|
);
|
|
1987
2039
|
}
|
|
1988
2040
|
if (!this.opts.disableScriptCheck) checkScript(res.script, res.redeemScript, res.witnessScript);
|
|
@@ -2146,10 +2198,10 @@ export class Transaction {
|
|
|
2146
2198
|
// Used pretty often, should be fast
|
|
2147
2199
|
private prevOut(input: TransactionInput): P.UnwrapCoder<typeof RawOutput> {
|
|
2148
2200
|
if (input.nonWitnessUtxo) {
|
|
2149
|
-
if (input.index === undefined) throw new Error('
|
|
2201
|
+
if (input.index === undefined) throw new Error('Unknown input index');
|
|
2150
2202
|
return input.nonWitnessUtxo.outputs[input.index];
|
|
2151
2203
|
} else if (input.witnessUtxo) return input.witnessUtxo;
|
|
2152
|
-
else throw new Error('Cannot find previous output info
|
|
2204
|
+
else throw new Error('Cannot find previous output info');
|
|
2153
2205
|
}
|
|
2154
2206
|
private inputType(input: TransactionInput) {
|
|
2155
2207
|
let txType = 'legacy';
|
|
@@ -2210,12 +2262,7 @@ export class Transaction {
|
|
|
2210
2262
|
}
|
|
2211
2263
|
|
|
2212
2264
|
// 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 {
|
|
2265
|
+
signIdx(privateKey: Signer, idx: number, allowedSighash?: SigHash[], _auxRand?: Bytes): boolean {
|
|
2219
2266
|
this.checkInputIdx(idx);
|
|
2220
2267
|
const input = this.inputs[idx];
|
|
2221
2268
|
const inputType = this.inputType(input);
|
|
@@ -2240,7 +2287,8 @@ export class Transaction {
|
|
|
2240
2287
|
}
|
|
2241
2288
|
// Sighash checks
|
|
2242
2289
|
// Just for compat with bitcoinjs-lib, so users won't face unexpected behaviour.
|
|
2243
|
-
if (!allowedSighash) allowedSighash = [inputType.defaultSighash];
|
|
2290
|
+
if (!allowedSighash) allowedSighash = [inputType.defaultSighash as unknown as SigHash];
|
|
2291
|
+
else allowedSighash.forEach(validateSigHash);
|
|
2244
2292
|
const sighash = inputType.sighash;
|
|
2245
2293
|
if (!allowedSighash.includes(sighash)) {
|
|
2246
2294
|
throw new Error(
|
|
@@ -2251,7 +2299,7 @@ export class Transaction {
|
|
|
2251
2299
|
// however this was because of bug in bitcoin-core, which remains here because of consensus.
|
|
2252
2300
|
// If this is absolutely neccessary for your case, please open issue.
|
|
2253
2301
|
// We disable it to avoid complicated workflow where SINGLE will block adding new outputs
|
|
2254
|
-
const {
|
|
2302
|
+
const { sigOutputs } = this.inputSighash(idx);
|
|
2255
2303
|
if (sigOutputs === SignatureHash.SINGLE && idx >= this.outputs.length) {
|
|
2256
2304
|
throw new Error(
|
|
2257
2305
|
`Input with sighash SINGLE, but there is no output with corresponding index=${idx}`
|
|
@@ -2280,7 +2328,7 @@ export class Transaction {
|
|
|
2280
2328
|
input.tapInternalKey,
|
|
2281
2329
|
merkleRoot
|
|
2282
2330
|
);
|
|
2283
|
-
const [taprootPubKey,
|
|
2331
|
+
const [taprootPubKey, _] = taprootTweakPubkey(input.tapInternalKey, merkleRoot);
|
|
2284
2332
|
if (P.equalBytes(taprootPubKey, pubKey)) {
|
|
2285
2333
|
const hash = this.preimageWitnessV1(idx, prevOutScript, sighash, amount);
|
|
2286
2334
|
const sig = concat(
|
|
@@ -2293,18 +2341,13 @@ export class Transaction {
|
|
|
2293
2341
|
}
|
|
2294
2342
|
if (input.tapLeafScript) {
|
|
2295
2343
|
input.tapScriptSig = input.tapScriptSig || [];
|
|
2296
|
-
for (const [
|
|
2344
|
+
for (const [_, _script] of input.tapLeafScript) {
|
|
2297
2345
|
const script = _script.subarray(0, -1);
|
|
2298
2346
|
const scriptDecoded = Script.decode(script);
|
|
2299
2347
|
const ver = _script[_script.length - 1];
|
|
2300
2348
|
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));
|
|
2349
|
+
// NOTE: no need to tweak internal key here, since we don't support nested p2tr
|
|
2350
|
+
const pos = scriptDecoded.findIndex((i) => isBytes(i) && P.equalBytes(i, schnorrPub));
|
|
2308
2351
|
// Skip if there is no public key in tapLeafScript
|
|
2309
2352
|
if (pos === -1) continue;
|
|
2310
2353
|
const msg = this.preimageWitnessV1(
|
|
@@ -2317,12 +2360,12 @@ export class Transaction {
|
|
|
2317
2360
|
ver
|
|
2318
2361
|
);
|
|
2319
2362
|
const sig = concat(
|
|
2320
|
-
schnorr.sign(msg,
|
|
2363
|
+
schnorr.sign(msg, privateKey, _auxRand),
|
|
2321
2364
|
sighash !== SignatureHash.DEFAULT ? new Uint8Array([sighash]) : P.EMPTY
|
|
2322
2365
|
);
|
|
2323
2366
|
this.updateInput(
|
|
2324
2367
|
idx,
|
|
2325
|
-
{ tapScriptSig: [[{ pubKey:
|
|
2368
|
+
{ tapScriptSig: [[{ pubKey: schnorrPub, leafHash: hash }, sig]] },
|
|
2326
2369
|
true
|
|
2327
2370
|
);
|
|
2328
2371
|
signed = true;
|
|
@@ -2426,7 +2469,7 @@ export class Transaction {
|
|
|
2426
2469
|
signatures.push(scriptSig[sigIdx][1]);
|
|
2427
2470
|
}
|
|
2428
2471
|
if (signatures.length !== outScript.pubkeys.length) continue;
|
|
2429
|
-
} else if (outScript.type === 'unknown' && this.opts.
|
|
2472
|
+
} else if (outScript.type === 'unknown' && this.opts.allowUnknownInputs) {
|
|
2430
2473
|
// Trying our best to sign what we can
|
|
2431
2474
|
const scriptDecoded = Script.decode(script);
|
|
2432
2475
|
signatures = scriptSig
|
|
@@ -2483,7 +2526,7 @@ export class Transaction {
|
|
|
2483
2526
|
} else if (inputType.last.type === 'wpkh') {
|
|
2484
2527
|
inputScript = P.EMPTY;
|
|
2485
2528
|
witness = [input.partialSig[0][1], input.partialSig[0][0]];
|
|
2486
|
-
} else if (inputType.last.type === 'unknown' && !this.opts.
|
|
2529
|
+
} else if (inputType.last.type === 'unknown' && !this.opts.allowUnknownInputs)
|
|
2487
2530
|
throw new Error('Unknown inputs not allowed');
|
|
2488
2531
|
|
|
2489
2532
|
// Create final scripts (generic part)
|
|
@@ -2547,7 +2590,7 @@ export class Transaction {
|
|
|
2547
2590
|
}
|
|
2548
2591
|
clone() {
|
|
2549
2592
|
// deepClone probably faster, but this enforces that encoding is valid
|
|
2550
|
-
return Transaction.fromPSBT(this.toPSBT(
|
|
2593
|
+
return Transaction.fromPSBT(this.toPSBT(this.opts.PSBTVersion), this.opts);
|
|
2551
2594
|
}
|
|
2552
2595
|
}
|
|
2553
2596
|
// User facing API?
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scure/btc-signer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
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,17 +14,17 @@
|
|
|
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.
|
|
20
|
-
"micro-packed": "~0.
|
|
17
|
+
"@noble/curves": "~1.3.0",
|
|
18
|
+
"@noble/hashes": "~1.3.3",
|
|
19
|
+
"@scure/base": "~1.1.4",
|
|
20
|
+
"micro-packed": "~0.4.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@scure/bip32": "~1.3.
|
|
24
|
-
"micro-packed-debugger": "0.
|
|
23
|
+
"@scure/bip32": "~1.3.3",
|
|
24
|
+
"micro-packed-debugger": "0.4.1",
|
|
25
25
|
"micro-should": "0.4.0",
|
|
26
|
-
"prettier": "
|
|
27
|
-
"typescript": "5.
|
|
26
|
+
"prettier": "3.1.1",
|
|
27
|
+
"typescript": "5.3.2"
|
|
28
28
|
},
|
|
29
29
|
"author": "Paul Miller (https://paulmillr.com)",
|
|
30
30
|
"license": "MIT",
|
|
@@ -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
|
+
}
|