Skip to content

Commit e6c8a99

Browse files
authored
Merge pull request #1742 from bitcoincoretech/p2tr-v1
feat: add support for pay to taproot
2 parents 54259d3 + b994d46 commit e6c8a99

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+6040
-597
lines changed

package-lock.json

+120-189
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bitcoinjs-lib",
3-
"version": "6.0.2",
3+
"version": "6.1.0-rc.0",
44
"description": "Client-side Bitcoin JavaScript library",
55
"main": "./src/index.js",
66
"types": "./src/index.d.ts",
@@ -50,7 +50,7 @@
5050
],
5151
"dependencies": {
5252
"bech32": "^2.0.0",
53-
"bip174": "^2.0.1",
53+
"bip174": "^2.1.0",
5454
"bs58check": "^2.1.2",
5555
"create-hash": "^1.1.0",
5656
"ripemd160": "^2.0.2",
@@ -73,7 +73,6 @@
7373
"bip39": "^3.0.2",
7474
"bip65": "^1.0.1",
7575
"bip68": "^1.0.3",
76-
"bn.js": "^4.11.8",
7776
"bs58": "^4.0.0",
7877
"dhttp": "^3.0.0",
7978
"ecpair": "^2.0.1",
@@ -86,7 +85,7 @@
8685
"randombytes": "^2.1.0",
8786
"regtest-client": "0.2.0",
8887
"rimraf": "^2.6.3",
89-
"tiny-secp256k1": "^2.1.2",
88+
"tiny-secp256k1": "^2.2.0",
9089
"ts-node": "^8.3.0",
9190
"tslint": "^6.1.3",
9291
"typescript": "^4.4.4"

src/address.js

+12-4
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@ exports.toOutputScript = exports.fromOutputScript = exports.toBech32 = exports.t
44
const networks = require('./networks');
55
const payments = require('./payments');
66
const bscript = require('./script');
7-
const types = require('./types');
7+
const types_1 = require('./types');
88
const bech32_1 = require('bech32');
99
const bs58check = require('bs58check');
10-
const { typeforce } = types;
1110
const FUTURE_SEGWIT_MAX_SIZE = 40;
1211
const FUTURE_SEGWIT_MIN_SIZE = 2;
1312
const FUTURE_SEGWIT_MAX_VERSION = 16;
14-
const FUTURE_SEGWIT_MIN_VERSION = 1;
13+
const FUTURE_SEGWIT_MIN_VERSION = 2;
1514
const FUTURE_SEGWIT_VERSION_DIFF = 0x50;
1615
const FUTURE_SEGWIT_VERSION_WARNING =
1716
'WARNING: Sending to a future segwit version address can lead to loss of funds. ' +
@@ -69,7 +68,10 @@ function fromBech32(address) {
6968
}
7069
exports.fromBech32 = fromBech32;
7170
function toBase58Check(hash, version) {
72-
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments);
71+
(0, types_1.typeforce)(
72+
(0, types_1.tuple)(types_1.Hash160bit, types_1.UInt8),
73+
arguments,
74+
);
7375
const payload = Buffer.allocUnsafe(21);
7476
payload.writeUInt8(version, 0);
7577
hash.copy(payload, 1);
@@ -99,6 +101,9 @@ function fromOutputScript(output, network) {
99101
try {
100102
return payments.p2wsh({ output, network }).address;
101103
} catch (e) {}
104+
try {
105+
return payments.p2tr({ output, network }).address;
106+
} catch (e) {}
102107
try {
103108
return _toFutureSegwitAddress(output, network);
104109
} catch (e) {}
@@ -129,6 +134,9 @@ function toOutputScript(address, network) {
129134
return payments.p2wpkh({ hash: decodeBech32.data }).output;
130135
if (decodeBech32.data.length === 32)
131136
return payments.p2wsh({ hash: decodeBech32.data }).output;
137+
} else if (decodeBech32.version === 1) {
138+
if (decodeBech32.data.length === 32)
139+
return payments.p2tr({ pubkey: decodeBech32.data }).output;
132140
} else if (
133141
decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION &&
134142
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&

src/ecc_lib.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { TinySecp256k1Interface } from './types';
2+
export declare function initEccLib(eccLib: TinySecp256k1Interface | undefined): void;
3+
export declare function getEccLib(): TinySecp256k1Interface;

src/ecc_lib.js

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
'use strict';
2+
Object.defineProperty(exports, '__esModule', { value: true });
3+
exports.getEccLib = exports.initEccLib = void 0;
4+
const _ECCLIB_CACHE = {};
5+
function initEccLib(eccLib) {
6+
if (!eccLib) {
7+
// allow clearing the library
8+
_ECCLIB_CACHE.eccLib = eccLib;
9+
} else if (eccLib !== _ECCLIB_CACHE.eccLib) {
10+
// new instance, verify it
11+
verifyEcc(eccLib);
12+
_ECCLIB_CACHE.eccLib = eccLib;
13+
}
14+
}
15+
exports.initEccLib = initEccLib;
16+
function getEccLib() {
17+
if (!_ECCLIB_CACHE.eccLib)
18+
throw new Error(
19+
'No ECC Library provided. You must call initEccLib() with a valid TinySecp256k1Interface instance',
20+
);
21+
return _ECCLIB_CACHE.eccLib;
22+
}
23+
exports.getEccLib = getEccLib;
24+
const h = hex => Buffer.from(hex, 'hex');
25+
function verifyEcc(ecc) {
26+
assert(typeof ecc.isXOnlyPoint === 'function');
27+
assert(
28+
ecc.isXOnlyPoint(
29+
h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
30+
),
31+
);
32+
assert(
33+
ecc.isXOnlyPoint(
34+
h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffeeffffc2e'),
35+
),
36+
);
37+
assert(
38+
ecc.isXOnlyPoint(
39+
h('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9'),
40+
),
41+
);
42+
assert(
43+
ecc.isXOnlyPoint(
44+
h('0000000000000000000000000000000000000000000000000000000000000001'),
45+
),
46+
);
47+
assert(
48+
!ecc.isXOnlyPoint(
49+
h('0000000000000000000000000000000000000000000000000000000000000000'),
50+
),
51+
);
52+
assert(
53+
!ecc.isXOnlyPoint(
54+
h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'),
55+
),
56+
);
57+
assert(typeof ecc.xOnlyPointAddTweak === 'function');
58+
tweakAddVectors.forEach(t => {
59+
const r = ecc.xOnlyPointAddTweak(h(t.pubkey), h(t.tweak));
60+
if (t.result === null) {
61+
assert(r === null);
62+
} else {
63+
assert(r !== null);
64+
assert(r.parity === t.parity);
65+
assert(Buffer.from(r.xOnlyPubkey).equals(h(t.result)));
66+
}
67+
});
68+
}
69+
function assert(bool) {
70+
if (!bool) throw new Error('ecc library invalid');
71+
}
72+
const tweakAddVectors = [
73+
{
74+
pubkey: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798',
75+
tweak: 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
76+
parity: -1,
77+
result: null,
78+
},
79+
{
80+
pubkey: '1617d38ed8d8657da4d4761e8057bc396ea9e4b9d29776d4be096016dbd2509b',
81+
tweak: 'a8397a935f0dfceba6ba9618f6451ef4d80637abf4e6af2669fbc9de6a8fd2ac',
82+
parity: 1,
83+
result: 'e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf',
84+
},
85+
{
86+
pubkey: '2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991',
87+
tweak: '823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47',
88+
parity: 0,
89+
result: '9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c',
90+
},
91+
];

src/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export { Transaction } from './transaction';
1212
export { Network } from './networks';
1313
export { Payment, PaymentCreator, PaymentOpts, Stack, StackElement, } from './payments';
1414
export { Input as TxInput, Output as TxOutput } from './transaction';
15+
export { initEccLib } from './ecc_lib';

src/index.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22
Object.defineProperty(exports, '__esModule', { value: true });
3-
exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0;
3+
exports.initEccLib = exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0;
44
const address = require('./address');
55
exports.address = address;
66
const crypto = require('./crypto');
@@ -39,3 +39,10 @@ Object.defineProperty(exports, 'Transaction', {
3939
return transaction_1.Transaction;
4040
},
4141
});
42+
var ecc_lib_1 = require('./ecc_lib');
43+
Object.defineProperty(exports, 'initEccLib', {
44+
enumerable: true,
45+
get: function() {
46+
return ecc_lib_1.initEccLib;
47+
},
48+
});

src/ops.js

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ const OPS = {
117117
OP_NOP8: 183,
118118
OP_NOP9: 184,
119119
OP_NOP10: 185,
120+
OP_CHECKSIGADD: 186,
120121
OP_PUBKEYHASH: 253,
121122
OP_PUBKEY: 254,
122123
OP_INVALIDOPCODE: 255,

src/payments/bip341.d.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/// <reference types="node" />
2+
import { Tapleaf, Taptree } from '../types';
3+
export declare const LEAF_VERSION_TAPSCRIPT = 192;
4+
export declare const MAX_TAPTREE_DEPTH = 128;
5+
interface HashLeaf {
6+
hash: Buffer;
7+
}
8+
interface HashBranch {
9+
hash: Buffer;
10+
left: HashTree;
11+
right: HashTree;
12+
}
13+
interface TweakedPublicKey {
14+
parity: number;
15+
x: Buffer;
16+
}
17+
/**
18+
* Binary tree representing leaf, branch, and root node hashes of a Taptree.
19+
* Each node contains a hash, and potentially left and right branch hashes.
20+
* This tree is used for 2 purposes: Providing the root hash for tweaking,
21+
* and calculating merkle inclusion proofs when constructing a control block.
22+
*/
23+
export declare type HashTree = HashLeaf | HashBranch;
24+
export declare function rootHashFromPath(controlBlock: Buffer, leafHash: Buffer): Buffer;
25+
/**
26+
* Build a hash tree of merkle nodes from the scripts binary tree.
27+
* @param scriptTree - the tree of scripts to pairwise hash.
28+
*/
29+
export declare function toHashTree(scriptTree: Taptree): HashTree;
30+
/**
31+
* Given a HashTree, finds the path from a particular hash to the root.
32+
* @param node - the root of the tree
33+
* @param hash - the hash to search for
34+
* @returns - array of sibling hashes, from leaf (inclusive) to root
35+
* (exclusive) needed to prove inclusion of the specified hash. undefined if no
36+
* path is found
37+
*/
38+
export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[] | undefined;
39+
export declare function tapleafHash(leaf: Tapleaf): Buffer;
40+
export declare function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer;
41+
export declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null;
42+
export {};

src/payments/bip341.js

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
'use strict';
2+
Object.defineProperty(exports, '__esModule', { value: true });
3+
exports.tweakKey = exports.tapTweakHash = exports.tapleafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.MAX_TAPTREE_DEPTH = exports.LEAF_VERSION_TAPSCRIPT = void 0;
4+
const buffer_1 = require('buffer');
5+
const ecc_lib_1 = require('../ecc_lib');
6+
const bcrypto = require('../crypto');
7+
const bufferutils_1 = require('../bufferutils');
8+
const types_1 = require('../types');
9+
exports.LEAF_VERSION_TAPSCRIPT = 0xc0;
10+
exports.MAX_TAPTREE_DEPTH = 128;
11+
const isHashBranch = ht => 'left' in ht && 'right' in ht;
12+
function rootHashFromPath(controlBlock, leafHash) {
13+
if (controlBlock.length < 33)
14+
throw new TypeError(
15+
`The control-block length is too small. Got ${
16+
controlBlock.length
17+
}, expected min 33.`,
18+
);
19+
const m = (controlBlock.length - 33) / 32;
20+
let kj = leafHash;
21+
for (let j = 0; j < m; j++) {
22+
const ej = controlBlock.slice(33 + 32 * j, 65 + 32 * j);
23+
if (kj.compare(ej) < 0) {
24+
kj = tapBranchHash(kj, ej);
25+
} else {
26+
kj = tapBranchHash(ej, kj);
27+
}
28+
}
29+
return kj;
30+
}
31+
exports.rootHashFromPath = rootHashFromPath;
32+
/**
33+
* Build a hash tree of merkle nodes from the scripts binary tree.
34+
* @param scriptTree - the tree of scripts to pairwise hash.
35+
*/
36+
function toHashTree(scriptTree) {
37+
if ((0, types_1.isTapleaf)(scriptTree))
38+
return { hash: tapleafHash(scriptTree) };
39+
const hashes = [toHashTree(scriptTree[0]), toHashTree(scriptTree[1])];
40+
hashes.sort((a, b) => a.hash.compare(b.hash));
41+
const [left, right] = hashes;
42+
return {
43+
hash: tapBranchHash(left.hash, right.hash),
44+
left,
45+
right,
46+
};
47+
}
48+
exports.toHashTree = toHashTree;
49+
/**
50+
* Given a HashTree, finds the path from a particular hash to the root.
51+
* @param node - the root of the tree
52+
* @param hash - the hash to search for
53+
* @returns - array of sibling hashes, from leaf (inclusive) to root
54+
* (exclusive) needed to prove inclusion of the specified hash. undefined if no
55+
* path is found
56+
*/
57+
function findScriptPath(node, hash) {
58+
if (isHashBranch(node)) {
59+
const leftPath = findScriptPath(node.left, hash);
60+
if (leftPath !== undefined) return [...leftPath, node.right.hash];
61+
const rightPath = findScriptPath(node.right, hash);
62+
if (rightPath !== undefined) return [...rightPath, node.left.hash];
63+
} else if (node.hash.equals(hash)) {
64+
return [];
65+
}
66+
return undefined;
67+
}
68+
exports.findScriptPath = findScriptPath;
69+
function tapleafHash(leaf) {
70+
const version = leaf.version || exports.LEAF_VERSION_TAPSCRIPT;
71+
return bcrypto.taggedHash(
72+
'TapLeaf',
73+
buffer_1.Buffer.concat([
74+
buffer_1.Buffer.from([version]),
75+
serializeScript(leaf.output),
76+
]),
77+
);
78+
}
79+
exports.tapleafHash = tapleafHash;
80+
function tapTweakHash(pubKey, h) {
81+
return bcrypto.taggedHash(
82+
'TapTweak',
83+
buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]),
84+
);
85+
}
86+
exports.tapTweakHash = tapTweakHash;
87+
function tweakKey(pubKey, h) {
88+
if (!buffer_1.Buffer.isBuffer(pubKey)) return null;
89+
if (pubKey.length !== 32) return null;
90+
if (h && h.length !== 32) return null;
91+
const tweakHash = tapTweakHash(pubKey, h);
92+
const res = (0, ecc_lib_1.getEccLib)().xOnlyPointAddTweak(pubKey, tweakHash);
93+
if (!res || res.xOnlyPubkey === null) return null;
94+
return {
95+
parity: res.parity,
96+
x: buffer_1.Buffer.from(res.xOnlyPubkey),
97+
};
98+
}
99+
exports.tweakKey = tweakKey;
100+
function tapBranchHash(a, b) {
101+
return bcrypto.taggedHash('TapBranch', buffer_1.Buffer.concat([a, b]));
102+
}
103+
function serializeScript(s) {
104+
const varintLen = bufferutils_1.varuint.encodingLength(s.length);
105+
const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better
106+
bufferutils_1.varuint.encode(s.length, buffer);
107+
return buffer_1.Buffer.concat([buffer, s]);
108+
}

src/payments/index.d.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
/// <reference types="node" />
22
import { Network } from '../networks';
3+
import { Taptree } from '../types';
34
import { p2data as embed } from './embed';
45
import { p2ms } from './p2ms';
56
import { p2pk } from './p2pk';
67
import { p2pkh } from './p2pkh';
78
import { p2sh } from './p2sh';
89
import { p2wpkh } from './p2wpkh';
910
import { p2wsh } from './p2wsh';
11+
import { p2tr } from './p2tr';
1012
export interface Payment {
1113
name?: string;
1214
network?: Network;
@@ -17,11 +19,14 @@ export interface Payment {
1719
pubkeys?: Buffer[];
1820
input?: Buffer;
1921
signatures?: Buffer[];
22+
internalPubkey?: Buffer;
2023
pubkey?: Buffer;
2124
signature?: Buffer;
2225
address?: string;
2326
hash?: Buffer;
2427
redeem?: Payment;
28+
redeemVersion?: number;
29+
scriptTree?: Taptree;
2530
witness?: Buffer[];
2631
}
2732
export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
@@ -33,4 +38,4 @@ export interface PaymentOpts {
3338
export declare type StackElement = Buffer | number;
3439
export declare type Stack = StackElement[];
3540
export declare type StackFunction = () => Stack;
36-
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh };
41+
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr };

0 commit comments

Comments
 (0)