Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-estate-side/tests/one_order_check.rs

//! ★ **One order check** (TASKS-4-v2 T16, A28 M11): monetize-embed's `fleet::verify_order` (the
//! check monetize's own tests and trials run as "the box") and the shared pump's
//! `pump::check` (what Holger's boxes run) give the same verdict on the same orders. Both are
//! strict Ed25519, both check the signature first, both read `not_after` 0 as no window, and
//! both refuse an order dated more than five minutes ahead.
//!
//! Verdicts: embed `Ok` = pump `Fresh`; embed `Signature` = pump `Forged`; any other embed
//! refusal = pump `Refused`.

use ed25519_dalek::{Signer as _, SigningKey, VerifyingKey};
use monetize_embed::fleet::{
    order_message, verify_order, OrderRefusal, SignedOrder, FUTURE_SKEW_MS,
};
use monetize_estate_side::pump::{check, Addressee, Checked};

const MONETIZE: [u8; 32] = [7u8; 32];
const NOW: u64 = 1_800_000_100_000;

fn order() -> SignedOrder {
    SignedOrder {
        seq: 5,
        product: "gunnar".into(),
        instance: "acme".into(),
        role: "main".into(),
        kind: "recharge_from_twin".into(),
        body: r#"{"epoch":5}"#.into(),
        issued_unix_ms: NOW - 1_000,
        not_after_unix_ms: NOW + 900_000,
        signature: Vec::new(),
    }
}

fn sign(mut o: SignedOrder, seed: [u8; 32]) -> SignedOrder {
    o.signature = SigningKey::from_bytes(&seed)
        .sign(&order_message(&o))
        .to_bytes()
        .to_vec();
    o
}

fn good() -> SignedOrder {
    sign(order(), MONETIZE)
}

fn monetize() -> VerifyingKey {
    SigningKey::from_bytes(&MONETIZE).verifying_key()
}

fn me() -> Addressee {
    Addressee {
        product: "gunnar".into(),
        instance: "acme".into(),
        role: "main".into(),
    }
}

#[derive(Debug, PartialEq, Eq)]
enum Verdict {
    Run,
    Forged,
    Refused,
}

fn embed(o: &SignedOrder, key: &VerifyingKey, last: u64, now: u64) -> Verdict {
    match verify_order(o, key, ("gunnar", "acme", "main"), now, last) {
        Ok(()) => Verdict::Run,
        Err(OrderRefusal::Signature) => Verdict::Forged,
        Err(_) => Verdict::Refused,
    }
}

fn pump(o: &SignedOrder, key: &VerifyingKey, last: u64, now: u64) -> Verdict {
    match check(o, &me(), key, last, now) {
        Checked::Fresh => Verdict::Run,
        Checked::Forged(_) => Verdict::Forged,
        Checked::Refused(_) => Verdict::Refused,
    }
}

/// Both checks, and the verdict they must agree on.
fn agree(what: &str, o: &SignedOrder, key: &VerifyingKey, last: u64, now: u64, want: Verdict) {
    assert_eq!(embed(o, key, last, now), want, "embed: {what}");
    assert_eq!(pump(o, key, last, now), want, "pump: {what}");
}

#[test]
fn a_good_order_runs_under_both_checks() {
    agree("good", &good(), &monetize(), 4, NOW, Verdict::Run);
}

#[test]
fn a_tampered_order_is_forged_under_both_checks() {
    type Tamper = fn(&mut SignedOrder);
    let tampers: [(&str, Tamper); 12] = [
        ("seq", |o| o.seq += 1),
        ("product", |o| o.product = "holger".into()),
        ("instance", |o| o.instance = "other".into()),
        ("role", |o| o.role = "twin".into()),
        ("kind", |o| o.kind = "fence_primary".into()),
        ("body", |o| o.body = "{}".into()),
        ("issued", |o| o.issued_unix_ms += 1),
        ("not_after", |o| o.not_after_unix_ms += 1),
        ("a flipped bit", |o| o.signature[9] ^= 0x10),
        ("a short signature", |o| o.signature.truncate(32)),
        ("no signature", |o| o.signature.clear()),
        ("a long signature", |o| o.signature.push(0)),
    ];
    for (what, t) in tampers {
        let mut o = good();
        t(&mut o);
        agree(what, &o, &monetize(), 0, NOW, Verdict::Forged);
    }
    let other = SigningKey::from_bytes(&[9u8; 32]).verifying_key();
    agree("another key", &good(), &other, 0, NOW, Verdict::Forged);
    agree(
        "signed by another key",
        &sign(order(), [9u8; 32]),
        &monetize(),
        0,
        NOW,
        Verdict::Forged,
    );
}

/// The all-zero signature (R = the identity, s = 0) under a small-order key (the identity point)
/// verifies EVERY message with plain Ed25519. Both checks are strict and refuse it.
#[test]
fn a_small_order_key_with_the_all_zero_signature_is_forged_under_both_checks() {
    let mut identity = [0u8; 32];
    identity[0] = 1;
    let weak = VerifyingKey::from_bytes(&identity).expect("the identity point decompresses");
    let mut o = order();
    o.signature = vec![0u8; 64];
    o.signature[0] = 1;
    agree("the all-zero signature", &o, &weak, 0, NOW, Verdict::Forged);
}

#[test]
fn a_refused_order_is_refused_under_both_checks() {
    agree("a replay", &good(), &monetize(), 5, NOW, Verdict::Refused);
    agree(
        "an older one",
        &good(),
        &monetize(),
        9,
        NOW,
        Verdict::Refused,
    );
    agree(
        "expired",
        &good(),
        &monetize(),
        0,
        NOW + 900_001,
        Verdict::Refused,
    );
    let mut twin = order();
    twin.role = "twin".into();
    agree(
        "the twin's order",
        &sign(twin, MONETIZE),
        &monetize(),
        0,
        NOW,
        Verdict::Refused,
    );
    let mut theirs = order();
    theirs.instance = "other".into();
    agree(
        "another estate's order",
        &sign(theirs, MONETIZE),
        &monetize(),
        0,
        NOW,
        Verdict::Refused,
    );
    let mut ahead = order();
    ahead.issued_unix_ms = NOW + FUTURE_SKEW_MS + 1;
    agree(
        "dated ahead",
        &sign(ahead, MONETIZE),
        &monetize(),
        0,
        NOW,
        Verdict::Refused,
    );
}

#[test]
fn skew_and_no_window_run_under_both_checks() {
    let mut skew = order();
    skew.issued_unix_ms = NOW + FUTURE_SKEW_MS;
    agree(
        "five minutes of skew",
        &sign(skew, MONETIZE),
        &monetize(),
        0,
        NOW,
        Verdict::Run,
    );
    let mut open = order();
    open.not_after_unix_ms = 0;
    agree(
        "no window",
        &sign(open, MONETIZE),
        &monetize(),
        0,
        NOW + 10 * 365 * 86_400_000,
        Verdict::Run,
    );
}

/// A forged order addressed to another box is FORGED under both, so it never moves a sequence.
#[test]
fn a_forged_order_for_another_box_is_forged_under_both_checks() {
    let mut o = good();
    o.role = "twin".into();
    o.seq = 99;
    agree(
        "forged, for the twin",
        &o,
        &monetize(),
        0,
        NOW,
        Verdict::Forged,
    );
}

#[test]
fn both_checks_allow_the_same_skew() {
    assert_eq!(
        FUTURE_SKEW_MS,
        monetize_estate_side::pump::puller::FUTURE_SKEW_MS
    );
}