use crate::error::Error;
use crate::primitives::bsv::TransactionSignature;
use crate::primitives::ec::PrivateKey;
use crate::primitives::hash::{hash160, ripemd160, sha1, sha256, sha256d};
use crate::primitives::BigNumber;
use crate::script::op::*;
use crate::script::template::{
compute_sighash_scope, ScriptTemplate, ScriptTemplateUnlock, SignOutputs, SigningContext,
};
use crate::script::{LockingScript, Script, ScriptChunk, UnlockingScript};
use crate::Result;
use k256::elliptic_curve::ops::Reduce;
use k256::elliptic_curve::point::AffineCoordinates;
use k256::elliptic_curve::subtle::CtOption;
use k256::{FieldBytes, NonZeroScalar, ProjectivePoint, Scalar, U256};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RPuzzleType {
#[default]
Raw,
Sha1,
Sha256,
Hash256,
Ripemd160,
Hash160,
}
impl RPuzzleType {
fn hash_opcode(self) -> Option<u8> {
match self {
RPuzzleType::Raw => None,
RPuzzleType::Sha1 => Some(OP_SHA1),
RPuzzleType::Sha256 => Some(OP_SHA256),
RPuzzleType::Hash256 => Some(OP_HASH256),
RPuzzleType::Ripemd160 => Some(OP_RIPEMD160),
RPuzzleType::Hash160 => Some(OP_HASH160),
}
}
pub fn hash(self, data: &[u8]) -> Vec<u8> {
match self {
RPuzzleType::Raw => data.to_vec(),
RPuzzleType::Sha1 => sha1(data).to_vec(),
RPuzzleType::Sha256 => sha256(data).to_vec(),
RPuzzleType::Hash256 => sha256d(data).to_vec(),
RPuzzleType::Ripemd160 => ripemd160(data).to_vec(),
RPuzzleType::Hash160 => hash160(data).to_vec(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct RPuzzle {
pub puzzle_type: RPuzzleType,
}
impl Default for RPuzzle {
fn default() -> Self {
Self::new(RPuzzleType::Raw)
}
}
impl RPuzzle {
pub fn new(puzzle_type: RPuzzleType) -> Self {
Self { puzzle_type }
}
pub fn compute_r_from_k(k: &BigNumber) -> Result<[u8; 32]> {
let k_bytes = k.to_bytes_be(32);
let k_uint = U256::from_be_slice(&k_bytes);
let k_scalar: CtOption<NonZeroScalar> = NonZeroScalar::from_uint(k_uint);
let k_scalar = k_scalar
.into_option()
.ok_or_else(|| Error::CryptoError("Invalid K value (zero or >= order)".to_string()))?;
let point = ProjectivePoint::GENERATOR * k_scalar.as_ref();
let point_affine = point.to_affine();
let x: FieldBytes = point_affine.x();
let mut r_bytes = [0u8; 32];
r_bytes.copy_from_slice(&x);
Ok(r_bytes)
}
pub fn unlock(
k: &BigNumber,
private_key: &PrivateKey,
sign_outputs: SignOutputs,
anyone_can_pay: bool,
) -> ScriptTemplateUnlock {
let k_value = k.clone();
let key = private_key.clone();
let scope = compute_sighash_scope(sign_outputs, anyone_can_pay);
ScriptTemplateUnlock::new(
move |context: &SigningContext| {
let sighash = context.compute_sighash(scope)?;
let signature = sign_with_k(&key, &sighash, &k_value)?;
let tx_sig = TransactionSignature::new(signature, scope);
let sig_bytes = tx_sig.to_checksig_format();
let pubkey_bytes = key.public_key().to_compressed();
let mut script = Script::new();
script.write_bin(&sig_bytes).write_bin(&pubkey_bytes);
Ok(UnlockingScript::from_script(script))
},
|| {
108
},
)
}
pub fn sign_with_sighash(
k: &BigNumber,
private_key: &PrivateKey,
sighash: &[u8; 32],
sign_outputs: SignOutputs,
anyone_can_pay: bool,
) -> Result<UnlockingScript> {
let scope = compute_sighash_scope(sign_outputs, anyone_can_pay);
let signature = sign_with_k(private_key, sighash, k)?;
let tx_sig = TransactionSignature::new(signature, scope);
let sig_bytes = tx_sig.to_checksig_format();
let pubkey_bytes = private_key.public_key().to_compressed();
let mut script = Script::new();
script.write_bin(&sig_bytes).write_bin(&pubkey_bytes);
Ok(UnlockingScript::from_script(script))
}
}
impl ScriptTemplate for RPuzzle {
fn lock(&self, params: &[u8]) -> Result<LockingScript> {
let mut chunks = vec![
ScriptChunk::new_opcode(OP_OVER),
ScriptChunk::new_opcode(OP_3),
ScriptChunk::new_opcode(OP_SPLIT),
ScriptChunk::new_opcode(OP_NIP),
ScriptChunk::new_opcode(OP_1),
ScriptChunk::new_opcode(OP_SPLIT),
ScriptChunk::new_opcode(OP_SWAP),
ScriptChunk::new_opcode(OP_SPLIT),
ScriptChunk::new_opcode(OP_DROP),
];
if let Some(hash_op) = self.puzzle_type.hash_opcode() {
chunks.push(ScriptChunk::new_opcode(hash_op));
}
let op = if params.len() < OP_PUSHDATA1 as usize {
params.len() as u8
} else if params.len() < 256 {
OP_PUSHDATA1
} else {
OP_PUSHDATA2
};
chunks.push(ScriptChunk::new(op, Some(params.to_vec())));
chunks.push(ScriptChunk::new_opcode(OP_EQUALVERIFY));
chunks.push(ScriptChunk::new_opcode(OP_CHECKSIG));
Ok(LockingScript::from_chunks(chunks))
}
}
fn sign_with_k(
private_key: &PrivateKey,
msg_hash: &[u8; 32],
k: &BigNumber,
) -> Result<crate::primitives::ec::Signature> {
use k256::ecdsa::Signature as K256Signature;
let secret_key = k256::SecretKey::from_slice(&private_key.to_bytes())
.map_err(|e| Error::CryptoError(format!("Invalid private key: {}", e)))?;
let d = secret_key.to_nonzero_scalar();
let k_bytes = k.to_bytes_be(32);
let k_uint = U256::from_be_slice(&k_bytes);
let k_scalar: CtOption<NonZeroScalar> = NonZeroScalar::from_uint(k_uint);
let k_nonzero = k_scalar
.into_option()
.ok_or_else(|| Error::CryptoError("Invalid K value".to_string()))?;
let r_point = (ProjectivePoint::GENERATOR * k_nonzero.as_ref()).to_affine();
let r_bytes: FieldBytes = r_point.x();
let r = <Scalar as Reduce<U256>>::reduce_bytes(&r_bytes);
#[allow(deprecated)]
let z_bytes: FieldBytes = *FieldBytes::from_slice(msg_hash);
let z = <Scalar as Reduce<U256>>::reduce_bytes(&z_bytes);
let k_inv_opt: CtOption<Scalar> = k_nonzero.invert();
let k_inv: Scalar = k_inv_opt
.into_option()
.ok_or_else(|| Error::CryptoError("K has no inverse".to_string()))?;
let s = k_inv * (z + r * d.as_ref());
let r_nonzero: CtOption<NonZeroScalar> = NonZeroScalar::new(r);
let s_nonzero: CtOption<NonZeroScalar> = NonZeroScalar::new(s);
let r_nz = r_nonzero
.into_option()
.ok_or_else(|| Error::CryptoError("R is zero".to_string()))?;
let s_nz = s_nonzero
.into_option()
.ok_or_else(|| Error::CryptoError("S is zero".to_string()))?;
let k256_sig = K256Signature::from_scalars(r_nz, s_nz)
.map_err(|e| Error::CryptoError(format!("Failed to create signature: {}", e)))?;
let r_bytes = k256_sig.r().to_bytes();
let s_bytes = k256_sig.s().to_bytes();
let mut r_arr = [0u8; 32];
let mut s_arr = [0u8; 32];
r_arr.copy_from_slice(&r_bytes);
s_arr.copy_from_slice(&s_bytes);
let signature = crate::primitives::ec::Signature::new(r_arr, s_arr);
Ok(signature.to_low_s())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rpuzzle_lock_raw() {
let r_value = [0x42u8; 32];
let template = RPuzzle::new(RPuzzleType::Raw);
let locking = template.lock(&r_value).unwrap();
let asm = locking.to_asm();
assert!(asm.contains("OP_OVER"));
assert!(asm.contains("OP_3"));
assert!(asm.contains("OP_SPLIT"));
assert!(asm.contains("OP_NIP"));
assert!(asm.contains("OP_SWAP"));
assert!(asm.contains("OP_DROP"));
assert!(asm.contains("OP_EQUALVERIFY"));
assert!(asm.contains("OP_CHECKSIG"));
assert!(!asm.contains("OP_HASH160"));
assert!(!asm.contains("OP_SHA256"));
}
#[test]
fn test_rpuzzle_lock_hash160() {
let r_hash = [0x42u8; 20];
let template = RPuzzle::new(RPuzzleType::Hash160);
let locking = template.lock(&r_hash).unwrap();
let asm = locking.to_asm();
assert!(asm.contains("OP_HASH160"));
}
#[test]
fn test_rpuzzle_lock_sha256() {
let r_hash = [0x42u8; 32];
let template = RPuzzle::new(RPuzzleType::Sha256);
let locking = template.lock(&r_hash).unwrap();
let asm = locking.to_asm();
assert!(asm.contains("OP_SHA256"));
}
#[test]
fn test_rpuzzle_type_hash() {
let data = b"test data";
assert_eq!(RPuzzleType::Raw.hash(data), data.to_vec());
assert_eq!(RPuzzleType::Sha1.hash(data).len(), 20);
assert_eq!(RPuzzleType::Sha256.hash(data).len(), 32);
assert_eq!(RPuzzleType::Hash256.hash(data).len(), 32);
assert_eq!(RPuzzleType::Ripemd160.hash(data).len(), 20);
assert_eq!(RPuzzleType::Hash160.hash(data).len(), 20);
}
#[test]
fn test_compute_r_from_k() {
let k =
BigNumber::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
.unwrap();
let r = RPuzzle::compute_r_from_k(&k).unwrap();
let expected_r =
hex::decode("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798")
.unwrap();
assert_eq!(r.to_vec(), expected_r);
}
#[test]
fn test_rpuzzle_unlock_produces_valid_script() {
let k =
BigNumber::from_hex("0000000000000000000000000000000000000000000000000000000000000002")
.unwrap();
let private_key = PrivateKey::random();
let sighash = [1u8; 32];
let unlocking =
RPuzzle::sign_with_sighash(&k, &private_key, &sighash, SignOutputs::All, false)
.unwrap();
let chunks = unlocking.chunks();
assert_eq!(chunks.len(), 2);
assert!(chunks[0].data.is_some());
let sig_data = chunks[0].data.as_ref().unwrap();
let expected_r = RPuzzle::compute_r_from_k(&k).unwrap();
let r_len = sig_data[3] as usize;
let r_start = 4;
let r_bytes = &sig_data[r_start..r_start + r_len];
let r_trimmed: Vec<u8> = r_bytes.iter().copied().skip_while(|&b| b == 0).collect();
let expected_trimmed: Vec<u8> =
expected_r.iter().copied().skip_while(|&b| b == 0).collect();
assert_eq!(r_trimmed, expected_trimmed);
}
#[test]
fn test_rpuzzle_estimate_length() {
let k = BigNumber::from_i64(1);
let private_key = PrivateKey::random();
let unlock = RPuzzle::unlock(&k, &private_key, SignOutputs::All, false);
assert_eq!(unlock.estimate_length(), 108);
}
#[test]
fn test_rpuzzle_all_hash_types_lock() {
let data = [0x42u8; 32];
for puzzle_type in [
RPuzzleType::Raw,
RPuzzleType::Sha1,
RPuzzleType::Sha256,
RPuzzleType::Hash256,
RPuzzleType::Ripemd160,
RPuzzleType::Hash160,
] {
let hash = puzzle_type.hash(&data);
let template = RPuzzle::new(puzzle_type);
let locking = template.lock(&hash);
assert!(locking.is_ok(), "Failed for {:?}", puzzle_type);
}
}
}