UNPKG

@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.

264 lines 11.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkTaprootInputForSigs = exports.tapTreeFromList = exports.tapTreeToList = exports.tweakInternalPubKey = exports.checkTaprootOutputFields = exports.checkTaprootInputFields = exports.isTaprootOutput = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0; const types_1 = require("../types"); const transaction_1 = require("../transaction"); const psbtutils_1 = require("./psbtutils"); const bip341_1 = require("../payments/bip341"); const payments_1 = require("../payments"); const psbtutils_2 = require("./psbtutils"); const toXOnly = (pubKey) => pubKey.length === 32 ? pubKey : pubKey.slice(1, 33); exports.toXOnly = toXOnly; function tapScriptFinalizer(inputIndex, input, tapLeafHashToFinalize) { const tapLeaf = findTapLeafToFinalize(input, inputIndex, tapLeafHashToFinalize); try { const sigs = sortSignatures(input, tapLeaf); const witness = sigs.concat(tapLeaf.script).concat(tapLeaf.controlBlock); return { finalScriptWitness: (0, psbtutils_1.witnessStackToScriptWitness)(witness) }; } catch (err) { throw new Error(`Can not finalize taproot input #${inputIndex}: ${err}`); } } exports.tapScriptFinalizer = tapScriptFinalizer; function serializeTaprootSignature(sig, sighashType) { const sighashTypeByte = sighashType ? Buffer.from([sighashType]) : Buffer.from([]); return Buffer.concat([sig, sighashTypeByte]); } exports.serializeTaprootSignature = serializeTaprootSignature; function isTaprootInput(input) { return (input && !!(input.tapInternalKey || input.tapMerkleRoot || (input.tapLeafScript && input.tapLeafScript.length) || (input.tapBip32Derivation && input.tapBip32Derivation.length) || (input.witnessUtxo && (0, psbtutils_1.isP2TR)(input.witnessUtxo.script)))); } exports.isTaprootInput = isTaprootInput; function isTaprootOutput(output, script) { return (output && !!(output.tapInternalKey || output.tapTree || (output.tapBip32Derivation && output.tapBip32Derivation.length) || (script && (0, psbtutils_1.isP2TR)(script)))); } exports.isTaprootOutput = isTaprootOutput; function checkTaprootInputFields(inputData, newInputData, action) { checkMixedTaprootAndNonTaprootInputFields(inputData, newInputData, action); checkIfTapLeafInTree(inputData, newInputData, action); } exports.checkTaprootInputFields = checkTaprootInputFields; function checkTaprootOutputFields(outputData, newOutputData, action) { checkMixedTaprootAndNonTaprootOutputFields(outputData, newOutputData, action); checkTaprootScriptPubkey(outputData, newOutputData); } exports.checkTaprootOutputFields = checkTaprootOutputFields; function checkTaprootScriptPubkey(outputData, newOutputData) { if (!newOutputData.tapTree && !newOutputData.tapInternalKey) return; const tapInternalKey = newOutputData.tapInternalKey || outputData.tapInternalKey; const tapTree = newOutputData.tapTree || outputData.tapTree; if (tapInternalKey) { const { script: scriptPubkey } = outputData; const script = getTaprootScripPubkey(tapInternalKey, tapTree); if (scriptPubkey && !scriptPubkey.equals(script)) throw new Error('Error adding output. Script or address missmatch.'); } } function getTaprootScripPubkey(tapInternalKey, tapTree) { const scriptTree = tapTree && tapTreeFromList(tapTree.leaves); const { output } = (0, payments_1.p2tr)({ internalPubkey: tapInternalKey, scriptTree, }); return output; } function tweakInternalPubKey(inputIndex, input) { const tapInternalKey = input.tapInternalKey; const outputKey = tapInternalKey && (0, bip341_1.tweakKey)(tapInternalKey, input.tapMerkleRoot); if (!outputKey) throw new Error(`Cannot tweak tap internal key for input #${inputIndex}. Public key: ${tapInternalKey && tapInternalKey.toString('hex')}`); return outputKey.x; } exports.tweakInternalPubKey = tweakInternalPubKey; function tapTreeToList(tree) { if (!(0, types_1.isTaptree)(tree)) throw new Error('Cannot convert taptree to tapleaf list. Expecting a tapree structure.'); return _tapTreeToList(tree); } exports.tapTreeToList = tapTreeToList; function tapTreeFromList(leaves = []) { if (leaves.length === 1 && leaves[0].depth === 0) return { output: leaves[0].script, version: leaves[0].leafVersion, }; return instertLeavesInTree(leaves); } exports.tapTreeFromList = tapTreeFromList; function checkTaprootInputForSigs(input, action) { const sigs = extractTaprootSigs(input); return sigs.some(sig => (0, psbtutils_2.signatureBlocksAction)(sig, decodeSchnorrSignature, action)); } exports.checkTaprootInputForSigs = checkTaprootInputForSigs; function decodeSchnorrSignature(signature) { return { signature: signature.slice(0, 64), hashType: signature.slice(64)[0] || transaction_1.Transaction.SIGHASH_DEFAULT, }; } function extractTaprootSigs(input) { const sigs = []; if (input.tapKeySig) sigs.push(input.tapKeySig); if (input.tapScriptSig) sigs.push(...input.tapScriptSig.map(s => s.signature)); if (!sigs.length) { const finalTapKeySig = getTapKeySigFromWithness(input.finalScriptWitness); if (finalTapKeySig) sigs.push(finalTapKeySig); } return sigs; } function getTapKeySigFromWithness(finalScriptWitness) { if (!finalScriptWitness) return; const witness = finalScriptWitness.slice(2); if (witness.length === 64 || witness.length === 65) return witness; } function _tapTreeToList(tree, leaves = [], depth = 0) { if (depth > bip341_1.MAX_TAPTREE_DEPTH) throw new Error('Max taptree depth exceeded.'); if (!tree) return []; if ((0, types_1.isTapleaf)(tree)) { leaves.push({ depth, leafVersion: tree.version || bip341_1.LEAF_VERSION_TAPSCRIPT, script: tree.output, }); return leaves; } if (tree[0]) _tapTreeToList(tree[0], leaves, depth + 1); if (tree[1]) _tapTreeToList(tree[1], leaves, depth + 1); return leaves; } function instertLeavesInTree(leaves) { let tree; for (const leaf of leaves) { tree = instertLeafInTree(leaf, tree); if (!tree) throw new Error(`No room left to insert tapleaf in tree`); } return tree; } function instertLeafInTree(leaf, tree, depth = 0) { if (depth > bip341_1.MAX_TAPTREE_DEPTH) throw new Error('Max taptree depth exceeded.'); if (leaf.depth === depth) { if (!tree) return { output: leaf.script, version: leaf.leafVersion, }; return; } if ((0, types_1.isTapleaf)(tree)) return; const leftSide = instertLeafInTree(leaf, tree && tree[0], depth + 1); if (leftSide) return [leftSide, tree && tree[1]]; const rightSide = instertLeafInTree(leaf, tree && tree[1], depth + 1); if (rightSide) return [tree && tree[0], rightSide]; } function checkMixedTaprootAndNonTaprootInputFields(inputData, newInputData, action) { const isBadTaprootUpdate = isTaprootInput(inputData) && hasNonTaprootFields(newInputData); const isBadNonTaprootUpdate = hasNonTaprootFields(inputData) && isTaprootInput(newInputData); const hasMixedFields = inputData === newInputData && isTaprootInput(newInputData) && hasNonTaprootFields(newInputData); if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields) throw new Error(`Invalid arguments for Psbt.${action}. ` + `Cannot use both taproot and non-taproot fields.`); } function checkMixedTaprootAndNonTaprootOutputFields(inputData, newInputData, action) { const isBadTaprootUpdate = isTaprootOutput(inputData) && hasNonTaprootFields(newInputData); const isBadNonTaprootUpdate = hasNonTaprootFields(inputData) && isTaprootOutput(newInputData); const hasMixedFields = inputData === newInputData && isTaprootOutput(newInputData) && hasNonTaprootFields(newInputData); if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields) throw new Error(`Invalid arguments for Psbt.${action}. ` + `Cannot use both taproot and non-taproot fields.`); } function checkIfTapLeafInTree(inputData, newInputData, action) { if (newInputData.tapMerkleRoot) { const newLeafsInTree = (newInputData.tapLeafScript || []).every(l => isTapLeafInTree(l, newInputData.tapMerkleRoot)); const oldLeafsInTree = (inputData.tapLeafScript || []).every(l => isTapLeafInTree(l, newInputData.tapMerkleRoot)); if (!newLeafsInTree || !oldLeafsInTree) throw new Error(`Invalid arguments for Psbt.${action}. Tapleaf not part of taptree.`); } else if (inputData.tapMerkleRoot) { const newLeafsInTree = (newInputData.tapLeafScript || []).every(l => isTapLeafInTree(l, inputData.tapMerkleRoot)); if (!newLeafsInTree) throw new Error(`Invalid arguments for Psbt.${action}. Tapleaf not part of taptree.`); } } function isTapLeafInTree(tapLeaf, merkleRoot) { if (!merkleRoot) return true; const leafHash = (0, bip341_1.tapleafHash)({ output: tapLeaf.script, version: tapLeaf.leafVersion, }); const rootHash = (0, bip341_1.rootHashFromPath)(tapLeaf.controlBlock, leafHash); return rootHash.equals(merkleRoot); } function sortSignatures(input, tapLeaf) { const leafHash = (0, bip341_1.tapleafHash)({ output: tapLeaf.script, version: tapLeaf.leafVersion, }); return (input.tapScriptSig || []) .filter(tss => tss.leafHash.equals(leafHash)) .map(tss => addPubkeyPositionInScript(tapLeaf.script, tss)) .sort((t1, t2) => t2.positionInScript - t1.positionInScript) .map(t => t.signature); } function addPubkeyPositionInScript(script, tss) { return Object.assign({ positionInScript: (0, psbtutils_1.pubkeyPositionInScript)(tss.pubkey, script), }, tss); } function findTapLeafToFinalize(input, inputIndex, leafHashToFinalize) { if (!input.tapScriptSig || !input.tapScriptSig.length) throw new Error(`Can not finalize taproot input #${inputIndex}. No tapleaf script signature provided.`); const tapLeaf = (input.tapLeafScript || []) .sort((a, b) => a.controlBlock.length - b.controlBlock.length) .find(leaf => canFinalizeLeaf(leaf, input.tapScriptSig, leafHashToFinalize)); if (!tapLeaf) throw new Error(`Can not finalize taproot input #${inputIndex}. Signature for tapleaf script not found.`); return tapLeaf; } function canFinalizeLeaf(leaf, tapScriptSig, hash) { const leafHash = (0, bip341_1.tapleafHash)({ output: leaf.script, version: leaf.leafVersion, }); const whiteListedHash = !hash || hash.equals(leafHash); return (whiteListedHash && tapScriptSig.find(tss => tss.leafHash.equals(leafHash)) !== undefined); } function hasNonTaprootFields(io) { return (io && !!(io.redeemScript || io.witnessScript || (io.bip32Derivation && io.bip32Derivation.length))); } //# sourceMappingURL=bip371.js.map