rialo-cdk 0.4.2

Rialo CDK - A comprehensive toolkit for building with the Rialo blockchain
Documentation
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Cross-platform compatibility tests.
//! Validates Rust implementation against shared test vectors.

use std::fs;

use ed25519_dalek::{Signer, SigningKey, Verifier};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize)]
struct TestVectors {
    version: String,
    vectors: Vectors,
}

#[derive(Debug, Deserialize, Serialize)]
struct Vectors {
    mnemonic_to_keypair: Vec<MnemonicVector>,
    signing_verification: Vec<SigningVector>,
    encoding: Vec<EncodingVector>,
    key_generation: Vec<KeyGenerationVector>,
}

#[derive(Debug, Deserialize, Serialize)]
struct MnemonicVector {
    id: String,
    description: String,
    input: MnemonicInput,
    expected: MnemonicExpected,
}

#[derive(Debug, Deserialize, Serialize)]
struct MnemonicInput {
    mnemonic: String,
    passphrase: String,
    account_index: u32,
    derivation_path: String,
}

#[derive(Debug, Deserialize, Serialize)]
struct MnemonicExpected {
    public_key_hex: String,
    public_key_base58: String,
    secret_key_hex: String,
}

#[derive(Debug, Deserialize, Serialize)]
struct SigningVector {
    id: String,
    description: String,
    input: SigningInput,
    expected: SigningExpected,
}

#[derive(Debug, Deserialize, Serialize)]
struct SigningInput {
    secret_key_hex: String,
    message_hex: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    message_utf8: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
struct SigningExpected {
    signature_hex: String,
    signature_base58: String,
    verification_result: bool,
}

#[derive(Debug, Deserialize, Serialize)]
struct EncodingVector {
    id: String,
    description: String,
    input: EncodingInput,
    expected: EncodingExpected,
}

#[derive(Debug, Deserialize, Serialize)]
struct EncodingInput {
    #[serde(skip_serializing_if = "Option::is_none")]
    public_key_hex: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    signature_hex: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
struct EncodingExpected {
    base58: String,
}

#[derive(Debug, Deserialize, Serialize)]
struct KeyGenerationVector {
    id: String,
    description: String,
    input: KeyGenerationInput,
    expected: KeyGenerationExpected,
}

#[derive(Debug, Deserialize, Serialize)]
struct KeyGenerationInput {
    secret_key_hex: String,
}

#[derive(Debug, Deserialize, Serialize)]
struct KeyGenerationExpected {
    public_key_hex: String,
    public_key_base58: String,
}

// Helper functions
fn hex_to_bytes(hex: &str) -> Vec<u8> {
    (0..hex.len())
        .step_by(2)
        .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
        .collect()
}

fn bytes_to_hex(bytes: &[u8]) -> String {
    bytes.iter().map(|b| format!("{:02x}", b)).collect()
}

fn load_test_vectors() -> TestVectors {
    let manifest_dir = env!("CARGO_MANIFEST_DIR");
    let vectors_path = format!("{manifest_dir}/test-vectors/crypto-vectors.json");
    let data = fs::read_to_string(&vectors_path)
        .unwrap_or_else(|_| panic!("Failed to read test vectors from {}", vectors_path));
    serde_json::from_str(&data).expect("Failed to parse test vectors")
}

#[cfg(all(feature = "mnemonic", feature = "hd-wallet"))]
#[test]
fn test_mnemonic_to_keypair_vectors() {
    use rialo_cdk::wallet::mnemonic::derive_keypair;

    let vectors = load_test_vectors();

    for vector in vectors.vectors.mnemonic_to_keypair {
        println!("Testing: {}", vector.description);

        let passphrase = if vector.input.passphrase.is_empty() {
            None
        } else {
            Some(vector.input.passphrase.as_str())
        };

        // Derive keypair using Rust implementation with account index
        let keypair = derive_keypair(
            &vector.input.mnemonic,
            passphrase,
            vector.input.account_index,
        )
        .unwrap_or_else(|e| panic!("Failed to derive keypair for {}: {}", vector.id, e));

        // Verify public key matches (using dalek's verifying_key())
        let verifying_key = keypair.verifying_key();
        let public_bytes = verifying_key.to_bytes();
        let actual_public_key_hex = bytes_to_hex(&public_bytes);
        assert_eq!(
            actual_public_key_hex, vector.expected.public_key_hex,
            "Public key mismatch for {}: expected {}, got {}",
            vector.id, vector.expected.public_key_hex, actual_public_key_hex
        );

        // Verify public key base58 encoding
        let actual_public_key_base58 = bs58::encode(&public_bytes).into_string();
        assert_eq!(
            actual_public_key_base58, vector.expected.public_key_base58,
            "Public key base58 mismatch for {}: expected {}, got {}",
            vector.id, vector.expected.public_key_base58, actual_public_key_base58
        );

        // Verify secret key matches (using dalek's to_bytes())
        let private_bytes = keypair.to_bytes();
        let actual_secret_key_hex = bytes_to_hex(&private_bytes);
        assert_eq!(
            actual_secret_key_hex, vector.expected.secret_key_hex,
            "Secret key mismatch for {}: expected {}, got {}",
            vector.id, vector.expected.secret_key_hex, actual_secret_key_hex
        );

        println!("{} passed", vector.id);
    }
}

#[test]
fn test_signing_verification_vectors() {
    let vectors = load_test_vectors();

    for vector in vectors.vectors.signing_verification {
        println!("Testing: {}", vector.description);

        let secret_key_bytes = hex_to_bytes(&vector.input.secret_key_hex);
        let secret_key_array: [u8; 32] = secret_key_bytes
            .try_into()
            .expect("Secret key must be 32 bytes");
        let keypair = SigningKey::from_bytes(&secret_key_array);

        let message_bytes = hex_to_bytes(&vector.input.message_hex);
        let signature = keypair.sign(&message_bytes);

        // Verify signature matches expected
        let actual_signature_hex = bytes_to_hex(signature.to_bytes().as_ref());
        assert_eq!(
            actual_signature_hex, vector.expected.signature_hex,
            "Signature mismatch for {}: expected {}, got {}",
            vector.id, vector.expected.signature_hex, actual_signature_hex
        );

        // Verify signature base58 encoding
        let actual_signature_base58 = bs58::encode(signature.to_bytes()).into_string();
        assert_eq!(
            actual_signature_base58, vector.expected.signature_base58,
            "Signature base58 mismatch for {}: expected {}, got {}",
            vector.id, vector.expected.signature_base58, actual_signature_base58
        );

        // Verify signature is valid
        let public_key = keypair.verifying_key();
        let is_valid = public_key.verify(&message_bytes, &signature).is_ok();
        assert_eq!(
            is_valid, vector.expected.verification_result,
            "Verification result mismatch for {}: expected {}, got {}",
            vector.id, vector.expected.verification_result, is_valid
        );

        println!("{} passed", vector.id);
    }
}

#[test]
fn test_encoding_vectors() {
    let vectors = load_test_vectors();

    for vector in vectors.vectors.encoding {
        println!("Testing: {}", vector.description);

        if let Some(public_key_hex) = &vector.input.public_key_hex {
            let bytes = hex_to_bytes(public_key_hex);
            let base58 = bs58::encode(&bytes).into_string();
            assert_eq!(
                base58, vector.expected.base58,
                "Public key base58 mismatch for {}: expected {}, got {}",
                vector.id, vector.expected.base58, base58
            );
        } else if let Some(signature_hex) = &vector.input.signature_hex {
            let bytes = hex_to_bytes(signature_hex);
            let base58 = bs58::encode(&bytes).into_string();
            assert_eq!(
                base58, vector.expected.base58,
                "Signature base58 mismatch for {}: expected {}, got {}",
                vector.id, vector.expected.base58, base58
            );
        }

        println!("{} passed", vector.id);
    }
}

#[test]
fn test_key_generation_vectors() {
    let vectors = load_test_vectors();

    for vector in vectors.vectors.key_generation {
        println!("Testing: {}", vector.description);

        let secret_key_bytes = hex_to_bytes(&vector.input.secret_key_hex);
        let secret_key_array: [u8; 32] = secret_key_bytes
            .try_into()
            .expect("Secret key must be 32 bytes");
        let keypair = SigningKey::from_bytes(&secret_key_array);

        // Verify public key matches
        let actual_public_key_hex = bytes_to_hex(keypair.verifying_key().as_bytes());
        assert_eq!(
            actual_public_key_hex, vector.expected.public_key_hex,
            "Public key mismatch for {}: expected {}, got {}",
            vector.id, vector.expected.public_key_hex, actual_public_key_hex
        );

        // Verify public key base58 encoding
        let actual_public_key_base58 =
            bs58::encode(keypair.verifying_key().as_bytes()).into_string();
        assert_eq!(
            actual_public_key_base58, vector.expected.public_key_base58,
            "Public key base58 mismatch for {}: expected {}, got {}",
            vector.id, vector.expected.public_key_base58, actual_public_key_base58
        );

        println!("{} passed", vector.id);
    }
}

#[test]
fn test_round_trip_encoding() {
    // Generate a random keypair
    let keypair = SigningKey::generate(&mut rand::thread_rng());

    // Bytes → Base58 → Bytes for public key
    let verifying_key = keypair.verifying_key();
    let original_public_bytes = verifying_key.as_bytes();
    let base58 = bs58::encode(original_public_bytes).into_string();
    let decoded_bytes = bs58::decode(&base58).into_vec().unwrap();

    assert_eq!(
        original_public_bytes,
        decoded_bytes.as_slice(),
        "Public key round-trip encoding failed"
    );

    // Bytes → Base58 → Bytes for signature
    let message = b"test message";
    let signature = keypair.sign(message);
    let original_sig_bytes = signature.to_bytes();
    let sig_base58 = bs58::encode(&original_sig_bytes).into_string();
    let decoded_sig_bytes = bs58::decode(&sig_base58).into_vec().unwrap();

    assert_eq!(
        original_sig_bytes.as_ref(),
        decoded_sig_bytes.as_slice(),
        "Signature round-trip encoding failed"
    );
}