1use crate::error::Error;
60use crate::primitives::bsv::TransactionSignature;
61use crate::primitives::ec::PrivateKey;
62use crate::primitives::hash::{hash160, ripemd160, sha1, sha256, sha256d};
63use crate::primitives::BigNumber;
64use crate::script::op::*;
65use crate::script::template::{
66 compute_sighash_scope, ScriptTemplate, ScriptTemplateUnlock, SignOutputs, SigningContext,
67};
68use crate::script::{LockingScript, Script, ScriptChunk, UnlockingScript};
69use crate::Result;
70
71use k256::elliptic_curve::ops::Reduce;
72use k256::elliptic_curve::point::AffineCoordinates;
73use k256::elliptic_curve::subtle::CtOption;
74use k256::{FieldBytes, NonZeroScalar, ProjectivePoint, Scalar, U256};
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
80pub enum RPuzzleType {
81 #[default]
83 Raw,
84 Sha1,
86 Sha256,
88 Hash256,
90 Ripemd160,
92 Hash160,
94}
95
96impl RPuzzleType {
97 fn hash_opcode(self) -> Option<u8> {
99 match self {
100 RPuzzleType::Raw => None,
101 RPuzzleType::Sha1 => Some(OP_SHA1),
102 RPuzzleType::Sha256 => Some(OP_SHA256),
103 RPuzzleType::Hash256 => Some(OP_HASH256),
104 RPuzzleType::Ripemd160 => Some(OP_RIPEMD160),
105 RPuzzleType::Hash160 => Some(OP_HASH160),
106 }
107 }
108
109 pub fn hash(self, data: &[u8]) -> Vec<u8> {
111 match self {
112 RPuzzleType::Raw => data.to_vec(),
113 RPuzzleType::Sha1 => sha1(data).to_vec(),
114 RPuzzleType::Sha256 => sha256(data).to_vec(),
115 RPuzzleType::Hash256 => sha256d(data).to_vec(),
116 RPuzzleType::Ripemd160 => ripemd160(data).to_vec(),
117 RPuzzleType::Hash160 => hash160(data).to_vec(),
118 }
119 }
120}
121
122#[derive(Debug, Clone, Copy)]
142pub struct RPuzzle {
143 pub puzzle_type: RPuzzleType,
145}
146
147impl Default for RPuzzle {
148 fn default() -> Self {
149 Self::new(RPuzzleType::Raw)
150 }
151}
152
153impl RPuzzle {
154 pub fn new(puzzle_type: RPuzzleType) -> Self {
160 Self { puzzle_type }
161 }
162
163 pub fn compute_r_from_k(k: &BigNumber) -> Result<[u8; 32]> {
175 let k_bytes = k.to_bytes_be(32);
177 let k_uint = U256::from_be_slice(&k_bytes);
178 let k_scalar: CtOption<NonZeroScalar> = NonZeroScalar::from_uint(k_uint);
179
180 let k_scalar = k_scalar
181 .into_option()
182 .ok_or_else(|| Error::CryptoError("Invalid K value (zero or >= order)".to_string()))?;
183
184 let point = ProjectivePoint::GENERATOR * k_scalar.as_ref();
186 let point_affine = point.to_affine();
187
188 let x: FieldBytes = point_affine.x();
190 let mut r_bytes = [0u8; 32];
191 r_bytes.copy_from_slice(&x);
192
193 Ok(r_bytes)
194 }
195
196 pub fn unlock(
224 k: &BigNumber,
225 private_key: &PrivateKey,
226 sign_outputs: SignOutputs,
227 anyone_can_pay: bool,
228 ) -> ScriptTemplateUnlock {
229 let k_value = k.clone();
230 let key = private_key.clone();
231 let scope = compute_sighash_scope(sign_outputs, anyone_can_pay);
232
233 ScriptTemplateUnlock::new(
234 move |context: &SigningContext| {
235 let sighash = context.compute_sighash(scope)?;
237
238 let signature = sign_with_k(&key, &sighash, &k_value)?;
240 let tx_sig = TransactionSignature::new(signature, scope);
241
242 let sig_bytes = tx_sig.to_checksig_format();
244 let pubkey_bytes = key.public_key().to_compressed();
245
246 let mut script = Script::new();
247 script.write_bin(&sig_bytes).write_bin(&pubkey_bytes);
248
249 Ok(UnlockingScript::from_script(script))
250 },
251 || {
252 108
254 },
255 )
256 }
257
258 pub fn sign_with_sighash(
272 k: &BigNumber,
273 private_key: &PrivateKey,
274 sighash: &[u8; 32],
275 sign_outputs: SignOutputs,
276 anyone_can_pay: bool,
277 ) -> Result<UnlockingScript> {
278 let scope = compute_sighash_scope(sign_outputs, anyone_can_pay);
279
280 let signature = sign_with_k(private_key, sighash, k)?;
282 let tx_sig = TransactionSignature::new(signature, scope);
283
284 let sig_bytes = tx_sig.to_checksig_format();
286 let pubkey_bytes = private_key.public_key().to_compressed();
287
288 let mut script = Script::new();
289 script.write_bin(&sig_bytes).write_bin(&pubkey_bytes);
290
291 Ok(UnlockingScript::from_script(script))
292 }
293}
294
295impl ScriptTemplate for RPuzzle {
296 fn lock(&self, params: &[u8]) -> Result<LockingScript> {
315 let mut chunks = vec![
333 ScriptChunk::new_opcode(OP_OVER),
334 ScriptChunk::new_opcode(OP_3),
335 ScriptChunk::new_opcode(OP_SPLIT),
336 ScriptChunk::new_opcode(OP_NIP),
337 ScriptChunk::new_opcode(OP_1),
338 ScriptChunk::new_opcode(OP_SPLIT),
339 ScriptChunk::new_opcode(OP_SWAP),
340 ScriptChunk::new_opcode(OP_SPLIT),
341 ScriptChunk::new_opcode(OP_DROP),
342 ];
343
344 if let Some(hash_op) = self.puzzle_type.hash_opcode() {
346 chunks.push(ScriptChunk::new_opcode(hash_op));
347 }
348
349 let op = if params.len() < OP_PUSHDATA1 as usize {
351 params.len() as u8
352 } else if params.len() < 256 {
353 OP_PUSHDATA1
354 } else {
355 OP_PUSHDATA2
356 };
357 chunks.push(ScriptChunk::new(op, Some(params.to_vec())));
358
359 chunks.push(ScriptChunk::new_opcode(OP_EQUALVERIFY));
361 chunks.push(ScriptChunk::new_opcode(OP_CHECKSIG));
362
363 Ok(LockingScript::from_chunks(chunks))
364 }
365}
366
367fn sign_with_k(
372 private_key: &PrivateKey,
373 msg_hash: &[u8; 32],
374 k: &BigNumber,
375) -> Result<crate::primitives::ec::Signature> {
376 use k256::ecdsa::Signature as K256Signature;
377
378 let secret_key = k256::SecretKey::from_slice(&private_key.to_bytes())
380 .map_err(|e| Error::CryptoError(format!("Invalid private key: {}", e)))?;
381 let d = secret_key.to_nonzero_scalar();
382
383 let k_bytes = k.to_bytes_be(32);
385 let k_uint = U256::from_be_slice(&k_bytes);
386 let k_scalar: CtOption<NonZeroScalar> = NonZeroScalar::from_uint(k_uint);
387 let k_nonzero = k_scalar
388 .into_option()
389 .ok_or_else(|| Error::CryptoError("Invalid K value".to_string()))?;
390
391 let r_point = (ProjectivePoint::GENERATOR * k_nonzero.as_ref()).to_affine();
393 let r_bytes: FieldBytes = r_point.x();
394 let r = <Scalar as Reduce<U256>>::reduce_bytes(&r_bytes);
395
396 #[allow(deprecated)]
399 let z_bytes: FieldBytes = *FieldBytes::from_slice(msg_hash);
400 let z = <Scalar as Reduce<U256>>::reduce_bytes(&z_bytes);
401 let k_inv_opt: CtOption<Scalar> = k_nonzero.invert();
402 let k_inv: Scalar = k_inv_opt
403 .into_option()
404 .ok_or_else(|| Error::CryptoError("K has no inverse".to_string()))?;
405 let s = k_inv * (z + r * d.as_ref());
406
407 let r_nonzero: CtOption<NonZeroScalar> = NonZeroScalar::new(r);
409 let s_nonzero: CtOption<NonZeroScalar> = NonZeroScalar::new(s);
410
411 let r_nz = r_nonzero
412 .into_option()
413 .ok_or_else(|| Error::CryptoError("R is zero".to_string()))?;
414 let s_nz = s_nonzero
415 .into_option()
416 .ok_or_else(|| Error::CryptoError("S is zero".to_string()))?;
417
418 let k256_sig = K256Signature::from_scalars(r_nz, s_nz)
420 .map_err(|e| Error::CryptoError(format!("Failed to create signature: {}", e)))?;
421
422 let r_bytes = k256_sig.r().to_bytes();
424 let s_bytes = k256_sig.s().to_bytes();
425
426 let mut r_arr = [0u8; 32];
427 let mut s_arr = [0u8; 32];
428 r_arr.copy_from_slice(&r_bytes);
429 s_arr.copy_from_slice(&s_bytes);
430
431 let signature = crate::primitives::ec::Signature::new(r_arr, s_arr);
432
433 Ok(signature.to_low_s())
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440
441 #[test]
442 fn test_rpuzzle_lock_raw() {
443 let r_value = [0x42u8; 32];
444 let template = RPuzzle::new(RPuzzleType::Raw);
445 let locking = template.lock(&r_value).unwrap();
446
447 let asm = locking.to_asm();
448
449 assert!(asm.contains("OP_OVER"));
451 assert!(asm.contains("OP_3"));
452 assert!(asm.contains("OP_SPLIT"));
453 assert!(asm.contains("OP_NIP"));
454 assert!(asm.contains("OP_SWAP"));
455 assert!(asm.contains("OP_DROP"));
456
457 assert!(asm.contains("OP_EQUALVERIFY"));
459 assert!(asm.contains("OP_CHECKSIG"));
460
461 assert!(!asm.contains("OP_HASH160"));
463 assert!(!asm.contains("OP_SHA256"));
464 }
465
466 #[test]
467 fn test_rpuzzle_lock_hash160() {
468 let r_hash = [0x42u8; 20];
469 let template = RPuzzle::new(RPuzzleType::Hash160);
470 let locking = template.lock(&r_hash).unwrap();
471
472 let asm = locking.to_asm();
473
474 assert!(asm.contains("OP_HASH160"));
476 }
477
478 #[test]
479 fn test_rpuzzle_lock_sha256() {
480 let r_hash = [0x42u8; 32];
481 let template = RPuzzle::new(RPuzzleType::Sha256);
482 let locking = template.lock(&r_hash).unwrap();
483
484 let asm = locking.to_asm();
485
486 assert!(asm.contains("OP_SHA256"));
488 }
489
490 #[test]
491 fn test_rpuzzle_type_hash() {
492 let data = b"test data";
493
494 assert_eq!(RPuzzleType::Raw.hash(data), data.to_vec());
496 assert_eq!(RPuzzleType::Sha1.hash(data).len(), 20);
497 assert_eq!(RPuzzleType::Sha256.hash(data).len(), 32);
498 assert_eq!(RPuzzleType::Hash256.hash(data).len(), 32);
499 assert_eq!(RPuzzleType::Ripemd160.hash(data).len(), 20);
500 assert_eq!(RPuzzleType::Hash160.hash(data).len(), 20);
501 }
502
503 #[test]
504 fn test_compute_r_from_k() {
505 let k =
507 BigNumber::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
508 .unwrap();
509
510 let r = RPuzzle::compute_r_from_k(&k).unwrap();
511
512 let expected_r =
515 hex::decode("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798")
516 .unwrap();
517 assert_eq!(r.to_vec(), expected_r);
518 }
519
520 #[test]
521 fn test_rpuzzle_unlock_produces_valid_script() {
522 let k =
523 BigNumber::from_hex("0000000000000000000000000000000000000000000000000000000000000002")
524 .unwrap();
525 let private_key = PrivateKey::random();
526 let sighash = [1u8; 32];
527
528 let unlocking =
529 RPuzzle::sign_with_sighash(&k, &private_key, &sighash, SignOutputs::All, false)
530 .unwrap();
531
532 let chunks = unlocking.chunks();
534 assert_eq!(chunks.len(), 2);
535
536 assert!(chunks[0].data.is_some());
538 let sig_data = chunks[0].data.as_ref().unwrap();
539
540 let expected_r = RPuzzle::compute_r_from_k(&k).unwrap();
542
543 let r_len = sig_data[3] as usize;
547 let r_start = 4;
548 let r_bytes = &sig_data[r_start..r_start + r_len];
549
550 let r_trimmed: Vec<u8> = r_bytes.iter().copied().skip_while(|&b| b == 0).collect();
552 let expected_trimmed: Vec<u8> =
553 expected_r.iter().copied().skip_while(|&b| b == 0).collect();
554
555 assert_eq!(r_trimmed, expected_trimmed);
556 }
557
558 #[test]
559 fn test_rpuzzle_estimate_length() {
560 let k = BigNumber::from_i64(1);
561 let private_key = PrivateKey::random();
562
563 let unlock = RPuzzle::unlock(&k, &private_key, SignOutputs::All, false);
564
565 assert_eq!(unlock.estimate_length(), 108);
567 }
568
569 #[test]
570 fn test_rpuzzle_all_hash_types_lock() {
571 let data = [0x42u8; 32];
572
573 for puzzle_type in [
575 RPuzzleType::Raw,
576 RPuzzleType::Sha1,
577 RPuzzleType::Sha256,
578 RPuzzleType::Hash256,
579 RPuzzleType::Ripemd160,
580 RPuzzleType::Hash160,
581 ] {
582 let hash = puzzle_type.hash(&data);
583 let template = RPuzzle::new(puzzle_type);
584 let locking = template.lock(&hash);
585 assert!(locking.is_ok(), "Failed for {:?}", puzzle_type);
586 }
587 }
588}