@okxweb3/coin-bitcoin
Version:
@ok/coin-bitcoin is a Bitcoin SDK for building Web3 wallets and applications. It supports BTC, BSV, DOGE, LTC, and TBTC, enabling private key management, transaction signing, address generation, and inscriptions like BRC-20, Runes, CAT, and Atomicals.
300 lines • 12.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.p2tr = void 0;
const buffer_1 = require("buffer");
const networks_1 = require("../networks");
const bscript = __importStar(require("../script"));
const types_1 = require("../types");
const bip341_1 = require("./bip341");
const lazy = __importStar(require("./lazy"));
const coin_base_1 = require("@okxweb3/coin-base");
const OPS = bscript.OPS;
const TAPROOT_WITNESS_VERSION = 0x01;
const ANNEX_PREFIX = 0x50;
function p2tr(a, opts) {
if (!a.address &&
!a.output &&
!a.pubkey &&
!a.internalPubkey &&
!(a.witness && a.witness.length > 1))
throw new TypeError('Not enough data');
opts = Object.assign({ validate: true }, opts || {});
(0, types_1.typeforce)({
address: types_1.typeforce.maybe(types_1.typeforce.String),
input: types_1.typeforce.maybe(types_1.typeforce.BufferN(0)),
network: types_1.typeforce.maybe(types_1.typeforce.Object),
output: types_1.typeforce.maybe(types_1.typeforce.BufferN(34)),
internalPubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)),
hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)),
pubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)),
witness: types_1.typeforce.maybe(types_1.typeforce.arrayOf(types_1.typeforce.Buffer)),
scriptTree: types_1.typeforce.maybe(types_1.isTaptree),
redeem: types_1.typeforce.maybe({
output: types_1.typeforce.maybe(types_1.typeforce.Buffer),
redeemVersion: types_1.typeforce.maybe(types_1.typeforce.Number),
witness: types_1.typeforce.maybe(types_1.typeforce.arrayOf(types_1.typeforce.Buffer)),
}),
redeemVersion: types_1.typeforce.maybe(types_1.typeforce.Number),
}, a);
const _address = lazy.value(() => {
const result = coin_base_1.base.bech32m.decode(a.address);
const version = result.words.shift();
const data = coin_base_1.base.bech32m.fromWords(result.words);
return {
version,
prefix: result.prefix,
data: buffer_1.Buffer.from(data),
};
});
const _witness = lazy.value(() => {
if (!a.witness || !a.witness.length)
return;
if (a.witness.length >= 2 &&
a.witness[a.witness.length - 1][0] === ANNEX_PREFIX) {
return a.witness.slice(0, -1);
}
return a.witness.slice();
});
const _hashTree = lazy.value(() => {
if (a.scriptTree)
return (0, bip341_1.toHashTree)(a.scriptTree);
if (a.hash)
return { hash: a.hash };
return;
});
const network = a.network || networks_1.bitcoin;
const o = { name: 'p2tr', network };
lazy.prop(o, 'address', () => {
if (!o.pubkey)
return;
const words = coin_base_1.base.bech32m.toWords(o.pubkey);
words.unshift(TAPROOT_WITNESS_VERSION);
return coin_base_1.base.bech32m.encode(network.bech32, words);
});
lazy.prop(o, 'hash', () => {
const hashTree = _hashTree();
if (hashTree)
return hashTree.hash;
const w = _witness();
if (w && w.length > 1) {
const controlBlock = w[w.length - 1];
const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK;
const script = w[w.length - 2];
const leafHash = (0, bip341_1.tapleafHash)({ output: script, version: leafVersion });
return (0, bip341_1.rootHashFromPath)(controlBlock, leafHash);
}
return null;
});
lazy.prop(o, 'output', () => {
if (!o.pubkey)
return;
return bscript.compile([OPS.OP_1, o.pubkey]);
});
lazy.prop(o, 'redeemVersion', () => {
if (a.redeemVersion)
return a.redeemVersion;
if (a.redeem &&
a.redeem.redeemVersion !== undefined &&
a.redeem.redeemVersion !== null) {
return a.redeem.redeemVersion;
}
return bip341_1.LEAF_VERSION_TAPSCRIPT;
});
lazy.prop(o, 'redeem', () => {
const witness = _witness();
if (!witness || witness.length < 2)
return;
return {
output: witness[witness.length - 2],
witness: witness.slice(0, -2),
redeemVersion: witness[witness.length - 1][0] & types_1.TAPLEAF_VERSION_MASK,
};
});
lazy.prop(o, 'pubkey', () => {
if (a.pubkey)
return a.pubkey;
if (a.output)
return a.output.slice(2);
if (a.address)
return _address().data;
if (o.internalPubkey) {
const tweakedKey = (0, bip341_1.tweakKey)(o.internalPubkey, o.hash);
if (tweakedKey)
return tweakedKey.x;
}
});
lazy.prop(o, 'internalPubkey', () => {
if (a.internalPubkey)
return a.internalPubkey;
const witness = _witness();
if (witness && witness.length > 1)
return witness[witness.length - 1].slice(1, 33);
});
lazy.prop(o, 'signature', () => {
if (a.signature)
return a.signature;
const witness = _witness();
if (!witness || witness.length !== 1)
return;
return witness[0];
});
lazy.prop(o, 'witness', () => {
if (a.witness)
return a.witness;
const hashTree = _hashTree();
if (hashTree && a.redeem && a.redeem.output && a.internalPubkey) {
const leafHash = (0, bip341_1.tapleafHash)({
output: a.redeem.output,
version: o.redeemVersion,
});
const path = (0, bip341_1.findScriptPath)(hashTree, leafHash);
if (!path)
return;
const outputKey = (0, bip341_1.tweakKey)(a.internalPubkey, hashTree.hash);
if (!outputKey)
return;
const controlBock = buffer_1.Buffer.concat([
buffer_1.Buffer.from([o.redeemVersion | outputKey.parity]),
a.internalPubkey,
].concat(path));
return [a.redeem.output, controlBock];
}
if (a.signature)
return [a.signature];
});
if (opts.validate) {
let pubkey = buffer_1.Buffer.from([]);
if (a.address) {
if (network && network.bech32 !== _address().prefix)
throw new TypeError('Invalid prefix or Network mismatch');
if (_address().version !== TAPROOT_WITNESS_VERSION)
throw new TypeError('Invalid address version');
if (_address().data.length !== 32)
throw new TypeError('Invalid address data');
pubkey = _address().data;
}
if (a.pubkey) {
if (pubkey.length > 0 && !pubkey.equals(a.pubkey))
throw new TypeError('Pubkey mismatch');
else
pubkey = a.pubkey;
}
if (a.output) {
if (a.output.length !== 34 ||
a.output[0] !== OPS.OP_1 ||
a.output[1] !== 0x20)
throw new TypeError('Output is invalid');
if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2)))
throw new TypeError('Pubkey mismatch');
else
pubkey = a.output.slice(2);
}
if (a.internalPubkey) {
const tweakedKey = (0, bip341_1.tweakKey)(a.internalPubkey, o.hash);
if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x))
throw new TypeError('Pubkey mismatch');
else
pubkey = tweakedKey.x;
}
if (pubkey && pubkey.length) {
if (pubkey.length !== 32)
throw new TypeError('Invalid pubkey for p2tr');
}
const hashTree = _hashTree();
if (a.hash && hashTree) {
if (!a.hash.equals(hashTree.hash))
throw new TypeError('Hash mismatch');
}
if (a.redeem && a.redeem.output && hashTree) {
const leafHash = (0, bip341_1.tapleafHash)({
output: a.redeem.output,
version: o.redeemVersion,
});
if (!(0, bip341_1.findScriptPath)(hashTree, leafHash))
throw new TypeError('Redeem script not in tree');
}
const witness = _witness();
if (a.redeem && o.redeem) {
if (a.redeem.redeemVersion) {
if (a.redeem.redeemVersion !== o.redeem.redeemVersion)
throw new TypeError('Redeem.redeemVersion and witness mismatch');
}
if (a.redeem.output) {
if (bscript.decompile(a.redeem.output).length === 0)
throw new TypeError('Redeem.output is invalid');
if (o.redeem.output && !a.redeem.output.equals(o.redeem.output))
throw new TypeError('Redeem.output and witness mismatch');
}
if (a.redeem.witness) {
if (o.redeem.witness &&
!stacksEqual(a.redeem.witness, o.redeem.witness))
throw new TypeError('Redeem.witness and witness mismatch');
}
}
if (witness && witness.length) {
if (witness.length === 1) {
if (a.signature && !a.signature.equals(witness[0]))
throw new TypeError('Signature mismatch');
}
else {
const controlBlock = witness[witness.length - 1];
if (controlBlock.length < 33)
throw new TypeError(`The control-block length is too small. Got ${controlBlock.length}, expected min 33.`);
if ((controlBlock.length - 33) % 32 !== 0)
throw new TypeError(`The control-block length of ${controlBlock.length} is incorrect!`);
const m = (controlBlock.length - 33) / 32;
if (m > 128)
throw new TypeError(`The script path is too long. Got ${m}, expected max 128.`);
const internalPubkey = controlBlock.slice(1, 33);
if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey))
throw new TypeError('Internal pubkey mismatch');
if (internalPubkey.length !== 32)
throw new TypeError('Invalid internalPubkey for p2tr witness');
const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK;
const script = witness[witness.length - 2];
const leafHash = (0, bip341_1.tapleafHash)({ output: script, version: leafVersion });
const hash = (0, bip341_1.rootHashFromPath)(controlBlock, leafHash);
const outputKey = (0, bip341_1.tweakKey)(internalPubkey, hash);
if (!outputKey)
throw new TypeError('Invalid outputKey for p2tr witness');
if (pubkey.length && !pubkey.equals(outputKey.x))
throw new TypeError('Pubkey mismatch for p2tr witness');
if (outputKey.parity !== (controlBlock[0] & 1))
throw new Error('Incorrect parity');
}
}
}
return Object.assign(o, a);
}
exports.p2tr = p2tr;
function stacksEqual(a, b) {
if (a.length !== b.length)
return false;
return a.every((x, i) => {
return x.equals(b[i]);
});
}
//# sourceMappingURL=p2tr.js.map