open-standards/jcp/jcp-rs/crates/jcp-logseal/tests/attest.rs
Transaction Science 6c15eb004c
Some checks failed
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Failing after 19m56s
Log seals: one receipt countersigned post-quantum, and a steward's keys attested by its witnesses
A receipt written after the last seal stood on Ed25519 until the next seal.
A steward may now countersign one at once: its hybrid key over a record
naming the receipt's content id (jcp:receipt-countersignature, log-seal
§5.7). A holder of the Ed25519 key alone cannot make one.

A reader needed each steward's key id carried to it by hand (§4). A
witness that has cosigned a steward's seal now attests the keys it last
saw sign one, with that seal and its count, under its own hybrid key
(§4.2). A reader trusting a set of witnesses takes the steward's key id
from a quorum of their latest attestations and starts reading from the
newest seal they name; fewer than a quorum is keys_unattested, two key
ids each with one keys_disputed, and a witness that has cosigned nothing
refuses (witness_unfollowed).

The pack gains 8 countersigned receipts and 13 attested key cases; the
Python second reader decides all of them with its own ML-DSA-65 and
notices each of its 13 new rules removed. 13 seeded faults in the Rust
code, each caught. C33 and SPEC's summary of it say so.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-11 23:03:22 -05:00

195 lines
9.6 KiB
Rust

//! One receipt countersigned post-quantum, and a steward's keys attested by
//! the witnesses that follow its seals (log-seal §4.2, §5.7).
use ed25519_dalek::SigningKey;
use jcp_grant::issue;
use jcp_logseal::attest::{attest_stating, attested_keys, countersign, read_key_attestation, verify_countersigned, AttestedKeys};
use jcp_logseal::{
keys_for, prove_consistency, seal_log, seal_log_committing, verify_following, HybridKey, Policy, SealError, StewardKeys, Trust, Witness,
Witnessing,
};
use jcp_receipt::ReceiptLog;
use jcp_runtime::{Price, Runtime};
use jcp_schema::{Capability, ContentId, Did, Energy, Frame, FrameKind, Funds, Grant, Trust as FrameTrust};
const STEWARD: &str = "did:example:sealed";
fn sk(s: u8) -> SigningKey {
SigningKey::from_bytes(&[s; 32])
}
fn did(s: u8) -> Did {
Did::from_verifying_key(&sk(s).verifying_key())
}
fn steward_key() -> HybridKey {
HybridKey::new(sk(7), [9; 32])
}
fn keys_of(k: &HybridKey) -> StewardKeys {
StewardKeys { ed: k.ed_public(), pq: k.pq_public().to_vec() }
}
/// A log of `n` decisions; from `turn` on its receipts are under `then`.
fn log(n: usize, turn: Option<(usize, SigningKey)>) -> Vec<Vec<u8>> {
let sg = issue(
&sk(1),
Grant {
issuer: did(1),
subject: did(2),
capability: Capability::new(ContentId::of_bytes(b"sealed"), ["pay"]),
budget_uj: 1_000_000,
budget_funds: Some(Funds::new("USD", 1_000_000)),
caveats: Vec::new(),
expiry: 0,
parent: None,
delegate_depth: None,
nonce: [5; 16],
},
)
.unwrap();
let mut rt = Runtime::new();
let mut log = ReceiptLog::new(sk(7), STEWARD);
let mut turn = turn;
for i in 0..n {
if turn.as_ref().is_some_and(|(at, _)| *at == i) {
log.rekey(turn.take().expect("one change").1);
}
let frame = Frame {
kind: FrameKind::ToolCall,
trust: FrameTrust::Control,
grant_id: sg.grant.id(),
action: "pay".into(),
resource: ContentId::of_bytes(b"sealed"),
payload_hash: ContentId::of_bytes(format!("attested:{i}").as_bytes()),
model: None,
};
let d = rt.decide_priced(&frame, &sg, did(2), Energy::measured(10), &Price::new("USD", 100), 10);
log.append(&frame, &sg.grant, &d);
}
log.entries().to_vec()
}
fn witness(seed: u8, key_id: ContentId) -> Witness {
let mut w = Witness::new(HybridKey::new(sk(40 + seed), [40 + seed; 32]));
w.watch(STEWARD, key_id);
w
}
#[test]
fn a_receipt_countersigned_stands_post_quantum_before_any_seal_covers_it() {
let key = steward_key();
let entries = log(5, None);
// the last receipt, after a seal of the first four
let _seal = seal_log(&entries[..4], STEWARD, &key, None).unwrap();
let c = countersign(&entries[4], STEWARD, &key).unwrap();
assert_eq!(verify_countersigned(&entries[4], &c, STEWARD, &keys_of(&key)), Ok(()));
assert_eq!(verify_countersigned(&entries[3], &c, STEWARD, &keys_of(&key)), Err(SealError::ProofInvalid));
// the receipt key alone, beside a post-quantum key not the steward's, is not the steward's countersignature
let half = HybridKey::new(sk(7), [12; 32]);
let by_half = countersign(&entries[4], STEWARD, &half).unwrap();
assert_eq!(verify_countersigned(&entries[4], &by_half, STEWARD, &keys_of(&key)), Err(SealError::WrongKey));
// a member a countersignature does not define, its signatures otherwise intact
let mut record: serde_json::Value = jcp_schema::from_canonical_cbor(&c).unwrap();
record.as_object_mut().unwrap().insert("note".into(), serde_json::json!("extra"));
let padded = jcp_receipt::jcr1::canonical_cbor(&record).unwrap();
assert!(matches!(verify_countersigned(&entries[4], &padded, STEWARD, &keys_of(&key)), Err(SealError::Malformed(_))));
// a bit changed in either signature
let mut bent = c.clone();
let at = bent.len() - 10;
bent[at] ^= 1;
assert!(verify_countersigned(&entries[4], &bent, STEWARD, &keys_of(&key)).is_err());
}
#[test]
fn witnesses_attest_the_keys_of_the_seals_they_cosigned() {
let key = steward_key();
let id = keys_of(&key).id();
let entries = log(9, None);
let s1 = seal_log(&entries[..4], STEWARD, &key, None).unwrap();
let s2 = seal_log(&entries[..7], STEWARD, &key, Some(&s1)).unwrap();
let path = prove_consistency(&entries, 4, 7).unwrap();
let mut ws: Vec<Witness> = (1..=3).map(|i| witness(i, id)).collect();
for w in &mut ws {
w.cosign(STEWARD, std::slice::from_ref(&s1), &[], 100).unwrap();
w.cosign(STEWARD, &[s1.clone(), s2.clone()], &path, 200).unwrap();
}
let trusted: Vec<ContentId> = ws.iter().map(|w| w.keys().id()).collect();
let attestations: Vec<Vec<u8>> = ws.iter().map(|w| w.attest_keys(STEWARD, 300).unwrap()).collect();
let a = read_key_attestation(&attestations[0], STEWARD).unwrap();
assert_eq!((a.key_id, a.seal, a.count, a.time, a.witness), (id, ContentId::of_bytes(&s2), 7, 300, trusted[0]));
let rule = Witnessing { witnesses: &trusted, quorum: 2, not_before: 250 };
let got = attested_keys(STEWARD, &attestations, &rule).unwrap();
assert_eq!(got, AttestedKeys { key_id: id, witnesses: { let mut t = trusted.clone(); t.sort(); t }, seal: ContentId::of_bytes(&s2), count: 7 });
// a reader given no steward key id takes the keys from the seal the witnesses name and reads on
let keys = keys_for(&s2, &got.key_id).unwrap();
let reading = verify_following(&entries[..7], &[s1.clone(), s2.clone()], STEWARD, &keys, Trust::SealsAlone, Policy::RequireSealed, None).unwrap();
assert_eq!(reading.standing.sealed, 7);
// witnesses that last saw different seals: the reader starts from the newest one named
let behind = attest_stating(STEWARD, &id, &ContentId::of_bytes(&s1), 4, 300, &HybridKey::new(sk(43), [43; 32])).unwrap();
let mixed = [attestations[0].clone(), attestations[1].clone(), behind];
let got = attested_keys(STEWARD, &mixed, &rule).unwrap();
assert_eq!((got.seal, got.count, got.witnesses.len()), (ContentId::of_bytes(&s2), 7, 3));
// one trusted attestation is not a quorum; untrusted, stale, altered and other stewards' ones do not count
assert_eq!(attested_keys(STEWARD, &attestations[..1], &rule), Err(SealError::KeysUnattested { have: 1, need: 2 }));
assert_eq!(
attested_keys(STEWARD, &attestations, &Witnessing { witnesses: &trusted[..1], quorum: 2, not_before: 0 }),
Err(SealError::KeysUnattested { have: 1, need: 2 })
);
assert_eq!(
attested_keys(STEWARD, &attestations, &Witnessing { witnesses: &trusted, quorum: 2, not_before: 301 }),
Err(SealError::KeysUnattested { have: 0, need: 2 })
);
let mut altered = attestations.clone();
for a in altered.iter_mut().take(2) {
let at = a.len() - 10;
a[at] ^= 1;
}
assert_eq!(attested_keys(STEWARD, &altered, &rule), Err(SealError::KeysUnattested { have: 1, need: 2 }));
assert_eq!(attested_keys("did:example:other", &attestations, &rule), Err(SealError::KeysUnattested { have: 0, need: 2 }));
// a witness that attests other keys later: its latest attestation is the one that counts
let liar = HybridKey::new(sk(41), [41; 32]);
let other_id = keys_of(&HybridKey::new(sk(8), [10; 32])).id();
let lie = attest_stating(STEWARD, &other_id, &ContentId::of_bytes(&s2), 7, 310, &liar).unwrap();
let with_lie = [attestations.clone(), vec![lie.clone()]].concat();
assert_eq!(attested_keys(STEWARD, &with_lie, &rule).map(|k| k.key_id), Ok(id), "two of three still agree");
assert_eq!(attested_keys(STEWARD, &with_lie, &Witnessing { quorum: 3, ..rule }), Err(SealError::KeysUnattested { have: 2, need: 3 }));
// a quorum of no more than half: two witnesses, two key ids, each with one
assert_eq!(attested_keys(STEWARD, &[attestations[1].clone(), lie], &Witnessing { quorum: 1, ..rule }), Err(SealError::KeysDisputed));
}
#[test]
fn an_attestation_follows_the_steward_through_a_change_of_keys() {
let old = steward_key();
let new = HybridKey::new(sk(17), [19; 32]);
let entries = log(9, Some((5, sk(17))));
let first = seal_log_committing(&entries[..5], STEWARD, &old, None, &keys_of(&new).id()).unwrap();
let second = seal_log(&entries, STEWARD, &new, Some(&first)).unwrap();
let path = prove_consistency(&entries, 5, 9).unwrap();
let mut ws: Vec<Witness> = (1..=3).map(|i| witness(i, keys_of(&old).id())).collect();
for w in &mut ws {
w.cosign(STEWARD, std::slice::from_ref(&first), &[], 100).unwrap();
}
let trusted: Vec<ContentId> = ws.iter().map(|w| w.keys().id()).collect();
let rule = Witnessing { witnesses: &trusted, quorum: 2, not_before: 0 };
let before: Vec<Vec<u8>> = ws.iter().map(|w| w.attest_keys(STEWARD, 150).unwrap()).collect();
assert_eq!(attested_keys(STEWARD, &before, &rule).map(|k| k.key_id), Ok(keys_of(&old).id()));
for w in &mut ws {
w.cosign(STEWARD, &[first.clone(), second.clone()], &path, 200).unwrap();
}
let after: Vec<Vec<u8>> = ws.iter().map(|w| w.attest_keys(STEWARD, 250).unwrap()).collect();
let got = attested_keys(STEWARD, &[before, after].concat(), &rule).unwrap();
assert_eq!((got.key_id, got.seal, got.count), (keys_of(&new).id(), ContentId::of_bytes(&second), 9));
assert_eq!(keys_for(&second, &got.key_id), Ok(keys_of(&new)));
}
#[test]
fn a_witness_attests_only_keys_it_has_seen_sign_a_seal() {
let w = witness(1, keys_of(&steward_key()).id());
assert_eq!(w.attest_keys(STEWARD, 1), Err(SealError::Unfollowed));
assert_eq!(w.attest_keys("did:example:unwatched", 1), Err(SealError::WrongSteward));
}