Skip to content

Commit

Permalink
Merge pull request #1742 from bitcoincoretech/p2tr-v1
Browse files Browse the repository at this point in the history
feat: add support for pay to taproot
  • Loading branch information
junderw authored Nov 29, 2022
2 parents 54259d3 + b994d46 commit e6c8a99
Show file tree
Hide file tree
Showing 42 changed files with 6,040 additions and 597 deletions.
309 changes: 120 additions & 189 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bitcoinjs-lib",
"version": "6.0.2",
"version": "6.1.0-rc.0",
"description": "Client-side Bitcoin JavaScript library",
"main": "./src/index.js",
"types": "./src/index.d.ts",
Expand Down Expand Up @@ -50,7 +50,7 @@
],
"dependencies": {
"bech32": "^2.0.0",
"bip174": "^2.0.1",
"bip174": "^2.1.0",
"bs58check": "^2.1.2",
"create-hash": "^1.1.0",
"ripemd160": "^2.0.2",
Expand All @@ -73,7 +73,6 @@
"bip39": "^3.0.2",
"bip65": "^1.0.1",
"bip68": "^1.0.3",
"bn.js": "^4.11.8",
"bs58": "^4.0.0",
"dhttp": "^3.0.0",
"ecpair": "^2.0.1",
Expand All @@ -86,7 +85,7 @@
"randombytes": "^2.1.0",
"regtest-client": "0.2.0",
"rimraf": "^2.6.3",
"tiny-secp256k1": "^2.1.2",
"tiny-secp256k1": "^2.2.0",
"ts-node": "^8.3.0",
"tslint": "^6.1.3",
"typescript": "^4.4.4"
Expand Down
16 changes: 12 additions & 4 deletions src/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ exports.toOutputScript = exports.fromOutputScript = exports.toBech32 = exports.t
const networks = require('./networks');
const payments = require('./payments');
const bscript = require('./script');
const types = require('./types');
const types_1 = require('./types');
const bech32_1 = require('bech32');
const bs58check = require('bs58check');
const { typeforce } = types;
const FUTURE_SEGWIT_MAX_SIZE = 40;
const FUTURE_SEGWIT_MIN_SIZE = 2;
const FUTURE_SEGWIT_MAX_VERSION = 16;
const FUTURE_SEGWIT_MIN_VERSION = 1;
const FUTURE_SEGWIT_MIN_VERSION = 2;
const FUTURE_SEGWIT_VERSION_DIFF = 0x50;
const FUTURE_SEGWIT_VERSION_WARNING =
'WARNING: Sending to a future segwit version address can lead to loss of funds. ' +
Expand Down Expand Up @@ -69,7 +68,10 @@ function fromBech32(address) {
}
exports.fromBech32 = fromBech32;
function toBase58Check(hash, version) {
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments);
(0, types_1.typeforce)(
(0, types_1.tuple)(types_1.Hash160bit, types_1.UInt8),
arguments,
);
const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(version, 0);
hash.copy(payload, 1);
Expand Down Expand Up @@ -99,6 +101,9 @@ function fromOutputScript(output, network) {
try {
return payments.p2wsh({ output, network }).address;
} catch (e) {}
try {
return payments.p2tr({ output, network }).address;
} catch (e) {}
try {
return _toFutureSegwitAddress(output, network);
} catch (e) {}
Expand Down Expand Up @@ -129,6 +134,9 @@ function toOutputScript(address, network) {
return payments.p2wpkh({ hash: decodeBech32.data }).output;
if (decodeBech32.data.length === 32)
return payments.p2wsh({ hash: decodeBech32.data }).output;
} else if (decodeBech32.version === 1) {
if (decodeBech32.data.length === 32)
return payments.p2tr({ pubkey: decodeBech32.data }).output;
} else if (
decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION &&
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&
Expand Down
3 changes: 3 additions & 0 deletions src/ecc_lib.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { TinySecp256k1Interface } from './types';
export declare function initEccLib(eccLib: TinySecp256k1Interface | undefined): void;
export declare function getEccLib(): TinySecp256k1Interface;
91 changes: 91 additions & 0 deletions src/ecc_lib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.getEccLib = exports.initEccLib = void 0;
const _ECCLIB_CACHE = {};
function initEccLib(eccLib) {
if (!eccLib) {
// allow clearing the library
_ECCLIB_CACHE.eccLib = eccLib;
} else if (eccLib !== _ECCLIB_CACHE.eccLib) {
// new instance, verify it
verifyEcc(eccLib);
_ECCLIB_CACHE.eccLib = eccLib;
}
}
exports.initEccLib = initEccLib;
function getEccLib() {
if (!_ECCLIB_CACHE.eccLib)
throw new Error(
'No ECC Library provided. You must call initEccLib() with a valid TinySecp256k1Interface instance',
);
return _ECCLIB_CACHE.eccLib;
}
exports.getEccLib = getEccLib;
const h = hex => Buffer.from(hex, 'hex');
function verifyEcc(ecc) {
assert(typeof ecc.isXOnlyPoint === 'function');
assert(
ecc.isXOnlyPoint(
h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
),
);
assert(
ecc.isXOnlyPoint(
h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffeeffffc2e'),
),
);
assert(
ecc.isXOnlyPoint(
h('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9'),
),
);
assert(
ecc.isXOnlyPoint(
h('0000000000000000000000000000000000000000000000000000000000000001'),
),
);
assert(
!ecc.isXOnlyPoint(
h('0000000000000000000000000000000000000000000000000000000000000000'),
),
);
assert(
!ecc.isXOnlyPoint(
h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'),
),
);
assert(typeof ecc.xOnlyPointAddTweak === 'function');
tweakAddVectors.forEach(t => {
const r = ecc.xOnlyPointAddTweak(h(t.pubkey), h(t.tweak));
if (t.result === null) {
assert(r === null);
} else {
assert(r !== null);
assert(r.parity === t.parity);
assert(Buffer.from(r.xOnlyPubkey).equals(h(t.result)));
}
});
}
function assert(bool) {
if (!bool) throw new Error('ecc library invalid');
}
const tweakAddVectors = [
{
pubkey: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798',
tweak: 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
parity: -1,
result: null,
},
{
pubkey: '1617d38ed8d8657da4d4761e8057bc396ea9e4b9d29776d4be096016dbd2509b',
tweak: 'a8397a935f0dfceba6ba9618f6451ef4d80637abf4e6af2669fbc9de6a8fd2ac',
parity: 1,
result: 'e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf',
},
{
pubkey: '2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991',
tweak: '823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47',
parity: 0,
result: '9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c',
},
];
1 change: 1 addition & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export { Transaction } from './transaction';
export { Network } from './networks';
export { Payment, PaymentCreator, PaymentOpts, Stack, StackElement, } from './payments';
export { Input as TxInput, Output as TxOutput } from './transaction';
export { initEccLib } from './ecc_lib';
9 changes: 8 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0;
exports.initEccLib = exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0;
const address = require('./address');
exports.address = address;
const crypto = require('./crypto');
Expand Down Expand Up @@ -39,3 +39,10 @@ Object.defineProperty(exports, 'Transaction', {
return transaction_1.Transaction;
},
});
var ecc_lib_1 = require('./ecc_lib');
Object.defineProperty(exports, 'initEccLib', {
enumerable: true,
get: function() {
return ecc_lib_1.initEccLib;
},
});
1 change: 1 addition & 0 deletions src/ops.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ const OPS = {
OP_NOP8: 183,
OP_NOP9: 184,
OP_NOP10: 185,
OP_CHECKSIGADD: 186,
OP_PUBKEYHASH: 253,
OP_PUBKEY: 254,
OP_INVALIDOPCODE: 255,
Expand Down
42 changes: 42 additions & 0 deletions src/payments/bip341.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/// <reference types="node" />
import { Tapleaf, Taptree } from '../types';
export declare const LEAF_VERSION_TAPSCRIPT = 192;
export declare const MAX_TAPTREE_DEPTH = 128;
interface HashLeaf {
hash: Buffer;
}
interface HashBranch {
hash: Buffer;
left: HashTree;
right: HashTree;
}
interface TweakedPublicKey {
parity: number;
x: Buffer;
}
/**
* Binary tree representing leaf, branch, and root node hashes of a Taptree.
* Each node contains a hash, and potentially left and right branch hashes.
* This tree is used for 2 purposes: Providing the root hash for tweaking,
* and calculating merkle inclusion proofs when constructing a control block.
*/
export declare type HashTree = HashLeaf | HashBranch;
export declare function rootHashFromPath(controlBlock: Buffer, leafHash: Buffer): Buffer;
/**
* Build a hash tree of merkle nodes from the scripts binary tree.
* @param scriptTree - the tree of scripts to pairwise hash.
*/
export declare function toHashTree(scriptTree: Taptree): HashTree;
/**
* Given a HashTree, finds the path from a particular hash to the root.
* @param node - the root of the tree
* @param hash - the hash to search for
* @returns - array of sibling hashes, from leaf (inclusive) to root
* (exclusive) needed to prove inclusion of the specified hash. undefined if no
* path is found
*/
export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[] | undefined;
export declare function tapleafHash(leaf: Tapleaf): Buffer;
export declare function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer;
export declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null;
export {};
108 changes: 108 additions & 0 deletions src/payments/bip341.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.tweakKey = exports.tapTweakHash = exports.tapleafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.MAX_TAPTREE_DEPTH = exports.LEAF_VERSION_TAPSCRIPT = void 0;
const buffer_1 = require('buffer');
const ecc_lib_1 = require('../ecc_lib');
const bcrypto = require('../crypto');
const bufferutils_1 = require('../bufferutils');
const types_1 = require('../types');
exports.LEAF_VERSION_TAPSCRIPT = 0xc0;
exports.MAX_TAPTREE_DEPTH = 128;
const isHashBranch = ht => 'left' in ht && 'right' in ht;
function rootHashFromPath(controlBlock, leafHash) {
if (controlBlock.length < 33)
throw new TypeError(
`The control-block length is too small. Got ${
controlBlock.length
}, expected min 33.`,
);
const m = (controlBlock.length - 33) / 32;
let kj = leafHash;
for (let j = 0; j < m; j++) {
const ej = controlBlock.slice(33 + 32 * j, 65 + 32 * j);
if (kj.compare(ej) < 0) {
kj = tapBranchHash(kj, ej);
} else {
kj = tapBranchHash(ej, kj);
}
}
return kj;
}
exports.rootHashFromPath = rootHashFromPath;
/**
* Build a hash tree of merkle nodes from the scripts binary tree.
* @param scriptTree - the tree of scripts to pairwise hash.
*/
function toHashTree(scriptTree) {
if ((0, types_1.isTapleaf)(scriptTree))
return { hash: tapleafHash(scriptTree) };
const hashes = [toHashTree(scriptTree[0]), toHashTree(scriptTree[1])];
hashes.sort((a, b) => a.hash.compare(b.hash));
const [left, right] = hashes;
return {
hash: tapBranchHash(left.hash, right.hash),
left,
right,
};
}
exports.toHashTree = toHashTree;
/**
* Given a HashTree, finds the path from a particular hash to the root.
* @param node - the root of the tree
* @param hash - the hash to search for
* @returns - array of sibling hashes, from leaf (inclusive) to root
* (exclusive) needed to prove inclusion of the specified hash. undefined if no
* path is found
*/
function findScriptPath(node, hash) {
if (isHashBranch(node)) {
const leftPath = findScriptPath(node.left, hash);
if (leftPath !== undefined) return [...leftPath, node.right.hash];
const rightPath = findScriptPath(node.right, hash);
if (rightPath !== undefined) return [...rightPath, node.left.hash];
} else if (node.hash.equals(hash)) {
return [];
}
return undefined;
}
exports.findScriptPath = findScriptPath;
function tapleafHash(leaf) {
const version = leaf.version || exports.LEAF_VERSION_TAPSCRIPT;
return bcrypto.taggedHash(
'TapLeaf',
buffer_1.Buffer.concat([
buffer_1.Buffer.from([version]),
serializeScript(leaf.output),
]),
);
}
exports.tapleafHash = tapleafHash;
function tapTweakHash(pubKey, h) {
return bcrypto.taggedHash(
'TapTweak',
buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]),
);
}
exports.tapTweakHash = tapTweakHash;
function tweakKey(pubKey, h) {
if (!buffer_1.Buffer.isBuffer(pubKey)) return null;
if (pubKey.length !== 32) return null;
if (h && h.length !== 32) return null;
const tweakHash = tapTweakHash(pubKey, h);
const res = (0, ecc_lib_1.getEccLib)().xOnlyPointAddTweak(pubKey, tweakHash);
if (!res || res.xOnlyPubkey === null) return null;
return {
parity: res.parity,
x: buffer_1.Buffer.from(res.xOnlyPubkey),
};
}
exports.tweakKey = tweakKey;
function tapBranchHash(a, b) {
return bcrypto.taggedHash('TapBranch', buffer_1.Buffer.concat([a, b]));
}
function serializeScript(s) {
const varintLen = bufferutils_1.varuint.encodingLength(s.length);
const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better
bufferutils_1.varuint.encode(s.length, buffer);
return buffer_1.Buffer.concat([buffer, s]);
}
7 changes: 6 additions & 1 deletion src/payments/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/// <reference types="node" />
import { Network } from '../networks';
import { Taptree } from '../types';
import { p2data as embed } from './embed';
import { p2ms } from './p2ms';
import { p2pk } from './p2pk';
import { p2pkh } from './p2pkh';
import { p2sh } from './p2sh';
import { p2wpkh } from './p2wpkh';
import { p2wsh } from './p2wsh';
import { p2tr } from './p2tr';
export interface Payment {
name?: string;
network?: Network;
Expand All @@ -17,11 +19,14 @@ export interface Payment {
pubkeys?: Buffer[];
input?: Buffer;
signatures?: Buffer[];
internalPubkey?: Buffer;
pubkey?: Buffer;
signature?: Buffer;
address?: string;
hash?: Buffer;
redeem?: Payment;
redeemVersion?: number;
scriptTree?: Taptree;
witness?: Buffer[];
}
export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
Expand All @@ -33,4 +38,4 @@ export interface PaymentOpts {
export declare type StackElement = Buffer | number;
export declare type Stack = StackElement[];
export declare type StackFunction = () => Stack;
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh };
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr };
Loading

0 comments on commit e6c8a99

Please sign in to comment.