summaryrefslogtreecommitdiffstats
path: root/docs/FOG-SPHINX-PROFILES.md
blob: 2c4aac8fb7e435568484ec1f45ac38f15fb65f85 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
# FOG Sphinx Profiles

Status: Draft 0.1

Date: 2026-08-08

## 1. Purpose

This document defines `FOG-SPHINX-PROFILES`, the authenticated profile
framework for fixed-size KEMSphinx packets used by FOG.

It fixes the structural packet contract, route shape, routing commands, SURB
rules, replay behavior, wire ownership, SDK boundary, transition behavior,
and conformance evidence required before a concrete packet suite can be
activated.

It also records one calculated but non-active candidate geometry named
`FOG-SPHINX-CANDIDATE-MLKEM768-X25519-1`. The candidate is an engineering
input for benchmarks and review. It is not yet `FOG-SPHINX-1`, is not
authorized for a public network, and does not make a deployed security claim.

The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, and MAY describe
normative requirements in the sense of BCP 14 when they appear in uppercase.

## 2. Scope

`FOG-SPHINX-PROFILES` owns:

- immutable packet-profile identifiers and their consensus authorization;
- fixed KEMSphinx packet geometry and exact route length;
- the mapping from PKI nodes to 32-byte packet node identifiers;
- per-hop routing command sequences and command padding;
- forward payload and SURB slots;
- single-use reply blocks and private reply tokens;
- replay-tag derivation, durable replay-state behavior, and retirement;
- packet lifetime and epoch binding;
- the logical bodies carried by FOG-WIRE packet commands;
- parser limits, uniform failures, and key lifecycle;
- conformance-vector and SDK requirements;
- isolation requirements for foreign Sphinx-family bridges.

This document does not own:

- message-level end-to-end encryption;
- contact, retry, acknowledgement, or application deduplication semantics;
- entry-capsule encryption and blind-relay entry selection;
- rendezvous capability allocation and offline return import;
- storage capabilities or replica protocols;
- adjacent-link Noise framing or its record size;
- the concrete delay distribution and cover rate;
- consensus production, signatures, or topology assignment.

Those contracts belong to FOG-MESSAGING, FOG-STORAGE, the entry-capsule and
return-rendezvous specifications, FOG-WIRE, the cover profile, and FOG-PKI.

## 3. Security Boundaries

KEMSphinx protects per-hop routing information and transforms the packet at
every hop. It does not replace message-level encryption. The terminal receives
the fixed outer FOG payload and MUST still treat the native message or storage
operation as end-to-end encrypted unless the owning application protocol
explicitly defines a different public object.

FOG-WIRE authenticates adjacent links but does not replace KEMSphinx. A valid
Noise connection is only a carrier for one packet whose KEMSphinx
authentication, route command, replay status, epoch, and geometry must still
be validated.

Fixed packet size alone does not provide anonymity against timing,
intersection, n-1, volume, active tagging, or global observation. Delay,
traffic volume, cover traffic, topology, endpoint safety, and operator
independence remain necessary parts of any anonymity claim.

## 4. Protocol Invariants

### SPHINX-INV-01: One exact profile per epoch context

A packet is processed under exactly one packet profile selected by the
accepted consensus and the authenticated FOG-WIRE epoch context. A node MUST
NOT guess a profile from packet length, try a list of suites, negotiate a
suite inside a packet, or fall back after failure.

### SPHINX-INV-02: Four KEMSphinx hops

Every core forward route and every core reply route has exactly four
KEMSphinx hops. A shorter or longer core route is invalid even if an
underlying Sphinx library can pad a variable path to the same header length.

### SPHINX-INV-03: Entry is outside the forward mix route

The forward entry is not a KEMSphinx hop. The blind relay submits an
entry-bound capsule; the entry releases the already constructed packet only
to its bound layer-1 node.

### SPHINX-INV-04: One transform per hop

An accepted KEMSphinx hop performs one and only one unwrap. No role unwraps
two layers, forwards a packet without the required unwrap, or changes a route
outside the authenticated command obtained from that unwrap.

### SPHINX-INV-05: Fixed external geometry

Every packet using one packet profile has one exact byte length. Forward,
reply, padding, loop, drop, mailbox, and asynchronous-message packets use the
same geometry. Source applications do not select packet sizes.

### SPHINX-INV-06: Durable replay before action

After successful KEM decapsulation and header authentication, a hop MUST
atomically check and durably insert its replay identifier before scheduling,
forwarding, terminal delivery, or returning success to an upstream worker.

### SPHINX-INV-07: Single-use replies

A SURB, its private decryption token, its `surb_id`, and the associated return
rendezvous are single-use. Uncertain delivery burns them. Retrying with the
same reply material is forbidden.

### SPHINX-INV-08: No packet-layer fragmentation identity

KEMSphinx packets contain no application message identifier, fragment index,
or fragment count. Application fragmentation occurs inside the fixed opaque
user payload under the owning end-to-end protocol.

### SPHINX-INV-09: Purpose-separated keys and state

Mix KEM keys, terminal KEM keys, Noise keys, entry-capsule keys, replay-state
protection keys, SURB private tokens, message keys, and storage keys are
distinct. Sharing one private key or replay database across roles is
forbidden.

### SPHINX-INV-10: Failure never changes topology or profile

Failure cannot remove a layer, substitute the entry as a mix, route directly
to storage, activate an old profile, send plaintext, or hand a foreign packet
to a core parser.

## 5. Terminology and Encoding

Terms used in this specification:

- **packet profile**: permanent mapping from a numeric identifier to exact
  KEMSphinx primitives, geometry, commands, epoch rules, limits, and SDK
  behavior;
- **packet epoch context**: the epoch and accepted consensus already bound by
  the FOG-WIRE connection carrying the packet;
- **packet node ID**: fixed 32-byte KEMSphinx routing identifier derived from
  one consensus-authorized node and one packet profile;
- **forward route**: layer 1, layer 2, layer 3, then a courier or native
  terminal;
- **reply route**: layer 3, layer 2, layer 1, then an entry return
  rendezvous;
- **SURB**: a public Single Use Reply Block given to a terminal;
- **private reply token**: Composer-only payload decryption material paired
  with one SURB;
- **raw replay tag**: profile-domain-separated digest of the current hop KEM
  ciphertext;
- **stored replay ID**: keyed local representation committed to durable replay
  state.

All integers defined directly by FOG are unsigned and encoded in network byte
order. Fixed arrays have exactly the declared length. Reserved and padding
bytes are zero after decryption. Parsers reject non-zero reserved bytes,
truncation, trailing bytes, duplicate commands, reordered commands, unknown
commands, and lengths other than the active profile's exact values.

## 6. Packet Profile Registry

Every `packet_profile_id` is a non-zero unsigned 32-bit integer whose meaning
is permanent. An identifier MUST NOT be reused for changed bytes, algorithms,
command rules, route shape, limits, or behavior.

An exact profile record contains at least:

```text
[
  packet_profile_id,
  kemsphinx_format_id,
  kem_suite_id,
  primitive_suite_id,
  packet_node_id_hash_id,
  replay_hash_id,
  replay_store_mac_id,
  additional_data,
  hop_count,
  route_shape_id,
  command_registry_id,
  kem_ciphertext_length,
  header_mac_length,
  payload_tag_length,
  sprp_key_material_length,
  per_hop_routing_info_length,
  routing_info_length,
  header_length,
  surb_length,
  sphinx_plaintext_header_length,
  user_forward_payload_length,
  forward_payload_length,
  packet_length,
  delay_profile_id,
  maximum_packet_lifetime,
  epoch_drain_limit,
  parser_limits_profile_id,
  sdk_contract_id,
  conformance_vector_set_id
]
```

Registry metadata MAY label a reviewed profile `alpha`, `active`, `draining`,
or `retired`, but that mutable label is not part of the immutable profile
meaning. Only the accepted consensus authorizes actual use. An incomplete
candidate does not receive a numeric `packet_profile_id`.

The profile record is published as an immutable release artifact and its
identifier is listed by FOG-PKI. Consensus `active_profile_ids` selects the
exact record. Numeric parameters are not operator-tunable.

One packet profile MUST be compatible with the FOG-WIRE command-shape
registry active on every link that carries it. A consensus containing an
incompatible pair is invalid.

## 7. Epoch and Profile Selection

The packet bytes do not carry a negotiable profile identifier or epoch. The
authenticated FOG-WIRE connection context supplies:

- network identity;
- epoch;
- accepted consensus hash;
- link context;
- wire profile;
- the one packet profile mapped to that epoch and command shape.

For a given link context and packet class, one epoch maps to exactly one
packet profile. Old-epoch packets continue only on old-epoch connections
during the bounded drain interval. A node MUST NOT trial-decrypt an old
packet with several epoch keys.

A Composer MUST NOT create a packet unless:

```text
current_time_upper_bound
  + maximum_route_delay
  + relay_and_entry_queue_budget
  + wire_delivery_budget
  < packet_epoch_hard_expiry
```

The same check applies to a SURB's expected round trip. A packet or SURB that
cannot complete within its epoch budget is not created.

## 8. Core Route Shape

### 8.1 Forward route

The only core forward shape is:

```text
entry capsule -> L1 -> L2 -> L3 -> terminal
```

The entry capsule is outside this four-hop KEMSphinx packet. Its specification
must cryptographically bind the exact packet to the exact L1 packet node ID
without revealing that ID to the blind relay.

The four KEMSphinx hops are:

| Hop | Required role | Required routing result |
|---:|---|---|
| 0 | mix layer 1 | delay, then layer 2 |
| 1 | mix layer 2 | delay, then layer 3 |
| 2 | mix layer 3 | delay, then selected terminal |
| 3 | courier or native terminal | terminal recipient and payload |

### 8.2 Reply route

The only core reply shape is:

```text
terminal uses SURB -> L3 -> L2 -> L1 -> entry rendezvous
```

The four KEMSphinx hops are:

| Hop | Required role | Required routing result |
|---:|---|---|
| 0 | mix layer 3 | delay, then layer 2 |
| 1 | mix layer 2 | delay, then layer 1 |
| 2 | mix layer 1 | delay, then entry |
| 3 | entry | return rendezvous plus `surb_id` |

The reply sender learns the public first-hop packet node ID contained in the
SURB. It does not learn later hops, the destination relay, or the Composer's
private reply token.

### 8.3 Eligibility checks

The Composer builds routes only from one accepted full consensus. Every hop
must have the required role, exact layer, active packet profile, current epoch
KEM key, endpoint reachability through the next role, and no effective
revocation. The route must also pass the consensus topology and diversity
rules.

Each online hop independently verifies that the authenticated upstream role,
its own role and layer, the next packet node ID, and the next FOG-WIRE context
match the consensus. A valid header is not sufficient authorization for a
role-invalid edge.

## 9. Packet Node IDs

KEMSphinx routing uses a fixed 32-byte `packet_node_id`. It is not a private
key and is not a substitute for the PKI `node_id`.

For profile `P`, it is derived as:

```text
packet_node_id = HASH32(
  "FOG-SPHINX-NODE-ID-1" ||
  u32be(length(network_id)) ||
  network_id ||
  u32be(P.packet_profile_id) ||
  u32be(length(node_id)) ||
  node_id
)
```

`HASH32` is fixed by `packet_node_id_hash_id`. Consensus validation derives
all active packet node IDs and rejects any duplicate. Nodes build an immutable
epoch-local lookup table from packet node ID to the exact authorized role,
layer, KEM key ID, and link endpoint.

Packet node IDs may be logged only in coarse configuration validation. They
MUST NOT be logged per packet.

## 10. Routing Commands

The core profile uses the maintained Sphinx command model with these exact
tags:

| Tag | Command | Body length | Meaning |
|---:|---|---:|---|
| `0x00` | `NULL` | 0 | terminates command parsing; remaining bytes are zero |
| `0x01` | `NEXT_NODE` | 64 | 32-byte next node ID and 32-byte next header MAC |
| `0x02` | `RECIPIENT` | 32 | opaque terminal recipient or return capability |
| `0x03` | `SURB_REPLY` | 16 | random single-use `surb_id` |
| `0x80` | `NODE_DELAY` | 4 | unsigned delay in profile-defined units |

Unknown tags are invalid. No vendor command range is accepted by core nodes.

### 10.1 Intermediate mix command sequence

Every nonterminal mix hop contains exactly:

```text
NODE_DELAY || NEXT_NODE || NULL || zero padding || next KEM ciphertext
```

The implementation may build commands in an internal representation, but the
encrypted routing bytes and parser result must preserve this semantic order.
There is exactly one delay and one next node. Zero delay is valid only if the
active cover profile explicitly includes it in the same public distribution
used for real and cover packets.

The `NODE_DELAY` value is validated before queue insertion. Values outside the
active delay profile, arithmetic overflow, or a deadline past epoch expiry
cause a uniform drop.

### 10.2 Forward terminal command sequence

The terminal hop contains exactly:

```text
RECIPIENT || NULL || zero padding || unused zero KEM slot
```

`RECIPIENT` is a random or derived 32-byte capability defined by the terminal
contract. It must not be a human address, username, public mailbox name, or
application-specific string. The terminal validates it before acting on the
payload.

### 10.3 Reply terminal command sequence

The entry terminal hop contains exactly:

```text
RECIPIENT || SURB_REPLY || NULL || zero padding || unused zero KEM slot
```

Here `RECIPIENT` is an opaque short-lived return-rendezvous capability and
`SURB_REPLY` selects the Composer's private reply token. The entry atomically
consumes the rendezvous before queueing the returned opaque payload.

### 10.4 Terminal padding

The current maintained KEMSphinx geometry reserves one KEM ciphertext-sized
tail in every per-hop routing block, including the terminal block. The
terminal tail is all zero before routing encryption. FOG does not apply the
possible one-ciphertext optimization until a new separately identified
profile has complete vectors and interoperability review.

## 11. Forward Plaintext Block

After the terminal KEMSphinx unwrap and payload-tag validation, a forward
payload has this exact layout:

```text
offset  length  field
0       1       surb_flag
1       1       reserved
2       S       surb_slot
2+S     U       user_payload
```

`reserved` is zero. `S` is the profile's exact `surb_length`. `U` is the exact
`user_forward_payload_length`.

`surb_flag` is:

- `0x00`: no usable SURB; the entire `surb_slot` is zero after decryption;
- `0x01`: `surb_slot` contains one valid SURB for the same packet profile and
  epoch.

All other values are invalid. The slot always exists and always occupies the
same bytes. Application code receives exactly `U` opaque bytes and an optional
validated public SURB. It does not receive short lengths from the packet
layer.

The 4,096-byte candidate user payload is owned internally by the next
protocol. FOG-MESSAGING or FOG-STORAGE defines authentication, actual body
length, padding, fragmentation, retries, deduplication, and acknowledgements
inside those bytes.

## 12. SURB Contract

### 12.1 Public SURB

The public SURB encoding is exactly:

```text
prebuilt_kemsphinx_header[header_length] ||
first_hop_packet_node_id[32] ||
reply_payload_key_material[sprp_key_material_length]
```

The packet profile and epoch are supplied by the containing authenticated
context and the local SURB object. They are not inserted into the opaque SURB
and do not change its size.

The prebuilt reply path has exactly four hops and ends in the entry terminal
commands described in section 10.3.

### 12.2 Private reply token

The Composer stores, separately from the public SURB:

```text
[
  token_version,
  packet_profile_id,
  epoch,
  surb_id,
  hard_expiry,
  status,
  reverse_order_payload_key_material
]
```

For four hops, `reverse_order_payload_key_material` contains five fixed key
and IV pairs: one per KEMSphinx hop plus the final reply payload pair. In the
candidate geometry this secret field is 320 bytes.

The token is secret Composer state. It is encrypted and integrity-protected
at rest under the Composer state profile, excluded from logs and routine
backups unless the backup design explicitly protects it, and erased after
successful use, expiry, cancellation, or uncertain duplicate handling.

### 12.3 Single-use state machine

The only valid state transitions are:

```text
AVAILABLE -> COMMITTED -> CONSUMED
AVAILABLE -> EXPIRED
COMMITTED -> BURNED
```

The terminal changes a public SURB from `AVAILABLE` to `COMMITTED` before
building or submitting a reply packet. A confirmed local construction error
before any packet or KEM operation may return it to `AVAILABLE`; after packet
construction or any send attempt, uncertainty results in `BURNED`.

The Composer atomically changes the private token to `CONSUMED` before
releasing successfully authenticated plaintext to an application. A second
return with the same `surb_id` is discarded without another decryption
attempt.

Applications needing retries provide multiple independently generated SURBs.
They never clone a SURB or its private token.

### 12.4 Tagging and compulsion limits

SURBs do not remove active-tagging or reply-compulsion risk. Implementations
MUST apply the same fixed packet schedule, reply size, and terminal queue
policy to successful replies, errors, and cover outcomes. A terminal cannot
send arbitrary immediate diagnostic replies outside the cover schedule.

## 13. Replay Protection

### 13.1 Raw replay tag

For an authenticated hop, the raw tag is:

```text
raw_replay_tag = HASH(
  "FOG-SPHINX-REPLAY-TAG-1" ||
  network_id ||
  u32be(packet_profile_id) ||
  u64be(epoch) ||
  kem_key_id ||
  current_hop_kem_ciphertext
)
```

Lengths for variable fields are fixed by the active PKI and packet profiles.
`HASH` and its output length are fixed by `replay_hash_id`.

The current hop KEM ciphertext is the public ciphertext at the front of the
current KEMSphinx header, not a future ciphertext hidden in routing data.

### 13.2 Stored replay ID

The durable database stores:

```text
stored_replay_id = MAC(
  replay_state_key_epoch,
  "FOG-SPHINX-REPLAY-STORE-1" || raw_replay_tag
)
```

This local keyed representation reduces direct correlation between a stolen
database and previously captured packet headers. It is not a substitute for
disk, process, or host protection.

`replay_state_key_epoch` is independently generated per node, packet profile,
and epoch. It is not derived from a KEMSphinx private key and is never shared
with another role or node.

### 13.3 Processing order

A hop processes an incoming packet in this order:

1. verify authenticated FOG-WIRE context, exact command body, and packet
   length;
2. enforce current epoch, profile, role, layer, upstream, and local key ID;
3. parse only fixed header offsets;
4. decapsulate the current KEM ciphertext;
5. derive hop keys and verify the current header MAC;
6. derive the raw replay tag and stored replay ID;
7. atomically check and durably insert the stored replay ID;
8. parse and validate the exact routing command sequence;
9. validate next-hop authorization and delay bounds;
10. transform the packet exactly once;
11. queue the transformed packet or terminal payload;
12. erase per-hop shared secrets and temporary keys.

An unauthenticated random ciphertext is not inserted. A header that
successfully authenticates is inserted before later command validation, even
when the command, next hop, delay, or terminal capability is invalid. This
prevents repeated authenticated malformed work from bypassing replay state.

### 13.4 Durable database

Replay insertion is a crash-consistent transaction. The packet is not made
eligible for forwarding until the write-ahead record or equivalent durable
commit succeeds. Group commit is permitted only if scheduling and forwarding
wait for the corresponding durable barrier.

A keyed in-memory filter MAY avoid many exact lookups, but it is only a front
cache. A positive filter result is confirmed against the exact durable set;
therefore filter false positives do not discard valid packets.

Replay databases are separated by node, role, packet profile, epoch, and KEM
key ID. They are excluded from telemetry, snapshots shared across nodes, and
ordinary backups. Aggregate counts may be exported only under the observer
privacy profile.

### 13.5 Restart and loss

On restart, a node verifies database integrity, replay-state key availability,
epoch ownership, and committed sequence state before accepting packets. It
rebuilds any in-memory filter from the exact durable set.

Missing, rolled-back, corrupt, or unverifiable replay state is fail-closed.
The node stops packet processing for that profile and epoch. It does not start
with an empty cache. Service can resume only from safely restored monotonic
state or a new epoch with fresh KEM and replay-state keys.

### 13.6 Retirement

An epoch replay database and its local key are retained until:

```text
epoch_hard_expiry
  + maximum_packet_lifetime
  + maximum_clock_uncertainty
  + crash_recovery_margin
```

has passed. Retirement erases the replay-state key and removes the database
through the deployment's recoverable secure-deletion policy. No node accepts
new work merely because an old database still exists.

## 14. KEMSphinx Hop Processing

Every mix worker has fixed-size buffers from its active geometry. It does not
allocate based on decrypted command values.

For a valid intermediate hop it:

- decrypts the current routing block;
- obtains the authenticated delay and next node;
- shifts the routing information according to KEMSphinx;
- copies the hidden next-hop KEM ciphertext into the public KEM field;
- applies one payload permutation;
- commits replay state;
- enters the bounded delay queue;
- emits the exact transformed packet on the authorized next link.

For a valid terminal it:

- verifies the terminal command sequence;
- verifies the final payload integrity tag;
- validates the fixed forward plaintext block for a forward packet, or
  returns the still SURB-protected payload and `surb_id` for a reply packet;
- consumes any terminal or rendezvous capability atomically;
- hands only the bounded opaque object to the owning role contract.

No mix exposes whether failure was KEM, MAC, replay, command, delay, route,
queue, payload tag, recipient, or epoch. Remote behavior is the same uniform
drop class and is subject to the cover schedule.

## 15. Delay Contract

`NODE_DELAY` contains a 32-bit count in the unit fixed by the active delay
profile. Implementations convert using checked arithmetic and a monotonic
clock.

The delay profile fixes at least:

```text
[
  delay_profile_id,
  unit_nanoseconds,
  maximum_encoded_delay,
  maximum_per_hop_delay,
  maximum_route_delay,
  sampling_distribution_id,
  quantization_rule,
  queue_deadline_rule,
  cover_schedule_profile_id
]
```

The Composer samples every mix delay from this authenticated distribution.
Operators do not alter it locally. Mixes validate the encoded value but do not
resample it. Queue pressure does not convert delayed traffic to immediate
traffic; overload follows the profile's uniform drop or shutdown behavior.

The initial distribution and numeric limits remain simulation outputs. No
anonymity or latency claim follows from this structural specification.

`FOG-SIMULATION.md` records an initial 500 ms versus 5 second exponential-delay
sensitivity comparison. It selects neither value. The longer value improved
local pool overlap under one high-cover scenario while increasing modeled
latency by roughly an order of magnitude; formal end-to-end and operational
evidence remains open.

## 16. Padding and Fragmentation

### 16.1 Packet padding

Routing blocks, forward SURB slots, user payloads, reply payloads, and unused
fields always occupy their full profile lengths. Plain structural padding is
zero before the applicable cryptographic layer. Random bytes are used only
where the selected reviewed construction requires randomness.

Application code MUST NOT create a shorter KEMSphinx payload. It supplies an
exact fixed-size inner envelope whose internal padding is authenticated by the
owning end-to-end protocol.

### 16.2 Application fragmentation

Messages larger than `user_forward_payload_length` are fragmented by
FOG-MESSAGING or FOG-STORAGE before KEMSphinx construction. Fragment metadata
is inside the end-to-end protected envelope. Each fragment becomes an
independent fixed-size KEMSphinx packet with independent route randomness,
KEM ciphertexts, replay tags, and optional SURB.

KEMSphinx does not retransmit or deduplicate fragments. Reusing a packet for a
retry is forbidden. A retry constructs a fresh packet under the application
protocol's idempotency rules.

### 16.3 FOG-WIRE fragmentation

`PACKET_FORWARD` and the packet portion released from `PACKET_SUBMIT` are one
logical FOG-WIRE message with an exact KEMSphinx packet body. FOG-WIRE may
split that logical message across its fixed DATA records when the active wire
profile requires it.

Wire fragments are link-local and are fully reassembled, bounded, and
authenticated before KEMSphinx parsing. They are never individually queued,
replayed, forwarded, or stored as KEMSphinx packets.

## 17. FOG-WIRE Command Bodies

This document owns these logical command shapes:

```text
PACKET_FORWARD_BODY = kemsphinx_packet[packet_length]

PACKET_RETURN_BODY =
  surb_id[16] ||
  returned_payload[payload_tag_length + forward_payload_length]
```

`PACKET_FORWARD_BODY` is used for entry-to-L1, mix-to-mix, and L3-to-terminal
links. Link context and direction determine which role pair is legal.

`PACKET_RETURN_BODY` is created only after the entry completes the final reply
hop and atomically consumes the return rendezvous. The returned payload remains
protected by the Composer's private reply token. The relay cannot decrypt or
modify it successfully.

`PACKET_SUBMIT_BODY` remains owned by the entry-capsule specification because
it must hide and bind the first internal hop from the blind relay.

A compatible FOG-WIRE command-shape registry has compile-time exact body
limits for these commands. Generic byte-string RPCs are not conforming.

## 18. Candidate Geometry

### 18.1 Status

The following profile is a calculated candidate:

```text
name               = FOG-SPHINX-CANDIDATE-MLKEM768-X25519-1
packet_profile_id  = UNASSIGNED
candidate_status   = geometry-only
```

The candidate name is permanently bound to this calculated geometry. It is
not a complete packet profile because final primitive IDs, dependency
revisions, delay limits, replay lifetime, and conformance vector IDs are still
unresolved. A numeric packet profile ID is assigned only after every field in
section 6 is frozen. It will not reuse an identifier from another candidate.

This candidate MUST NOT be placed in a claim-bearing public consensus until
section 24's activation gates pass.

`FOG-CRYPTO-SUITES.md` admits the exact calculated construction to
complete-packet benchmarking but does not activate it. The unresolved
non-KEM primitives and implementation evidence keep the complete profile
non-active.

No active FOG implementation benchmark exists for this geometry. Every future
implementation MUST enforce the exact path, packet, payload, SURB, encrypted
reply, and reply-key lengths at the FOG boundary before cryptographic
processing. This requirement follows from the profile contract itself, not
from an archived external implementation experiment.

### 18.2 Candidate primitive inputs

The geometry calculation uses:

- KEMSphinx with one KEM ciphertext per hop;
- exactly four hops;
- hybrid `MLKEM768-X25519` built by the maintained HPQC security-preserving
  split-PRF combiner;
- component and ciphertext concatenation order of X25519 hashed-ElGamal KEM
  first, then ML-KEM-768, matching the evaluated HPQC registry despite the
  display name; the split-PRF is order-sensitive;
- ML-KEM-768 ciphertext length of 1,088 bytes;
- X25519 hashed-ElGamal KEM ciphertext length of 32 bytes;
- combined KEM ciphertext length of 1,120 bytes;
- 32-byte packet node IDs;
- 32-byte header MACs;
- 16-byte SURB IDs;
- 32-byte payload integrity tags;
- 48-byte SPRP keys and 16-byte SPRP IVs;
- two additional-data bytes fixed to `0x0000` for compatibility with the
  evaluated maintained KEMSphinx format;
- a two-byte forward plaintext header;
- a 4,096-byte user forward payload;
- one fixed SURB slot in every forward plaintext block.

The primitive suite currently evaluated with this geometry includes the
maintained Katzenpost KDF, header MAC, header stream, and AEZ-based payload
SPRP parameterization. Geometry compatibility does not constitute approval of
that primitive suite. In particular, the exact dependency revisions,
side-channel behavior, licensing, AEZ usage, deterministic vectors, and
independent review remain activation gates.

### 18.3 Exact calculation

```text
KEM_CIPHERTEXT_LENGTH = 1088 + 32
                      = 1120

NEXT_NODE_LENGTH      = 1 + 32 + 32
                      = 65

SURB_REPLY_LENGTH     = 1 + 16
                      = 17

PER_HOP_ROUTING_INFO  = 65 + 17 + 1120
                      = 1202

ROUTING_INFO_LENGTH   = 4 * 1202
                      = 4808

HEADER_LENGTH         = 2 + 1120 + 4808 + 32
                      = 5962

SPRP_KEY_MATERIAL     = 48 + 16
                      = 64

SURB_LENGTH           = 5962 + 32 + 64
                      = 6058

FORWARD_PAYLOAD       = 2 + 6058 + 4096
                      = 10156

PACKET_LENGTH         = 5962 + 32 + 10156
                      = 16150

PRIVATE_REPLY_KEYS    = (4 + 1) * 64
                      = 320
```

### 18.4 Candidate geometry table

| Field | Bytes |
|---|---:|
| Hop count | 4 hops |
| Additional data | 2 |
| KEM ciphertext | 1,120 |
| Per-hop routing information | 1,202 |
| Routing information | 4,808 |
| Header MAC | 32 |
| Header | 5,962 |
| Payload tag | 32 |
| Public SURB | 6,058 |
| Forward plaintext header | 2 |
| User forward payload | 4,096 |
| Forward payload | 10,156 |
| Complete KEMSphinx packet | 16,150 |
| Private reply key material | 320 |

Every arithmetic value is a protocol constant for this candidate. Runtime
configuration cannot change it.

## 19. Key Lifecycle

Every mix and terminal has an independently generated KEMSphinx key for each
authorized packet profile and epoch. Descriptors bind the public key, key ID,
purpose, owner node, role, layer, profile, and validity interval.

Private keys:

- are generated with an approved operating-system randomness source;
- are written only to role-local protected storage;
- are loaded only by the owning role process;
- are never copied to another layer, co-located role, authority, relay, or
  observer;
- are never used for Noise, entry capsules, storage, signatures, or messages;
- remain available only for the exact old-epoch drain interval;
- are erased after packet and replay retirement conditions both hold.

Key generation and persistence are crash-safe. A descriptor is not published
until the private key is durably available to its owner. A node never creates
a fresh private key under an already published key ID.

KEM decapsulation failures, malformed public keys, and component failures in
a hybrid KEM are fatal to that packet. A hybrid implementation must reject
incorrect component lengths before component decapsulation and must not reveal
which component failed.

## 20. Parser and Resource Limits

Before cryptographic work, an implementation enforces:

- exact logical message length;
- exact packet profile from connection context;
- one bounded packet buffer;
- no recursive, compressed, map-based, or self-describing packet data;
- no allocation based on routing commands;
- no profile or algorithm name supplied by the peer;
- per-connection, per-peer, per-key, and global cryptographic work budgets;
- bounded queues and deadlines.

After decryption, it enforces:

- exact command count, type, order, and zero padding;
- one legal next role and layer;
- delay bounds and epoch completion bounds;
- terminal capability length and one-time state;
- exact forward block flags and zero reserved bytes;
- exact payload and SURB lengths.

Memory containing shared secrets, per-hop keys, private reply keys, and
decrypted routing blocks is cleared promptly using the reviewed library's
supported mechanism. Memory clearing is defense in depth and does not replace
process isolation.

## 21. Logging and Observability

Core roles MUST NOT log:

- packet bytes or packet digests;
- raw replay tags or stored replay IDs;
- KEM ciphertexts or shared secrets;
- `surb_id`, SURBs, private reply tokens, or rendezvous capabilities;
- per-packet routes, next-hop IDs, delay values, or fine timing;
- terminal recipient capabilities or user payloads.

Permitted local diagnostics are coarse reason counters, queue occupancy
buckets, bounded latency histograms, replay database health state, and
profile-level totals under FOG-OBSERVER privacy rules. Operators cannot enable
packet tracing on a claim-bearing profile.

Test builds may use deterministic vectors and verbose traces only with public
test keys and synthetic packets. Such builds and keys are rejected by
production configuration.

## 22. SDK Boundary

The stable public SDK exposes typed operations, not raw cryptographic knobs.
It contains at least:

```text
ValidatePacketProfile(profile, consensus_context)
DerivePacketNodeID(profile, node_id)
BuildForwardRoute(consensus, terminal, rng)
BuildReplyRoute(consensus, rendezvous, surb_id, rng)
CreateSURB(profile, epoch, reply_route, rng)
CreateForwardPacket(profile, route, fixed_payload, optional_surb, rng)
CreateReplyPacket(profile, public_surb, fixed_payload)
UnwrapOneHop(profile, epoch_context, private_key, exact_packet)
DecryptSURBReply(private_token, surb_id, exact_returned_payload)
```

The SDK does not expose:

- arbitrary hop counts for core profiles;
- operator-selected algorithms or geometry;
- raw private-key serialization through ordinary application APIs;
- reuse or cloning of SURBs or reply tokens;
- packet parsing without an authenticated profile and epoch context;
- automatic foreign-profile detection;
- application-dependent packet sizes.

SURB and private-token types are move-only or guarded by an atomic persistent
state abstraction. Copyable byte slices are not the primary API.

The same reference codec and vector set are used by Composer, entry, mix,
terminal, relay import, simulator, and conformance tools. Each executable
still imports only the operations needed by its role.

## 23. Conformance Evidence

Each packet profile release includes machine-readable vectors generated with
fixed public test entropy. The vector manifest records dependency versions,
source revision, profile record hash, and generation command.

The required vector set covers:

1. packet node ID derivation;
2. hybrid KEM component key and ciphertext lengths;
3. complete candidate geometry arithmetic;
4. every routing command encoding and invalid tag;
5. zero padding and terminal unused KEM slot;
6. one complete four-hop forward packet at every unwrap;
7. one complete four-hop SURB reply at every unwrap and final decryption;
8. replay tags at each hop and durable duplicate rejection;
9. changed ciphertext, MAC, command, padding, payload tag, and SURB failures;
10. wrong profile, epoch, role, layer, route length, and upstream failures;
11. maximum and invalid delay values;
12. SURB state transitions, double use, expiry, and uncertain send;
13. replay database restart, rollback, corruption, and retirement;
14. FOG-WIRE fragmentation and exact logical body reassembly;
15. profile overlap with separate old and new epoch connections;
16. cross-implementation byte equality.

Property and fuzz tests additionally cover all fixed-offset parsers,
truncations at every byte boundary, trailing bytes, unknown commands, command
reordering, integer boundaries, malformed KEM component lengths, and queue
resource limits.

At least two independently integrated implementations must reproduce the
complete vectors before a claim-bearing profile becomes active. Calling the
same library through two thin wrappers is not independent evidence.

## 24. Activation Gates

`FOG-SPHINX-CANDIDATE-MLKEM768-X25519-1` remains non-active until all of the
following are complete:

- reproducible create, unwrap, SURB, and replay benchmarks on every supported
  hardware class using the exact 16,150-byte geometry; the first older x86-64
  create, unwrap, and SURB baseline is complete, while replay and the remaining
  hardware classes are open;
- memory, queue, storage, and maximum-throughput budgets for every role;
- dependency revision pinning, license review, and reproducible builds;
- review of the ML-KEM-768 implementation and X25519 adapter;
- confirmation that the exact security-preserving combiner meets the
  KEMSphinx hybrid-KEM requirement;
- review of KDF, MAC, stream, payload SPRP, fragile payload-tag construction,
  and side-channel behavior;
- complete deterministic vectors and negative corpus;
- fuzzing and restart-safe replay tests;
- simulator evidence for packet size, delay, cover traffic, n-1, and long-term
  disclosure behavior;
- compatibility verification with the selected maintained KEMSphinx target;
- independent protocol and implementation review;
- a separately documented decision promoting an exact immutable record to
  `alpha` and later `active`.

Failure of a gate produces a newly named geometry candidate or a newly
numbered complete packet profile, as applicable. It does not silently change
this candidate.

## 25. Profile Transitions

A packet-profile transition follows the FOG-PKI preannouncement and bounded
overlap rules.

During overlap:

- new work uses the profile mapped to the new epoch;
- old work drains only on old-epoch FOG-WIRE connections;
- each profile has separate KEM keys, replay-state keys, replay databases,
  queues, command-shape registry, and SDK object types;
- a packet is never converted in place from one profile to another;
- a failure in the new profile does not reactivate the old profile;
- minimum accepted profile state is monotonic.

After hard expiry, old connections close, old packets and SURBs are rejected,
private reply tokens expire, replay state completes its retention period, and
private KEM keys are erased.

Emergency retirement can stop creation and acceptance immediately, but it
cannot downgrade. In-flight delivery may be lost.

## 26. External Sphinx-Family Bridges

Foreign Sphinx, KEMSphinx, Katzenpost, Nym, YAMN, SMTP, NNTP, and other network
formats never enter a core packet parser. A bridge has:

- a separate `fog-bridge-*` executable and service identity;
- separate ports, FOG-WIRE link context, keys, writable state, queues, replay
  domains, metrics, and deployment policy;
- one explicitly named foreign profile and version;
- strict parsing and resource bounds for that profile only;
- no access to Composer plaintext or core private keys;
- a fresh core packet constructed after policy validation, never a header
  reinterpretation or in-place conversion;
- explicit disclosure as a correlation and availability point.

A bridge terminates one network anonymity context and originates another. It
cannot claim end-to-end mix-path unlinkability across the boundary, even when
the application payload remains end-to-end encrypted.

Automatic port sharing, packet sniffing, trial parsing, or silent downgrade
between core and foreign profiles is forbidden.

## 27. Failure Matrix

| Condition | Required behavior |
|---|---|
| Wrong packet length | reject before KEM work |
| Wrong epoch or profile context | reject, no alternate-key trial |
| KEM decapsulation failure | uniform drop |
| Header MAC failure | uniform drop, no durable replay insertion |
| Authenticated duplicate | uniform drop after exact replay lookup |
| Authenticated invalid command | durable replay insert, then uniform drop |
| Invalid next role or layer | durable replay insert, then uniform drop |
| Invalid delay or expired deadline | durable replay insert, then uniform drop |
| Replay commit failure | stop affected profile and epoch processing |
| Replay state missing or rolled back | fail closed until safe restore or new epoch |
| Delay queue overload | profile-defined uniform drop or role shutdown |
| Final payload tag failure | uniform drop, no terminal action |
| Invalid terminal capability | uniform drop after one-time-state rules |
| SURB reused or expired | discard without constructing another reply |
| Private token missing or consumed | discard returned payload |
| Old profile failure during transition | do not fall back or extend expiry |
| Foreign packet on core port | reject before foreign parsing |

Local diagnostics use coarse stable reason classes. Remote peers receive no
fine-grained error response.

## 28. Threat Traceability

| Threat-model concern | Packet-profile response |
|---|---|
| `TM-NET-01` traffic correlation | one fixed geometry, four hops, authenticated delay and cover profile |
| `TM-NET-02` low anonymity set | no packet-format claim; simulator and deployment gates remain required |
| `TM-NET-03` compromised mixes | stratified route, one transform, role validation, purpose-separated keys |
| `TM-NET-04` replay and compulsion | durable per-hop replay, single-use SURBs and rendezvous state |
| `TM-NET-05` tagging | authenticated header and payload processing, uniform terminal failure |
| `TM-NET-06` profile fingerprinting | consensus-selected immutable profiles, no autodetection or negotiation |
| `TM-PKI-02` stale consensus | epoch-bound packet context and monotonic transition state |
| `TM-ROLE-01` role collapse | separate KEM keys, replay state, ports, commands, and bridge processes |
| `TM-ROLE-02` relay knowledge | entry capsule hides L1; relay handles only opaque fixed packets |
| `TM-ROLE-03` terminal exposure | terminal gets fixed opaque application payload, not source address or full route |
| `TM-CRYPTO-01` primitive misuse | reviewed parameterized library, immutable suite record, activation gates |
| `TM-CRYPTO-02` key reuse | role, purpose, profile, and epoch separation |
| `TM-AVAIL-01` resource exhaustion | fixed buffers, crypto budgets, durable replay, bounded queues and failures |

## 29. Residual Risks

Even a conforming implementation remains exposed to:

- traffic analysis from timing, volume, routes, endpoints, and sparse use;
- n-1 and active-delay attacks by sufficiently placed malicious nodes;
- denial of service through connection, KEM, replay-store, queue, or terminal
  exhaustion within residual budgets;
- endpoint or Composer compromise;
- malicious or colluding entry, mix, terminal, relay, storage, and authority
  operators within the threat model's residual cases;
- reply tagging and compulsion not eliminated by SURBs;
- implementation, dependency, side-channel, randomness, and erasure defects;
- correlation introduced by external bridges;
- loss caused by fail-closed replay and single-use reply behavior;
- incorrect anonymity conclusions from a local PoC or low-traffic network.

These risks must appear in deployment documentation and public claims.

## 30. References

- George Danezis and Ian Goldberg, *Sphinx: A Compact and Provably Secure Mix
  Format*, 2009: <https://eprint.iacr.org/2008/475>
- Katzenpost, *The KEMSphinx Cryptographic Packet Format*:
  <https://katzenpost.network/docs/specs/kemsphinx/>
- Katzenpost, *The Sphinx Cryptographic Packet Format*:
  <https://katzenpost.network/docs/specs/sphinx/>
- Katzenpost, *The Katzenpost Mix Network Wire Protocol*:
  <https://katzenpost.network/docs/specs/mixnet/>
- Katzenpost, *Katzenpost Mix Network Replay Detection*:
  <https://katzenpost.network/docs/specs/packet_replay/>
- Federico Giacon, Felix Heuer, and Bertram Poettering, *KEM Combiners*,
  2018: <https://eprint.iacr.org/2018/024>
- NIST, *FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard*,
  2024: <https://csrc.nist.gov/pubs/fips/203/final>
- RFC 7748, *Elliptic Curves for Security*:
  <https://www.rfc-editor.org/rfc/rfc7748>
- FOG Composer protocol: `FOG-COMPOSER.md`
- FOG cryptographic suite evaluation: `FOG-CRYPTO-SUITES.md`
- FOG traffic and topology simulation: `FOG-SIMULATION.md`

## 31. Completion Checklist

Before implementation work for an active packet profile begins, reviewers
must be able to answer yes to all of these questions:

- Is one immutable profile selected by authenticated consensus and epoch
  context without packet guessing?
- Are both directions exactly four KEMSphinx hops with the entry outside the
  forward route?
- Are packet, header, routing, payload, SURB, and reply-token sizes proven by
  arithmetic and tests?
- Are command sequences exact, padded, role-valid, and parser-bounded?
- Is replay state durable before forwarding and fail-closed after rollback or
  loss?
- Are SURBs, private tokens, IDs, and rendezvous capabilities atomic and
  single-use?
- Is application fragmentation inside end-to-end protection and independent
  of packet size?
- Are KEM, replay, Noise, entry, storage, and message keys purpose-separated?
- Do FOG-WIRE command shapes and packet geometry agree exactly?
- Are complete vectors, fuzzing, benchmarks, simulation, and independent
  review available for the exact dependency set?
- Are external formats isolated in separate bridge processes and disclosed as
  correlation points?
- Do failures stop safely without topology, profile, or plaintext fallback?