From 7ef9a893a1389dc9947f5e5ff7235e303b5de219 Mon Sep 17 00:00:00 2001 From: dmitrii-f-t27 Date: Sat, 17 Oct 2026 05:40:05 -0300 Subject: [PATCH] spec(port): port the commit path secret AEAD keying mismatch guard (Refs #8883) The first CR-CHAT-03 ring target (W3, L-CHAT-3-cpakm, R-CHAT-3, RFC 9420 7.7/8/12.4.3.2): the UPDATE-PATH keying rules, in the original's fixed order - the canonical GroupID length (thirty-two), the epoch advancing by exactly one (the saturating edge spelled explicitly), the sender's leaf inside the tree, the receiver's resolution slot present, the ciphertext length canonical (forty-eight, rejected before any decap), the recipient init key equal to the local one, and the AAD context equal to the canonical encoding of group, epoch, sender leaf and node - with the slot's own node index answering the same rule - as the D5 verdict (all seven original variants carry no payload, so the code is the whole verdict), the original's rule order, and all ten CPAKM tests transcribed plus helper and rule-order tests. The original's commit and view structs become flat parameters; the Vec fields become fixed capacities plus lengths where a rule reads the length (the ciphertext gains a sixty-four capacity for the short and long fixtures); the canonical AAD encoding is answered by a positional matcher over a table of powers of two hundred fifty-six (t27 has no variable shifts), and the AAD lanes widen to u64 - t27 forbids the narrowing cast back to u8, every lane carrying one byte's worth. Checks (conventions 0-14): t27b test 13 passed 0 failed; t27c test-report 13/13, 0 vacuous; gen-rust output compiles under rustc; coverage Untested 0; ASCII only; seal saved, all hashes MATCH; --- ...mmit_path_secret_aead_keying_mismatch.json | 28 ++ docs/reports/t27b_expectations.json | 1 + ...ommit_path_secret_aead_keying_mismatch.t27 | 372 ++++++++++++++++++ 3 files changed, 401 insertions(+) create mode 100644 .trinity/seals/src_port::trios::crates::trios-chat::rings::CR-CHAT-03::src::commit_path_secret_aead_keying_mismatch.json create mode 100644 specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/commit_path_secret_aead_keying_mismatch.t27 diff --git a/.trinity/seals/src_port::trios::crates::trios-chat::rings::CR-CHAT-03::src::commit_path_secret_aead_keying_mismatch.json b/.trinity/seals/src_port::trios::crates::trios-chat::rings::CR-CHAT-03::src::commit_path_secret_aead_keying_mismatch.json new file mode 100644 index 0000000000..2139cfa436 --- /dev/null +++ b/.trinity/seals/src_port::trios::crates::trios-chat::rings::CR-CHAT-03::src::commit_path_secret_aead_keying_mismatch.json @@ -0,0 +1,28 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+01d0a635f", + "closure": [], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:1d337eddf19bbf82fe1725768eb70607c4e7c857f8c533c9f626a99e9aede549", + "gen_hash_rust": "sha256:a837ee5955fec2e54a8343f519365f13cb5eb9eafbc2fa9e8eacdda8ea1c9d92", + "gen_hash_verilog": "sha256:beac8ea2200f0c79539a3a5492f2193767aac41849c158669745fad5af232eb2", + "gen_hash_zig": "sha256:245c8c81ae09f93672bfe0934f77100f169f3005b5e46cb7a3629f03ef302cf8", + "module": "port::trios::crates::trios-chat::rings::CR-CHAT-03::src::commit_path_secret_aead_keying_mismatch", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-17T08:39:42Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:d98c635e1c59e51c2901de063b10f1f13d22cd2fd8c60134560aa621104e8cae", + "spec_path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/commit_path_secret_aead_keying_mismatch.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 13, + "total": 13, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index c041cbab56..fcdcd4f17e 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -851,6 +851,7 @@ {"path": "specs/port/trios/crates/trios-bridge/src/main.t27", "t27b": "pass"}, {"path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-01/src/key_package_expiry_guard.t27", "t27b": "pass"}, {"path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-02/src/epoch_rollover_wraparound_guard.t27", "t27b": "pass"}, + {"path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/commit_path_secret_aead_keying_mismatch.t27", "t27b": "pass"}, {"path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/ratchet_tree_resolution_guard.t27", "t27b": "pass"}, {"path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/unmerged_leaves_bound_guard.t27", "t27b": "pass"}, {"path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-05/src/envelope_size_distribution_uniformity.t27", "t27b": "pass"}, diff --git a/specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/commit_path_secret_aead_keying_mismatch.t27 b/specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/commit_path_secret_aead_keying_mismatch.t27 new file mode 100644 index 0000000000..53cc762c6f --- /dev/null +++ b/specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/commit_path_secret_aead_keying_mismatch.t27 @@ -0,0 +1,372 @@ +module port::trios::crates::trios-chat::rings::CR-CHAT-03::src::commit_path_secret_aead_keying_mismatch; + +// Port of gHashTag/trios crates/trios-chat/rings/CR-CHAT-03/src/commit_path_secret_aead_keying_mismatch.rs +// (CR-CHAT-03 - Commit path-secret AEAD keying mismatch, +// Wave-36, L-CHAT-3-cpakm, R-CHAT-3, RFC 9420 7.7/8/12.4.3.2). +// +// A Commit that ships an UPDATE-PATH seals the new path +// secret once per leaf in each updated node's resolution, +// bound to the recipient's init key, to a nonce and to an +// AAD pinning the group, the epoch, the sender's leaf and +// the node. A mis-keyed slot - sealed to the wrong leaf, or +// with an AAD that lies about the node - poisons the whole +// epoch, and the receiver rejects on a single deny. The +// rules, in the original's fixed order: the canonical +// GroupID length (thirty-two), the epoch advancing by +// exactly one, the sender's leaf inside the tree, the +// receiver's resolution slot present, the ciphertext length +// canonical (forty-eight - the sealed secret plus the +// Poly1305 tag, rejected before any decap), the recipient +// init key equal to the local one, and the AAD context equal +// to the canonical encoding of group, epoch, sender leaf +// and node - with the slot's own node index answering the +// same rule. +// +// The original returns Result<(), PathSecretAeadKeyingError>. +// This port carries the verdict as a named struct (owner +// decision D5): Ok first, then the seven variants in the +// original's declaration order - NonCanonicalGroupIdLength, +// EpochMismatch, SenderLeafOutOfRange, +// ResolutionSlotOutOfRange, RecipientInitKeyMismatch, +// AadContextMismatch, NonCanonicalCiphertextLength. Every +// original variant carries no payload, so the code is the +// whole verdict. +// +// The original's commit and view structs become flat +// parameters (slices as parameters); the slot stays a +// record. The Vec fields become fixed capacities plus +// lengths where a rule reads the length: the ciphertext +// gains a sixty-four capacity with its length (the original's +// short and long fixtures live there), the GroupID arrives +// as a plain byte slice whose length rule one reads, and the +// init key and AAD context stay at their canonical +// capacities - every original fixture carries them +// canonical, only the content ever disagrees. The canonical +// AAD encoding is answered by a positional matcher - the +// first thirty-two bytes the GroupID, then the big-endian +// epoch, sender leaf and node index read from a table of +// powers of two hundred fifty-six (t27 has no shifts by a +// variable amount; the table walks are exact). The original's +// saturating epoch advance differs from the plain successor +// only at the top of u64, where both answer the same +// maximum - the port spells the edge explicitly. + +// Canonical MLS GroupID length (32 bytes). +pub const CPAKM_GROUP_ID_LEN: usize = 32; + +// Canonical HPKE init_key length (32 bytes). +pub const CPAKM_INIT_KEY_LEN: usize = 32; + +// Canonical AAD-context length (32 + 8 + 8 + 8 = 56 bytes). +pub const CPAKM_AAD_CONTEXT_LEN: usize = 56; + +// Canonical path-secret ciphertext length (32 + 16 = 48 bytes). +pub const CPAKM_PATH_SECRET_CIPHERTEXT_LEN: usize = 48; + +// The ciphertext record capacity: the original's longest +// fixture is sixty-four bytes. +pub const CPAKM_CIPHERTEXT_CAP: usize = 64; + +// The top of u64 - the saturating epoch edge. +pub const CPAKM_U64_TOP: u64 = 18446744073709551615; + +// The big-endian byte weights of a u64, as a table - byte k +// of the encoding is (x / P256[k]) mod 256. +pub const P256: [8]u64 = [8]u64{1, 256, 65536, 16777216, 4294967296, 1099511627776, 281474976710656, 72057594037927936,} + +// One HPKE encryption inside a Commit's UPDATE-PATH, +// addressed to a single leaf in a node's resolution. +pub struct UpdatePathSlot { + node_index: u64, + recipient_init_key: [32]u8, + aad_context: [56]u64, + ciphertext: [64]u8, + ciphertext_len: usize, +} + +// Verdict codes: Ok(()) and PathSecretAeadKeyingError's variants, in declaration order. +pub enum CpakmCode { + Ok, + NonCanonicalGroupIdLength, + EpochMismatch, + SenderLeafOutOfRange, + ResolutionSlotOutOfRange, + RecipientInitKeyMismatch, + AadContextMismatch, + NonCanonicalCiphertextLength, +} + +// The verdict of the path-secret validation - the original's +// variants carry no payload, so the code alone carries it. +pub struct CpakmVerdict { + code: CpakmCode, +} + +// Whole-key equality over the canonical init-key capacity - +// the original's Vec equality on keys that every fixture +// carries canonical. +pub fn init_keys_equal(a: [32]u8, b: [32]u8) -> bool { + for i in 0..32 { + if (a[i] != b[i]) { + return false; + } + } + return true; +} + +// Whether the AAD context equals the canonical encoding +// group_id then epoch then sender leaf then node index, all +// big-endian - the original's Vec extend walk answered +// positionally, the GroupID already proven canonical by rule +// one. +pub fn aad_context_matches(aad: [56]u64, group_id: []const u8, epoch: u64, sender_leaf: u64, node_index: u64) -> bool { + for i in 0..32 { + if (aad[i] != @as(u64, group_id[i])) { + return false; + } + } + for k in 0..8 { + if (aad[32 + k] != (epoch / P256[7 - k]) % 256) { + return false; + } + if (aad[40 + k] != (sender_leaf / P256[7 - k]) % 256) { + return false; + } + if (aad[48 + k] != (node_index / P256[7 - k]) % 256) { + return false; + } + } + return true; +} + +// [VERIFIED] Validate a single Commit's UPDATE-PATH slot +// targeted at the local receiver. Returns Ok only when every +// rule holds, checked in the original's fixed order: the +// GroupID length, the epoch advance, the sender's leaf, the +// slot's presence, the ciphertext length before any crypto +// binding, the recipient init key, then the AAD context with +// the slot's node index. +pub fn validate_commit_path_secret(group_id: []const u8, epoch: u64, sender_leaf: u64, slots: []const UpdatePathSlot, local_epoch: u64, tree_size: u64, local_init_key: [32]u8, local_resolution_index: usize, local_node_index: u64) -> CpakmVerdict { + if (group_id.len != CPAKM_GROUP_ID_LEN) { + return CpakmVerdict { code: CpakmCode.NonCanonicalGroupIdLength, }; + } + var expected_epoch: u64 = CPAKM_U64_TOP; + if (local_epoch < CPAKM_U64_TOP) { + expected_epoch = local_epoch + 1; + } + if (epoch != expected_epoch) { + return CpakmVerdict { code: CpakmCode.EpochMismatch, }; + } + if (sender_leaf >= tree_size) { + return CpakmVerdict { code: CpakmCode.SenderLeafOutOfRange, }; + } + if (local_resolution_index >= slots.len) { + return CpakmVerdict { code: CpakmCode.ResolutionSlotOutOfRange, }; + } + if (slots[local_resolution_index].ciphertext_len != CPAKM_PATH_SECRET_CIPHERTEXT_LEN) { + return CpakmVerdict { code: CpakmCode.NonCanonicalCiphertextLength, }; + } + if (!init_keys_equal(slots[local_resolution_index].recipient_init_key, local_init_key)) { + return CpakmVerdict { code: CpakmCode.RecipientInitKeyMismatch, }; + } + if (!aad_context_matches(slots[local_resolution_index].aad_context, group_id, epoch, sender_leaf, local_node_index)) { + return CpakmVerdict { code: CpakmCode.AadContextMismatch, }; + } + if (slots[local_resolution_index].node_index != local_node_index) { + return CpakmVerdict { code: CpakmCode.AadContextMismatch, }; + } + return CpakmVerdict { code: CpakmCode.Ok, }; +} + +// The fixture bytes: the GroupID of eleven-hex-eleven, the +// local init key of twenty-two-hex, the stranger's key of +// thirty-three-hex, the ciphertext of forty-four-hex over +// its full capacity, and the canonical AAD contexts of the +// fixtures. +pub const GOOD_GROUP_ID: [32]u8 = [32]u8{17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,} +pub const LOCAL_INIT_KEY: [32]u8 = [32]u8{34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,} +pub const OTHER_INIT_KEY: [32]u8 = [32]u8{51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,} +pub const GOOD_CIPHERTEXT: [64]u8 = [64]u8{68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68,} +pub const OK_AAD: [56]u64 = [56]u8{17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7,} + +// **CPAKM-01** - the short GroupID rejected: sixteen bytes of +// a canonical thirty-two. +test cpakm_01_short_group_id_rejected { + var short_gid: [16]u8; + for j in 0..16 { + short_gid[j] = 17; + } + const slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 7, recipient_init_key: LOCAL_INIT_KEY, aad_context: OK_AAD, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 48, }, + }; + const v = validate_commit_path_secret(&short_gid, 42, 3, &slots, 41, 16, LOCAL_INIT_KEY, 0, 7); + assert(v.code == CpakmCode.NonCanonicalGroupIdLength); +} + +// **CPAKM-02** - the epoch skip rejected: forty-four of a +// required forty-two over the local forty-one. +test cpakm_02_epoch_skip_rejected { + const slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 7, recipient_init_key: LOCAL_INIT_KEY, aad_context: OK_AAD, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 48, }, + }; + const v = validate_commit_path_secret(&GOOD_GROUP_ID, 44, 3, &slots, 41, 16, LOCAL_INIT_KEY, 0, 7); + assert(v.code == CpakmCode.EpochMismatch); +} + +// **CPAKM-03** - the sender's leaf out of range rejected: +// sixteen of a tree of sixteen, the AAD adjusted to the bad +// sender exactly as the original's fixture does. +test cpakm_03_sender_leaf_out_of_range_rejected { + var sender16_aad: [56]u64 = OK_AAD; + for k in 0..8 { + sender16_aad[40 + k] = (16 / P256[7 - k]) % 256; + } + const slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 7, recipient_init_key: LOCAL_INIT_KEY, aad_context: sender16_aad, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 48, }, + }; + const v = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 16, &slots, 41, 16, LOCAL_INIT_KEY, 0, 7); + assert(v.code == CpakmCode.SenderLeafOutOfRange); +} + +// **CPAKM-04** - the receiver's resolution slot out of range +// rejected: index one of a single slot. +test cpakm_04_resolution_slot_out_of_range_rejected { + const slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 7, recipient_init_key: LOCAL_INIT_KEY, aad_context: OK_AAD, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 48, }, + }; + const v = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 3, &slots, 41, 16, LOCAL_INIT_KEY, 1, 7); + assert(v.code == CpakmCode.ResolutionSlotOutOfRange); +} + +// **CPAKM-05** - the recipient init key mismatch rejected: +// the stranger's key in the slot. +test cpakm_05_recipient_init_key_mismatch_rejected { + const slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 7, recipient_init_key: OTHER_INIT_KEY, aad_context: OK_AAD, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 48, }, + }; + const v = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 3, &slots, 41, 16, LOCAL_INIT_KEY, 0, 7); + assert(v.code == CpakmCode.RecipientInitKeyMismatch); +} + +// **CPAKM-06** - the AAD context with the wrong epoch +// rejected: nine hundred ninety-nine of a canonical +// forty-two. +test cpakm_06_aad_context_epoch_mismatch_rejected { + var epoch999_aad: [56]u64 = OK_AAD; + for k in 0..8 { + epoch999_aad[32 + k] = (999 / P256[7 - k]) % 256; + } + const slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 7, recipient_init_key: LOCAL_INIT_KEY, aad_context: epoch999_aad, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 48, }, + }; + const v = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 3, &slots, 41, 16, LOCAL_INIT_KEY, 0, 7); + assert(v.code == CpakmCode.AadContextMismatch); +} + +// **CPAKM-07** - the AAD context disagreeing on the node +// index rejected: the slot claims node nine of a local seven, +// the AAD encoding nine with it exactly as the original's +// fixture does. +test cpakm_07_aad_context_node_index_mismatch_rejected { + var node9_aad: [56]u64 = OK_AAD; + for k in 0..8 { + node9_aad[48 + k] = (9 / P256[7 - k]) % 256; + } + const slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 9, recipient_init_key: LOCAL_INIT_KEY, aad_context: node9_aad, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 48, }, + }; + const v = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 3, &slots, 41, 16, LOCAL_INIT_KEY, 0, 7); + assert(v.code == CpakmCode.AadContextMismatch); +} + +// **CPAKM-08** - the short ciphertext rejected: thirty-two +// bytes, the sealed secret without its tag. +test cpakm_08_short_ciphertext_rejected { + const slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 7, recipient_init_key: LOCAL_INIT_KEY, aad_context: OK_AAD, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 32, }, + }; + const v = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 3, &slots, 41, 16, LOCAL_INIT_KEY, 0, 7); + assert(v.code == CpakmCode.NonCanonicalCiphertextLength); +} + +// **CPAKM-09** - the long ciphertext rejected: sixty-four +// bytes of a canonical forty-eight. +test cpakm_09_long_ciphertext_rejected { + const slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 7, recipient_init_key: LOCAL_INIT_KEY, aad_context: OK_AAD, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 64, }, + }; + const v = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 3, &slots, 41, 16, LOCAL_INIT_KEY, 0, 7); + assert(v.code == CpakmCode.NonCanonicalCiphertextLength); +} + +// **CPAKM-10** - the canonical update-path slot accepted. +test cpakm_10_canonical_commit_accepted { + const slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 7, recipient_init_key: LOCAL_INIT_KEY, aad_context: OK_AAD, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 48, }, + }; + const v = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 3, &slots, 41, 16, LOCAL_INIT_KEY, 0, 7); + assert(v.code == CpakmCode.Ok); +} + +// The helpers have direct tests (coverage: Untested 0). +test init_keys_equal_answers { + assert(init_keys_equal(LOCAL_INIT_KEY, LOCAL_INIT_KEY)); + assert(!init_keys_equal(LOCAL_INIT_KEY, OTHER_INIT_KEY)); +} + +test aad_context_matches_answers { + assert(aad_context_matches(OK_AAD, &GOOD_GROUP_ID, 42, 3, 7)); + assert(!aad_context_matches(OK_AAD, &GOOD_GROUP_ID, 999, 3, 7)); + assert(!aad_context_matches(OK_AAD, &GOOD_GROUP_ID, 42, 3, 9)); +} + +// Rule order is the original's fixed ladder, each probe +// stacking the violation of the earlier rule on the later +// one: a short GroupID over an epoch skip, the epoch skip +// over a phantom sender, the phantom sender over a missing +// slot, the missing slot over a short ciphertext, the short +// ciphertext over a stranger's key (the pre-decap order), +// the stranger's key over a lying AAD - and the slot's node +// index alone answering the AAD rule. +test rule_order_matches_original { + var short_gid: [16]u8; + for j in 0..16 { + short_gid[j] = 17; + } + const ok_slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 7, recipient_init_key: LOCAL_INIT_KEY, aad_context: OK_AAD, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 48, }, + }; + const v1 = validate_commit_path_secret(&short_gid, 44, 16, &ok_slots, 41, 16, LOCAL_INIT_KEY, 1, 7); + assert(v1.code == CpakmCode.NonCanonicalGroupIdLength); + + const v2 = validate_commit_path_secret(&GOOD_GROUP_ID, 44, 16, &ok_slots, 41, 16, LOCAL_INIT_KEY, 1, 7); + assert(v2.code == CpakmCode.EpochMismatch); + + const v3 = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 16, &ok_slots, 41, 16, LOCAL_INIT_KEY, 1, 7); + assert(v3.code == CpakmCode.SenderLeafOutOfRange); + + const v4 = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 3, &ok_slots, 41, 16, LOCAL_INIT_KEY, 1, 7); + assert(v4.code == CpakmCode.ResolutionSlotOutOfRange); + + const bad_ct_slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 7, recipient_init_key: OTHER_INIT_KEY, aad_context: OK_AAD, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 32, }, + }; + const v5 = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 3, &bad_ct_slots, 41, 16, LOCAL_INIT_KEY, 0, 7); + assert(v5.code == CpakmCode.NonCanonicalCiphertextLength); + + var lying_aad: [56]u64 = OK_AAD; + for k in 0..8 { + lying_aad[32 + k] = (999 / P256[7 - k]) % 256; + } + const wrong_key_slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 7, recipient_init_key: OTHER_INIT_KEY, aad_context: lying_aad, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 48, }, + }; + const v6 = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 3, &wrong_key_slots, 41, 16, LOCAL_INIT_KEY, 0, 7); + assert(v6.code == CpakmCode.RecipientInitKeyMismatch); + + const node9_slots: [1]UpdatePathSlot = [1]UpdatePathSlot{ + UpdatePathSlot { node_index: 9, recipient_init_key: LOCAL_INIT_KEY, aad_context: OK_AAD, ciphertext: GOOD_CIPHERTEXT, ciphertext_len: 48, }, + }; + const v7 = validate_commit_path_secret(&GOOD_GROUP_ID, 42, 3, &node9_slots, 41, 16, LOCAL_INIT_KEY, 0, 7); + assert(v7.code == CpakmCode.AadContextMismatch); +}