zera-sdk 0.1.0

Rust SDK for ZERA transactions, validator APIs, and bridge workflows
Documentation
use ed25519_dalek::{
    Signature as Ed25519Signature, Signer as _, SigningKey as Ed25519SigningKey, Verifier as _,
    VerifyingKey as Ed25519VerifyingKey,
};
use ed448_rust::{PrivateKey as Ed448PrivateKey, PublicKey as Ed448PublicKey};
use sha3::{Digest, Sha3_256};

use crate::crypto::address::get_key_type_from_public_key;
use crate::crypto::constants::KeyType;
use crate::error::{Result, ZeraError};

pub fn expand_ed448_seed_to_private_key(seed_32: &[u8]) -> Result<[u8; 57]> {
    if seed_32.len() != 32 {
        return Err(ZeraError::Crypto(format!(
            "SLIP-0010 private key must be 32 bytes, got {}",
            seed_32.len()
        )));
    }

    let seed_hash = Sha3_256::digest(seed_32);

    use hmac::{Hmac, Mac};
    use sha2::Sha512;

    let mut mac = Hmac::<Sha512>::new_from_slice(seed_hash.as_slice())
        .map_err(|e| ZeraError::Crypto(format!("failed to init ed448 expansion HMAC: {e}")))?;
    mac.update(b"ed448-expansion");
    let expanded = mac.finalize().into_bytes();

    let mut out = [0_u8; 57];
    out.copy_from_slice(&expanded[..57]);
    out[56] &= 0xFC;
    Ok(out)
}

#[derive(Debug, Clone)]
pub struct Ed25519KeyPair {
    private_key: [u8; 32],
    public_key: [u8; 32],
}

impl Ed25519KeyPair {
    pub fn new_random() -> Self {
        let private_key: [u8; 32] = rand::random();
        let signing = Ed25519SigningKey::from_bytes(&private_key);
        let public_key = signing.verifying_key().to_bytes();
        Self {
            private_key,
            public_key,
        }
    }

    pub fn from_private_key(private_key: &[u8]) -> Result<Self> {
        let pk: [u8; 32] = private_key.try_into().map_err(|_| {
            ZeraError::Crypto(format!(
                "invalid Ed25519 private key length: {}",
                private_key.len()
            ))
        })?;
        let signing = Ed25519SigningKey::from_bytes(&pk);
        Ok(Self {
            private_key: pk,
            public_key: signing.verifying_key().to_bytes(),
        })
    }

    pub fn private_key_bytes(&self) -> [u8; 32] {
        self.private_key
    }

    pub fn public_key_bytes(&self) -> [u8; 32] {
        self.public_key
    }

    pub fn sign(&self, data: &[u8]) -> Vec<u8> {
        let signing = Ed25519SigningKey::from_bytes(&self.private_key);
        signing.sign(data).to_bytes().to_vec()
    }

    pub fn verify(&self, signature: &[u8], data: &[u8]) -> bool {
        let Ok(sig_bytes): std::result::Result<[u8; 64], _> = signature.try_into() else {
            return false;
        };

        let signature = Ed25519Signature::from_bytes(&sig_bytes);
        let Ok(verifying) = Ed25519VerifyingKey::from_bytes(&self.public_key) else {
            return false;
        };

        verifying.verify(data, &signature).is_ok()
    }

    pub fn private_key_base58(&self) -> String {
        bs58::encode(self.private_key).into_string()
    }

    pub fn public_key_base58(&self) -> String {
        bs58::encode(self.public_key).into_string()
    }
}

#[derive(Debug, Clone)]
pub struct Ed448KeyPair {
    private_key: [u8; 57],
    public_key: [u8; 57],
}

impl Ed448KeyPair {
    pub fn new_random() -> Self {
        let secret = Ed448PrivateKey::new(&mut rand::rngs::OsRng);
        let private_key = *secret.as_bytes();
        let public_key = Ed448PublicKey::from(&secret).as_byte();
        Self {
            private_key,
            public_key,
        }
    }

    pub fn from_private_key(private_key: &[u8]) -> Result<Self> {
        let private_key: [u8; 57] = match private_key.len() {
            32 => expand_ed448_seed_to_private_key(private_key)?,
            57 => private_key.try_into().map_err(|_| {
                ZeraError::Crypto("failed to parse 57-byte Ed448 private key".into())
            })?,
            n => {
                return Err(ZeraError::Crypto(format!(
                    "Invalid private key length: {n}. Expected 32 (SLIP0010 seed) or 57 (ED448 private key) bytes."
                )))
            }
        };

        let secret = Ed448PrivateKey::from(private_key);
        let public_key = Ed448PublicKey::from(&secret).as_byte();

        Ok(Self {
            private_key,
            public_key,
        })
    }

    pub fn private_key_bytes(&self) -> [u8; 57] {
        self.private_key
    }

    pub fn public_key_bytes(&self) -> [u8; 57] {
        self.public_key
    }

    pub fn sign(&self, data: &[u8]) -> Vec<u8> {
        let secret = Ed448PrivateKey::from(self.private_key);
        secret
            .sign(data, None)
            .expect("ed448 sign should succeed")
            .to_vec()
    }

    pub fn verify(&self, signature: &[u8], data: &[u8]) -> bool {
        if signature.len() != 114 {
            return false;
        }

        let secret = Ed448PrivateKey::from(self.private_key);
        let public = Ed448PublicKey::from(&secret);

        std::panic::catch_unwind(|| public.verify(data, signature, None).is_ok()).unwrap_or(false)
    }

    pub fn private_key_base58(&self) -> String {
        bs58::encode(self.private_key).into_string()
    }

    pub fn public_key_base58(&self) -> String {
        bs58::encode(self.public_key).into_string()
    }
}

pub fn sign_transaction_data(
    data: &[u8],
    private_key_base58: &str,
    public_key_identifier: &str,
) -> Result<Vec<u8>> {
    let key_type = get_key_type_from_public_key(public_key_identifier)?;
    let mut private_key_bytes = bs58::decode(private_key_base58)
        .into_vec()
        .map_err(|e| ZeraError::Crypto(format!("invalid base58 private key: {e}")))?;

    match key_type {
        KeyType::Ed25519 => {
            if private_key_bytes.len() == 64 {
                private_key_bytes = private_key_bytes[..32].to_vec();
            }
            let key = Ed25519KeyPair::from_private_key(&private_key_bytes)?;
            Ok(key.sign(data))
        }
        KeyType::Ed448 => {
            let key = Ed448KeyPair::from_private_key(&private_key_bytes)?;
            Ok(key.sign(data))
        }
    }
}

pub fn create_transaction_hash(data: &[u8]) -> Vec<u8> {
    Sha3_256::digest(data).to_vec()
}