use crate::error::{Error, Result};
use k256::ecdsa::signature::hazmat::PrehashVerifier;
use k256::ecdsa::{RecoveryId, VerifyingKey};
use super::{PrivateKey, PublicKey, Signature};
pub fn sign(msg_hash: &[u8; 32], private_key: &PrivateKey) -> Result<Signature> {
private_key.sign(msg_hash)
}
pub fn verify(msg_hash: &[u8; 32], signature: &Signature, public_key: &PublicKey) -> bool {
let verifying_key = match VerifyingKey::from_sec1_bytes(&public_key.to_compressed()) {
Ok(vk) => vk,
Err(_) => return false,
};
let mut sig_bytes = [0u8; 64];
sig_bytes[..32].copy_from_slice(signature.r());
sig_bytes[32..].copy_from_slice(signature.s());
let k256_sig = match k256::ecdsa::Signature::from_slice(&sig_bytes) {
Ok(s) => s,
Err(_) => return false,
};
verifying_key.verify_prehash(msg_hash, &k256_sig).is_ok()
}
pub fn recover_public_key(
msg_hash: &[u8; 32],
signature: &Signature,
recovery_id: u8,
) -> Result<PublicKey> {
if recovery_id > 1 {
return Err(Error::InvalidSignature(format!(
"Recovery ID must be 0 or 1, got {}",
recovery_id
)));
}
let mut sig_bytes = [0u8; 64];
sig_bytes[..32].copy_from_slice(signature.r());
sig_bytes[32..].copy_from_slice(signature.s());
let k256_sig = k256::ecdsa::Signature::from_slice(&sig_bytes)
.map_err(|e| Error::InvalidSignature(format!("Invalid signature format: {}", e)))?;
let recid = RecoveryId::from_byte(recovery_id)
.ok_or_else(|| Error::InvalidSignature("Invalid recovery ID".to_string()))?;
let verifying_key = VerifyingKey::recover_from_prehash(msg_hash, &k256_sig, recid)
.map_err(|e| Error::CryptoError(format!("Public key recovery failed: {}", e)))?;
let encoded = verifying_key.to_encoded_point(true);
PublicKey::from_bytes(encoded.as_bytes())
}
pub fn calculate_recovery_id(
msg_hash: &[u8; 32],
signature: &Signature,
public_key: &PublicKey,
) -> Result<u8> {
let expected = public_key.to_compressed();
for recovery_id in 0..2u8 {
if let Ok(recovered) = recover_public_key(msg_hash, signature, recovery_id) {
if recovered.to_compressed() == expected {
return Ok(recovery_id);
}
}
}
Err(Error::CryptoError(
"Could not find valid recovery ID".to_string(),
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::hash::sha256;
#[test]
fn test_sign_and_verify() {
let key = PrivateKey::random();
let pubkey = key.public_key();
let msg_hash = sha256(b"Hello, BSV!");
let signature = sign(&msg_hash, &key).unwrap();
assert!(verify(&msg_hash, &signature, &pubkey));
}
#[test]
fn test_verify_wrong_key() {
let key = PrivateKey::random();
let other_key = PrivateKey::random();
let msg_hash = sha256(b"Hello, BSV!");
let signature = sign(&msg_hash, &key).unwrap();
assert!(!verify(&msg_hash, &signature, &other_key.public_key()));
}
#[test]
fn test_verify_wrong_message() {
let key = PrivateKey::random();
let pubkey = key.public_key();
let msg_hash1 = sha256(b"Hello, BSV!");
let msg_hash2 = sha256(b"Goodbye, BSV!");
let signature = sign(&msg_hash1, &key).unwrap();
assert!(!verify(&msg_hash2, &signature, &pubkey));
}
#[test]
fn test_recover_public_key() {
let key = PrivateKey::random();
let pubkey = key.public_key();
let msg_hash = sha256(b"Hello, BSV!");
let signature = sign(&msg_hash, &key).unwrap();
let mut found = false;
for recovery_id in 0..2u8 {
if let Ok(recovered) = recover_public_key(&msg_hash, &signature, recovery_id) {
if recovered.to_compressed() == pubkey.to_compressed() {
found = true;
break;
}
}
}
assert!(found);
}
#[test]
fn test_calculate_recovery_id() {
let key = PrivateKey::random();
let pubkey = key.public_key();
let msg_hash = sha256(b"Hello, BSV!");
let signature = sign(&msg_hash, &key).unwrap();
let recovery_id = calculate_recovery_id(&msg_hash, &signature, &pubkey).unwrap();
assert!(recovery_id < 2);
let recovered = recover_public_key(&msg_hash, &signature, recovery_id).unwrap();
assert_eq!(recovered.to_compressed(), pubkey.to_compressed());
}
#[test]
fn test_invalid_recovery_id() {
let msg_hash = sha256(b"test");
let sig = Signature::new([1u8; 32], [2u8; 32]);
assert!(recover_public_key(&msg_hash, &sig, 2).is_err());
assert!(recover_public_key(&msg_hash, &sig, 255).is_err());
}
#[test]
fn test_signature_low_s() {
let key = PrivateKey::random();
let msg_hash = sha256(b"Test message");
let signature = sign(&msg_hash, &key).unwrap();
assert!(signature.is_low_s());
}
#[test]
fn test_deterministic_signing() {
let key = PrivateKey::from_hex(
"0000000000000000000000000000000000000000000000000000000000000001",
)
.unwrap();
let msg_hash = sha256(b"test");
let sig1 = sign(&msg_hash, &key).unwrap();
let sig2 = sign(&msg_hash, &key).unwrap();
assert_eq!(sig1.r(), sig2.r());
assert_eq!(sig1.s(), sig2.s());
}
}