csv_adapter_bitcoin/
tapret.rs1use bitcoin::{
20 opcodes::all::OP_RETURN,
21 script::{Builder, PushBytesBuf},
22 ScriptBuf,
23};
24
25use csv_adapter_core::hash::Hash;
26
27pub const TAPRET_SCRIPT_SIZE: usize = 67;
29
30pub const OPRET_SCRIPT_SIZE: usize = 66;
32
33#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct TapretCommitment {
36 pub protocol_id: [u8; 32],
37 pub commitment: Hash,
38}
39
40impl TapretCommitment {
41 pub fn new(protocol_id: [u8; 32], commitment: Hash) -> Self {
42 Self {
43 protocol_id,
44 commitment,
45 }
46 }
47
48 pub fn payload(&self) -> [u8; 64] {
49 let mut payload = [0u8; 64];
50 payload[..32].copy_from_slice(&self.protocol_id);
51 payload[32..].copy_from_slice(self.commitment.as_bytes());
52 payload
53 }
54
55 pub fn leaf_script(&self) -> ScriptBuf {
56 let payload = self.payload();
57 let push_bytes = PushBytesBuf::try_from(payload.to_vec()).unwrap();
58 Builder::new()
59 .push_opcode(OP_RETURN)
60 .push_slice(push_bytes)
61 .into_script()
62 }
63
64 pub fn leaf_script_with_nonce(&self, nonce: u8) -> ScriptBuf {
69 let mut payload = [0u8; 65];
70 payload[..32].copy_from_slice(&self.protocol_id);
71 payload[32] = nonce;
72 payload[33..65].copy_from_slice(self.commitment.as_bytes());
73 let push_bytes = PushBytesBuf::try_from(payload.to_vec()).unwrap();
74 Builder::new()
75 .push_opcode(OP_RETURN)
76 .push_slice(push_bytes)
77 .into_script()
78 }
79}
80
81#[derive(Clone, Debug, PartialEq, Eq)]
83pub struct OpretCommitment {
84 pub protocol_id: [u8; 32],
85 pub commitment: Hash,
86}
87
88impl OpretCommitment {
89 pub fn new(protocol_id: [u8; 32], commitment: Hash) -> Self {
90 Self {
91 protocol_id,
92 commitment,
93 }
94 }
95
96 pub fn script(&self) -> ScriptBuf {
97 let mut data = Vec::with_capacity(64);
98 data.extend_from_slice(&self.protocol_id);
99 data.extend_from_slice(self.commitment.as_bytes());
100 let push_bytes = PushBytesBuf::try_from(data).unwrap();
101 Builder::new()
102 .push_opcode(OP_RETURN)
103 .push_slice(push_bytes)
104 .into_script()
105 }
106}
107
108pub fn mine_tapret_nonce(
125 tapret: &TapretCommitment,
126 max_attempts: u32,
127) -> Result<(u8, ScriptBuf), TapretError> {
128 use rand::RngCore;
129 let mut rng = rand::thread_rng();
130
131 for _attempt in 0..max_attempts {
132 let nonce = rng.next_u32() as u8;
133 let script = tapret.leaf_script_with_nonce(nonce);
134
135 if script.is_op_return() && script.len() == TAPRET_SCRIPT_SIZE {
139 return Ok((nonce, script));
140 }
141 }
142
143 Err(TapretError::NonceMiningFailed(max_attempts))
144}
145
146#[derive(Debug, thiserror::Error)]
148pub enum TapretError {
149 #[error("Nonce mining failed after {0} attempts")]
150 NonceMiningFailed(u32),
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 fn test_commitment() -> TapretCommitment {
158 TapretCommitment::new([1u8; 32], Hash::new([2u8; 32]))
159 }
160
161 #[test]
162 fn test_tapret_payload() {
163 let tc = test_commitment();
164 let payload = tc.payload();
165 assert_eq!(payload.len(), 64);
166 assert_eq!(&payload[..32], &[1u8; 32]);
167 assert_eq!(&payload[32..], &[2u8; 32]);
168 }
169
170 #[test]
171 fn test_tapret_leaf_script() {
172 let tc = test_commitment();
173 let script = tc.leaf_script();
174 assert_eq!(script.len(), OPRET_SCRIPT_SIZE);
176 }
177
178 #[test]
179 fn test_tapret_leaf_with_nonce() {
180 let tc = test_commitment();
181 let script_no_nonce = tc.leaf_script();
182 let script_with_nonce = tc.leaf_script_with_nonce(42);
183 assert_eq!(script_with_nonce.len(), TAPRET_SCRIPT_SIZE);
185 assert_eq!(script_with_nonce.len(), script_no_nonce.len() + 1);
186 }
187
188 #[test]
189 fn test_nonce_mining() {
190 let tc = test_commitment();
191 let (nonce, script) = mine_tapret_nonce(&tc, 256).unwrap();
192 assert_eq!(script.len(), TAPRET_SCRIPT_SIZE);
194 assert!(script.as_bytes().contains(&nonce));
196 }
197
198 #[test]
199 fn test_opret_script() {
200 let oc = OpretCommitment::new([1u8; 32], Hash::new([2u8; 32]));
201 let script = oc.script();
202 assert!(script.is_op_return());
203 assert_eq!(script.len(), OPRET_SCRIPT_SIZE);
205 }
206
207 #[test]
208 fn test_opret_script_content() {
209 let oc = OpretCommitment::new([0xAB; 32], Hash::new([0xCD; 32]));
210 let script = oc.script();
211 let bytes = script.as_bytes();
212 assert_eq!(bytes[0], 0x6a); assert_eq!(bytes[1], 0x40); }
216}