sbtc_core/operations/commit_reveal/
utils.rs1use std::{iter::once, num::TryFromIntError};
3
4use bdk::bitcoin::{
5 blockdata::{
6 opcodes::all::{OP_CHECKSIG, OP_DROP, OP_RETURN},
7 script::Builder,
8 },
9 schnorr::UntweakedPublicKey,
10 secp256k1::Secp256k1,
11 util::taproot::{
12 LeafVersion, TaprootBuilder, TaprootBuilderError, TaprootSpendInfo,
13 },
14 Address as BitcoinAddress, Network, OutPoint, PackedLockTime, Script,
15 Sequence, Transaction, TxIn, TxOut, Witness, XOnlyPublicKey,
16};
17use thiserror::Error;
18
19#[derive(Error, Debug)]
20pub enum CommitRevealError {
22 #[error("Signature is invalid: {0}")]
23 InvalidRecoveryId(TryFromIntError),
25 #[error("Control block could not be built from script")]
26 NoControlBlock,
28 #[error("Could not build taproot spend info: {0}: {1}")]
29 InvalidTaproot(&'static str, TaprootBuilderError),
31}
32
33pub type CommitRevealResult<T> = Result<T, CommitRevealError>;
35
36fn internal_key() -> UntweakedPublicKey {
37 let internal_key_vec = hex::decode(
43 "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
44 )
45 .unwrap();
46
47 XOnlyPublicKey::from_slice(&internal_key_vec)
48 .expect("Could not build internal key")
49}
50
51fn reveal_op_return_script(stacks_magic_bytes: &[u8; 2]) -> Script {
52 let op_return_bytes: Vec<u8> = stacks_magic_bytes
53 .iter()
54 .cloned()
55 .chain(once(b'w'))
56 .collect();
57
58 Builder::new()
59 .push_opcode(OP_RETURN)
60 .push_slice(&op_return_bytes)
61 .into_script()
62}
63
64fn reclaim_script(reclaim_key: &XOnlyPublicKey) -> Script {
65 Builder::new()
66 .push_x_only_key(reclaim_key)
67 .push_opcode(OP_CHECKSIG)
68 .into_script()
69}
70
71fn op_drop_script(data: &[u8], revealer_key: &XOnlyPublicKey) -> Script {
72 Builder::new()
73 .push_slice(data)
74 .push_opcode(OP_DROP)
75 .push_x_only_key(revealer_key)
76 .push_opcode(OP_CHECKSIG)
77 .into_script()
78}
79
80fn address_from_taproot_spend_info(
81 spend_info: TaprootSpendInfo,
82) -> BitcoinAddress {
83 let secp = Secp256k1::new(); BitcoinAddress::p2tr(
86 &secp,
87 spend_info.internal_key(),
88 spend_info.merkle_root(),
89 Network::Testnet, )
91}
92
93fn taproot_spend_info(
94 data: &[u8],
95 revealer_key: &XOnlyPublicKey,
96 reclaim_key: &XOnlyPublicKey,
97) -> CommitRevealResult<TaprootSpendInfo> {
98 let reveal_script = op_drop_script(data, revealer_key);
99 let reclaim_script = reclaim_script(reclaim_key);
100
101 let secp = Secp256k1::new(); let internal_key = internal_key();
103
104 Ok(TaprootBuilder::new()
105 .add_leaf(1, reveal_script)
106 .map_err(|err| CommitRevealError::InvalidTaproot("Invalid reveal script", err))?
107 .add_leaf(1, reclaim_script)
108 .map_err(|err| CommitRevealError::InvalidTaproot("Invalid reclaim script", err))?
109 .finalize(&secp, internal_key)
110 .expect("Taproot builder should be able to finalize after adding the internal key"))
112}
113
114pub fn commit(
116 data: &[u8],
117 revealer_key: &XOnlyPublicKey,
118 reclaim_key: &XOnlyPublicKey,
119) -> CommitRevealResult<BitcoinAddress> {
120 let spend_info = taproot_spend_info(data, revealer_key, reclaim_key)?;
121 Ok(address_from_taproot_spend_info(spend_info))
122}
123
124pub struct RevealInputs<'r> {
126 pub commit_output: OutPoint,
128 pub stacks_magic_bytes: &'r [u8; 2],
130 pub revealer_key: &'r XOnlyPublicKey,
132 pub reclaim_key: &'r XOnlyPublicKey,
134}
135
136pub fn reveal(
138 data: &[u8],
139 RevealInputs {
140 commit_output,
141 stacks_magic_bytes,
142 revealer_key,
143 reclaim_key,
144 }: RevealInputs,
145) -> CommitRevealResult<Transaction> {
146 let spend_info = taproot_spend_info(data, revealer_key, reclaim_key)?;
147
148 let script = op_drop_script(data, revealer_key);
149 let control_block = spend_info
150 .control_block(&(script.clone(), LeafVersion::TapScript))
151 .ok_or(CommitRevealError::NoControlBlock)?;
152
153 let mut witness = Witness::new();
154 witness.push(script);
155 witness.push(control_block.serialize());
156
157 let tx = Transaction {
158 version: 2,
159 lock_time: PackedLockTime::ZERO,
160 input: vec![TxIn {
161 previous_output: commit_output,
162 script_sig: Script::new(),
163 sequence: Sequence::MAX,
164 witness,
165 }],
166 output: vec![TxOut {
167 value: 0,
168 script_pubkey: reveal_op_return_script(stacks_magic_bytes),
169 }],
170 };
171
172 Ok(tx)
173}