use crate::error::{Error, Result};
use crate::primitives::ec::{calculate_recovery_id, recover_public_key, PrivateKey, PublicKey};
use crate::primitives::encoding::Writer;
use crate::primitives::hash::sha256d;
const BSM_MAGIC: &[u8] = b"Bitcoin Signed Message:\n";
pub fn sign_message(private_key: &PrivateKey, message: &[u8]) -> Result<Vec<u8>> {
sign_message_with_compression(private_key, message, true)
}
pub fn sign_message_with_compression(
private_key: &PrivateKey,
message: &[u8],
compressed: bool,
) -> Result<Vec<u8>> {
let msg_hash = compute_message_hash(message);
let signature = private_key.sign(&msg_hash)?;
let public_key = private_key.public_key();
let recovery_id = calculate_recovery_id(&msg_hash, &signature, &public_key)?;
let recovery_flag = recovery_id + 27 + if compressed { 4 } else { 0 };
let mut result = Vec::with_capacity(65);
result.push(recovery_flag);
result.extend_from_slice(signature.r());
result.extend_from_slice(signature.s());
Ok(result)
}
pub fn verify_message(address: &str, signature: &[u8], message: &[u8]) -> Result<bool> {
let (public_key, was_compressed) = recover_public_key_from_signature(signature, message)?;
let recovered_address = if was_compressed {
public_key.to_address()
} else {
let hash = crate::primitives::hash::hash160(&public_key.to_uncompressed());
crate::primitives::encoding::to_base58_check(&hash, &[0x00])
};
Ok(recovered_address == address)
}
pub fn recover_public_key_from_signature(
signature: &[u8],
message: &[u8],
) -> Result<(PublicKey, bool)> {
if signature.len() != 65 {
return Err(Error::InvalidSignature(format!(
"Expected 65 bytes, got {}",
signature.len()
)));
}
let recovery_flag = signature[0];
if !(27..=34).contains(&recovery_flag) {
return Err(Error::InvalidSignature(format!(
"Invalid recovery flag: {}, expected 27-34",
recovery_flag
)));
}
let was_compressed = recovery_flag >= 31;
let recovery_id = if was_compressed {
recovery_flag - 31
} else {
recovery_flag - 27
};
let r: [u8; 32] = signature[1..33]
.try_into()
.map_err(|_| Error::InvalidSignature("Invalid r value".to_string()))?;
let s: [u8; 32] = signature[33..65]
.try_into()
.map_err(|_| Error::InvalidSignature("Invalid s value".to_string()))?;
let sig = crate::primitives::ec::Signature::new(r, s);
let msg_hash = compute_message_hash(message);
let public_key = recover_public_key(&msg_hash, &sig, recovery_id)?;
Ok((public_key, was_compressed))
}
pub fn magic_hash(message: &[u8]) -> [u8; 32] {
let mut writer = Writer::new();
writer.write_var_int(BSM_MAGIC.len() as u64);
writer.write_bytes(BSM_MAGIC);
writer.write_var_int(message.len() as u64);
writer.write_bytes(message);
sha256d(writer.as_bytes())
}
pub fn verify_message_der(
der_signature: &[u8],
public_key: &PublicKey,
message: &[u8],
) -> Result<bool> {
let sig = crate::primitives::ec::Signature::from_der(der_signature)?;
let msg_hash = compute_message_hash(message);
Ok(public_key.verify(&msg_hash, &sig))
}
#[inline]
fn compute_message_hash(message: &[u8]) -> [u8; 32] {
magic_hash(message)
}
#[cfg(test)]
mod tests {
use super::*;
use base64::Engine;
#[test]
fn test_sign_and_verify() {
let key = PrivateKey::random();
let address = key.public_key().to_address();
let message = b"Hello, BSV!";
let signature = sign_message(&key, message).unwrap();
assert_eq!(signature.len(), 65);
assert!(verify_message(&address, &signature, message).unwrap());
}
#[test]
fn test_sign_and_verify_compressed() {
let key = PrivateKey::random();
let address = key.public_key().to_address();
let message = b"Test message";
let signature = sign_message_with_compression(&key, message, true).unwrap();
assert!(verify_message(&address, &signature, message).unwrap());
assert!(signature[0] >= 31);
}
#[test]
fn test_sign_and_verify_uncompressed() {
let key = PrivateKey::random();
let message = b"Test message";
let hash = crate::primitives::hash::hash160(&key.public_key().to_uncompressed());
let address = crate::primitives::encoding::to_base58_check(&hash, &[0x00]);
let signature = sign_message_with_compression(&key, message, false).unwrap();
assert!(verify_message(&address, &signature, message).unwrap());
assert!(signature[0] < 31);
}
#[test]
fn test_recover_public_key() {
let key = PrivateKey::random();
let message = b"Hello!";
let signature = sign_message(&key, message).unwrap();
let (recovered, compressed) =
recover_public_key_from_signature(&signature, message).unwrap();
assert_eq!(recovered.to_compressed(), key.public_key().to_compressed());
assert!(compressed);
}
#[test]
fn test_invalid_signature_length() {
let result = recover_public_key_from_signature(&[0u8; 64], b"test");
assert!(result.is_err());
}
#[test]
fn test_invalid_recovery_flag() {
let mut sig = [0u8; 65];
sig[0] = 26; let result = recover_public_key_from_signature(&sig, b"test");
assert!(result.is_err());
sig[0] = 35; let result = recover_public_key_from_signature(&sig, b"test");
assert!(result.is_err());
}
#[test]
fn test_verify_wrong_address() {
let key1 = PrivateKey::random();
let key2 = PrivateKey::random();
let message = b"Hello!";
let signature = sign_message(&key1, message).unwrap();
let wrong_address = key2.public_key().to_address();
assert!(!verify_message(&wrong_address, &signature, message).unwrap());
}
#[test]
fn test_verify_wrong_message() {
let key = PrivateKey::random();
let address = key.public_key().to_address();
let signature = sign_message(&key, b"Hello!").unwrap();
assert!(!verify_message(&address, &signature, b"Goodbye!").unwrap());
}
#[test]
fn test_message_hash_format() {
let message = b"test";
let hash = compute_message_hash(message);
assert_eq!(hash.len(), 32);
let hash2 = compute_message_hash(message);
assert_eq!(hash, hash2);
let hash3 = compute_message_hash(b"other");
assert_ne!(hash, hash3);
}
#[test]
fn test_magic_hash_public_api() {
let message = b"Hello, BSV!";
let hash = magic_hash(message);
assert_eq!(hash.len(), 32);
let hash2 = magic_hash(message);
assert_eq!(hash, hash2);
let hash3 = magic_hash(b"Different message");
assert_ne!(hash, hash3);
}
#[test]
fn test_magic_hash_empty_message() {
let hash = magic_hash(b"");
assert_eq!(hash.len(), 32);
}
#[test]
fn test_magic_hash_long_message() {
let message = vec![b'a'; 10000];
let hash = magic_hash(&message);
assert_eq!(hash.len(), 32);
}
#[test]
fn test_magic_hash_matches_signing() {
let key = PrivateKey::random();
let message = b"Test message";
let hash = magic_hash(message);
let signature = sign_message(&key, message).unwrap();
let (recovered, _) = recover_public_key_from_signature(&signature, message).unwrap();
assert_eq!(recovered.to_compressed(), key.public_key().to_compressed());
assert_eq!(hash.len(), 32);
}
#[test]
fn test_known_vector() {
let key = PrivateKey::from_hex(
"e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
)
.unwrap();
let address = key.public_key().to_address();
let message = b"This is a test message";
let signature = sign_message(&key, message).unwrap();
assert!(verify_message(&address, &signature, message).unwrap());
let (recovered, _) = recover_public_key_from_signature(&signature, message).unwrap();
assert_eq!(recovered.to_compressed(), key.public_key().to_compressed());
}
#[test]
fn test_cross_sdk_bsm_known_vector() {
let key =
PrivateKey::from_wif("L211enC224G1kV8pyyq7bjVd9SxZebnRYEzzM3i7ZHCc1c5E7dQu").unwrap();
let message = b"hello world";
let signature = sign_message(&key, message).unwrap();
assert_eq!(signature.len(), 65, "BSM signature must be 65 bytes");
let sig_base64 = base64::engine::general_purpose::STANDARD.encode(&signature);
assert_eq!(
sig_base64,
"H4T8Asr0WkC6wYfBESR6pCAfECtdsPM4fwiSQ2qndFi8dVtv/mrOFaySx9xQE7j24ugoJ4iGnsRwAC8QwaoHOXk=",
"Cross-SDK BSM signature base64 mismatch with TS SDK"
);
let address = key.public_key().to_address();
assert!(
verify_message(&address, &signature, message).unwrap(),
"Cross-SDK BSM signature should verify"
);
let (recovered, compressed) =
recover_public_key_from_signature(&signature, message).unwrap();
assert_eq!(
recovered.to_compressed(),
key.public_key().to_compressed(),
"Recovered public key should match original"
);
assert!(compressed, "Signature should indicate compressed key");
}
#[test]
fn test_empty_message() {
let key = PrivateKey::random();
let address = key.public_key().to_address();
let message = b"";
let signature = sign_message(&key, message).unwrap();
assert!(verify_message(&address, &signature, message).unwrap());
}
#[test]
fn test_long_message() {
let key = PrivateKey::random();
let address = key.public_key().to_address();
let message = vec![b'a'; 10000];
let signature = sign_message(&key, &message).unwrap();
assert!(verify_message(&address, &signature, &message).unwrap());
}
#[test]
fn test_verify_message_der_basic() {
let key = PrivateKey::random();
let message = b"Hello, BSV!";
let msg_hash = magic_hash(message);
let sig = key.sign(&msg_hash).unwrap();
let der = sig.to_der();
assert!(verify_message_der(&der, &key.public_key(), message).unwrap());
}
#[test]
fn test_verify_message_der_wrong_key() {
let key1 = PrivateKey::random();
let key2 = PrivateKey::random();
let message = b"Hello!";
let msg_hash = magic_hash(message);
let sig = key1.sign(&msg_hash).unwrap();
let der = sig.to_der();
assert!(!verify_message_der(&der, &key2.public_key(), message).unwrap());
}
#[test]
fn test_verify_message_der_wrong_message() {
let key = PrivateKey::random();
let message = b"Hello!";
let msg_hash = magic_hash(message);
let sig = key.sign(&msg_hash).unwrap();
let der = sig.to_der();
assert!(!verify_message_der(&der, &key.public_key(), b"Goodbye!").unwrap());
}
#[test]
fn test_verify_message_der_invalid_der() {
let key = PrivateKey::random();
let message = b"Hello!";
let result = verify_message_der(&[0x30, 0x00], &key.public_key(), message);
assert!(result.is_err());
}
#[test]
fn test_verify_message_der_empty_message() {
let key = PrivateKey::random();
let message = b"";
let msg_hash = magic_hash(message);
let sig = key.sign(&msg_hash).unwrap();
let der = sig.to_der();
assert!(verify_message_der(&der, &key.public_key(), message).unwrap());
}
#[test]
fn test_verify_message_der_long_message() {
let key = PrivateKey::random();
let message = vec![b'a'; 10000];
let msg_hash = magic_hash(&message);
let sig = key.sign(&msg_hash).unwrap();
let der = sig.to_der();
assert!(verify_message_der(&der, &key.public_key(), &message).unwrap());
}
#[test]
fn test_verify_message_der_known_key() {
let key = PrivateKey::from_hex(
"e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
)
.unwrap();
let message = b"This is a test message";
let msg_hash = magic_hash(message);
let sig = key.sign(&msg_hash).unwrap();
let der = sig.to_der();
assert!(verify_message_der(&der, &key.public_key(), message).unwrap());
}
#[test]
fn test_verify_message_der_consistent_with_compact() {
let key = PrivateKey::random();
let message = b"Consistency check";
let compact_sig = sign_message(&key, message).unwrap();
let address = key.public_key().to_address();
let compact_result = verify_message(&address, &compact_sig, message).unwrap();
let msg_hash = magic_hash(message);
let sig = key.sign(&msg_hash).unwrap();
let der = sig.to_der();
let der_result = verify_message_der(&der, &key.public_key(), message).unwrap();
assert_eq!(compact_result, der_result);
assert!(compact_result);
}
#[test]
fn test_verify_message_der_from_compact_roundtrip() {
let key = PrivateKey::random();
let message = b"Roundtrip test";
let compact_sig = sign_message(&key, message).unwrap();
let r: [u8; 32] = compact_sig[1..33].try_into().unwrap();
let s: [u8; 32] = compact_sig[33..65].try_into().unwrap();
let sig = crate::primitives::ec::Signature::new(r, s);
let der = sig.to_der();
assert!(verify_message_der(&der, &key.public_key(), message).unwrap());
}
}