miden-validator 0.17.3

Miden validator
Documentation
use std::num::NonZeroUsize;
use std::sync::Arc;

use iroh::address_lookup::memory::MemoryLookup;
use iroh::endpoint::presets;
use iroh::{Endpoint, EndpointId, SecretKey as IrohSecretKey};
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, SigningKey};
use miden_protocol::utils::serde::Deserializable;
use miden_validator::{StorageKeyEpoch, ValidatorSigner};
use rand_core_06::OsRng;

use super::super::wire::WireCodec;
use super::ceremony_config::CeremonyConfig;
use super::session::{CeremonyNonce, SessionId};
use super::{Ceremony, ParticipantRegistry};

mod authentication;
mod configuration;
mod connections;

type TestResult = Result<(), Box<dyn std::error::Error>>;
type TestResultWith<T> = Result<T, Box<dyn std::error::Error>>;

fn test_signing_key(seed: u8) -> SigningKey {
    SigningKey::read_from_bytes(&[seed; 32]).expect("test signing key must decode")
}

#[test]
fn ceremony_config_codec_roundtrip() {
    let expected =
        CeremonyConfig::new(&[SigningKey::new().public_key()], 1, StorageKeyEpoch::new([9; 32]));
    let encoded = expected.encode();
    let decoded = CeremonyConfig::decode(&encoded).unwrap();

    assert_eq!(decoded, expected);
}

fn test_ceremony(
    signing_key: &SigningKey,
    endpoint_secret: IrohSecretKey,
    peers: Vec<(EndpointId, PublicKey)>,
) -> Ceremony {
    let mut validator_set = vec![signing_key.public_key()];
    validator_set.extend(peers.iter().map(|(_, key)| key.clone()));
    Ceremony {
        validator_set,
        endpoint_secret,
        enable_public_relay: false,
        bind_address: Some("127.0.0.1:0".parse().unwrap()),
        peers: peers.into_iter().map(|(id, key)| (id, (id.into(), key))).collect(),
        threshold: NonZeroUsize::new(2).unwrap(),
        epoch: StorageKeyEpoch::new([9; 32]),
        signer: Arc::new(ValidatorSigner::new_local(signing_key.clone())),
    }
}

async fn bind_test_endpoint(secret_key: IrohSecretKey) -> TestResultWith<(Endpoint, MemoryLookup)> {
    let address_lookup = MemoryLookup::new();
    let endpoint = Endpoint::builder(presets::Minimal)
        .secret_key(secret_key)
        .alpns(vec![Ceremony::ALPN.to_vec()])
        .address_lookup(address_lookup.clone())
        .clear_ip_transports()
        .bind_addr("127.0.0.1:0")?
        .bind()
        .await?;
    Ok((endpoint, address_lookup))
}

#[tokio::test]
async fn registry_confirmation_rejects_different_registry_roots() -> TestResult {
    let endpoint_secret_a = IrohSecretKey::from_bytes(&[31; 32]);
    let endpoint_secret_b = IrohSecretKey::from_bytes(&[32; 32]);
    let (endpoint_a, lookup_a) = bind_test_endpoint(endpoint_secret_a.clone()).await?;
    let (endpoint_b, lookup_b) = bind_test_endpoint(endpoint_secret_b.clone()).await?;
    lookup_a.add_endpoint_info(endpoint_b.addr());
    lookup_b.add_endpoint_info(endpoint_a.addr());

    let signing_key_a = test_signing_key(33);
    let signing_key_b = test_signing_key(34);
    let ceremony_a = test_ceremony(
        &signing_key_a,
        endpoint_secret_a,
        vec![(endpoint_b.id(), signing_key_b.public_key())],
    );
    let ceremony_b = test_ceremony(
        &signing_key_b,
        endpoint_secret_b,
        vec![(endpoint_a.id(), signing_key_a.public_key())],
    );

    let (peers_a, peers_b) = tokio::try_join!(
        ceremony_a.authenticate_peers(&endpoint_a),
        ceremony_b.authenticate_peers(&endpoint_b),
    )?;
    let (peers_a, peers_b) = tokio::try_join!(
        ceremony_a.exchange_configs(peers_a),
        ceremony_b.exchange_configs(peers_b),
    )?;
    let (session_a, session_b) = tokio::try_join!(
        ceremony_a.exchange_nonces(peers_a),
        ceremony_b.exchange_nonces(peers_b),
    )?;
    let (session_a, session_b) = tokio::try_join!(
        ceremony_a.confirm_session(session_a),
        ceremony_b.confirm_session(session_b),
    )?;
    let (participants_a, mut participants_b) = tokio::try_join!(
        ceremony_a.exchange_dkg_public_keys(session_a),
        ceremony_b.exchange_dkg_public_keys(session_b),
    )?;

    let mut entries = participants_b
        .registry
        .entries()
        .map(|(index, public_key)| (index, *public_key))
        .collect::<Vec<_>>();
    let first_public_key = entries[0].1;
    entries[0].1 = entries[1].1;
    entries[1].1 = first_public_key;
    participants_b.registry = ParticipantRegistry::new(entries)?;

    let (result_a, result_b) = tokio::join!(
        ceremony_a.confirm_dkg_registry(participants_a),
        ceremony_b.confirm_dkg_registry(participants_b),
    );
    for result in [result_a, result_b] {
        let error = result.err().expect("different DKG registry roots must be rejected");
        assert!(format!("{error:#}").contains("built a different DKG registry"));
    }

    endpoint_a.close().await;
    endpoint_b.close().await;
    Ok(())
}

#[tokio::test]
async fn session_confirmation_rejects_different_session_ids() -> TestResult {
    let endpoint_secret_a = IrohSecretKey::from_bytes(&[18; 32]);
    let endpoint_secret_b = IrohSecretKey::from_bytes(&[19; 32]);
    let (endpoint_a, lookup_a) = bind_test_endpoint(endpoint_secret_a.clone()).await?;
    let (endpoint_b, lookup_b) = bind_test_endpoint(endpoint_secret_b.clone()).await?;
    lookup_a.add_endpoint_info(endpoint_b.addr());
    lookup_b.add_endpoint_info(endpoint_a.addr());

    let signing_key_a = test_signing_key(29);
    let signing_key_b = test_signing_key(30);
    let ceremony_a = test_ceremony(
        &signing_key_a,
        endpoint_secret_a,
        vec![(endpoint_b.id(), signing_key_b.public_key())],
    );
    let ceremony_b = test_ceremony(
        &signing_key_b,
        endpoint_secret_b,
        vec![(endpoint_a.id(), signing_key_a.public_key())],
    );

    let (peers_a, peers_b) = tokio::try_join!(
        ceremony_a.authenticate_peers(&endpoint_a),
        ceremony_b.authenticate_peers(&endpoint_b),
    )?;
    let (peers_a, peers_b) = tokio::try_join!(
        ceremony_a.exchange_configs(peers_a),
        ceremony_b.exchange_configs(peers_b),
    )?;
    let (session_a, mut session_b) = tokio::try_join!(
        ceremony_a.exchange_nonces(peers_a),
        ceremony_b.exchange_nonces(peers_b),
    )?;
    assert_eq!(session_a.id, session_b.id);

    session_b.id = SessionId::derive(
        &ceremony_b.config()?,
        vec![
            (signing_key_a.public_key(), CeremonyNonce::random(&mut OsRng)),
            (signing_key_b.public_key(), CeremonyNonce::random(&mut OsRng)),
        ],
    );
    assert_ne!(session_a.id, session_b.id);

    let (result_a, result_b) =
        tokio::join!(ceremony_a.confirm_session(session_a), ceremony_b.confirm_session(session_b),);
    let errors = [result_a, result_b].map(|result| {
        let error = result.err().expect("different session IDs must be rejected");
        format!("{error:#}")
    });
    assert!(
        errors.iter().any(|error| error.contains("derived a different session ID")),
        "expected a session ID mismatch in {errors:?}",
    );

    endpoint_a.close().await;
    endpoint_b.close().await;
    Ok(())
}