use core::fmt;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::hash::word32;
use crate::HASH_LEN;
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct SkSeed([u8; HASH_LEN]);
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct PrfSeed([u8; HASH_LEN]);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PkSeed([u8; HASH_LEN]);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Root([u8; HASH_LEN]);
impl SkSeed {
pub const fn new(bytes: [u8; HASH_LEN]) -> Self {
Self(bytes)
}
pub fn from_slice(bytes: &[u8]) -> Option<Self> {
Some(Self(word32(bytes)?))
}
pub fn as_bytes(&self) -> &[u8; HASH_LEN] {
&self.0
}
}
impl TryFrom<&[u8]> for SkSeed {
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Self::from_slice(value).ok_or(())
}
}
impl PrfSeed {
pub const fn new(bytes: [u8; HASH_LEN]) -> Self {
Self(bytes)
}
pub fn from_slice(bytes: &[u8]) -> Option<Self> {
Some(Self(word32(bytes)?))
}
pub fn as_bytes(&self) -> &[u8; HASH_LEN] {
&self.0
}
}
impl TryFrom<&[u8]> for PrfSeed {
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Self::from_slice(value).ok_or(())
}
}
impl PkSeed {
pub const fn new(bytes: [u8; HASH_LEN]) -> Self {
Self(bytes)
}
pub fn from_slice(bytes: &[u8]) -> Option<Self> {
Some(Self(word32(bytes)?))
}
pub fn as_bytes(&self) -> &[u8; HASH_LEN] {
&self.0
}
}
impl TryFrom<&[u8]> for PkSeed {
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Self::from_slice(value).ok_or(())
}
}
impl Root {
pub const fn new(bytes: [u8; HASH_LEN]) -> Self {
Self(bytes)
}
pub fn from_slice(bytes: &[u8]) -> Option<Self> {
Some(Self(word32(bytes)?))
}
pub fn as_bytes(&self) -> &[u8; HASH_LEN] {
&self.0
}
}
impl TryFrom<&[u8]> for Root {
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Self::from_slice(value).ok_or(())
}
}
impl fmt::Debug for SkSeed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SkSeed(<redacted>)")
}
}
impl fmt::Debug for PrfSeed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("PrfSeed(<redacted>)")
}
}
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct PrivateKey {
sk_seed: SkSeed,
prf_seed: PrfSeed,
}
impl fmt::Debug for PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PrivateKey")
.field("sk_seed", &"<redacted>")
.field("prf_seed", &"<redacted>")
.finish()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PublicKey {
pub pk_seed: PkSeed,
pub root: Root,
}
#[derive(Clone, PartialEq, Eq)]
pub struct Key {
secret: PrivateKey,
pub public_key: PublicKey,
}
impl fmt::Debug for Key {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Key")
.field("secret", &self.secret)
.field("public_key", &self.public_key)
.finish()
}
}
impl PrivateKey {
pub const fn new(sk_seed: SkSeed, prf_seed: PrfSeed) -> Self {
Self { sk_seed, prf_seed }
}
pub fn as_sk_seed(&self) -> &SkSeed {
&self.sk_seed
}
pub fn as_prf_seed(&self) -> &PrfSeed {
&self.prf_seed
}
pub fn to_bytes(&self) -> [u8; 64] {
let mut out = [0u8; 64];
out[..HASH_LEN].copy_from_slice(self.sk_seed.as_bytes());
out[HASH_LEN..].copy_from_slice(self.prf_seed.as_bytes());
out
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != 64 {
return None;
}
Some(Self {
sk_seed: SkSeed::from_slice(bytes.get(..HASH_LEN)?)?,
prf_seed: PrfSeed::from_slice(bytes.get(HASH_LEN..)?)?,
})
}
}
impl TryFrom<&[u8]> for PrivateKey {
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Self::from_bytes(value).ok_or(())
}
}
impl PublicKey {
pub fn to_bytes(&self) -> [u8; 64] {
let mut out = [0u8; 64];
out[..HASH_LEN].copy_from_slice(self.pk_seed.as_bytes());
out[HASH_LEN..].copy_from_slice(self.root.as_bytes());
out
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != 64 {
return None;
}
Some(Self {
pk_seed: PkSeed::from_slice(bytes.get(..HASH_LEN)?)?,
root: Root::from_slice(bytes.get(HASH_LEN..)?)?,
})
}
pub fn from_slices(pk_seed: &[u8], root: &[u8]) -> Option<Self> {
Some(Self {
pk_seed: PkSeed::from_slice(pk_seed)?,
root: Root::from_slice(root)?,
})
}
}
impl TryFrom<&[u8]> for PublicKey {
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Self::from_bytes(value).ok_or(())
}
}
impl Key {
pub const fn new(secret: PrivateKey, public_key: PublicKey) -> Self {
Self { secret, public_key }
}
pub fn secret(&self) -> &PrivateKey {
&self.secret
}
pub fn to_bytes(&self) -> [u8; 128] {
let mut out = [0u8; 128];
out[..64].copy_from_slice(&self.secret.to_bytes());
out[64..].copy_from_slice(&self.public_key.to_bytes());
out
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != 128 {
return None;
}
Some(Self {
secret: PrivateKey::from_bytes(bytes.get(..64)?)?,
public_key: PublicKey::from_bytes(bytes.get(64..)?)?,
})
}
}
impl TryFrom<&[u8]> for Key {
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Self::from_bytes(value).ok_or(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_key() -> Key {
Key::new(
PrivateKey::new(SkSeed::new([1u8; HASH_LEN]), PrfSeed::new([2u8; HASH_LEN])),
PublicKey {
pk_seed: PkSeed::new([3u8; HASH_LEN]),
root: Root::new([4u8; HASH_LEN]),
},
)
}
#[test]
fn key_bytes_round_trip() {
let key = sample_key();
let bytes = key.to_bytes();
assert_eq!(Key::from_bytes(&bytes), Some(key));
}
#[test]
fn secret_and_public_split_at_64() {
let key = sample_key();
let bytes = key.to_bytes();
assert_eq!(&key.secret().to_bytes(), &bytes[..64]);
assert_eq!(&key.public_key.to_bytes(), &bytes[64..]);
}
#[test]
fn from_bytes_rejects_wrong_length() {
assert_eq!(Key::from_bytes(&[0u8; 127]), None);
assert_eq!(PrivateKey::from_bytes(&[0u8; 63]), None);
assert_eq!(PublicKey::from_bytes(&[0u8; 65]), None);
}
#[test]
fn secret_debug_is_redacted() {
let key = sample_key();
let shown = alloc::format!("{:?}", key.secret());
assert!(!shown.contains("01"));
assert!(shown.contains("redacted"));
}
}