1use crate::primitives::big_number::BigNumber;
9use crate::primitives::ecdsa::ecdsa_sign_with_k;
10use crate::primitives::hash::sha256;
11use crate::primitives::private_key::PrivateKey;
12use crate::primitives::transaction_signature::{SIGHASH_ALL, SIGHASH_FORKID};
13use crate::script::error::ScriptError;
14use crate::script::locking_script::LockingScript;
15use crate::script::op::Op;
16use crate::script::script::Script;
17use crate::script::script_chunk::ScriptChunk;
18use crate::script::templates::{ScriptTemplateLock, ScriptTemplateUnlock};
19use crate::script::unlocking_script::UnlockingScript;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum RPuzzleType {
24 Raw,
26 SHA1,
28 SHA256,
30 Hash256,
32 RIPEMD160,
34 Hash160,
36}
37
38#[derive(Clone, Debug)]
44pub struct RPuzzle {
45 pub puzzle_type: RPuzzleType,
47 pub value: Vec<u8>,
49 pub k_value: Option<BigNumber>,
51 pub private_key: Option<PrivateKey>,
53 pub sighash_type: u32,
55}
56
57impl RPuzzle {
58 pub fn from_value(puzzle_type: RPuzzleType, value: Vec<u8>) -> Self {
63 RPuzzle {
64 puzzle_type,
65 value,
66 k_value: None,
67 private_key: None,
68 sighash_type: SIGHASH_ALL | SIGHASH_FORKID,
69 }
70 }
71
72 pub fn from_k(puzzle_type: RPuzzleType, value: Vec<u8>, k: BigNumber, key: PrivateKey) -> Self {
77 RPuzzle {
78 puzzle_type,
79 value,
80 k_value: Some(k),
81 private_key: Some(key),
82 sighash_type: SIGHASH_ALL | SIGHASH_FORKID,
83 }
84 }
85
86 pub fn unlock(&self, preimage: &[u8]) -> Result<UnlockingScript, ScriptError> {
91 let key = self.private_key.as_ref().ok_or_else(|| {
92 ScriptError::InvalidScript("RPuzzle: no private key for unlock".into())
93 })?;
94 let k = self
95 .k_value
96 .as_ref()
97 .ok_or_else(|| ScriptError::InvalidScript("RPuzzle: no k-value for unlock".into()))?;
98
99 let msg_hash = sha256(preimage);
100 let sig = ecdsa_sign_with_k(&msg_hash, key.bn(), k, true)
101 .map_err(|e| ScriptError::InvalidSignature(format!("ECDSA sign with k failed: {e}")))?;
102
103 let mut sig_bytes = sig.to_der();
104 sig_bytes.push(self.sighash_type as u8);
105
106 let chunks = vec![ScriptChunk::new_raw(sig_bytes.len() as u8, Some(sig_bytes))];
107
108 Ok(UnlockingScript::from_script(Script::from_chunks(chunks)))
109 }
110
111 pub fn estimate_unlock_length(&self) -> usize {
115 74
116 }
117
118 fn r_extraction_chunks() -> Vec<ScriptChunk> {
124 vec![
125 ScriptChunk::new_opcode(Op::OpDup), ScriptChunk::new_opcode(Op::Op3), ScriptChunk::new_opcode(Op::OpSplit), ScriptChunk::new_opcode(Op::OpNip), ScriptChunk::new_opcode(Op::Op1), ScriptChunk::new_opcode(Op::OpSplit), ScriptChunk::new_opcode(Op::OpSwap), ScriptChunk::new_opcode(Op::OpSplit), ScriptChunk::new_opcode(Op::OpDrop), ]
135 }
136
137 fn hash_opcode(&self) -> Option<Op> {
139 match self.puzzle_type {
140 RPuzzleType::Raw => None,
141 RPuzzleType::SHA1 => Some(Op::OpSha1),
142 RPuzzleType::SHA256 => Some(Op::OpSha256),
143 RPuzzleType::Hash256 => Some(Op::OpHash256),
144 RPuzzleType::RIPEMD160 => Some(Op::OpRipemd160),
145 RPuzzleType::Hash160 => Some(Op::OpHash160),
146 }
147 }
148}
149
150impl ScriptTemplateLock for RPuzzle {
151 fn lock(&self) -> Result<LockingScript, ScriptError> {
158 if self.value.is_empty() {
159 return Err(ScriptError::InvalidScript(
160 "RPuzzle: value must not be empty".into(),
161 ));
162 }
163
164 let mut chunks = Self::r_extraction_chunks();
165
166 if let Some(hash_op) = self.hash_opcode() {
168 chunks.push(ScriptChunk::new_opcode(hash_op));
169 }
170
171 let val_len = self.value.len();
173 if val_len < 0x4c {
174 chunks.push(ScriptChunk::new_raw(
175 val_len as u8,
176 Some(self.value.clone()),
177 ));
178 } else {
179 chunks.push(ScriptChunk::new_raw(
180 Op::OpPushData1.to_byte(),
181 Some(self.value.clone()),
182 ));
183 }
184
185 chunks.push(ScriptChunk::new_opcode(Op::OpEqualVerify));
186 chunks.push(ScriptChunk::new_opcode(Op::OpCheckSig));
187
188 Ok(LockingScript::from_script(Script::from_chunks(chunks)))
189 }
190}
191
192impl ScriptTemplateUnlock for RPuzzle {
193 fn sign(&self, preimage: &[u8]) -> Result<UnlockingScript, ScriptError> {
194 self.unlock(preimage)
195 }
196
197 fn estimate_length(&self) -> Result<usize, ScriptError> {
198 Ok(self.estimate_unlock_length())
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use crate::primitives::base_point::BasePoint;
206 use crate::primitives::big_number::Endian;
207 use crate::primitives::hash::sha256;
208
209 fn bytes_to_hex(bytes: &[u8]) -> String {
210 bytes.iter().map(|b| format!("{b:02x}")).collect()
211 }
212
213 #[test]
218 fn test_rpuzzle_lock_raw() {
219 let value = vec![0xaa; 32];
220 let rp = RPuzzle::from_value(RPuzzleType::Raw, value.clone());
221
222 let lock_script = rp.lock().unwrap();
223 let chunks = lock_script.chunks();
224
225 assert_eq!(chunks.len(), 12, "Raw RPuzzle should have 12 chunks");
227
228 assert_eq!(chunks[0].op, Op::OpDup);
230 assert_eq!(chunks[1].op, Op::Op3);
231 assert_eq!(chunks[2].op, Op::OpSplit);
232 assert_eq!(chunks[3].op, Op::OpNip);
233 assert_eq!(chunks[4].op, Op::Op1);
234 assert_eq!(chunks[5].op, Op::OpSplit);
235 assert_eq!(chunks[6].op, Op::OpSwap);
236 assert_eq!(chunks[7].op, Op::OpSplit);
237 assert_eq!(chunks[8].op, Op::OpDrop);
238
239 assert_eq!(chunks[9].data.as_ref().unwrap(), &value);
241 assert_eq!(chunks[10].op, Op::OpEqualVerify);
242 assert_eq!(chunks[11].op, Op::OpCheckSig);
243 }
244
245 #[test]
250 fn test_rpuzzle_lock_sha256() {
251 let value = vec![0xbb; 32];
252 let rp = RPuzzle::from_value(RPuzzleType::SHA256, value.clone());
253
254 let lock_script = rp.lock().unwrap();
255 let chunks = lock_script.chunks();
256
257 assert_eq!(chunks.len(), 13, "SHA256 RPuzzle should have 13 chunks");
259
260 assert_eq!(chunks[9].op, Op::OpSha256);
262 assert_eq!(chunks[10].data.as_ref().unwrap(), &value);
264 }
265
266 #[test]
271 fn test_rpuzzle_lock_hash_types() {
272 let value = vec![0xcc; 20];
273
274 let test_cases = vec![
275 (RPuzzleType::SHA1, Op::OpSha1, 13),
276 (RPuzzleType::Hash256, Op::OpHash256, 13),
277 (RPuzzleType::RIPEMD160, Op::OpRipemd160, 13),
278 (RPuzzleType::Hash160, Op::OpHash160, 13),
279 ];
280
281 for (ptype, expected_op, expected_chunks) in test_cases {
282 let rp = RPuzzle::from_value(ptype, value.clone());
283 let lock_script = rp.lock().unwrap();
284 let chunks = lock_script.chunks();
285
286 assert_eq!(
287 chunks.len(),
288 expected_chunks,
289 "{ptype:?} should have {expected_chunks} chunks"
290 );
291 assert_eq!(chunks[9].op, expected_op, "{ptype:?} hash opcode mismatch");
292 }
293 }
294
295 #[test]
300 fn test_rpuzzle_unlock_with_k() {
301 let key = PrivateKey::from_hex("1").unwrap();
302 let k = BigNumber::from_number(42);
303
304 let base_point = BasePoint::instance();
306 let r_point = base_point.mul(&k);
307 let r_bytes = r_point.get_x().to_array(Endian::Big, Some(32));
308
309 let rp = RPuzzle::from_k(RPuzzleType::Raw, r_bytes, k, key);
311
312 let unlock_script = rp.unlock(b"test preimage").unwrap();
313 assert_eq!(unlock_script.chunks().len(), 1);
314
315 let sig_data = unlock_script.chunks()[0].data.as_ref().unwrap();
316 assert!(sig_data.len() >= 70 && sig_data.len() <= 74);
317 }
318
319 #[test]
324 fn test_rpuzzle_roundtrip_raw() {
325 let key = PrivateKey::from_hex("ff").unwrap();
326 let k = BigNumber::from_number(12345);
327
328 let base_point = BasePoint::instance();
330 let r_point = base_point.mul(&k);
331 let r_value = r_point.get_x().to_array(Endian::Big, Some(32));
332
333 let rp = RPuzzle::from_k(RPuzzleType::Raw, r_value.clone(), k.clone(), key.clone());
335
336 let lock_script = rp.lock().unwrap();
338 let lock_chunks = lock_script.chunks();
339
340 let embedded_value = lock_chunks[9].data.as_ref().unwrap();
342 assert_eq!(
343 embedded_value, &r_value,
344 "embedded value should match R-value"
345 );
346
347 let unlock_script = rp.unlock(b"test roundtrip").unwrap();
349 assert_eq!(unlock_script.chunks().len(), 1);
350
351 let sig_with_sighash = unlock_script.chunks()[0].data.as_ref().unwrap();
353 let sig_der = &sig_with_sighash[..sig_with_sighash.len() - 1]; assert_eq!(sig_der[0], 0x30);
358 assert_eq!(sig_der[2], 0x02);
359 let r_len = sig_der[3] as usize;
360 let r_bytes = &sig_der[4..4 + r_len];
361
362 let r_trimmed = if !r_bytes.is_empty() && r_bytes[0] == 0x00 {
364 &r_bytes[1..]
365 } else {
366 r_bytes
367 };
368
369 let mut r_padded = vec![0u8; 32];
371 let start = 32 - r_trimmed.len();
372 r_padded[start..].copy_from_slice(r_trimmed);
373
374 assert_eq!(
375 r_padded, r_value,
376 "signature R-value should match the puzzle value"
377 );
378 }
379
380 #[test]
385 fn test_rpuzzle_roundtrip_sha256() {
386 let key = PrivateKey::from_hex("abcd").unwrap();
387 let k = BigNumber::from_number(9999);
388
389 let base_point = BasePoint::instance();
391 let r_point = base_point.mul(&k);
392 let r_value = r_point.get_x().to_array(Endian::Big, Some(32));
393
394 let r_hash = sha256(&r_value);
396
397 let rp = RPuzzle::from_k(RPuzzleType::SHA256, r_hash.to_vec(), k, key);
399
400 let lock_script = rp.lock().unwrap();
402 let lock_chunks = lock_script.chunks();
403
404 let embedded_hash = lock_chunks[10].data.as_ref().unwrap();
406 assert_eq!(embedded_hash, &r_hash.to_vec());
407
408 let unlock_script = rp.unlock(b"sha256 test").unwrap();
410 assert_eq!(unlock_script.chunks().len(), 1);
411 }
412
413 #[test]
418 fn test_rpuzzle_lock_empty_value() {
419 let rp = RPuzzle::from_value(RPuzzleType::Raw, vec![]);
420 assert!(rp.lock().is_err());
421 }
422
423 #[test]
424 fn test_rpuzzle_unlock_no_key() {
425 let rp = RPuzzle::from_value(RPuzzleType::Raw, vec![0xaa; 32]);
426 assert!(rp.unlock(b"test").is_err());
427 }
428
429 #[test]
430 fn test_rpuzzle_unlock_no_k() {
431 let rp = RPuzzle {
432 puzzle_type: RPuzzleType::Raw,
433 value: vec![0xaa; 32],
434 k_value: None,
435 private_key: Some(PrivateKey::from_hex("1").unwrap()),
436 sighash_type: SIGHASH_ALL | SIGHASH_FORKID,
437 };
438 assert!(rp.unlock(b"test").is_err());
439 }
440
441 #[test]
446 fn test_rpuzzle_estimate_length() {
447 let rp = RPuzzle::from_value(RPuzzleType::Raw, vec![0xaa; 32]);
448 assert_eq!(rp.estimate_unlock_length(), 74);
449 }
450
451 #[test]
456 fn test_rpuzzle_lock_binary_roundtrip() {
457 let value = vec![0xde, 0xad, 0xbe, 0xef];
458 let rp = RPuzzle::from_value(RPuzzleType::SHA256, value);
459
460 let lock_script = rp.lock().unwrap();
461 let binary = lock_script.to_binary();
462
463 let reparsed = Script::from_binary(&binary);
464 assert_eq!(
465 reparsed.to_binary(),
466 binary,
467 "binary roundtrip should match"
468 );
469 }
470}