什么是p2tr
想要创建一个铭文,首先得了解下什么是p2tr ?
P2tr: 即Pay-to-Taproot
是比特币的一种新的交易类型,由比特币的升级提案BIP341提出。
一个P2TR类型的交易,资金不再是锁到固定的地址上,而是锁到一个Taproot上。
什么是Taproot呢?
Taproot和merkle root类似,是一颗merkle树的根。 如下图所示,这个merkle上的所有节点都是一个witness script。
上面说到,p2tr的交易,是将btc锁定在Taproot的地址上的。想要将这个地址上的比特币花费出去,需要提供一份merkle证明。
如下图所示,你可以提供任意一条的merkle证明路径,只要能最终得到这个merkle root hash,就证明了你有资格去解锁。
铭文是怎么被创建出来的
btc的铭文创建需要用到上面的p2tr交易。
- 首先,我们随机生成一个btc的私钥;
- 将这个私钥转化成符合p2tr格式的私钥;
- 然后构建一个witness script, 这个witness script其实很简单,代码如下。其实生效的就是10~11行,验证用户的签名是否和公钥一致;
ts
// 拿到p2tr类型的公钥
const tweakedPublicKey = toXOnly(keypair.publicKey)
const json = `{ "p": "brc-20", "op": "mint", "tick": "cccc", "amt": "100" }`;
// 构建铭文的script
const inscriptionWitnessScript = bitcoin.script.compile([
tweakedPublicKey,
opcodes.OP_CHECKSIG, // 验证公钥
opcodes.OP_FALSE, // referred to as an ENVELOPE
opcodes.OP_IF, // adding false enables prunable content
opPush("ord"), // specifies its an ordinal inscription
1,
1,
opPush("text/plain;charset=utf-8"), // defines media type
opcodes.OP_0,
opPush(json), // media content
bitcoin.opcodes.OP_ENDIF // end of ENVELOPE
]);
// 构建一个tap leaf
const scriptTree: Tapleaf = { output: inscriptionWitnessScript }
const redeemScript = {
output: inscriptionWitnessScript,
redeemVersion: 192
}
const inscriptionPayment = bitcoin.payments.p2tr({ // 这个就是一个包含了铭文的p2tr对象
internalPubkey: tweakedPublicKey,
network: network,
scriptTree: scriptTree,
redeem: redeemScript
});
const commitAndRevealAddress = inscriptionPayment.address; // 返回铭文的地址
console.log(`commit_and_reveal_address = `, commitAndRevealAddress)
- 然后生成一个包含了铭文的p2tr对象。
- 我们将这个p2tr的地址打印出来,然后向里构建一笔utxo,向这个地址里发送一些btc,用来reveal铭文;
- 接下来我们用redeem_script将p2tr里的btc再花费掉,转给某一个地址,这样铭文就被reveal这个地址上了;
代码片段:花p2tr地址上的钱,revel铭文
ts
// PSBT 是 Partially Signed Bitcoin Transaction 的缩写,即部分签名的比特币交易
// 构建PSBT去花这个inscriptionPayment.address上的钱
const psbt = new Psbt({ network });
const tapLeafScript = {
leafVersion: 192,
script: inscriptionWitnessScript,
controlBlock: inscriptionPayment.witness![inscriptionPayment.witness!.length - 1]
};
// 添加输入
psbt.addInput({
hash: utxos[utxos.length - 1].txid,
index: utxos[utxos.length - 1].vout,
tapInternalKey: tweakedPublicKey,
witnessUtxo: {
value: utxos[utxos.length - 1].value,
script: inscriptionPayment.output!
},
tapLeafScript: [
tapLeafScript,
]
});
// 添加输出
psbt.addOutputs([{
// 往这个地址发铭文
address: "tb1plf84y3ak54k6m3kr9rzka9skynkn5mhfjmaenn70epdzamtgpadqu8uxx9",
value: 1000
}, {
// 找零地址
address: "tb1pjjq4snuntgja3ggyluncdlkvhxw26gm8pkfgjc8jvhh3asyaj6as4uctjg",
value: utxos[utxos.length - 1].value - 1000 - 1000 // 扣除上面的1000, 再扣除1000给gas
}]);
// 签名
psbt.signInput(0, keypair);
// 验证所有的input都被正确签名了,
psbt.finalizeAllInputs();
// 拿到tx数据
const tx = psbt.extractTransaction().toHex();
console.log(`Broadcasting Transaction Hex: ${tx}`);
// 广播交易
const txid = await broadcast(tx);
console.log(`Success! Txid is ${txid}`);
截图举例
铭文的详情:testnet.unisat.io/brc20/coda
交易地址:mempool.space/testnet/add...
如下图所示,1个deploy铭文,实际上有2笔关联的交易,一笔是向taproot地址转账,我们称为commit阶段 另一笔taproot地址向其他地址转账,我们称为reveal阶段
仓库地址
运行这个repo里的examples/unisat-web3-demo,并且安装上unista钱包插件,可以完整体验brc20铭文deploy、mint、transfer的所有流程。