diff --git a/Cargo.lock b/Cargo.lock index 5fbad9c..4a29944 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -969,8 +969,8 @@ dependencies = [ [[package]] name = "ciris-crypto" -version = "14.2.0" -source = "git+https://github.com/CIRISAI/CIRISVerify?tag=v14.2.0#84fef3bdfc6d02e8d3e16d123750d890aae7fe01" +version = "15.0.0" +source = "git+https://github.com/CIRISAI/CIRISVerify?tag=v15.0.0#3531334e829395f0c2664aff1a3277981bcbd086" dependencies = [ "chacha20poly1305", "ed25519-dalek", @@ -996,7 +996,7 @@ dependencies = [ [[package]] name = "ciris-edge" -version = "20.3.0" +version = "21.0.0" dependencies = [ "async-trait", "axum", @@ -1059,8 +1059,8 @@ dependencies = [ [[package]] name = "ciris-keyring" -version = "14.2.0" -source = "git+https://github.com/CIRISAI/CIRISVerify?tag=v14.2.0#84fef3bdfc6d02e8d3e16d123750d890aae7fe01" +version = "15.0.0" +source = "git+https://github.com/CIRISAI/CIRISVerify?tag=v15.0.0#3531334e829395f0c2664aff1a3277981bcbd086" dependencies = [ "aes-gcm", "async-trait", @@ -1095,8 +1095,8 @@ dependencies = [ [[package]] name = "ciris-persist" -version = "41.2.0" -source = "git+https://github.com/CIRISAI/CIRISPersist?tag=v41.2.0#52f659257a480f3956824f3241b60fca4a5ece39" +version = "42.0.0" +source = "git+https://github.com/CIRISAI/CIRISPersist?tag=v42.0.0#13aa6347f15085cd8253a6265a3e873202650126" dependencies = [ "async-trait", "base64 0.22.1", @@ -1134,8 +1134,8 @@ dependencies = [ [[package]] name = "ciris-verify-core" -version = "14.2.0" -source = "git+https://github.com/CIRISAI/CIRISVerify?tag=v14.2.0#84fef3bdfc6d02e8d3e16d123750d890aae7fe01" +version = "15.0.0" +source = "git+https://github.com/CIRISAI/CIRISVerify?tag=v15.0.0#3531334e829395f0c2664aff1a3277981bcbd086" dependencies = [ "android_system_properties", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 1002e8d..d00c81d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ciris-edge" -version = "20.3.0" +version = "21.0.0" edition = "2021" rust-version = "1.75" authors = ["Eric Moore "] @@ -441,7 +441,7 @@ publish = false # registers a SINGLE-role `identity_type` — no fixture encoded the # repealed loophole. # * 2566bc54 still pins CIRISVerify v13.6.1 — one `ciris-verify-core`. -ciris-persist = { git = "https://github.com/CIRISAI/CIRISPersist", tag = "v41.2.0", version = "41", features = ["sqlite", "encrypted-kv"] } +ciris-persist = { git = "https://github.com/CIRISAI/CIRISPersist", tag = "v42.0.0", version = "42", features = ["sqlite", "encrypted-kv"] } # Keyring — Ed25519 + ML-DSA-65 hardware/software signers used by # Edge::send and Edge::send_durable to sign outbound envelopes. # v0.13.0 — bumped to v4.0.0 in lockstep with persist v3.0.0. Both @@ -465,8 +465,8 @@ ciris-persist = { git = "https://github.com/CIRISAI/CIRISPersist", tag = "v41.2. # moved to v4.4.2, and a mixed v4.2.0/v4.4.2 graph produces two # distinct trait-object vtables that the trait-bound check in # `Arc` cannot reconcile. -ciris-keyring = { git = "https://github.com/CIRISAI/CIRISVerify", tag = "v14.2.0", version = "14", features = ["software", "pqc-ml-dsa"] } -ciris-crypto = { git = "https://github.com/CIRISAI/CIRISVerify", tag = "v14.2.0", version = "14", features = ["ed25519", "pqc-ml-dsa", "hybrid-kex", "aes-gcm", "xchacha", "hpke", "scope-privacy", "hmac", "kdf"] } +ciris-keyring = { git = "https://github.com/CIRISAI/CIRISVerify", tag = "v15.0.0", version = "15", features = ["software", "pqc-ml-dsa"] } +ciris-crypto = { git = "https://github.com/CIRISAI/CIRISVerify", tag = "v15.0.0", version = "15", features = ["ed25519", "pqc-ml-dsa", "hybrid-kex", "aes-gcm", "xchacha", "hpke", "scope-privacy", "hmac", "kdf"] } # v2.0.0 (CIRISEdge#65 v2 wire cycle) — direct dep on ciris-verify-core # for `jcs::canonicalize` (the v2 `envelope_hash` basis per FSD §3.2.2: # `sha256(JCS(Signed*Record))`) + `threshold::ThresholdMember` (the @@ -475,7 +475,7 @@ ciris-crypto = { git = "https://github.com/CIRISAI/CIRISVerify", tag = "v14.2.0 # edge's to define"); the v2 wire-hash basis flips to JCS in lockstep # with CEG 1.0-RC2 §3.2.2 / §5.6.8.13. v5.1.0 is the lockstep # substrate floor with persist v5.1.1 (operational-data admit surface). -ciris-verify-core = { git = "https://github.com/CIRISAI/CIRISVerify", tag = "v14.2.0", version = "14" } +ciris-verify-core = { git = "https://github.com/CIRISAI/CIRISVerify", tag = "v15.0.0", version = "15" } # Async runtime tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time"] } @@ -1540,7 +1540,7 @@ async-trait = "0.1" # two `ciris-verify-core` cdylibs — the empty-stdout SIGSEGV class). # v38.6.0 (CIRISPersist#774) — held in lockstep with the runtime pin above # through the RC-adopt and back onto `tag`. These two move together, always. -ciris-persist = { git = "https://github.com/CIRISAI/CIRISPersist", tag = "v41.2.0", version = "41", features = ["sqlite", "cirisnode", "classify", "scrub", "encrypted-kv"] } +ciris-persist = { git = "https://github.com/CIRISAI/CIRISPersist", tag = "v42.0.0", version = "42", features = ["sqlite", "cirisnode", "classify", "scrub", "encrypted-kv"] } # CIRISEdge#23 / #49 — `tests/transport_http_hardening.rs` + # `tests/https_per_messagetype_roundtrip.rs` + `tests/https_pyedge_init.rs` # (v0.19.3) mint self-signed Ed25519 certs on the fly. v0.19.3 diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index b0e855d..e6c6ce1 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -1,5 +1,82 @@ # CIRISEdge Release Notes +# v21.0.0 — adopt CIRISPersist v42.0.0 + CIRISVerify v15.0.0 + +**2026-09-07** — A MAJOR, because it amends a held contract: CIRISServer is on +v20.3.0 (CIRISServer#545 closed), and this cut moves the wheel floor to +`ciris-persist>=42,<43` and re-pins the whole CIRISVerify graph to v15. A +consumer holding persist 41 or verify 14 in the same process cannot take it +without moving too. + +## What persist v42.0.0 refuses that v41 admitted — and whether edge is exposed + +persist numbered this MAJOR for two refusals of previously-valid input plus +the verify re-pin. Edge was checked against each; every dimension edge emits +and every roster edge authors was read, not assumed. + +**CIRISPersist#811 — an agent member whose only incoming edge is a plain +(unmarked) conferral is no longer steward-bound; its community is refused at +the write gate.** Edge is **not exposed in production.** Edge's only authored +community is the pair chat room (`chat::pair_community`), which rosters the +two *owners* — users, as `founder` — never agents; and persist's rule exempts +node members outright ("a node cannot accept for itself, so any delegation +naming it is still custody"). Edge calls none of `is_steward_bound`, +`steward_bindings_of` or `live_delegation_granters`, so the predicate/fold +drift the changelog describes never ran in edge. The residual risk was test +fixtures rostering an agent under a plain `delegates_to`; the full suite ran +against v42 and **none reddened** — nothing needed fixing. + +**CIRISPersist#814 part 3 — `config:admission` / `config:transport` rows above +`self` are refused.** Not exposed: edge names no `config:*` dimension anywhere. + +**CIRISPersist#815 — dimensions are case-sensitive, per segment.** Not exposed: +every dimension edge emits is lowercase (`chat:message:v1`, +`chat:key_package:v1`, `chat:welcome:v1`, `capacity:relay_delivery:v1`, +`ownership:responsible_party:node:v1`). Confirmed by execution, not inspection: +`chat_message_federates` and `chat_two_person_community` drive real admission +of every `chat:` row against v42 and pass. + +All four persist ABI constants are unchanged, so the floor moves for the +major alone. + +## CIRISVerify v15.0.0 — the break is `#[non_exhaustive]`, and it's the one edge asked for + +The v14.2.0 → v15.0.0 diff is *additive* in code — `FedCode::new(kind, key_id, +pubkey)` with `with_transport_hint` / `with_alias_hint` / `with_group_key_id` / +`with_owned_nodes` / `with_ml_dsa_65_pubkey_sha256`, `OwnedNode::new(key_id, +transport_pubkey)`, and `AdmittedHybridKey::admit(&code, pulled)`. The MAJOR +is that `FedCode` **and `OwnedNode`** are now `#[non_exhaustive]`, which +reddens every struct literal of either. Edge had thirteen `FedCode` literals +(`contact.rs` ×9, `role_matrix_gauntlet.rs` ×2, `tests/chat_harness_dx.rs` +×2) and one `OwnedNode`; all now use the constructors. Worth knowing for the +next such bump: `cargo check --lib` did not catch any of them — every literal +was in test code, and only `--all-targets` compiles that. Both changes are exactly what CIRISVerify#274 +asked for after v14.2.0 broke twelve of those same literals in a *minor*. + +`AdmittedHybridKey::admit` is now the **only** constructor of a hybrid +registration input, and it fails closed when the code carries no commitment — +so an unchecked input cannot exist to be passed anywhere. `CodeAdmission`'s +doc points hosts there instead of at the free function they merely had to +remember to call. + +All three verify crates (`ciris-verify-core`, `ciris-keyring`, `ciris-crypto`) +move to v15.0.0 in lockstep, matching persist v42's own pin; `cargo tree -i` +shows one copy of each. + +## Verification + +The persist tag was verified from the remote to deref to the exact tree this +was built and tested against (`refs/tags/v42.0.0^{}` → `13aa634`) before the +pin flipped from rev to tag. clippy `-D warnings` clean on pyo3-full +`--all-targets`; **1461 lib + 1867 integration tests (67 binaries)**, every +cargo exit captured; `cargo tree -i` shows one copy each of persist 42.0.0 and +the verify trio 15.0.0. + +## Nothing else moved + +leviculum stays v0.25.0+ciris.1. No public edge API changed shape; the break +is entirely the floor and the linked substrate graph. + # v20.3.0 — adopt persist v41.2.0 + leviculum v0.25.0; the dial outlives its round (#568); the pyo3 envelope helper signs hybrid (#573); the rotation seal names its hazard (leviculum#52) **2026-09-05** — Additive at every public surface. Two substrate adopts, one diff --git a/evidence/CIRISEdge.cc_impl.tsv b/evidence/CIRISEdge.cc_impl.tsv index 1e83e12..1ef90e9 100644 --- a/evidence/CIRISEdge.cc_impl.tsv +++ b/evidence/CIRISEdge.cc_impl.tsv @@ -7,10 +7,10 @@ # failure. Vendored by CIRISConstitution/tools/check_evidence.py. # Columns: cc_section clm repo path#symbol crate@version decimal_id claim_id repo path#symbol crate@version -5.3.3 CLM-nsproc-delivery-mode CIRISEdge src/delivery_mode.rs#decide ciris-edge@v20.3.0 -3.3.6 CLM-nsproc-cohort-scope CIRISEdge src/replication/bridge.rs#attestation_is_advertised ciris-edge@v20.3.0 -3.1 CLM-nsproc-dimension CIRISEdge src/replication/bridge.rs#attestation_is_advertised ciris-edge@v20.3.0 -3.4 CLM-nsproc-key-boundary-scope CIRISEdge src/key_boundary.rs#KeyBoundaryScope ciris-edge@v20.3.0 -5.3.3.5 CLM-nsproc-recipient-serve-capability CIRISEdge src/replication/bridge.rs#peer_has_serve_capability ciris-edge@v20.3.0 -5.3.2.4 CLM-nsproc-recipient-capability CIRISEdge src/replication/bridge.rs#recipient_capability_withholds ciris-edge@v20.3.0 -5.3.2.4 CLM-nsproc-attestation-prefixes CIRISEdge src/replication/bridge.rs#recipient_capability_withholds ciris-edge@v20.3.0 +5.3.3 CLM-nsproc-delivery-mode CIRISEdge src/delivery_mode.rs#decide ciris-edge@v21.0.0 +3.3.6 CLM-nsproc-cohort-scope CIRISEdge src/replication/bridge.rs#attestation_is_advertised ciris-edge@v21.0.0 +3.1 CLM-nsproc-dimension CIRISEdge src/replication/bridge.rs#attestation_is_advertised ciris-edge@v21.0.0 +3.4 CLM-nsproc-key-boundary-scope CIRISEdge src/key_boundary.rs#KeyBoundaryScope ciris-edge@v21.0.0 +5.3.3.5 CLM-nsproc-recipient-serve-capability CIRISEdge src/replication/bridge.rs#peer_has_serve_capability ciris-edge@v21.0.0 +5.3.2.4 CLM-nsproc-recipient-capability CIRISEdge src/replication/bridge.rs#recipient_capability_withholds ciris-edge@v21.0.0 +5.3.2.4 CLM-nsproc-attestation-prefixes CIRISEdge src/replication/bridge.rs#recipient_capability_withholds ciris-edge@v21.0.0 diff --git a/pyproject.toml b/pyproject.toml index 91eb1f6..2127e6a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ classifiers = [ # MAJOR always moves it: the constraint this line expresses is "the wheel in # this process is the same crate edge linked", and 40.x and 41.x are not. dependencies = [ - "ciris-persist>=41,<42", + "ciris-persist>=42,<43", ] dynamic = ["version"] diff --git a/src/contact.rs b/src/contact.rs index a41b281..f0bbe31 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -365,13 +365,14 @@ pub struct CodeAdmission { /// commitment support is inert at exactly the layer that needs it and /// [`ContactResolution::ReadyFromCode`] stays unreachable in practice. /// - /// **How to use it, in one line:** call - /// [`ciris_verify_core::fedcode::verify_pulled_ml_dsa_65_pubkey`] with - /// this [`CodeAdmission`]'s source code (or a reconstructed [`FedCode`]) - /// and the pulled key, and register only on `Ok`. That check FAILS CLOSED - /// when the commitment is absent — a pulled PQC key with nothing to bind - /// it to must not be admitted, so a v1/v2 code is not a hybrid - /// registration path at all. + /// **How to use it (verify ≥ v15.0.0):** build the registration input + /// with [`ciris_verify_core::fedcode::AdmittedHybridKey::admit`], passing + /// a [`FedCode`] carrying this commitment and the pulled key. That is the + /// ONLY constructor of `AdmittedHybridKey`, so an unchecked registration + /// input cannot exist to be passed anywhere — the class fix CIRISVerify#274 + /// asked for, replacing the free function the host merely had to remember + /// to call. It FAILS CLOSED when the commitment is absent: a v1/v2 code is + /// not a hybrid registration path at all. /// /// **Trust level, stated plainly** (verify's own words): a fedcode is /// unsigned, so this commitment inherits exactly the trust of the code @@ -1584,16 +1585,15 @@ mod tests { pubkey[31] = u8::try_from(label.len()).unwrap_or(0); let key_id = ciris_verify_core::fedcode::derive_key_id(label, &pubkey); - let code = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode { - kind, - key_id: key_id.clone(), - pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD.encode(pubkey), - transport_hint: Some("https://example.invalid".to_string()), - alias_hint: Some("Frank".to_string()), - group_key_id: None, - owned_nodes: Vec::new(), - ml_dsa_65_pubkey_sha256: None, - }) + let code = ciris_verify_core::fedcode::encode( + &ciris_verify_core::fedcode::FedCode::new( + kind, + key_id.clone(), + base64::engine::general_purpose::STANDARD.encode(pubkey), + ) + .with_transport_hint("https://example.invalid".to_string()) + .with_alias_hint("Frank".to_string()), + ) .expect("encode"); (code, key_id) } @@ -1691,16 +1691,11 @@ mod tests { // The id DERIVES correctly from these bytes, so the binding check // alone would pass it. Only the length check catches it. let key_id = ciris_verify_core::fedcode::derive_key_id("shorty", &bytes); - let hand_crafted = ciris_verify_core::fedcode::FedCode { - kind: ciris_verify_core::fedcode::FedKind::User, + let hand_crafted = ciris_verify_core::fedcode::FedCode::new( + ciris_verify_core::fedcode::FedKind::User, key_id, - pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD.encode(&bytes), - transport_hint: None, - alias_hint: None, - group_key_id: None, - owned_nodes: Vec::new(), - ml_dsa_65_pubkey_sha256: None, - }; + base64::engine::general_purpose::STANDARD.encode(&bytes), + ); match super::verify_code_binds_its_key(&hand_crafted) { Err(LadderStall::MalformedCode { detail }) => { assert!(detail.contains("32"), "state the contract: {detail}"); @@ -1762,17 +1757,14 @@ mod tests { // The attacker's own key, presented under the victim's address. let attacker_pubkey = [0xAAu8; 32]; - let forged = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode { - kind: ciris_verify_core::fedcode::FedKind::User, - key_id: victim_key_id.clone(), - pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD - .encode(attacker_pubkey), - transport_hint: None, - alias_hint: Some("Totally The Victim".to_string()), - group_key_id: None, - owned_nodes: Vec::new(), - ml_dsa_65_pubkey_sha256: None, - }) + let forged = ciris_verify_core::fedcode::encode( + &ciris_verify_core::fedcode::FedCode::new( + ciris_verify_core::fedcode::FedKind::User, + victim_key_id.clone(), + base64::engine::general_purpose::STANDARD.encode(attacker_pubkey), + ) + .with_alias_hint("Totally The Victim".to_string()), + ) .expect("a forgery encodes perfectly well — that is the point"); // It decodes cleanly. The CRC is fine. Only the BINDING catches it. @@ -1867,19 +1859,17 @@ mod tests { let mut pubkey = [0u8; 32]; pubkey[0] = 31; let key_id = ciris_verify_core::fedcode::derive_key_id("v3person", &pubkey); - let code = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode { - kind: ciris_verify_core::fedcode::FedKind::User, - key_id: key_id.clone(), - pubkey_ed25519_base64: b64.encode(pubkey), - transport_hint: None, - alias_hint: None, - group_key_id: None, - owned_nodes: vec![ciris_verify_core::fedcode::OwnedNode { - key_id: "their-laptop-abc234def5".to_string(), - transport_pubkey_ed25519_base64: b64.encode([9u8; 32]), - }], - ml_dsa_65_pubkey_sha256: None, - }) + let code = ciris_verify_core::fedcode::encode( + &ciris_verify_core::fedcode::FedCode::new( + ciris_verify_core::fedcode::FedKind::User, + key_id.clone(), + b64.encode(pubkey), + ) + .with_owned_nodes(vec![ciris_verify_core::fedcode::OwnedNode::new( + "their-laptop-abc234def5", + b64.encode([9u8; 32]), + )]), + ) .expect("encode"); let empty = FakeLens { @@ -1936,16 +1926,14 @@ mod tests { let mut pubkey = [0u8; 32]; pubkey[0] = u8::try_from(wire.len()).unwrap_or(1); let key_id = ciris_verify_core::fedcode::derive_key_id(wire, &pubkey); - let code = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode { - kind, - key_id: key_id.clone(), - pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD.encode(pubkey), - transport_hint: Some("https://example.invalid".into()), - alias_hint: None, - group_key_id: None, - owned_nodes: Vec::new(), - ml_dsa_65_pubkey_sha256: None, - }) + let code = ciris_verify_core::fedcode::encode( + &ciris_verify_core::fedcode::FedCode::new( + kind, + key_id.clone(), + base64::engine::general_purpose::STANDARD.encode(pubkey), + ) + .with_transport_hint("https://example.invalid"), + ) .expect("encode"); match super::resolve_contact(&empty, &code).await { @@ -1999,16 +1987,11 @@ mod tests { let mut pubkey = [0u8; 32]; pubkey[0] = 21; let key_id = ciris_verify_core::fedcode::derive_key_id("selfcontained", &pubkey); - let code = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode { - kind: ciris_verify_core::fedcode::FedKind::User, + let code = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode::new( + ciris_verify_core::fedcode::FedKind::User, key_id, - pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD.encode(pubkey), - transport_hint: None, - alias_hint: None, - group_key_id: None, - owned_nodes: Vec::new(), - ml_dsa_65_pubkey_sha256: None, - }) + base64::engine::general_purpose::STANDARD.encode(pubkey), + )) .expect("encode"); let lens = CountingLens { @@ -2048,16 +2031,14 @@ mod tests { let mut pubkey = [0u8; 32]; pubkey[0] = 3; let key_id = ciris_verify_core::fedcode::derive_key_id("book-club", &pubkey); - let code = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode { - kind: ciris_verify_core::fedcode::FedKind::Community, - key_id: key_id.clone(), - pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD.encode(pubkey), - transport_hint: None, - alias_hint: None, - group_key_id: Some(key_id.clone()), - owned_nodes: Vec::new(), - ml_dsa_65_pubkey_sha256: None, - }) + let code = ciris_verify_core::fedcode::encode( + &ciris_verify_core::fedcode::FedCode::new( + ciris_verify_core::fedcode::FedKind::Community, + key_id.clone(), + base64::engine::general_purpose::STANDARD.encode(pubkey), + ) + .with_group_key_id(key_id.clone()), + ) .expect("encode"); let empty = FakeLens { @@ -2093,16 +2074,11 @@ mod tests { lens.nodes .insert(key_id.clone(), vec!["frank-laptop-bbb".into()]); - let code = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode { - kind: ciris_verify_core::fedcode::FedKind::User, - key_id: key_id.clone(), - pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD.encode(pubkey), - transport_hint: None, - alias_hint: None, - group_key_id: None, - owned_nodes: Vec::new(), - ml_dsa_65_pubkey_sha256: None, - }) + let code = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode::new( + ciris_verify_core::fedcode::FedKind::User, + key_id.clone(), + base64::engine::general_purpose::STANDARD.encode(pubkey), + )) .expect("encode"); match super::resolve_contact(&lens, &code).await { @@ -2131,16 +2107,11 @@ mod tests { let mut lens = frank(); lens.types.insert(key_id.clone(), "steward".into()); - let code = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode { - kind: ciris_verify_core::fedcode::FedKind::User, - key_id: key_id.clone(), - pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD.encode(pubkey), - transport_hint: None, - alias_hint: None, - group_key_id: None, - owned_nodes: Vec::new(), - ml_dsa_65_pubkey_sha256: None, - }) + let code = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode::new( + ciris_verify_core::fedcode::FedKind::User, + key_id.clone(), + base64::engine::general_purpose::STANDARD.encode(pubkey), + )) .expect("encode"); match super::resolve_contact(&lens, &code).await { diff --git a/src/role_matrix_gauntlet.rs b/src/role_matrix_gauntlet.rs index d9299f1..6c8e8ed 100644 --- a/src/role_matrix_gauntlet.rs +++ b/src/role_matrix_gauntlet.rs @@ -502,16 +502,14 @@ fn r12_fedcode_is_self_contained_and_binding_checked() { let mut pubkey = [0u8; 32]; pubkey[0] = 42; let key_id = ciris_verify_core::fedcode::derive_key_id("offgrid", &pubkey); - let code = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode { - kind: ciris_verify_core::fedcode::FedKind::User, - key_id: key_id.clone(), - pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD.encode(pubkey), - transport_hint: Some("https://example.invalid".into()), - alias_hint: None, - group_key_id: None, - owned_nodes: Vec::new(), - ml_dsa_65_pubkey_sha256: None, - }) + let code = ciris_verify_core::fedcode::encode( + &ciris_verify_core::fedcode::FedCode::new( + ciris_verify_core::fedcode::FedKind::User, + key_id.clone(), + base64::engine::general_purpose::STANDARD.encode(pubkey), + ) + .with_transport_hint("https://example.invalid"), + ) .expect("encode"); let parsed = crate::contact::parse_contact_input(&code).expect("a good code decodes"); let admission = parsed @@ -525,16 +523,11 @@ fn r12_fedcode_is_self_contained_and_binding_checked() { // And a forged claim over a different key is refused (the CRC cannot catch // authorship; only the derivation binding can). - let forged = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode { - kind: ciris_verify_core::fedcode::FedKind::User, + let forged = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode::new( + ciris_verify_core::fedcode::FedKind::User, key_id, - pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD.encode([0xAA_u8; 32]), - transport_hint: None, - alias_hint: None, - group_key_id: None, - owned_nodes: Vec::new(), - ml_dsa_65_pubkey_sha256: None, - }) + base64::engine::general_purpose::STANDARD.encode([0xAA_u8; 32]), + )) .expect("a forgery encodes fine"); assert!( matches!( diff --git a/tests/chat_harness_dx.rs b/tests/chat_harness_dx.rs index d78b458..c6c2619 100644 --- a/tests/chat_harness_dx.rs +++ b/tests/chat_harness_dx.rs @@ -65,16 +65,14 @@ fn the_stranger_contact_surface_matches_the_guide() { let mut pubkey = [0u8; 32]; pubkey[0] = 77; let key_id = ciris_verify_core::fedcode::derive_key_id("stranger", &pubkey); - let code = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode { - kind: ciris_verify_core::fedcode::FedKind::User, - key_id: key_id.clone(), - pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD.encode(pubkey), - transport_hint: Some("https://example.invalid".into()), - alias_hint: None, - group_key_id: None, - owned_nodes: Vec::new(), - ml_dsa_65_pubkey_sha256: None, - }) + let code = ciris_verify_core::fedcode::encode( + &ciris_verify_core::fedcode::FedCode::new( + ciris_verify_core::fedcode::FedKind::User, + key_id.clone(), + base64::engine::general_purpose::STANDARD.encode(pubkey), + ) + .with_transport_hint("https://example.invalid"), + ) .expect("encode"); // §6: classification never demotes a code to an identifier. @@ -90,16 +88,11 @@ fn the_stranger_contact_surface_matches_the_guide() { ); // §6: a forged code is refused, and the guide says do not admit it. - let forged = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode { - kind: ciris_verify_core::fedcode::FedKind::User, + let forged = ciris_verify_core::fedcode::encode(&ciris_verify_core::fedcode::FedCode::new( + ciris_verify_core::fedcode::FedKind::User, key_id, - pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD.encode([0xAA_u8; 32]), - transport_hint: None, - alias_hint: None, - group_key_id: None, - owned_nodes: Vec::new(), - ml_dsa_65_pubkey_sha256: None, - }) + base64::engine::general_purpose::STANDARD.encode([0xAA_u8; 32]), + )) .expect("a forgery encodes fine — the CRC cannot see authorship"); assert!( matches!(