使用BitCoinjs-lib创建taproot脚本的指南
#javascript #bitcoinjs #taproot

Taproot和Schnorr是旨在增强比特币交易的隐私,效率和灵活性的比特币协议的升级。

Taproot介绍了Taptrees,该功能可降低交易数据的大小,并确保仅在区块链上揭示必要的信息,从而保留隐私。使用Taproot,由于隐藏了未使用的支出条件,因此Multisig Transactions也更加私密。

schnorr签名为64个字节,而不是当前ECDSA签名方案使用的72个字节。 Taproot还仅使用X值公共钥匙来保存1个字节的签名创建。

采用Taproot和Schnorr的采用,比特币交易可以更有效,灵活和私人。

我们将介绍两个示例。在我们的第一个示例中,我们将创建一个付费型式型号(P2TR)地址,该地址将把资金锁定到钥匙并为其创建支出交易。在我们的第二个示例中,我们将直接跳入Taproot脚本启动交易,我们将创建一个由两个脚本播种路径组成的Taptree,一个哈希锁脚本支出路径和一个付费 - 付费脚本脚本支出路径。我们将创建从这两条路径上花费的交易。

本文的完整代码可以在taproot-with-bitcoinjs的GitHub上找到。

Taproot键式启动交易

出于插图目的,我们将使用随机按键

const keypair = ECPair.makeRandom({ network });

我们与我们的Pubkey一起调整了此键盘。

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值。

您可以使用任何支持Taproot地址的测试网络水龙头。我在测试时使用了testnet-faucet.com/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}`);

Taproot脚本启动交易

我们将创建一个带有两个支出路径的水龙头树,一条锁定的支出路径和一条付费 - 付费。

Hash-Lock脚本带路径将要求Spender包含一个预先映射,该预映射将产生脚本中指定的哈希。

让我们为我们的哈希锁脚本做另一个随机按键

const hash_lock_keypair = ECPair.makeRandom({ network });

现在,我们将构建我们的哈希锁脚本

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);

您可以使用testnet-faucet.com/btc-testnet等测试网龙头将一些测试BTC存放到地址中。

要花费在任何叶子脚本上,您必须为该叶子脚本提供叶片,脚本和控制板。控制块是证明LEAF脚本存在于脚本树中所需的数据(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}`);

要使用哈希锁板脚本花费,我们必须创建一个自定义最终制度函数。在我们的自定义最终制度中,我们将创建我们的证人signature, preimage, original hash-lock script and our control block

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}`);

结论

通过阅读本文,您现在应该更好地了解如何使用BitCoinjs-lib来创建和花费P2TR(付款给Taproot)付款。有了这些知识,您就可以更近一步地利用Taproot在比特币交易中的好处,例如改善隐私性,可扩展性和创建更复杂的智能合约的能力。
您可以在bitcoinjs-lib's repo中找到更多示例。本文的完整代码可以在taproot-with-bitcoinjs的GitHub上找到。