use bsv::primitives::big_number::{BigNumber, Endian};
use bsv::primitives::curve::Curve;
use bsv::primitives::hash::sha256;
use bsv::primitives::private_key::PrivateKey;
use bsv::primitives::public_key::PublicKey;
use crate::error::{WalletError, WalletResult};
fn key_offset_to_hashed_secret(
pub_key: &PublicKey,
key_offset: &PrivateKey,
) -> WalletResult<BigNumber> {
let shared_secret_point = pub_key.point().mul(key_offset.bn());
let compressed = shared_secret_to_compressed(&shared_secret_point);
let hash = sha256(&compressed);
Ok(BigNumber::from_bytes(&hash, Endian::Big))
}
fn shared_secret_to_compressed(point: &bsv::primitives::point::Point) -> Vec<u8> {
point.to_der(true)
}
pub fn offset_pub_key(
commission_pub_key: &PublicKey,
key_offset: &PrivateKey,
) -> WalletResult<PublicKey> {
let hashed_secret = key_offset_to_hashed_secret(commission_pub_key, key_offset)?;
let curve = Curve::secp256k1();
let offset_point = curve.generator().mul(&hashed_secret);
let result_point = commission_pub_key.point().add(&offset_point);
Ok(PublicKey::from_point(result_point))
}
pub fn generate_key_offset() -> WalletResult<PrivateKey> {
PrivateKey::from_random()
.map_err(|e| WalletError::Internal(format!("Failed to generate random key offset: {}", e)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_offset_pub_key_produces_valid_pubkey() {
let commission_priv = PrivateKey::from_hex("cc").unwrap();
let commission_pub = commission_priv.to_public_key();
let key_offset = PrivateKey::from_hex("dd").unwrap();
let result = offset_pub_key(&commission_pub, &key_offset).unwrap();
let der = result.to_der();
assert_eq!(der.len(), 33, "compressed public key should be 33 bytes");
assert!(
der[0] == 0x02 || der[0] == 0x03,
"should start with 02 or 03"
);
}
#[test]
fn test_offset_pub_key_is_deterministic() {
let commission_priv = PrivateKey::from_hex("cc").unwrap();
let commission_pub = commission_priv.to_public_key();
let key_offset = PrivateKey::from_hex("dd").unwrap();
let result1 = offset_pub_key(&commission_pub, &key_offset).unwrap();
let result2 = offset_pub_key(&commission_pub, &key_offset).unwrap();
assert_eq!(
result1.to_der_hex(),
result2.to_der_hex(),
"offset_pub_key should be deterministic"
);
}
#[test]
fn test_offset_pub_key_differs_from_original() {
let commission_priv = PrivateKey::from_hex("cc").unwrap();
let commission_pub = commission_priv.to_public_key();
let key_offset = PrivateKey::from_hex("dd").unwrap();
let result = offset_pub_key(&commission_pub, &key_offset).unwrap();
assert_ne!(
result.to_der_hex(),
commission_pub.to_der_hex(),
"offset key should differ from original"
);
}
#[test]
fn test_different_offsets_produce_different_keys() {
let commission_priv = PrivateKey::from_hex("cc").unwrap();
let commission_pub = commission_priv.to_public_key();
let offset1 = PrivateKey::from_hex("dd").unwrap();
let offset2 = PrivateKey::from_hex("ee").unwrap();
let result1 = offset_pub_key(&commission_pub, &offset1).unwrap();
let result2 = offset_pub_key(&commission_pub, &offset2).unwrap();
assert_ne!(
result1.to_der_hex(),
result2.to_der_hex(),
"different offsets should produce different keys"
);
}
#[test]
fn test_generate_key_offset_produces_valid_key() {
let offset = generate_key_offset().unwrap();
let pub_key = offset.to_public_key();
let der = pub_key.to_der();
assert_eq!(der.len(), 33);
}
}