-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1742 from bitcoincoretech/p2tr-v1
feat: add support for pay to taproot
- Loading branch information
Showing
42 changed files
with
6,040 additions
and
597 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
}, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.