agent-rooms 0.1.0

Rust port of the parley protocol core (@p-vbordei/agent-rooms): canonical encoding, Ed25519 signing, message validation
Documentation
//! Conformance — replays every vector from `vectors/*.json` against the
//! Rust implementation. Vectors copied verbatim from
//! `conformance/vectors/` in the reference repo.

use std::fs;
use std::path::PathBuf;

use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
use serde_json::Value;

use agent_rooms::canonical::{canonical_json, parse};
use agent_rooms::keys::{pubkey_from_hex, sig_from_hex, sign, to_hex, verify};

fn vectors_dir() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("vectors")
}

fn read_array(name: &str) -> Vec<Value> {
    let path = vectors_dir().join(name);
    let raw = fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {:?}: {e}", path));
    let v: Value = serde_json::from_str(&raw).unwrap();
    v.as_array().cloned().unwrap_or_default()
}

#[test]
fn canonical_json_vectors() {
    let mut count = 0usize;
    for v in read_array("canonical_json.json") {
        let name = v["name"].as_str().unwrap();
        let input = &v["input"];
        let want_utf8 = v["expected_bytes_utf8"].as_str().unwrap();
        let want_b64 = v["expected_bytes_b64"].as_str().unwrap();
        let got = canonical_json(input);

        let got_str =
            std::str::from_utf8(&got).unwrap_or_else(|e| panic!("[{name}] non-utf8 output: {e}"));
        assert_eq!(got_str, want_utf8, "[{name}] canonical bytes mismatch");

        let want_bytes = B64.decode(want_b64).unwrap();
        assert_eq!(got, want_bytes, "[{name}] b64 mismatch");
        count += 1;
    }
    assert!(count > 0, "no canonical vectors loaded");
    eprintln!("canonical_json: {count} vectors passed");
}

#[test]
fn signature_vectors() {
    let mut count = 0usize;
    for v in read_array("signatures.json") {
        let name = v["name"].as_str().unwrap();
        let sk_hex = v["sk_hex"].as_str().unwrap();
        let pk_hex = v["pk_hex"].as_str().unwrap();
        let payload = &v["payload"];
        let want_canon = v["canonical_bytes_utf8"].as_str().unwrap();
        let want_sig = v["expected_sig_hex"].as_str().unwrap();

        // Canonical bytes match.
        let got_canon_bytes = canonical_json(payload);
        let got_canon = std::str::from_utf8(&got_canon_bytes).unwrap();
        assert_eq!(got_canon, want_canon, "[{name}] canonical drift");

        // Public key derivation matches.
        let sk_bytes = hex::decode(sk_hex).unwrap();
        let sk_arr: [u8; 32] = sk_bytes.try_into().unwrap();
        let derived_pk = {
            use ed25519_dalek::SigningKey;
            SigningKey::from_bytes(&sk_arr).verifying_key().to_bytes()
        };
        assert_eq!(to_hex(&derived_pk), pk_hex, "[{name}] pk derivation");

        // Sign and compare.
        let got_sig = sign(&sk_arr, &got_canon_bytes);
        assert_eq!(to_hex(&got_sig), want_sig, "[{name}] sig mismatch");

        // Self-verify.
        let pk_arr = pubkey_from_hex(pk_hex).unwrap();
        assert!(
            verify(&pk_arr, &got_canon_bytes, &got_sig),
            "[{name}] self-verify"
        );
        count += 1;
    }
    assert!(count > 0, "no signature vectors loaded");
    eprintln!("signatures: {count} vectors passed");
}

#[test]
fn mutation_vectors() {
    let mut count = 0usize;
    for v in read_array("mutation.json") {
        let name = v["name"].as_str().unwrap();
        let pk_hex = v["pk_hex"].as_str().unwrap();
        let canon_str = v["canonical_bytes_utf8"].as_str().unwrap();
        let sig_hex = v["sig_hex"].as_str().unwrap();
        let must_verify = v["must_verify"].as_bool().unwrap();

        let pk = pubkey_from_hex(pk_hex).unwrap();
        let canon_bytes = canon_str.as_bytes();

        // sig_hex may be malformed/truncated; that must yield must_verify=false.
        let got = match sig_from_hex(sig_hex) {
            Ok(sig) => verify(&pk, canon_bytes, &sig),
            Err(_) => false,
        };
        assert_eq!(
            got, must_verify,
            "[{name}] verify={got}, expected {must_verify}"
        );

        // For the "non-canonical wire" case, we should also be able to
        // re-canonicalize the wire bytes and confirm they produce the same
        // canonical output the signature was computed against.
        if let Some(wire) = v.get("non_canonical_wire_utf8").and_then(|x| x.as_str()) {
            let reparsed = parse(wire).expect("non-canonical wire is valid JSON");
            let recanon = canonical_json(&reparsed);
            let recanon_str = std::str::from_utf8(&recanon).unwrap();
            assert_eq!(
                recanon_str, canon_str,
                "[{name}] recanonicalized wire != expected canonical"
            );
        }
        count += 1;
    }
    assert!(count > 0, "no mutation vectors loaded");
    eprintln!("mutation: {count} vectors passed");
}