You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -222,7 +222,7 @@ These are genuine gaps that should be addressed before production use. They are
222
222
223
223
**Mode 2 tile server not implemented.** The SDK verifier accepts Mode 2 payloads without verifying Merkle inclusion. A complete Mode 2 deployment requires a tile server serving proof tiles at `GET /tile/{level}/{index}` and a scanner-side tile-fetching verifier that calls it. The protocol for tile format and addressing is not yet defined in `SPEC.md`.
224
224
225
-
**Revocation not implemented.** The verifier emits a `revocation check` step with a fixed "not implemented — no revocation list defined yet" message in all four implementations. The revocation protocol is now fully specified in SPEC.md §Revocation (Bloom filter cascade over revoked/valid index sets, signed with the issuer checkpoint key, distributed at `GET /revoked` on the charge-cycle schedule, modeled on CRLite). Implementation is the primary remaining SDK task before production use.
225
+
**Revocation not implemented.** The verifier emits a `revocation check` step with a fixed "not implemented — no revocation list defined yet" message in all four implementations. The revocation protocol is now fully specified in SPEC.md §Revocation (Bloom filter cascade over revoked/valid index sets, signed with the issuer checkpoint key, distributed at `GET /revoked` on the charge-cycle schedule, inspired by CRLite). Implementation is the primary remaining SDK task before production use.
226
226
227
227
**Mode 0 (embedded checkpoint) not implemented.** The payload codec defines `mode=0` where the cosigned checkpoint (root hash + issuer signature + witness cosignatures) is embedded directly in the payload. This eliminates the checkpoint fetch at scan time but still requires a pre-loaded trust configuration — the embedded signatures are verified against the issuer and witness public keys in that config. The issuer and verifier in all four SDKs handle only modes 1 and 2.
Copy file name to clipboardExpand all lines: SPEC.md
+71-27Lines changed: 71 additions & 27 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1206,10 +1206,13 @@ The filter cascade encodes two disjoint sets derived from the log:
1206
1206
The universe is indices 1 through `tree_size - 1`. Index 0 (null entry) is
1207
1207
always excluded from both sets.
1208
1208
1209
-
A Bloom filter cascade over (R, S) produces a structure with zero false negatives
1210
-
and a configurable false positive rate. The false positive direction is deliberately
1211
-
conservative: a false positive causes the verifier to incorrectly reject a valid
1212
-
assertion, but never to incorrectly accept a revoked one.
1209
+
A Bloom filter cascade over (R, S) has the following properties as a data structure:
1210
+
zero false negatives (an element in R is always reported as revoked) and a configurable
1211
+
false positive rate (an element in S may be reported as revoked). The false positive
1212
+
direction is deliberately conservative. Note that "zero false negatives" is a property
1213
+
of the cascade given a correctly populated R — it is not an end-to-end system guarantee.
1214
+
An issuer that omits an entry from R produces a validly signed artifact that correctly
1215
+
reports that entry as not revoked. See §Security Model — What the Protocol Does Not Guarantee.
1213
1216
1214
1217
### Security Model
1215
1218
@@ -1361,10 +1364,16 @@ if artifact.tree_size < verifier.latest_checkpoint_tree_size(origin):
1361
1364
reject artifact as stale
1362
1365
```
1363
1366
1364
-
`REVOCATION_STALE_THRESHOLD` is a deployment parameter. Recommended value:
1365
-
`2 * BATCH_SIZE` (32 for the reference implementation). This allows the issuer
1366
-
one full batch publication cycle of slack before the artifact is considered
1367
-
dangerously stale.
1367
+
`REVOCATION_STALE_THRESHOLD` is a deployment parameter. The recommended
1368
+
default is `2 * BATCH_SIZE` (32 for the reference implementation).
1369
+
1370
+
**Important:** this threshold is entry-count-based, not time-based. Its
1371
+
meaning depends on the issuer's issuance rate. A transit system issuing
1372
+
10,000 credentials per day has a 32-entry window representing minutes; a
1373
+
low-volume issuer issuing 100 credentials per month has a 32-entry window
1374
+
representing weeks. Deployments MUST calibrate this parameter to their
1375
+
issuance rate. A future version may add a timestamp to the artifact body
1376
+
to enable time-based staleness checks independent of issuance rate.
1368
1377
1369
1378
Issuers MUST publish a fresh revocation artifact every time they publish a new
1370
1379
checkpoint. An issuer that publishes checkpoints without updating the revocation
@@ -1409,6 +1418,16 @@ unsigned integer for hashing. No other encoding is permitted.
1409
1418
**Hash function.** SHA-256. Given an element `x` (8-byte big-endian uint64) and
1410
1419
hash function index `i` (0-indexed), the j-th bit position in a filter of `m` bits is:
1411
1420
1421
+
*(Note: conventional Bloom filter implementations use non-cryptographic hash
1422
+
functions such as MurmurHash3 for performance. SHA-256 is used here because it
1423
+
is universally available across all target platforms without additional
1424
+
dependencies, is already present in every MTA-QR implementation for Merkle tree
1425
+
and checkpoint operations, and at MTA-QR deployment scales the performance
1426
+
difference is negligible — building a cascade over 1,000 revoked entries requires
1427
+
fewer than 10,000 SHA-256 calls, completing in under 1ms on any modern hardware.
1428
+
Interoperability is the primary concern; a non-cryptographic hash that varies
1429
+
between implementations would silently break cross-language cascade compatibility.)*
1430
+
1412
1431
```
1413
1432
bit_position(x, i) = (big_endian_uint64(SHA-256(x || uint8(i))[0:8])) mod m
1414
1433
```
@@ -1458,7 +1477,11 @@ function BuildCascade(R, S):
1458
1477
if bits[bit] == 1:
1459
1478
new_fp_set.add(x)
1460
1479
1461
-
// Next level: encode the false positives to eliminate them
1480
+
// Next level: encode the false positives to eliminate them.
1481
+
// Invariant: include_set is the set this level encodes.
1482
+
// current_fp_set is the set whose false positives define the next level.
1483
+
// Both are new_fp_set because Level N+1 encodes exactly the false
1484
+
// positives of Level N — elements of S that made it through Level N.
1462
1485
include_set = new_fp_set
1463
1486
current_fp_set = new_fp_set
1464
1487
level_index += 1
@@ -1504,10 +1527,12 @@ The cascade is serialized as follows. All integer fields are big-endian.
1504
1527
uint8 num_levels (number of levels, 0..255)
1505
1528
for each level (index 0..num_levels-1):
1506
1529
uint32 bit_count (number of bits in this level's filter)
1507
-
uint8 k (number of hash functions; always 1 in this spec)
1530
+
uint8 k (number of hash functions; MUST be 1; reject if not 1)
1508
1531
ceil(bit_count/8) bytes bit_array (MSB of first byte = bit 0)
1509
1532
```
1510
1533
1534
+
Verifiers MUST reject any cascade where `k != 1` at any level. A parser that uses the `k` value to determine how many hash functions to apply will compute different bit positions than intended, silently producing wrong query results without any parse error. The field is included in the format to preserve extensibility, but `k = 1` is the only valid value in this version.
1535
+
1511
1536
Bit indexing: bit `i` of the filter is stored in byte `i/8` at bit position
1512
1537
`7 - (i mod 8)` (MSB-first within each byte). Bit 0 is the most significant
1513
1538
bit of byte 0.
@@ -1536,20 +1561,21 @@ as a checkpoint. The body is what the issuer signature covers.
1536
1561
```
1537
1562
<origin>\n
1538
1563
<tree_size decimal>\n
1539
-
crlite-v1\n
1564
+
mta-qr-revocation-v1\n
1540
1565
<base64(cascade_bytes)>\n
1541
1566
```
1542
1567
1543
-
The `crlite-v1` literal is the artifact type identifier. Verifiers MUST reject
1544
-
artifacts with unrecognized type identifiers. `cascade_bytes` is the binary
1568
+
The `mta-qr-revocation-v1` literal is the artifact type identifier, specific to
1569
+
MTA-QR. This format is not backward-compatible with CRLite artifacts. Verifiers
1570
+
MUST reject artifacts with unrecognized type identifiers. `cascade_bytes` is the binary
1545
1571
encoding defined above, base64-encoded per RFC 4648 §4 (standard alphabet,
- Mozilla CRLite reference implementation (MPL-2.0):
1728
+
- Mozilla CRLite reference implementation — filter cascade construction reference
1729
+
(MPL-2.0, binary format not compatible with MTA-QR):
1687
1730
https://github.com/mozilla/crlite
1688
-
- Mozilla rust-cascade library (MPL-2.0):
1731
+
- Mozilla rust-cascade library — cascade algorithm reference
1732
+
(MPL-2.0, binary format not compatible with MTA-QR):
1689
1733
https://github.com/mozilla/rust-cascade
1690
1734
1691
1735
---
@@ -1873,7 +1917,7 @@ Expected:
1873
1917
| R-REJ-2 | Valid signature, `bit_count = 0` at any level | Discard: malformed artifact |
1874
1918
| R-REJ-3 | Signature over correct body, but signed by wrong key | Discard: signature verification failure |
1875
1919
| R-REJ-4 | Artifact body `origin` does not match expected origin | Discard: origin mismatch |
1876
-
| R-REJ-5 | Artifact body `artifact_type` is `crlite-v2` (unrecognized) | Discard: unknown type |
1920
+
| R-REJ-5 | Artifact body `artifact_type` is `mta-qr-revocation-v2` (unrecognized) | Discard: unknown type |
1877
1921
| R-REJ-6 | Artifact `tree_size` is 0 | Discard: malformed (tree_size must be ≥ 1) |
1878
1922
| R-REJ-7 | Valid artifact, but `artifact.tree_size` is more than `REVOCATION_STALE_THRESHOLD` behind verifier's checkpoint `tree_size`| Discard: stale artifact |
1879
1923
| R-REJ-8 | Artifact body has only 3 lines (missing `filter_bytes_base64` line) | Discard: parse error |
0 commit comments