bitcoinjs-lib创建TapRoot脚本
Taproot 和 Schnorr 是比特币协议的升级,旨在增强比特币交易的隐私性、效率和灵活性。第一个示例将创建一个 pay-to-taproot(p2tr) 地址,该地址将资金锁定到一个密钥并为其创建一个支出交易;第二个示例将直接跳到 Taproot 脚本支出交易。
一键发币: SOL | BNB | ETH | BASE | Blast | ARB | OP | POLYGON | AVAX | FTM | OK
Taproot 和 Schnorr 是比特币协议的升级,旨在增强比特币交易的隐私性、效率和灵活性。
Taproot 引入了 Taptree,该功能可以减少交易数据的大小并确保仅在区块链上显示必要的信息,从而保护隐私。 使用 Taproot,多重签名交易也更加私密,因为隐藏了未使用的支出条件。
Schnorr 签名的长度为 64 字节,而不是当前 ECDSA 签名方案使用的 72 字节。 Taproot 还仅使用公钥的 x 值来创建签名,这节省了 1 个字节。
通过 Taproot 和 Schnorr 的采用,比特币交易可以更加高效、灵活和私密。
我们将看两个例子。 在我们的第一个示例中,将创建一个 pay-to-taproot(p2tr) 地址,该地址将资金锁定到一个密钥并为其创建一个支出交易。 在我们的第二个示例中,我们将直接跳到 Taproot 脚本支出交易,我们将创建一个由两个脚本支出路径、一个哈希锁脚本支出路径和一个支付到公钥脚本支出路径组成的 Taptree。 我们将创建从两条路径支出的交易。
本文的完整代码可以在 github 上的 taproot-with-bitcoinjs 上找到。
1、Taproot 密钥支出交易
出于说明目的,我们将使用随机密钥对:
const keypair = ECPair.makeRandom({ network });
我们用公钥调整这个密钥对:
const tweakedSigner = tweakSigner(keypair, { network });
bitcoinjs-lib 提供了 p2tr 函数来生成 p2tr 输出:
const p2pktr = payments.p2tr({
pubkey: toXOnly(tweakedSigner.publicKey),
network
});
const p2pktr_addr = p2pktr.address ?? "";
console.log(p2pktr_addr);
toXOnly 函数提取公钥的 x 值。
您可以使用任何支持主根地址的测试网水龙头。 我在测试时使用了 btc-testnet 。
使用 bitcoinjs-lib 为该地址创建支出交易非常简单:
const psbt = new Psbt({ network });
psbt.addInput({
hash: utxos[0].txid,
index: utxos[0].vout,
witnessUtxo: { value: utxos[0].value, script: p2pktr.output! },
tapInternalKey: toXOnly(keypair.publicKey)
});
psbt.addOutput({
address: "mohjSavDdQYHRYXcS3uS6ttaHP8amyvX78", // faucet address
value: utxos[0].value - 150
});
psbt.signInput(0, tweakedSigner);
psbt.finalizeAllInputs();
提取交易并广播交易十六进制:
const tx = psbt.extractTransaction();
console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`);
const txid = await broadcast(tx.toHex());
console.log(`Success! Txid is ${txid}`);
2、Taproot 脚本消费交易
我们将创建一个具有两个支出路径的 Tap 树,一个哈希锁支出路径和一个支付到公钥的支出路径。
哈希锁脚本花费路径将要求花费者包含一个原像(preimage),该原像将产生脚本中指定的散列。
让我们为哈希锁脚本创建另一个随机密钥对:
const secret_bytes = Buffer.from('SECRET');
const hash = crypto.hash160(secret_bytes);
// Construct script to pay to hash_lock_keypair if the correct preimage/secret is provided
const hash_script_asm = `OP_HASH160 ${hash.toString('hex')} OP_EQUALVERIFY ${toXOnly(hash_lock_keypair.publicKey).toString('hex')} OP_CHECKSIG`;
const hash_lock_script = script.fromASM(hash_script_asm);
请注意,脚本仍然需要签名才能解锁资金。付费到公钥的支出路径要简单得多:
const p2pk_script_asm = `${toXOnly(keypair.publicKey).toString('hex')} OP_CHECKSIG`;
const p2pk_script = script.fromASM(p2pk_script_asm);
我们现在可以创建 Taptree 和 p2tr 地址:
const scriptTree: Taptree = [
{
output: hash_lock_script
},
{
output: p2pk_script
}
];
const script_p2tr = payments.p2tr({
internalPubkey: toXOnly(keypair.publicKey),
scriptTree,
network
});
const script_addr = script_p2tr.address ?? '';
console.log(script_addr);
你可以使用btc-testnet 等测试网水龙头将一些测试 btc 存入该地址。
要花费在任何叶脚本上,你必须提供该叶脚本的叶版本、脚本和控制块。 控制块是证明叶脚本存在于脚本树中所需的数据(merkle 证明)。
bitcoinjs-lib将为我们生成控制块:
const hash_lock_redeem = {
output: hash_lock_script,
redeemVersion: 192,
};
const p2pk_redeem = {
output: p2pk_script,
redeemVersion: 192
}
const p2pk_p2tr = payments.p2tr({
internalPubkey: toXOnly(keypair.publicKey),
scriptTree,
redeem: p2pk_redeem,
network
});
const hash_lock_p2tr = payments.p2tr({
internalPubkey: toXOnly(keypair.publicKey),
scriptTree,
redeem: hash_lock_redeem,
network
});
console.log(`Waiting till UTXO is detected at this Address: ${script_addr}`);
let utxos = await waitUntilUTXO(script_addr)
console.log(`Trying the P2PK path with UTXO ${utxos[0].txid}:${utxos[0].vout}`);
const p2pk_psbt = new Psbt({ network });
p2pk_psbt.addInput({
hash: utxos[0].txid,
index: utxos[0].vout,
witnessUtxo: { value: utxos[0].value, script: p2pk_p2tr.output! },
tapLeafScript: [
{
leafVersion: p2pk_redeem.redeemVersion,
script: p2pk_redeem.output,
controlBlock: p2pk_p2tr.witness![p2pk_p2tr.witness!.length - 1] // extract control block from witness data
}
]
});
p2pk_psbt.addOutput({
address: "mohjSavDdQYHRYXcS3uS6ttaHP8amyvX78", // faucet address
value: utxos[0].value - 150
});
p2pk_psbt.signInput(0, keypair);
p2pk_psbt.finalizeAllInputs();
let tx = p2pk_psbt.extractTransaction();
console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`);
let txid = await broadcast(tx.toHex());
console.log(`Success! Txid is ${txid}`);
要使用哈希锁叶脚本,我们必须创建一个自定义终结器函数。 在我们的自定义终结器中,我们将创建签名、原像、原始哈希锁脚本和控制块的见证堆栈:
const tapLeafScript = {
leafVersion: hash_lock_redeem.redeemVersion,
script: hash_lock_redeem.output,
controlBlock: hash_lock_p2tr.witness![hash_lock_p2tr.witness!.length - 1]
};
const psbt = new Psbt({ network });
psbt.addInput({
hash: utxos[0].txid,
index: utxos[0].vout,
witnessUtxo: { value: utxos[0].value, script: hash_lock_p2tr.output! },
tapLeafScript: [
tapLeafScript
]
});
psbt.addOutput({
address: "mohjSavDdQYHRYXcS3uS6ttaHP8amyvX78", // faucet address
value: utxos[0].value - 150
});
psbt.signInput(0, hash_lock_keypair);
// We have to construct our witness script in a custom finalizer
const customFinalizer = (_inputIndex: number, input: any) => {
const scriptSolution = [
input.tapScriptSig[0].signature,
secret_bytes
];
const witness = scriptSolution
.concat(tapLeafScript.script)
.concat(tapLeafScript.controlBlock);
return {
finalScriptWitness: witnessStackToScriptWitness(witness)
}
}
psbt.finalizeInput(0, customFinalizer);
tx = psbt.extractTransaction();
console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`);
txid = await broadcast(tx.toHex());
console.log(`Success! Txid is ${txid}`);
3、结束语
通过阅读本文,你现在应该更好地了解如何使用 bitcoinjs-lib 来创建和使用 P2TR(Pay to Taproot)付款。 有了这些知识,你就离在比特币交易中利用 Taproot 的优势又近了一步,例如改进的隐私性、可扩展性以及创建更复杂的智能合约的能力。你可以在 bitcoinjs-lib 的存储库中找到更多示例。 本文的完整代码可以在 github 上的 taproot-with-bitcoinjs 上找到。
原文链接:A Guide to creating TapRoot Scripts with bitcoinjs-lib
DefiPlot翻译整理,转载请标明出处