use crate::secret::SecretWif;
use bitcoin_hashes::sha256;
use zeroize::Zeroizing;
#[must_use]
pub fn encode_wif(private_key: &crate::keygen::PrivateKey) -> SecretWif {
let mut payload = Zeroizing::new([0u8; 38]);
payload[0] = 0x80;
payload[1..33].copy_from_slice(private_key.as_bytes());
payload[33] = 0x01;
let hash1 = sha256::Hash::hash(&payload[..34]).to_byte_array();
let hash2 = sha256::Hash::hash(&hash1).to_byte_array();
payload[34..].copy_from_slice(&hash2[..4]);
base58_encode_wif(&payload)
}
const BASE58_ALPHABET: &[u8; 58] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
fn base58_encode_wif(payload: &[u8; 38]) -> SecretWif {
let mut num = Zeroizing::new(*payload);
let mut wif = SecretWif::zeroed();
let digits = wif.bytes_mut();
let mut first = 0;
let mut next = digits.len();
while first < num.len() {
let mut remainder = 0u32;
for byte in &mut num[first..] {
let accumulator = (remainder << 8) | u32::from(*byte);
*byte = (accumulator / 58) as u8;
remainder = accumulator % 58;
}
next -= 1;
digits[next] = BASE58_ALPHABET[remainder as usize];
while first < num.len() && num[first] == 0 {
first += 1;
}
}
assert_eq!(next, 0, "WIF payload must encode to exactly 52 digits");
wif
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keygen::PrivateKey;
fn key_from_hex(hex: &str) -> PrivateKey {
PrivateKey::from_hex(hex).unwrap()
}
#[test]
fn test_wif_vector_scalar_one() {
let key = key_from_hex("0000000000000000000000000000000000000000000000000000000000000001");
let wif = encode_wif(&key);
assert_eq!(
wif.expose_str(),
"KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"
);
}
#[test]
fn test_wif_vector_two() {
let key = key_from_hex("0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D");
let wif = encode_wif(&key);
assert_eq!(
wif.expose_str(),
"KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617"
);
}
#[test]
fn test_wif_starts_with_k_or_l() {
let keys = [
key_from_hex("0000000000000000000000000000000000000000000000000000000000000001"),
key_from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140"),
key_from_hex("0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"),
];
for key in &keys {
let wif = encode_wif(key);
assert!(
wif.expose_str().starts_with('K') || wif.expose_str().starts_with('L'),
"compressed mainnet WIF must start with K or L, got: {}",
wif.expose_str()
);
}
}
#[test]
fn test_wif_length_52() {
let key = key_from_hex("0000000000000000000000000000000000000000000000000000000000000001");
let wif = encode_wif(&key);
assert_eq!(
wif.expose_str().len(),
52,
"compressed mainnet WIF must be 52 characters"
);
}
#[test]
fn test_wif_valid_base58_characters() {
let key = key_from_hex("0000000000000000000000000000000000000000000000000000000000000001");
let wif = encode_wif(&key);
let base58_alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
for ch in wif.expose_str().chars() {
assert!(
base58_alphabet.contains(ch),
"WIF contains invalid Base58 character: '{}'",
ch
);
}
}
fn base58_decode(s: &str) -> Vec<u8> {
let alphabet = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
let leading_ones = s.chars().take_while(|&c| c == '1').count();
let mut num: Vec<u8> = Vec::new();
for ch in s.bytes() {
let val = alphabet
.iter()
.position(|&b| b == ch)
.expect("invalid base58 character") as u32;
let mut carry = val;
for byte in num.iter_mut().rev() {
carry += *byte as u32 * 58;
*byte = (carry & 0xFF) as u8;
carry >>= 8;
}
while carry > 0 {
num.insert(0, (carry & 0xFF) as u8);
carry >>= 8;
}
}
let mut result = vec![0u8; leading_ones];
result.extend_from_slice(&num);
result
}
#[test]
fn test_wif_checksum_valid() {
let key = key_from_hex("0000000000000000000000000000000000000000000000000000000000000001");
let wif = encode_wif(&key);
let decoded = base58_decode(wif.expose_str());
assert_eq!(decoded.len(), 38, "decoded WIF must be 38 bytes");
let payload = &decoded[..34];
let checksum = &decoded[34..38];
let hash1 = sha256::Hash::hash(payload).to_byte_array();
let hash2 = sha256::Hash::hash(&hash1).to_byte_array();
let expected_checksum = &hash2[..4];
assert_eq!(
checksum, expected_checksum,
"WIF checksum must match double-SHA256 of payload"
);
assert_eq!(payload[0], 0x80, "first byte must be mainnet prefix 0x80");
assert_eq!(
&payload[1..33],
key.as_bytes(),
"bytes 1-32 must be the private key"
);
assert_eq!(
payload[33], 0x01,
"last payload byte must be 0x01 (compressed flag)"
);
}
}