use anyhow::{Context, Result};
use bitcoin::hashes::{hash160, Hash};
use bitcoin::{Address, Network, PubkeyHash};
use k256::elliptic_curve::ops::MulByGenerator;
use k256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
use k256::elliptic_curve::PrimeField;
use k256::{AffinePoint, EncodedPoint, ProjectivePoint, Scalar};
pub type U256 = [u8; 32];
pub type Point = ProjectivePoint;
pub fn parse_pubkey(hex_str: &str) -> Result<Point> {
let bytes = hex::decode(hex_str.trim_start_matches("0x")).context("Invalid hex in pubkey")?;
let encoded = EncodedPoint::from_bytes(&bytes)
.map_err(|e| anyhow::anyhow!("Invalid encoded point: {e}"))?;
let affine = AffinePoint::from_encoded_point(&encoded);
if affine.is_some().into() {
Ok(ProjectivePoint::from(affine.unwrap()))
} else {
anyhow::bail!("Point not on curve")
}
}
pub fn parse_hex_u256(hex_str: &str) -> Result<U256> {
let hex_clean = hex_str.trim_start_matches("0x");
if hex_clean.len() > 64 {
anyhow::bail!(
"Invalid U256 length: {} hex chars (max 64)",
hex_clean.len()
);
}
let padded = format!("{:0>64}", hex_clean);
let bytes = hex::decode(&padded).context("Invalid hex")?;
let mut result = [0u8; 32];
result.copy_from_slice(&bytes);
result.reverse();
Ok(result)
}
pub fn verify_key_with_base(private_key: &[u8], public_key: &Point, base_point: &Point) -> bool {
if private_key.is_empty() || private_key.len() > 32 {
return false;
}
let mut key_be = [0u8; 32];
let offset = 32 - private_key.len();
key_be[offset..].copy_from_slice(private_key);
let scalar = match Scalar::from_repr_vartime(key_be.into()) {
Some(s) => s,
None => return false,
};
let computed = *base_point * scalar;
computed == *public_key
}
pub fn verify_key(private_key: &[u8], public_key: &Point) -> bool {
verify_key_with_base(private_key, public_key, &ProjectivePoint::GENERATOR)
}
pub fn privkey_to_pubkey(private_key: &[u8]) -> Result<Vec<u8>> {
if private_key.is_empty() || private_key.len() > 32 {
anyhow::bail!("Invalid private key length");
}
let mut key_be = [0u8; 32];
let offset = 32 - private_key.len();
key_be[offset..].copy_from_slice(private_key);
let scalar = Scalar::from_repr_vartime(key_be.into())
.ok_or_else(|| anyhow::anyhow!("Invalid scalar"))?;
let point = ProjectivePoint::mul_by_generator(&scalar);
let affine = point.to_affine();
let encoded = affine.to_encoded_point(true);
Ok(encoded.as_bytes().to_vec())
}
pub fn compute_hash160(data: &[u8]) -> [u8; 20] {
let hash = hash160::Hash::hash(data);
let mut result = [0u8; 20];
result.copy_from_slice(hash.as_ref());
result
}
pub fn pubkey_hash_to_address(hash: &[u8; 20]) -> String {
let pubkey_hash = PubkeyHash::from_slice(hash).expect("Invalid hash160");
Address::p2pkh(pubkey_hash, Network::Bitcoin).to_string()
}
#[derive(Debug)]
pub struct FullVerification {
pub pubkey_hex: String,
pub hash160_hex: String,
pub address: String,
pub pubkey_match: bool,
pub hash160_match: bool,
pub address_match: bool,
}
pub fn full_verify(
private_key: &[u8],
expected_pubkey: &str,
expected_hash160: &str,
expected_address: &str,
) -> Result<FullVerification> {
let pubkey = privkey_to_pubkey(private_key)?;
let pubkey_hex = hex::encode(&pubkey);
let h160 = compute_hash160(&pubkey);
let hash160_hex = hex::encode(h160);
let address = pubkey_hash_to_address(&h160);
Ok(FullVerification {
pubkey_hex: pubkey_hex.clone(),
hash160_hex: hash160_hex.clone(),
address: address.clone(),
pubkey_match: pubkey_hex == expected_pubkey,
hash160_match: hash160_hex == expected_hash160,
address_match: address == expected_address,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verify_key_with_base_generator() {
let key_bytes = [0x12u8, 0x34u8];
let mut key_be = [0u8; 32];
let offset = 32 - key_bytes.len();
key_be[offset..].copy_from_slice(&key_bytes);
let scalar = Scalar::from_repr_vartime(key_be.into()).unwrap();
let pubkey = ProjectivePoint::mul_by_generator(&scalar);
assert!(
verify_key(&key_bytes, &pubkey),
"verify_key should return true"
);
assert!(
verify_key_with_base(&key_bytes, &pubkey, &ProjectivePoint::GENERATOR),
"verify_key_with_base with generator should return true"
);
}
#[test]
fn test_verify_key_with_base_custom() {
let three = Scalar::from(3u64);
let five = Scalar::from(5u64);
let h = ProjectivePoint::GENERATOR * three;
let q = h * five;
let j_bytes = [5u8];
assert!(
verify_key_with_base(&j_bytes, &q, &h),
"verify_key_with_base with custom base H should return true"
);
assert!(
!verify_key(&j_bytes, &q),
"verify_key with generator should return false (5*G ≠ 15*G)"
);
}
#[test]
fn test_parse_hex_u256_rejects_too_long_input() {
let too_long = "11".repeat(33);
let err = parse_hex_u256(&too_long).unwrap_err();
assert!(err.to_string().contains("Invalid U256 length"));
}
#[test]
fn test_parse_hex_u256_accepts_short_hex() {
let parsed = parse_hex_u256("abcde").unwrap();
assert_eq!(parsed[0], 0xde);
assert_eq!(parsed[1], 0xbc);
assert_eq!(parsed[2], 0x0a);
assert!(parsed[3..].iter().all(|&b| b == 0));
}
}