use crate::{Result, SealedError};
use alloc::format;
use offline_protocol_core::Address;
pub const ED25519_PUBLIC_KEY_LEN: usize = 32;
pub fn derive_address(public_key: &[u8]) -> Result<Address> {
use sha2::{Digest, Sha256};
if public_key.len() != ED25519_PUBLIC_KEY_LEN {
return Err(SealedError::InvalidPublicKey(format!(
"Ed25519 public key must be {} bytes, got {}",
ED25519_PUBLIC_KEY_LEN,
public_key.len()
)));
}
let hash = Sha256::digest(public_key);
let mut truncated = [0u8; Address::HASH_LEN];
truncated.copy_from_slice(&hash[..Address::HASH_LEN]);
Ok(Address::from_hash_bytes(truncated))
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use alloc::string::ToString;
const RFC8032_TV1_PK: [u8; 32] = [
0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07,
0x3a, 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07,
0x51, 0x1a,
];
const RFC8032_TV1_ADDRESS: &str = "off1qysluvwl5922yctzd0u9gpr06gn3k7ldfvgtwgvn";
#[test]
fn derive_address_matches_the_pinned_vector() {
let address = derive_address(&RFC8032_TV1_PK).expect("32-byte key derives");
assert_eq!(address.to_string(), RFC8032_TV1_ADDRESS);
assert_eq!(
RFC8032_TV1_ADDRESS.parse::<Address>().expect("parses"),
address,
"the pinned string must round-trip back to the same address"
);
}
#[test]
fn derive_address_rejects_wrong_key_lengths() {
for len in [0usize, 31, 33, 64] {
let err = derive_address(&vec![0u8; len]).unwrap_err();
assert!(
matches!(err, SealedError::InvalidPublicKey(_)),
"{len}-byte key must be refused, got: {err}"
);
}
}
}