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).map_err(|e| {
101 ScriptError::InvalidSignature(format!("ECDSA sign with k failed: {}", e))
102 })?;
103
104 let mut sig_bytes = sig.to_der();
105 sig_bytes.push(self.sighash_type as u8);
106
107 let chunks = vec![ScriptChunk::new_raw(sig_bytes.len() as u8, Some(sig_bytes))];
108
109 Ok(UnlockingScript::from_script(Script::from_chunks(chunks)))
110 }
111
112 pub fn estimate_unlock_length(&self) -> usize {
116 74
117 }
118
119 fn r_extraction_chunks() -> Vec<ScriptChunk> {
125 vec![
126 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), ]
136 }
137
138 fn hash_opcode(&self) -> Option<Op> {
140 match self.puzzle_type {
141 RPuzzleType::Raw => None,
142 RPuzzleType::SHA1 => Some(Op::OpSha1),
143 RPuzzleType::SHA256 => Some(Op::OpSha256),
144 RPuzzleType::Hash256 => Some(Op::OpHash256),
145 RPuzzleType::RIPEMD160 => Some(Op::OpRipemd160),
146 RPuzzleType::Hash160 => Some(Op::OpHash160),
147 }
148 }
149}
150
151impl ScriptTemplateLock for RPuzzle {
152 fn lock(&self) -> Result<LockingScript, ScriptError> {
159 if self.value.is_empty() {
160 return Err(ScriptError::InvalidScript(
161 "RPuzzle: value must not be empty".into(),
162 ));
163 }
164
165 let mut chunks = Self::r_extraction_chunks();
166
167 if let Some(hash_op) = self.hash_opcode() {
169 chunks.push(ScriptChunk::new_opcode(hash_op));
170 }
171
172 let val_len = self.value.len();
174 if val_len < 0x4c {
175 chunks.push(ScriptChunk::new_raw(
176 val_len as u8,
177 Some(self.value.clone()),
178 ));
179 } else {
180 chunks.push(ScriptChunk::new_raw(
181 Op::OpPushData1.to_byte(),
182 Some(self.value.clone()),
183 ));
184 }
185
186 chunks.push(ScriptChunk::new_opcode(Op::OpEqualVerify));
187 chunks.push(ScriptChunk::new_opcode(Op::OpCheckSig));
188
189 Ok(LockingScript::from_script(Script::from_chunks(chunks)))
190 }
191}
192
193impl ScriptTemplateUnlock for RPuzzle {
194 fn sign(&self, preimage: &[u8]) -> Result<UnlockingScript, ScriptError> {
195 self.unlock(preimage)
196 }
197
198 fn estimate_length(&self) -> Result<usize, ScriptError> {
199 Ok(self.estimate_unlock_length())
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::primitives::base_point::BasePoint;
207 use crate::primitives::big_number::Endian;
208 use crate::primitives::hash::{hash160, hash256, ripemd160, sha1, sha256};
209
210 fn bytes_to_hex(bytes: &[u8]) -> String {
211 bytes.iter().map(|b| format!("{:02x}", b)).collect()
212 }
213
214 #[test]
219 fn test_rpuzzle_lock_raw() {
220 let value = vec![0xaa; 32];
221 let rp = RPuzzle::from_value(RPuzzleType::Raw, value.clone());
222
223 let lock_script = rp.lock().unwrap();
224 let chunks = lock_script.chunks();
225
226 assert_eq!(chunks.len(), 12, "Raw RPuzzle should have 12 chunks");
228
229 assert_eq!(chunks[0].op, Op::OpDup);
231 assert_eq!(chunks[1].op, Op::Op3);
232 assert_eq!(chunks[2].op, Op::OpSplit);
233 assert_eq!(chunks[3].op, Op::OpNip);
234 assert_eq!(chunks[4].op, Op::Op1);
235 assert_eq!(chunks[5].op, Op::OpSplit);
236 assert_eq!(chunks[6].op, Op::OpSwap);
237 assert_eq!(chunks[7].op, Op::OpSplit);
238 assert_eq!(chunks[8].op, Op::OpDrop);
239
240 assert_eq!(chunks[9].data.as_ref().unwrap(), &value);
242 assert_eq!(chunks[10].op, Op::OpEqualVerify);
243 assert_eq!(chunks[11].op, Op::OpCheckSig);
244 }
245
246 #[test]
251 fn test_rpuzzle_lock_sha256() {
252 let value = vec![0xbb; 32];
253 let rp = RPuzzle::from_value(RPuzzleType::SHA256, value.clone());
254
255 let lock_script = rp.lock().unwrap();
256 let chunks = lock_script.chunks();
257
258 assert_eq!(chunks.len(), 13, "SHA256 RPuzzle should have 13 chunks");
260
261 assert_eq!(chunks[9].op, Op::OpSha256);
263 assert_eq!(chunks[10].data.as_ref().unwrap(), &value);
265 }
266
267 #[test]
272 fn test_rpuzzle_lock_hash_types() {
273 let value = vec![0xcc; 20];
274
275 let test_cases = vec![
276 (RPuzzleType::SHA1, Op::OpSha1, 13),
277 (RPuzzleType::Hash256, Op::OpHash256, 13),
278 (RPuzzleType::RIPEMD160, Op::OpRipemd160, 13),
279 (RPuzzleType::Hash160, Op::OpHash160, 13),
280 ];
281
282 for (ptype, expected_op, expected_chunks) in test_cases {
283 let rp = RPuzzle::from_value(ptype, value.clone());
284 let lock_script = rp.lock().unwrap();
285 let chunks = lock_script.chunks();
286
287 assert_eq!(
288 chunks.len(),
289 expected_chunks,
290 "{:?} should have {} chunks",
291 ptype,
292 expected_chunks
293 );
294 assert_eq!(
295 chunks[9].op, expected_op,
296 "{:?} hash opcode mismatch",
297 ptype
298 );
299 }
300 }
301
302 #[test]
307 fn test_rpuzzle_unlock_with_k() {
308 let key = PrivateKey::from_hex("1").unwrap();
309 let k = BigNumber::from_number(42);
310
311 let base_point = BasePoint::instance();
313 let r_point = base_point.mul(&k);
314 let r_bytes = r_point.get_x().to_array(Endian::Big, Some(32));
315
316 let rp = RPuzzle::from_k(RPuzzleType::Raw, r_bytes, k, key);
318
319 let unlock_script = rp.unlock(b"test preimage").unwrap();
320 assert_eq!(unlock_script.chunks().len(), 1);
321
322 let sig_data = unlock_script.chunks()[0].data.as_ref().unwrap();
323 assert!(sig_data.len() >= 70 && sig_data.len() <= 74);
324 }
325
326 #[test]
331 fn test_rpuzzle_roundtrip_raw() {
332 let key = PrivateKey::from_hex("ff").unwrap();
333 let k = BigNumber::from_number(12345);
334
335 let base_point = BasePoint::instance();
337 let r_point = base_point.mul(&k);
338 let r_value = r_point.get_x().to_array(Endian::Big, Some(32));
339
340 let rp = RPuzzle::from_k(RPuzzleType::Raw, r_value.clone(), k.clone(), key.clone());
342
343 let lock_script = rp.lock().unwrap();
345 let lock_chunks = lock_script.chunks();
346
347 let embedded_value = lock_chunks[9].data.as_ref().unwrap();
349 assert_eq!(
350 embedded_value, &r_value,
351 "embedded value should match R-value"
352 );
353
354 let unlock_script = rp.unlock(b"test roundtrip").unwrap();
356 assert_eq!(unlock_script.chunks().len(), 1);
357
358 let sig_with_sighash = unlock_script.chunks()[0].data.as_ref().unwrap();
360 let sig_der = &sig_with_sighash[..sig_with_sighash.len() - 1]; assert_eq!(sig_der[0], 0x30);
365 assert_eq!(sig_der[2], 0x02);
366 let r_len = sig_der[3] as usize;
367 let r_bytes = &sig_der[4..4 + r_len];
368
369 let r_trimmed = if !r_bytes.is_empty() && r_bytes[0] == 0x00 {
371 &r_bytes[1..]
372 } else {
373 r_bytes
374 };
375
376 let mut r_padded = vec![0u8; 32];
378 let start = 32 - r_trimmed.len();
379 r_padded[start..].copy_from_slice(r_trimmed);
380
381 assert_eq!(
382 r_padded, r_value,
383 "signature R-value should match the puzzle value"
384 );
385 }
386
387 #[test]
392 fn test_rpuzzle_roundtrip_sha256() {
393 let key = PrivateKey::from_hex("abcd").unwrap();
394 let k = BigNumber::from_number(9999);
395
396 let base_point = BasePoint::instance();
398 let r_point = base_point.mul(&k);
399 let r_value = r_point.get_x().to_array(Endian::Big, Some(32));
400
401 let r_hash = sha256(&r_value);
403
404 let rp = RPuzzle::from_k(RPuzzleType::SHA256, r_hash.to_vec(), k, key);
406
407 let lock_script = rp.lock().unwrap();
409 let lock_chunks = lock_script.chunks();
410
411 let embedded_hash = lock_chunks[10].data.as_ref().unwrap();
413 assert_eq!(embedded_hash, &r_hash.to_vec());
414
415 let unlock_script = rp.unlock(b"sha256 test").unwrap();
417 assert_eq!(unlock_script.chunks().len(), 1);
418 }
419
420 #[test]
425 fn test_rpuzzle_lock_empty_value() {
426 let rp = RPuzzle::from_value(RPuzzleType::Raw, vec![]);
427 assert!(rp.lock().is_err());
428 }
429
430 #[test]
431 fn test_rpuzzle_unlock_no_key() {
432 let rp = RPuzzle::from_value(RPuzzleType::Raw, vec![0xaa; 32]);
433 assert!(rp.unlock(b"test").is_err());
434 }
435
436 #[test]
437 fn test_rpuzzle_unlock_no_k() {
438 let rp = RPuzzle {
439 puzzle_type: RPuzzleType::Raw,
440 value: vec![0xaa; 32],
441 k_value: None,
442 private_key: Some(PrivateKey::from_hex("1").unwrap()),
443 sighash_type: SIGHASH_ALL | SIGHASH_FORKID,
444 };
445 assert!(rp.unlock(b"test").is_err());
446 }
447
448 #[test]
453 fn test_rpuzzle_estimate_length() {
454 let rp = RPuzzle::from_value(RPuzzleType::Raw, vec![0xaa; 32]);
455 assert_eq!(rp.estimate_unlock_length(), 74);
456 }
457
458 #[test]
463 fn test_rpuzzle_lock_binary_roundtrip() {
464 let value = vec![0xde, 0xad, 0xbe, 0xef];
465 let rp = RPuzzle::from_value(RPuzzleType::SHA256, value);
466
467 let lock_script = rp.lock().unwrap();
468 let binary = lock_script.to_binary();
469
470 let reparsed = Script::from_binary(&binary);
471 assert_eq!(
472 reparsed.to_binary(),
473 binary,
474 "binary roundtrip should match"
475 );
476 }
477}