From 14d31e0426656c5208f2b6579a5217af3a6bb2bd Mon Sep 17 00:00:00 2001 From: TimeLordRaps Date: Sun, 23 Aug 2026 05:23:22 -0700 Subject: [PATCH 01/32] Add zero-knowledge proof experiment --- ROUND1_ZERO_KNOWLEDGE_REPORT.md | 210 + .../zizk_vstd/zero_knowledge/.gitignore | 4 + .../zero_knowledge/CLAIM_BOUNDARY.md | 46 + .../zizk_vstd/zero_knowledge/Cargo.lock | 3674 +++++++++++++++++ .../zizk_vstd/zero_knowledge/Cargo.toml | 11 + .../zizk_vstd/zero_knowledge/README.md | 147 + .../zizk_vstd/zero_knowledge/THREAT_MODEL.md | 90 + .../zero_knowledge/fixtures/README.md | 23 + .../zizk_vstd/zero_knowledge/host/Cargo.toml | 15 + .../zizk_vstd/zero_knowledge/host/src/main.rs | 454 ++ .../zero_knowledge/methods/Cargo.toml | 11 + .../zizk_vstd/zero_knowledge/methods/build.rs | 3 + .../zero_knowledge/methods/guest/Cargo.lock | 1485 +++++++ .../zero_knowledge/methods/guest/Cargo.toml | 11 + .../zero_knowledge/methods/guest/src/main.rs | 69 + .../zero_knowledge/methods/src/lib.rs | 1 + .../zero_knowledge/rust-toolchain.toml | 4 + .../zero_knowledge/scripts/run_real_proof.sh | 13 + .../zizk_vstd/zero_knowledge/types/Cargo.toml | 8 + .../zizk_vstd/zero_knowledge/types/src/lib.rs | 60 + tests/test_zizk_zero_knowledge_experiment.py | 47 + 21 files changed, 6386 insertions(+) create mode 100644 ROUND1_ZERO_KNOWLEDGE_REPORT.md create mode 100644 experiments/zizk_vstd/zero_knowledge/.gitignore create mode 100644 experiments/zizk_vstd/zero_knowledge/CLAIM_BOUNDARY.md create mode 100644 experiments/zizk_vstd/zero_knowledge/Cargo.lock create mode 100644 experiments/zizk_vstd/zero_knowledge/Cargo.toml create mode 100644 experiments/zizk_vstd/zero_knowledge/README.md create mode 100644 experiments/zizk_vstd/zero_knowledge/THREAT_MODEL.md create mode 100644 experiments/zizk_vstd/zero_knowledge/fixtures/README.md create mode 100644 experiments/zizk_vstd/zero_knowledge/host/Cargo.toml create mode 100644 experiments/zizk_vstd/zero_knowledge/host/src/main.rs create mode 100644 experiments/zizk_vstd/zero_knowledge/methods/Cargo.toml create mode 100644 experiments/zizk_vstd/zero_knowledge/methods/build.rs create mode 100644 experiments/zizk_vstd/zero_knowledge/methods/guest/Cargo.lock create mode 100644 experiments/zizk_vstd/zero_knowledge/methods/guest/Cargo.toml create mode 100644 experiments/zizk_vstd/zero_knowledge/methods/guest/src/main.rs create mode 100644 experiments/zizk_vstd/zero_knowledge/methods/src/lib.rs create mode 100644 experiments/zizk_vstd/zero_knowledge/rust-toolchain.toml create mode 100755 experiments/zizk_vstd/zero_knowledge/scripts/run_real_proof.sh create mode 100644 experiments/zizk_vstd/zero_knowledge/types/Cargo.toml create mode 100644 experiments/zizk_vstd/zero_knowledge/types/src/lib.rs create mode 100644 tests/test_zizk_zero_knowledge_experiment.py diff --git a/ROUND1_ZERO_KNOWLEDGE_REPORT.md b/ROUND1_ZERO_KNOWLEDGE_REPORT.md new file mode 100644 index 0000000..44b2c22 --- /dev/null +++ b/ROUND1_ZERO_KNOWLEDGE_REPORT.md @@ -0,0 +1,210 @@ +# Round 1 zero-knowledge experiment report + +**Date:** 2026-08-23 +**Status:** completed local experiment; not published or merged + +## Repository coordinates + +- Repository: `TimeLordRaps/verifier` +- Immutable base: `598c545be3833d6d81bb7e252ca5837f3bb2a449` +- Branch: `codex/zizk-zero-knowledge` +- Worktree: isolated worktree named `zizk-zk-codex`; its machine-specific absolute + path is intentionally excluded from this public report +- Primary worktree modified: no +- Existing frozen wire identifiers modified: no +- Push, pull request, merge, tag, release, or publication performed: no + +## Selected proof system + +Exactly one proof system was selected and used: + +| Coordinate | Value | +|---|---| +| SDK and verifier | RISC Zero zkVM `3.0.6` | +| Receipt kind | composite STARK | +| Program trust coordinate | RISC Zero image ID | +| Image ID | `e1e9bf4f68ef60ff9af6b50e144082bc475cc20cab47e8187201153da597dcd8` | +| Tool manager | `rzup 0.5.0` | +| Prover executable | `r0vm 3.0.6` | +| Guest build tool | `cargo-risczero 3.0.6` | +| Guest Rust toolchain | `rustc 1.97.0-dev` | +| Tested platform | Linux x86-64 under WSL2 | +| Trusted setup | transparent STARK setup; no experiment-specific ceremony | + +The official installer script used in the local environment had SHA-256 +`5699878af779351ec0f931fa84c3d5e35263279f66bd915af225f530a77341bf`. +The experiment pins every direct Rust dependency and commits both host and guest lock +files: + +- workspace `Cargo.lock`: `9b6f1a739c2acbe01581828fa37691af7288adb4642d158cb5f6a7383470483d` +- guest `Cargo.lock`: `1c1ef45133eb24090dfc136a479c1e007b0d2a6bab9b9ae0954a25f03dea27e9` + +No alternative proof system was attempted. + +## Selection basis + +RISC Zero was selected because its official 3.0 documentation supports local real-proof +generation on x86-64 Linux, describes `Receipt` as a zero-knowledge proof of execution, +binds verification to an image ID and authenticated journal, and provides a transparent +STARK path. The local environment had more than the documented 16 GB minimum RAM. + +The host crate compiles with `disable-dev-mode`, rejects `InnerReceipt::Fake`, requires +the selected `Composite` receipt variant, and rejects a truthy `RISC0_DEV_MODE` value. + +## Proved predicate + +The private witness contains: + +- one to 64 evidence bytes; +- a private 32-byte salt; +- a private measurement; and +- an experiment-local candidate state. + +The fixed guest accepts only an experiment-local `Supported` candidate state and a +measurement at least as large as the public threshold. It commits an authenticated public +journal containing the exact profile and predicate digests, subject digest, policy +digest, challenge, threshold, salted evidence commitment, and satisfied result. + +The proof does not establish whether the private input was truthful or whether the +`Supported` tag was assigned correctly. + +## Completeness, soundness, and zero-knowledge basis + +### Completeness + +One satisfying input produced a receipt that verified against the expected image ID and +authenticated journal. This is direct implementation evidence for the tested program and +environment, not a general proof about every possible input or platform. + +### Soundness + +The soundness basis is the selected RISC Zero STARK construction and its published +analysis, including the Fiat-Shamir transformation and documented hash assumptions. The +negative tests below provide implementation-level falsification attempts; they do not +replace the cryptographic analysis or an independent audit. + +### Zero knowledge + +The zero-knowledge basis is the RISC Zero protocol and verified non-fake receipt, which +hide guest execution inputs while exposing the journal. The exact private evidence and +salt byte strings were additionally scanned against every generated public artifact and +were absent. That byte scan checks this serializer path only; absence from files alone is +not a proof of zero knowledge. + +## Commands and observed results + +Toolchain and build: + +```text +rzup show +cargo-risczero 3.0.6; r0vm 3.0.6; rust 1.97.0 + +cargo check --locked --workspace +PASS + +cargo build --locked --release -p vstd-zk-host +PASS +``` + +Real proof plus automated negative cases: + +```text +RISC0_DEV_MODE=0 vstd-zk-host self-test local-artifacts/recorded-final +PASS +elapsed wall time: 6.10 seconds +maximum resident set: 1,214,664 KiB +``` + +Independent verifier invocation without the witness: + +```text +RISC0_DEV_MODE=0 vstd-zk-host verify receipt.msgpack public.json +PASS +elapsed wall time: 0.10 seconds +maximum resident set: 5,632 KiB +``` + +Repository validation: + +```text +python -m pytest -q +258 passed, 3 skipped + +python scripts/check_presentation.py +[PRESENTATION OK] links, versions, boundaries, paths, and visual assets + +python -m compileall -q src scripts +PASS +``` + +The three guest panic messages printed during self-test are the expected rejection paths +for below-threshold, `Unknown`, and `Conflicted` inputs. They do not contain witness bytes. + +## Negative-test results + +| Test | Result | +|---|---| +| valid proof and matching public inputs | pass | +| below-threshold private measurement | rejected | +| experiment-local `Unknown` input | rejected | +| experiment-local `Conflicted` input | rejected | +| mutated public threshold | rejected | +| wrong image ID | rejected | +| corrupted proof bytes | rejected | +| tampered authenticated journal | rejected | +| subject and challenge transplantation | rejected | +| private evidence or salt copied to public artifacts | not detected; test passed | + +All ten recorded Boolean checks were `true`. + +## Recorded local artifacts + +The generated proof files are ignored and are not proposed for version control. Their +coordinates make this run identifiable without publishing its ephemeral private witness. + +| Artifact | Bytes | SHA-256 | +|---|---:|---| +| `receipt.msgpack` | 301811 | `5fd33b0fbf6b54e34d4dd19c5ff068a8f82bacacc21881b5fa2cc5c0a90090df` | +| `public.json` | 2575 | `6324c3c5d77ea4df4034f61131059289d5228f190d69e34c59bd7416fa9ac823` | +| `self-test-results.json` | 377 | `e4c1bff21fb6161221276157fa96af6661af8635da35970ba12e462881f2c6fe` | +| `corrupted-receipt.msgpack` | 301811 | `389117e63a429e55c3f3616b9cbf2339fb1c99b48712ef7a3e5f5f16b32b6d81` | +| `tampered-journal.msgpack` | 301811 | `4e443f5084b8665a7185e6e4e62fd72eed5130673b3d7bfde488cdc6c405555c` | +| `mutated-public.json` | 2575 | `c2a056b71b2019daa8ac9f3aefcb4c2cc28a1b56ef97f8c61947bf37c5f2b7b9` | +| `transplanted-public.json` | 2575 | `95cf0d97e20777e2ad49234b7b27074dc8931ee7de215632a7d22a6b5466f2b6` | + +## Unresolved assumptions + +- The selected cryptographic implementation and transitive dependencies were not + independently audited in this work. +- The image ID was produced once in this environment; a second independent build has not + yet corroborated it. +- The host, compiler, installer, and operating system remain trusted for witness secrecy. +- The experiment does not establish constant-time or side-channel-resistant proving. +- The challenge is cryptographically bound, but challenge issuance, expiry, uniqueness, + and replay storage are external. +- Salt quality is generated from the host operating-system RNG but is not itself proved. +- The public subject and policy digests need external resolution and provenance rules. +- A private `Supported` tag is merely an input to this predicate, not independently + established VSTD evidence. + +## Public claims currently justified + +The local evidence justifies saying that an optional RISC Zero 3.0.6 experiment produced +and independently re-verified within the reference program a real composite STARK receipt +for one bounded hidden-witness predicate, with the recorded negative cases rejected. + +It also supports keeping VSTD core disclosure-neutral: this result demonstrates one +optional privacy mechanism without requiring or invalidating full-disclosure receipts. + +## Claims still prohibited + +Do not claim that this experiment proves: + +- real-world truth, completeness, provenance, authorization, independence, identity, + uniqueness, freshness, revocation, or legal compliance; +- protection against a malicious or compromised prover host; +- general zero-knowledge support for every VSTD predicate; +- independent implementation, third-party audit, external adoption, or production + readiness; +- a frozen `ZIZK-VSTD` wire profile; or +- that zero knowledge should be mandatory for VSTD. diff --git a/experiments/zizk_vstd/zero_knowledge/.gitignore b/experiments/zizk_vstd/zero_knowledge/.gitignore new file mode 100644 index 0000000..279e18f --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/.gitignore @@ -0,0 +1,4 @@ +target/ +local-artifacts/ +private-witness.json +private-*.json diff --git a/experiments/zizk_vstd/zero_knowledge/CLAIM_BOUNDARY.md b/experiments/zizk_vstd/zero_knowledge/CLAIM_BOUNDARY.md new file mode 100644 index 0000000..0635724 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/CLAIM_BOUNDARY.md @@ -0,0 +1,46 @@ +# Claim boundary + +## Permitted claim after the recorded real-proof run + +This optional experiment demonstrates that RISC Zero zkVM 3.0.6 can produce a real, +locally verified zero-knowledge receipt for one fixed bounded predicate, while keeping +the experiment's private witness out of the serialized public artifact package. + +The concrete verified statement is: + +> The program identified by the expected image ID halted successfully and authenticated +> a journal stating that its private encoded input satisfied the fixed experiment +> predicate and was bound to the journal's subject, policy, challenge, threshold, and +> salted evidence commitment. + +The zero-knowledge basis is the selected protocol and implementation, not merely the +absence of witness text from JSON. The artifact scan is an additional serialization +check, not a proof of zero knowledge. + +## Prohibited claims + +The experiment does not prove: + +- that the hidden evidence is true, complete, authentic, fresh, or lawfully obtained; +- that its producer is authorized, unique, independent, honest, or non-revoked; +- that the private `Supported` tag was assigned correctly; +- that the subject or policy digest resolves to trustworthy external content; +- freshness beyond possession of the journal's challenge; +- prevention of replay for the same challenge; +- host confidentiality, constant-time behavior, or side-channel resistance; +- security of every RISC Zero component or transitive dependency; +- independent implementation or external adoption; +- VSTD conformance for this experiment; or +- that VSTD should require zero knowledge for full-disclosure receipts. + +An `Unknown` or `Conflicted` experiment input is rejected by this particular predicate. +That rejection does not turn uncertainty into falsity, and it never upgrades either +state into a clean result. Other VSTD mechanisms must continue to preserve `UNKNOWN` and +`CONFLICTED` when those are the evidence-supported outcomes. + +## Architecture consequence + +The strongest architecture justified by this experiment is an optional, proof-system- +identified privacy profile adjacent to disclosure-neutral VSTD core behavior. No frozen +wire identifier, schema, canonical digest, lifecycle token, console alias, or existing +receipt interpretation changes. diff --git a/experiments/zizk_vstd/zero_knowledge/Cargo.lock b/experiments/zizk_vstd/zero_knowledge/Cargo.lock new file mode 100644 index 0000000..6490386 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/Cargo.lock @@ -0,0 +1,3674 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-r1cs-std", + "ark-std", +] + +[[package]] +name = "ark-crypto-primitives" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0c292754729c8a190e50414fd1a37093c786c709899f29c9f7daccecfa855e" +dependencies = [ + "ahash", + "ark-crypto-primitives-macros", + "ark-ec", + "ark-ff", + "ark-relations", + "ark-serialize", + "ark-snark", + "ark-std", + "blake2", + "derivative", + "digest", + "fnv", + "merlin", + "sha2", +] + +[[package]] +name = "ark-crypto-primitives-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e89fe77d1f0f4fe5b96dfc940923d88d17b6a773808124f21e764dfb063c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "arrayvec", + "digest", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-groth16" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88f1d0f3a534bb54188b8dcc104307db6c56cdae574ddc3212aec0625740fc7e" +dependencies = [ + "ark-crypto-primitives", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-relations", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-r1cs-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-relations", + "ark-std", + "educe", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" +dependencies = [ + "ark-ff", + "ark-std", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "arrayvec", + "digest", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-snark" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d368e2848c2d4c129ce7679a7d0d2d612b6a274d3ea6a13bad4445d61b381b88" +dependencies = [ + "ark-ff", + "ark-relations", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bonsai-sdk" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fc4edab3bb401344292b3de527d15663b6bbcba76d98485d96b1bd3061c7987" +dependencies = [ + "duplicate", + "maybe-async", + "reqwest", + "serde", + "thiserror", +] + +[[package]] +name = "borsh" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f347189c62a579b8cd5f80714efa178f52e461dc2e6d701d264f5ff22e566c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "camino" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.119", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "docker-generate" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf673e0848ef09fa4aeeba78e681cf651c0c7d35f76ee38cec8e55bc32fa111" + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "duplicate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e92f10a49176cbffacaedabfaa11d51db1ea0f80a83c26e1873b43cd1742c24" +dependencies = [ + "heck", + "proc-macro2", + "proc-macro2-diagnostics", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-ordinalize" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "include_bytes_aligned" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee796ad498c8d9a1d68e477df8f754ed784ef875de1414ebdaf169f70a6a784" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "lazy-regex" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4994ba703f78b083e2f7946dac9251abd83fd43a0365f030e99b69be5b4b9ef9" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd97232314824e6dbef1918a871bb93f51070455e3715bf26e19a6d01aa977a0" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.119", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "maybe-async" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.13.1", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "no_std_strings" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e" + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.13+spec-1.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "version_check", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bitflags 2.13.1", + "num-traits", + "rand 0.9.5", + "rand_chacha 0.9.0", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "risc0-binfmt" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d836c6ad82f4ced7c61d5feedf905a17780312e393aa681d29cc0bbc5131672b" +dependencies = [ + "anyhow", + "borsh", + "bytemuck", + "derive_more", + "elf", + "lazy_static", + "postcard", + "rand 0.9.5", + "risc0-zkp", + "risc0-zkvm-platform", + "ruint", + "semver", + "serde", + "tracing", +] + +[[package]] +name = "risc0-build" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8216cdd9f573808a94769767480b06ad1e74ae60841c9582fdf51b8e29ba53" +dependencies = [ + "anyhow", + "cargo_metadata", + "derive_builder", + "dirs", + "docker-generate", + "hex", + "risc0-binfmt", + "risc0-zkos-v1compat", + "risc0-zkp", + "risc0-zkvm-platform", + "rzup", + "semver", + "serde", + "serde_json", + "stability", + "tempfile", +] + +[[package]] +name = "risc0-circuit-keccak" +version = "4.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c731e12429eb4457e1ddc69c56ee7343a1e10b86e4aa55bc8f4d2b13734abb9" +dependencies = [ + "anyhow", + "bytemuck", + "paste", + "risc0-binfmt", + "risc0-circuit-recursion", + "risc0-core", + "risc0-zkp", + "tracing", +] + +[[package]] +name = "risc0-circuit-recursion" +version = "4.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40dd640122abcc67d4d4e4f055c68cbc3ad2efb8589c65c2b23d354632971b60" +dependencies = [ + "anyhow", + "bytemuck", + "hex", + "metal", + "risc0-core", + "risc0-zkp", + "tracing", +] + +[[package]] +name = "risc0-circuit-rv32im" +version = "4.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb11231aa4b74bcc0c8d16597893fbd7ea6f6a9ebbc35e16bfd06b467c7ee104" +dependencies = [ + "anyhow", + "bit-vec", + "bytemuck", + "derive_more", + "paste", + "risc0-binfmt", + "risc0-core", + "risc0-zkp", + "serde", + "tracing", +] + +[[package]] +name = "risc0-core" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6eb2d2b2c6cac0e43cbb2202daacee1a2f24d0dfa03fd08887a11dc6defdcc1" +dependencies = [ + "bytemuck", + "rand_core 0.9.5", +] + +[[package]] +name = "risc0-groth16" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0ca702ea7d0162766defe7ed6a79bda4a747ad9e2684000a6edd14df0a6d1f3" +dependencies = [ + "anyhow", + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-groth16", + "ark-serialize", + "bytemuck", + "hex", + "num-bigint", + "num-traits", + "risc0-binfmt", + "risc0-zkp", + "serde", +] + +[[package]] +name = "risc0-zkos-v1compat" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b0b598ba7946354b10ca5c56e382de801e6c7fce9fccad0396ec436bc5072b" +dependencies = [ + "include_bytes_aligned", + "no_std_strings", + "risc0-zkvm-platform", +] + +[[package]] +name = "risc0-zkp" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21c0c921e5e2d44197940d387a45e29c6165e318b5a168fdfdbd50f50ba03678" +dependencies = [ + "anyhow", + "blake2", + "borsh", + "bytemuck", + "cfg-if", + "digest", + "hex", + "hex-literal", + "metal", + "paste", + "rand_core 0.9.5", + "risc0-core", + "risc0-zkvm-platform", + "serde", + "sha2", + "stability", + "tracing", +] + +[[package]] +name = "risc0-zkvm" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d4f24ec767f71a1663a4d24cf9d02b6bfee44c64647cae677227817051007a" +dependencies = [ + "anyhow", + "bincode", + "bonsai-sdk", + "borsh", + "bytemuck", + "bytes", + "derive_more", + "hex", + "lazy-regex", + "prost", + "risc0-binfmt", + "risc0-build", + "risc0-circuit-keccak", + "risc0-circuit-recursion", + "risc0-circuit-rv32im", + "risc0-core", + "risc0-groth16", + "risc0-zkos-v1compat", + "risc0-zkp", + "risc0-zkvm-platform", + "rrs-lib", + "rzup", + "semver", + "serde", + "sha2", + "stability", + "tempfile", + "tracing", +] + +[[package]] +name = "risc0-zkvm-platform" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb37a97ff7e8e4ee1b2a1c43ec143b4887759883c343507af9e4787a57914cd" +dependencies = [ + "bytemuck", + "cfg-if", + "getrandom 0.2.17", + "getrandom 0.3.4", + "libm", + "num_enum", + "paste", + "stability", +] + +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "rrs-lib" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4382d3af3a4ebdae7f64ba6edd9114fff92c89808004c4943b393377a25d001" +dependencies = [ + "downcast-rs", + "paste", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "ruint" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e99bff0393163bb25029a6af25d3d8d202ba5b5438a74d1bd8789f5c822970" +dependencies = [ + "borsh", + "proptest", + "rand 0.8.5", + "rand 0.9.5", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "rzup" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96909a7ea8fdf7e18da727d7facbc43eea8a4f77635e7ec75a69794dede16fb6" +dependencies = [ + "hex", + "rsa", + "semver", + "serde", + "serde_with", + "sha2", + "strum", + "tempfile", + "thiserror", + "toml", + "yaml-rust2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "jiff", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.1", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vstd-zk-host" +version = "0.1.0" +dependencies = [ + "hex", + "rand 0.8.5", + "risc0-zkvm", + "rmp-serde", + "serde", + "serde_json", + "vstd-zk-methods", + "vstd-zk-types", +] + +[[package]] +name = "vstd-zk-methods" +version = "0.1.0" +dependencies = [ + "risc0-build", +] + +[[package]] +name = "vstd-zk-types" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yaml-rust2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] diff --git a/experiments/zizk_vstd/zero_knowledge/Cargo.toml b/experiments/zizk_vstd/zero_knowledge/Cargo.toml new file mode 100644 index 0000000..8f57aea --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/Cargo.toml @@ -0,0 +1,11 @@ +[workspace] +resolver = "2" +members = ["host", "methods", "types"] + +# RISC Zero guest builds are prohibitively slow without optimization. +[profile.dev] +opt-level = 3 + +[profile.release] +debug = 1 +lto = true diff --git a/experiments/zizk_vstd/zero_knowledge/README.md b/experiments/zizk_vstd/zero_knowledge/README.md new file mode 100644 index 0000000..5111f87 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/README.md @@ -0,0 +1,147 @@ +# Zero-knowledge experiment for ZIZK-VSTD + +**Status:** optional experiment; not a VSTD layer, wire identifier, conformance level, +or compatibility promise. + +This directory tests one narrow question: can a prover show that a hidden, bounded +evidence payload satisfies a fixed predicate while publishing enough authenticated +coordinates for another party to verify the proof? It does not make all VSTD receipts +zero knowledge. Existing full-disclosure receipts remain valid and unchanged. + +## Selected system + +The experiment selects exactly one proof system: **RISC Zero zkVM 3.0.6**, using its +local composite STARK receipt. The selection is pinned in every Cargo manifest and in +`Cargo.lock`. + +Reasons for selection: + +- the official SDK describes a `Receipt` as a zero-knowledge proof of execution; +- `Receipt::verify` checks successful execution, the expected image ID, and journal + integrity; +- arbitrary Rust guest code can express the bounded predicate without designing a + new arithmetic circuit; +- the composite STARK path uses transparent setup rather than an experiment-specific + trusted ceremony; and +- the documented local prover requires at least 16 GB of RAM, which the tested Linux + x86-64 environment satisfies. + +Primary references: + +- [RISC Zero installation](https://dev.risczero.com/api/zkvm/install) +- [RISC Zero real-proof quick start](https://dev.risczero.com/api/zkvm/quickstart) +- [`Receipt` verification contract](https://docs.rs/risc0-zkvm/3.0.6/risc0_zkvm/struct.Receipt.html) +- [`DevModeProver` warning](https://docs.rs/risc0-zkvm/3.0.6/risc0_zkvm/struct.DevModeProver.html) +- [RISC Zero proof-system analysis](https://dev.risczero.com/proof-system-in-detail.pdf) + +The host crate enables `disable-dev-mode`. It also rejects the `Fake` receipt variant +and refuses a truthy `RISC0_DEV_MODE` setting. Development-mode output cannot satisfy +this experiment. + +## Statement, witness, and public output + +The fixed predicate is defined byte-for-byte by `PREDICATE_TEXT` in the shared types +crate. A successful proof establishes that one private input accepted by the pinned +guest program contained: + +- a nonempty evidence byte string no longer than 64 bytes; +- an experiment-local `Supported` input tag rather than `Unknown` or `Conflicted`; +- a private measurement at least as large as the public threshold; and +- a private 32-byte salt used in the evidence commitment. + +The private witness consists of the evidence bytes, salt, measurement, and candidate +state. The authenticated public journal contains: + +- SHA-256 digests of the experiment profile and exact predicate text; +- subject and policy digests; +- a public challenge; +- the public threshold; +- a salted commitment to the private evidence, length, and measurement; and +- the Boolean result of the fixed predicate. + +The RISC Zero image ID is the program trust coordinate. The verifier supplies or uses +the compiled expected image ID; it does not trust the convenience metadata in +`public.json`. RISC Zero receipt metadata is not cryptographically bound and is not an +acceptance input here. + +Canonical evidence commitment input: + +```text +UTF8("vstd-zk-evidence-commitment-v1\\0") +|| U32_BE(evidence_length) +|| evidence_bytes +|| salt_32_bytes +|| U64_BE(private_measurement) +``` + +The commitment is SHA-256 of those bytes. The journal itself is encoded by the pinned +RISC Zero serde codec and authenticated by the receipt. + +## Platform and pinned setup + +The tested platform is Linux x86-64 under WSL2. The RISC Zero documentation lists +x86-64 Linux as a supported installer target. The selected components are: + +```text +rzup 0.5.0 +cargo-risczero 3.0.6 +r0vm 3.0.6 +RISC Zero Rust guest toolchain 1.97.0-dev +risc0-zkvm 3.0.6 +``` + +Install the official tool manager and then the pinned components: + +```bash +curl --proto '=https' --tlsv1.2 -fsSL https://risczero.com/install -o /tmp/rzup-install.sh +bash /tmp/rzup-install.sh +export PATH="$HOME/.risc0/bin:$HOME/.cargo/bin:$PATH" +rzup install cargo-risczero 3.0.6 +rzup install r0vm 3.0.6 +rzup install rust 1.97.0 +rzup default cargo-risczero 3.0.6 +rzup default r0vm 3.0.6 +rzup default rust 1.97.0 +``` + +No dependency from this Rust workspace is added to the `verifier-standard` Python +distribution. + +## Reproduce the proof and negative tests + +From this directory in the supported Linux environment: + +```bash +export PATH="$HOME/.risc0/bin:$HOME/.cargo/bin:$PATH" +export CARGO_TARGET_DIR="${HOME}/.cache/vstd-zk-target" +export RISC0_DEV_MODE=0 +cargo run --locked --release -p vstd-zk-host -- self-test local-artifacts/self-test +``` + +The self-test produces one real receipt, verifies it, and then exercises the negative +fixtures described in `fixtures/README.md`. Generated receipts and private inputs are +ignored by Git. + +For a separate prove/verify flow: + +```bash +mkdir -p local-artifacts/manual +cargo run --locked --release -p vstd-zk-host -- \ + generate-inputs local-artifacts/private-witness.json local-artifacts/manual/statement.json +cargo run --locked --release -p vstd-zk-host -- \ + prove local-artifacts/private-witness.json local-artifacts/manual/statement.json \ + local-artifacts/manual/receipt.msgpack local-artifacts/manual/public.json +rm local-artifacts/private-witness.json +cargo run --locked --release -p vstd-zk-host -- \ + verify local-artifacts/manual/receipt.msgpack local-artifacts/manual/public.json +``` + +The last command is the offline verifier path. It needs the receipt, public envelope, +pinned verifier implementation, and expected image ID. It does not need the private +witness or a network service. + +## Interpretation + +The experiment provides a concrete cryptographic privacy option for one bounded +predicate. It does not establish that zero knowledge should be mandatory for VSTD. +See `CLAIM_BOUNDARY.md` and `THREAT_MODEL.md` before making any public claim. diff --git a/experiments/zizk_vstd/zero_knowledge/THREAT_MODEL.md b/experiments/zizk_vstd/zero_knowledge/THREAT_MODEL.md new file mode 100644 index 0000000..121a93e --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/THREAT_MODEL.md @@ -0,0 +1,90 @@ +# Threat model + +**Scope:** the optional zero-knowledge experiment only. + +## Protected secret + +The intended secret is the private witness supplied to the pinned guest: evidence +bytes, a 32-byte salt, a measurement, and an experiment-local candidate state. The +receipt intentionally reveals the public journal. Subject, policy, challenge, +threshold, predicate result, and salted evidence commitment are not secrets. + +## Trust roots + +Acceptance depends on all of the following: + +1. the expected RISC Zero image ID obtained from the reviewed guest ELF; +2. RISC Zero zkVM 3.0.6 verification code and its proof-system parameters; +3. the pinned Rust sources and `Cargo.lock`; +4. SHA-256 collision and preimage resistance for the public digests; +5. correct public-statement comparison after receipt verification; and +6. a verifier obtaining the expected image ID independently rather than trusting an + unbound metadata field supplied by the prover. + +The composite STARK uses transparent public setup. Its non-interactive security relies +on the proof system's Fiat-Shamir construction and its documented hash assumptions. +This repository does not independently prove the cryptographic reduction. + +## Attacks tested + +| Attack | Required result | +|---|---| +| private measurement below threshold | proof attempt rejected | +| private `Unknown` candidate state | proof attempt rejected | +| private `Conflicted` candidate state | proof attempt rejected | +| mutated public threshold | wrapper verification rejected | +| different subject or challenge | statement transplantation rejected | +| wrong image ID | receipt verification rejected | +| corrupted receipt bytes | decoding or verification rejected | +| authenticated journal mutation | receipt verification rejected | +| private byte strings copied to public files | serialization scan rejected | + +## Residual risks + +### Host compromise and operational leakage + +The proof system hides guest inputs from a receipt verifier. It does not protect the +witness from the prover's operating system, shell history, swap, crash dumps, malware, +debuggers, or a modified host binary. The manual workflow writes a temporary private +JSON file and requires the operator to protect and remove it. + +### Side channels + +The experiment does not claim constant-time host behavior, traffic-analysis resistance, +or protection from proof-time, memory-use, file-size, power, or hardware side channels. +The evidence length is hidden by the proof but could be correlated with prover-side +observations. + +### Low-entropy evidence + +The public commitment includes a private random 32-byte salt to impede offline guessing. +Weak or reused salts, disclosure of the salt, or host compromise can make low-entropy +evidence guessable. The proof does not certify salt quality. + +### Replay and freshness + +The public challenge is authenticated by the journal, so a proof cannot be transplanted +to a different challenge without rejection. The same valid proof can still be replayed +for the same challenge. Challenge issuance, uniqueness, expiry, clock trust, and replay +storage are outside this experiment and must remain explicit assumptions or UNKNOWN. + +### Parser and denial of service + +Receipt and envelope reads have size limits. MessagePack is used because RISC Zero's +receipt documentation recommends a serde format with depth limits for untrusted input. +The experiment does not establish a complete resource-exhaustion bound for all malformed +receipts. + +### Supply chain + +Version pins and a committed lock file constrain dependencies but do not independently +audit every transitive crate, compiler binary, installer, or build host. Reproducing an +image ID on another trusted build host is useful evidence, not supplied here as an +independent implementation. + +### Semantic overreach + +A prover selects the private bytes and candidate tag. The proof does not show that those +bytes are truthful, complete, authorized, fresh, legally valid, independently sourced, +or causally connected to the real world. It proves only execution of the fixed predicate +over the committed input. diff --git a/experiments/zizk_vstd/zero_knowledge/fixtures/README.md b/experiments/zizk_vstd/zero_knowledge/fixtures/README.md new file mode 100644 index 0000000..1354582 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/fixtures/README.md @@ -0,0 +1,23 @@ +# Generated fixtures + +The real-proof self-test creates fixtures under the ignored `local-artifacts/` directory +instead of committing a reusable private witness or a large proof binary. + +Generated positive fixtures: + +- `receipt.msgpack` — real composite STARK receipt; +- `public.json` — authenticated journal plus non-authoritative convenience metadata. + +Generated negative fixtures: + +- `mutated-public.json` — changed public threshold; +- `transplanted-public.json` — changed subject and challenge; +- `corrupted-receipt.msgpack` — corrupted serialized receipt; +- `tampered-journal.msgpack` — decoded journal changed without regenerating the seal. + +Additional negative witnesses are generated only in memory: below-threshold, +`Unknown`, and `Conflicted`. The self-test requires every negative case to be rejected +and writes the Boolean results to `self-test-results.json`. + +This layout avoids publishing the private witness bytes in a fixture while retaining a +reproducible generator and verifier. diff --git a/experiments/zizk_vstd/zero_knowledge/host/Cargo.toml b/experiments/zizk_vstd/zero_knowledge/host/Cargo.toml new file mode 100644 index 0000000..a93f508 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/host/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "vstd-zk-host" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +hex = "=0.4.3" +rand = "=0.8.5" +risc0-zkvm = { version = "=3.0.6", features = ["disable-dev-mode"] } +rmp-serde = "=1.3.0" +serde = { version = "=1.0.228", features = ["derive"] } +serde_json = "=1.0.145" +vstd-zk-methods = { path = "../methods" } +vstd-zk-types = { path = "../types" } diff --git a/experiments/zizk_vstd/zero_knowledge/host/src/main.rs b/experiments/zizk_vstd/zero_knowledge/host/src/main.rs new file mode 100644 index 0000000..bdebe57 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/host/src/main.rs @@ -0,0 +1,454 @@ +use hex::FromHex; +use rand::{rngs::OsRng, RngCore}; +use risc0_zkvm::{ + default_prover, + sha::{Digest, Impl, Sha256}, + ExecutorEnv, InnerReceipt, Receipt, +}; +use serde::Serialize; +use std::{ + env, + error::Error, + fs, + io, + path::Path, +}; +use vstd_zk_methods::{VSTD_ZK_GUEST_ELF, VSTD_ZK_GUEST_ID}; +use vstd_zk_types::{ + CandidateState, PrivateWitness, ProverInput, PublicEnvelope, PublicJournal, + PublicStatement, COMMITMENT_DOMAIN, PREDICATE_TEXT, PROFILE_LABEL, +}; + +const PROOF_SYSTEM: &str = "risc0-zkvm-3.0.6-composite-stark"; +const MAX_RECEIPT_BYTES: u64 = 32 * 1024 * 1024; +const MAX_ENVELOPE_BYTES: u64 = 1024 * 1024; + +type AppResult = Result>; + +#[derive(Serialize)] +struct SelfTestResults { + real_proof_verified: bool, + unsatisfied_witness_rejected: bool, + unknown_rejected: bool, + conflicted_rejected: bool, + mutated_public_input_rejected: bool, + wrong_image_id_rejected: bool, + corrupted_proof_rejected: bool, + tampered_journal_rejected: bool, + statement_transplant_rejected: bool, + private_bytes_absent_from_public_artifacts: bool, +} + +fn main() { + if let Err(error) = run() { + eprintln!("error: {error}"); + std::process::exit(1); + } +} + +fn run() -> AppResult<()> { + let args: Vec = env::args().collect(); + match args.get(1).map(String::as_str) { + Some("generate-inputs") if args.len() == 4 => { + generate_inputs(Path::new(&args[2]), Path::new(&args[3])) + } + Some("prove") if args.len() == 6 => prove_from_files( + Path::new(&args[2]), + Path::new(&args[3]), + Path::new(&args[4]), + Path::new(&args[5]), + ), + Some("verify") if args.len() == 4 || args.len() == 5 => { + let expected_id = args.get(4).map(|value| parse_digest(value)).transpose()?; + verify_artifacts(Path::new(&args[2]), Path::new(&args[3]), expected_id)?; + println!("PASS: real RISC Zero receipt and public statement verified"); + Ok(()) + } + Some("image-id") if args.len() == 2 => { + println!("{}", method_id()); + Ok(()) + } + Some("self-test") if args.len() == 3 => self_test(Path::new(&args[2])), + _ => Err(usage_error()), + } +} + +fn usage_error() -> Box { + io::Error::new( + io::ErrorKind::InvalidInput, + "usage:\n vstd-zk-host generate-inputs PRIVATE.json STATEMENT.json\n vstd-zk-host prove PRIVATE.json STATEMENT.json RECEIPT.bin PUBLIC.json\n vstd-zk-host verify RECEIPT.bin PUBLIC.json [EXPECTED_IMAGE_ID]\n vstd-zk-host image-id\n vstd-zk-host self-test OUTPUT_DIR", + ) + .into() +} + +fn method_id() -> Digest { + Digest::from(VSTD_ZK_GUEST_ID) +} + +fn parse_digest(value: &str) -> AppResult { + Ok(Digest::from_hex(value)?) +} + +fn digest_bytes(value: &[u8]) -> [u8; 32] { + let digest = Impl::hash_bytes(value); + digest.as_bytes().try_into().expect("SHA-256 is 32 bytes") +} + +fn digest_hex(value: &[u8]) -> String { + hex::encode(digest_bytes(value)) +} + +fn evidence_commitment(witness: &PrivateWitness) -> [u8; 32] { + let mut input = Vec::with_capacity( + COMMITMENT_DOMAIN.len() + 4 + witness.evidence.len() + 32 + 8, + ); + input.extend_from_slice(COMMITMENT_DOMAIN); + input.extend_from_slice(&(witness.evidence.len() as u32).to_be_bytes()); + input.extend_from_slice(&witness.evidence); + input.extend_from_slice(&witness.salt); + input.extend_from_slice(&witness.measurement.to_be_bytes()); + digest_bytes(&input) +} + +fn random_array() -> [u8; 32] { + let mut value = [0_u8; 32]; + OsRng.fill_bytes(&mut value); + value +} + +fn sample_inputs() -> (PrivateWitness, PublicStatement) { + let mut evidence = vec![0_u8; 48]; + OsRng.fill_bytes(&mut evidence); + let witness = PrivateWitness { + evidence, + salt: random_array(), + measurement: 73, + candidate_state: CandidateState::Supported, + }; + let statement = PublicStatement { + subject_digest: random_array(), + policy_digest: digest_bytes(b"vstd-zk-fixed-threshold-policy-v1"), + challenge: random_array(), + threshold: 70, + }; + (witness, statement) +} + +fn generate_inputs(private_path: &Path, statement_path: &Path) -> AppResult<()> { + let (witness, statement) = sample_inputs(); + write_json(private_path, &witness)?; + write_json(statement_path, &statement)?; + println!( + "generated a local private witness and public statement; do not publish {}", + private_path.display() + ); + Ok(()) +} + +fn ensure_real_mode() -> AppResult<()> { + if let Ok(value) = env::var("RISC0_DEV_MODE") { + let normalized = value.trim().to_ascii_lowercase(); + if !normalized.is_empty() && normalized != "0" && normalized != "false" { + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "RISC0_DEV_MODE must be unset, 0, or false; this binary also compiles with disable-dev-mode", + ) + .into()); + } + } + Ok(()) +} + +fn prove_from_files( + private_path: &Path, + statement_path: &Path, + receipt_path: &Path, + public_path: &Path, +) -> AppResult<()> { + ensure_real_mode()?; + let witness: PrivateWitness = read_json_bounded(private_path, MAX_ENVELOPE_BYTES)?; + let statement: PublicStatement = read_json_bounded(statement_path, MAX_ENVELOPE_BYTES)?; + prove_to_files(&witness, &statement, receipt_path, public_path)?; + println!("wrote a verified real receipt and public envelope"); + Ok(()) +} + +fn prove_to_files( + witness: &PrivateWitness, + statement: &PublicStatement, + receipt_path: &Path, + public_path: &Path, +) -> AppResult { + ensure_real_mode()?; + let input = ProverInput { + statement: statement.clone(), + witness: witness.clone(), + }; + let env = ExecutorEnv::builder().write(&input)?.build()?; + let prove_info = default_prover().prove(env, VSTD_ZK_GUEST_ELF)?; + let receipt = prove_info.receipt; + require_composite_receipt(&receipt)?; + receipt.verify(method_id())?; + + let journal: PublicJournal = receipt.journal.decode()?; + validate_public_journal(&journal, statement)?; + if journal.evidence_commitment != evidence_commitment(witness) { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "authenticated evidence commitment does not match the supplied witness", + ) + .into()); + } + + let receipt_bytes = rmp_serde::to_vec_named(&receipt)?; + let envelope = PublicEnvelope { + experiment_profile: String::from_utf8(PROFILE_LABEL.to_vec())?, + proof_system: PROOF_SYSTEM.to_string(), + image_id: method_id().to_string(), + receipt_sha256: digest_hex(&receipt_bytes), + receipt_size: receipt_bytes.len() as u64, + journal, + }; + write_bytes(receipt_path, &receipt_bytes)?; + write_json(public_path, &envelope)?; + Ok(envelope) +} + +fn require_composite_receipt(receipt: &Receipt) -> AppResult<()> { + match &receipt.inner { + InnerReceipt::Composite(_) => Ok(()), + InnerReceipt::Fake(_) => Err(io::Error::new( + io::ErrorKind::InvalidData, + "fake RISC Zero receipt rejected", + ) + .into()), + _ => Err(io::Error::new( + io::ErrorKind::InvalidData, + "receipt kind differs from the selected composite STARK path", + ) + .into()), + } +} + +fn validate_public_journal( + journal: &PublicJournal, + expected: &PublicStatement, +) -> AppResult<()> { + if journal.profile_digest != digest_bytes(PROFILE_LABEL) + || journal.predicate_digest != digest_bytes(PREDICATE_TEXT) + || journal.subject_digest != expected.subject_digest + || journal.policy_digest != expected.policy_digest + || journal.challenge != expected.challenge + || journal.threshold != expected.threshold + || !journal.predicate_satisfied + { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "authenticated journal does not match the expected public statement", + ) + .into()); + } + Ok(()) +} + +fn verify_artifacts( + receipt_path: &Path, + public_path: &Path, + expected_id: Option, +) -> AppResult { + let receipt_bytes = read_bytes_bounded(receipt_path, MAX_RECEIPT_BYTES)?; + let envelope: PublicEnvelope = read_json_bounded(public_path, MAX_ENVELOPE_BYTES)?; + let receipt: Receipt = rmp_serde::from_slice(&receipt_bytes)?; + require_composite_receipt(&receipt)?; + + let trusted_id = expected_id.unwrap_or_else(method_id); + receipt.verify(trusted_id)?; + let journal: PublicJournal = receipt.journal.decode()?; + + if trusted_id != method_id() + || envelope.image_id != trusted_id.to_string() + || envelope.receipt_sha256 != digest_hex(&receipt_bytes) + || envelope.receipt_size != receipt_bytes.len() as u64 + || envelope.experiment_profile != String::from_utf8(PROFILE_LABEL.to_vec())? + || envelope.proof_system != PROOF_SYSTEM + || envelope.journal != journal + { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "public envelope, receipt, image ID, or authenticated journal mismatch", + ) + .into()); + } + + let expected_statement = PublicStatement { + subject_digest: envelope.journal.subject_digest, + policy_digest: envelope.journal.policy_digest, + challenge: envelope.journal.challenge, + threshold: envelope.journal.threshold, + }; + validate_public_journal(&journal, &expected_statement)?; + Ok(journal) +} + +fn proof_attempt_rejected( + witness: &PrivateWitness, + statement: &PublicStatement, +) -> AppResult { + let input = ProverInput { + statement: statement.clone(), + witness: witness.clone(), + }; + let env = ExecutorEnv::builder().write(&input)?.build()?; + match default_prover().prove(env, VSTD_ZK_GUEST_ELF) { + Ok(prove_info) => Ok(prove_info.receipt.verify(method_id()).is_err()), + Err(_) => Ok(true), + } +} + +fn self_test(output_dir: &Path) -> AppResult<()> { + ensure_real_mode()?; + if output_dir.exists() { + fs::remove_dir_all(output_dir)?; + } + fs::create_dir_all(output_dir)?; + + let (witness, statement) = sample_inputs(); + let receipt_path = output_dir.join("receipt.msgpack"); + let public_path = output_dir.join("public.json"); + let envelope = prove_to_files(&witness, &statement, &receipt_path, &public_path)?; + let real_proof_verified = verify_artifacts(&receipt_path, &public_path, None).is_ok(); + + let mut low_witness = witness.clone(); + low_witness.measurement = statement.threshold.saturating_sub(1); + let unsatisfied_witness_rejected = proof_attempt_rejected(&low_witness, &statement)?; + + let mut unknown_witness = witness.clone(); + unknown_witness.candidate_state = CandidateState::Unknown; + let unknown_rejected = proof_attempt_rejected(&unknown_witness, &statement)?; + + let mut conflicted_witness = witness.clone(); + conflicted_witness.candidate_state = CandidateState::Conflicted; + let conflicted_rejected = proof_attempt_rejected(&conflicted_witness, &statement)?; + + let mut mutated_envelope = envelope.clone(); + mutated_envelope.journal.threshold = mutated_envelope.journal.threshold.saturating_add(1); + let mutated_path = output_dir.join("mutated-public.json"); + write_json(&mutated_path, &mutated_envelope)?; + let mutated_public_input_rejected = + verify_artifacts(&receipt_path, &mutated_path, None).is_err(); + + let mut transplanted = envelope.clone(); + transplanted.journal.subject_digest[0] ^= 1; + transplanted.journal.challenge[0] ^= 1; + let transplanted_path = output_dir.join("transplanted-public.json"); + write_json(&transplanted_path, &transplanted)?; + let statement_transplant_rejected = + verify_artifacts(&receipt_path, &transplanted_path, None).is_err(); + + let mut wrong_id = method_id(); + wrong_id.as_mut_bytes()[0] ^= 1; + let wrong_image_id_rejected = + verify_artifacts(&receipt_path, &public_path, Some(wrong_id)).is_err(); + + let receipt_bytes = read_bytes_bounded(&receipt_path, MAX_RECEIPT_BYTES)?; + let mut corrupted_bytes = receipt_bytes.clone(); + let corrupt_index = corrupted_bytes.len() / 2; + corrupted_bytes[corrupt_index] ^= 1; + let corrupted_path = output_dir.join("corrupted-receipt.msgpack"); + write_bytes(&corrupted_path, &corrupted_bytes)?; + let corrupted_proof_rejected = + verify_artifacts(&corrupted_path, &public_path, None).is_err(); + + let mut tampered_receipt: Receipt = rmp_serde::from_slice(&receipt_bytes)?; + if tampered_receipt.journal.bytes.is_empty() { + return Err(io::Error::new(io::ErrorKind::InvalidData, "empty journal").into()); + } + tampered_receipt.journal.bytes[0] ^= 1; + let tampered_path = output_dir.join("tampered-journal.msgpack"); + write_bytes(&tampered_path, &rmp_serde::to_vec_named(&tampered_receipt)?)?; + let tampered_journal_rejected = + verify_artifacts(&tampered_path, &public_path, None).is_err(); + + let private_bytes_absent_from_public_artifacts = !directory_contains( + output_dir, + &[witness.evidence.as_slice(), witness.salt.as_slice()], + )?; + + let results = SelfTestResults { + real_proof_verified, + unsatisfied_witness_rejected, + unknown_rejected, + conflicted_rejected, + mutated_public_input_rejected, + wrong_image_id_rejected, + corrupted_proof_rejected, + tampered_journal_rejected, + statement_transplant_rejected, + private_bytes_absent_from_public_artifacts, + }; + let all_passed = results.real_proof_verified + && results.unsatisfied_witness_rejected + && results.unknown_rejected + && results.conflicted_rejected + && results.mutated_public_input_rejected + && results.wrong_image_id_rejected + && results.corrupted_proof_rejected + && results.tampered_journal_rejected + && results.statement_transplant_rejected + && results.private_bytes_absent_from_public_artifacts; + write_json(&output_dir.join("self-test-results.json"), &results)?; + println!("{}", serde_json::to_string_pretty(&results)?); + if !all_passed { + return Err(io::Error::new(io::ErrorKind::Other, "one or more self-tests failed").into()); + } + Ok(()) +} + +fn directory_contains(directory: &Path, needles: &[&[u8]]) -> AppResult { + for entry in fs::read_dir(directory)? { + let path = entry?.path(); + if !path.is_file() { + continue; + } + let bytes = fs::read(path)?; + for needle in needles { + if !needle.is_empty() && bytes.windows(needle.len()).any(|window| window == *needle) { + return Ok(true); + } + } + } + Ok(false) +} + +fn read_bytes_bounded(path: &Path, maximum: u64) -> AppResult> { + let metadata = fs::metadata(path)?; + if metadata.len() > maximum { + return Err(io::Error::new(io::ErrorKind::InvalidData, "input exceeds size bound").into()); + } + Ok(fs::read(path)?) +} + +fn read_json_bounded(path: &Path, maximum: u64) -> AppResult +where + T: serde::de::DeserializeOwned, +{ + let bytes = read_bytes_bounded(path, maximum)?; + Ok(serde_json::from_slice(&bytes)?) +} + +fn write_bytes(path: &Path, bytes: &[u8]) -> AppResult<()> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + fs::write(path, bytes)?; + Ok(()) +} + +fn write_json(path: &Path, value: &T) -> AppResult<()> +where + T: Serialize, +{ + let mut bytes = serde_json::to_vec_pretty(value)?; + bytes.push(b'\n'); + write_bytes(path, &bytes) +} diff --git a/experiments/zizk_vstd/zero_knowledge/methods/Cargo.toml b/experiments/zizk_vstd/zero_knowledge/methods/Cargo.toml new file mode 100644 index 0000000..1baf35a --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/methods/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "vstd-zk-methods" +version = "0.1.0" +edition = "2021" +publish = false + +[build-dependencies] +risc0-build = { version = "=3.0.6" } + +[package.metadata.risc0] +methods = ["guest"] diff --git a/experiments/zizk_vstd/zero_knowledge/methods/build.rs b/experiments/zizk_vstd/zero_knowledge/methods/build.rs new file mode 100644 index 0000000..08a8a4e --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/methods/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/experiments/zizk_vstd/zero_knowledge/methods/guest/Cargo.lock b/experiments/zizk_vstd/zero_knowledge/methods/guest/Cargo.lock new file mode 100644 index 0000000..9591684 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/methods/guest/Cargo.lock @@ -0,0 +1,1485 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-r1cs-std", + "ark-std", +] + +[[package]] +name = "ark-crypto-primitives" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0c292754729c8a190e50414fd1a37093c786c709899f29c9f7daccecfa855e" +dependencies = [ + "ahash", + "ark-crypto-primitives-macros", + "ark-ec", + "ark-ff", + "ark-relations", + "ark-serialize", + "ark-snark", + "ark-std", + "blake2", + "derivative", + "digest", + "fnv", + "merlin", + "sha2", +] + +[[package]] +name = "ark-crypto-primitives-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e89fe77d1f0f4fe5b96dfc940923d88d17b6a773808124f21e764dfb063c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "arrayvec", + "digest", + "educe", + "itertools", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-groth16" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88f1d0f3a534bb54188b8dcc104307db6c56cdae574ddc3212aec0625740fc7e" +dependencies = [ + "ark-crypto-primitives", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-relations", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-r1cs-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-relations", + "ark-std", + "educe", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" +dependencies = [ + "ark-ff", + "ark-std", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "arrayvec", + "digest", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-snark" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d368e2848c2d4c129ce7679a7d0d2d612b6a274d3ea6a13bad4445d61b381b88" +dependencies = [ + "ark-ff", + "ark-relations", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.7", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f347189c62a579b8cd5f80714efa178f52e461dc2e6d701d264f5ff22e566c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "enum-ordinalize" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "include_bytes_aligned" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee796ad498c8d9a1d68e477df8f754ed784ef875de1414ebdaf169f70a6a784" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.13.1", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "no_std_strings" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e" + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bitflags 2.13.1", + "num-traits", + "rand 0.9.5", + "rand_chacha 0.9.0", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "risc0-binfmt" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d836c6ad82f4ced7c61d5feedf905a17780312e393aa681d29cc0bbc5131672b" +dependencies = [ + "anyhow", + "borsh", + "bytemuck", + "derive_more", + "elf", + "lazy_static", + "postcard", + "rand 0.9.5", + "risc0-zkp", + "risc0-zkvm-platform", + "ruint", + "semver", + "serde", + "tracing", +] + +[[package]] +name = "risc0-circuit-keccak" +version = "4.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c731e12429eb4457e1ddc69c56ee7343a1e10b86e4aa55bc8f4d2b13734abb9" +dependencies = [ + "anyhow", + "bytemuck", + "paste", + "risc0-binfmt", + "risc0-circuit-recursion", + "risc0-core", + "risc0-zkp", + "tracing", +] + +[[package]] +name = "risc0-circuit-recursion" +version = "4.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40dd640122abcc67d4d4e4f055c68cbc3ad2efb8589c65c2b23d354632971b60" +dependencies = [ + "anyhow", + "bytemuck", + "hex", + "metal", + "risc0-core", + "risc0-zkp", + "tracing", +] + +[[package]] +name = "risc0-circuit-rv32im" +version = "4.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb11231aa4b74bcc0c8d16597893fbd7ea6f6a9ebbc35e16bfd06b467c7ee104" +dependencies = [ + "anyhow", + "bit-vec", + "bytemuck", + "derive_more", + "paste", + "risc0-binfmt", + "risc0-core", + "risc0-zkp", + "serde", + "tracing", +] + +[[package]] +name = "risc0-core" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6eb2d2b2c6cac0e43cbb2202daacee1a2f24d0dfa03fd08887a11dc6defdcc1" +dependencies = [ + "bytemuck", + "rand_core 0.9.5", +] + +[[package]] +name = "risc0-groth16" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0ca702ea7d0162766defe7ed6a79bda4a747ad9e2684000a6edd14df0a6d1f3" +dependencies = [ + "anyhow", + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-groth16", + "ark-serialize", + "bytemuck", + "hex", + "num-bigint", + "num-traits", + "risc0-binfmt", + "risc0-zkp", + "serde", +] + +[[package]] +name = "risc0-zkos-v1compat" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b0b598ba7946354b10ca5c56e382de801e6c7fce9fccad0396ec436bc5072b" +dependencies = [ + "include_bytes_aligned", + "no_std_strings", + "risc0-zkvm-platform", +] + +[[package]] +name = "risc0-zkp" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21c0c921e5e2d44197940d387a45e29c6165e318b5a168fdfdbd50f50ba03678" +dependencies = [ + "anyhow", + "blake2", + "borsh", + "bytemuck", + "cfg-if", + "digest", + "hex", + "hex-literal", + "metal", + "paste", + "rand_core 0.9.5", + "risc0-core", + "risc0-zkvm-platform", + "serde", + "sha2", + "stability", + "tracing", +] + +[[package]] +name = "risc0-zkvm" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d4f24ec767f71a1663a4d24cf9d02b6bfee44c64647cae677227817051007a" +dependencies = [ + "anyhow", + "borsh", + "bytemuck", + "derive_more", + "hex", + "risc0-binfmt", + "risc0-circuit-keccak", + "risc0-circuit-recursion", + "risc0-circuit-rv32im", + "risc0-core", + "risc0-groth16", + "risc0-zkos-v1compat", + "risc0-zkp", + "risc0-zkvm-platform", + "rrs-lib", + "semver", + "serde", + "sha2", + "stability", + "tracing", +] + +[[package]] +name = "risc0-zkvm-platform" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb37a97ff7e8e4ee1b2a1c43ec143b4887759883c343507af9e4787a57914cd" +dependencies = [ + "bytemuck", + "cfg-if", + "getrandom 0.2.17", + "getrandom 0.3.4", + "libm", + "num_enum", + "paste", + "stability", +] + +[[package]] +name = "rrs-lib" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4382d3af3a4ebdae7f64ba6edd9114fff92c89808004c4943b393377a25d001" +dependencies = [ + "downcast-rs", + "paste", +] + +[[package]] +name = "ruint" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e99bff0393163bb25029a6af25d3d8d202ba5b5438a74d1bd8789f5c822970" +dependencies = [ + "borsh", + "proptest", + "rand 0.8.7", + "rand 0.9.5", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vstd-zk-guest" +version = "0.1.0" +dependencies = [ + "risc0-zkvm", + "vstd-zk-types", +] + +[[package]] +name = "vstd-zk-types" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] diff --git a/experiments/zizk_vstd/zero_knowledge/methods/guest/Cargo.toml b/experiments/zizk_vstd/zero_knowledge/methods/guest/Cargo.toml new file mode 100644 index 0000000..6f11780 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/methods/guest/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "vstd-zk-guest" +version = "0.1.0" +edition = "2021" +publish = false + +[workspace] + +[dependencies] +risc0-zkvm = { version = "=3.0.6", default-features = false, features = ["std"] } +vstd-zk-types = { path = "../../types" } diff --git a/experiments/zizk_vstd/zero_knowledge/methods/guest/src/main.rs b/experiments/zizk_vstd/zero_knowledge/methods/guest/src/main.rs new file mode 100644 index 0000000..c8c3925 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/methods/guest/src/main.rs @@ -0,0 +1,69 @@ +use risc0_zkvm::{ + guest::env, + sha::{Impl, Sha256}, +}; +use vstd_zk_types::{ + CandidateState, ProverInput, PublicJournal, COMMITMENT_DOMAIN, MAX_EVIDENCE_LEN, + MAX_THRESHOLD, PREDICATE_TEXT, PROFILE_LABEL, +}; + +fn digest_bytes(value: &[u8]) -> [u8; 32] { + let digest = Impl::hash_bytes(value); + digest.as_bytes().try_into().expect("SHA-256 is 32 bytes") +} + +fn main() { + let input: ProverInput = env::read(); + + assert!(!input.witness.evidence.is_empty(), "evidence must not be empty"); + assert!( + input.witness.evidence.len() <= MAX_EVIDENCE_LEN, + "evidence exceeds the bounded predicate" + ); + assert!( + input.witness.candidate_state == CandidateState::Supported, + "UNKNOWN and CONFLICTED inputs do not satisfy this predicate" + ); + assert!( + input.statement.threshold <= MAX_THRESHOLD, + "threshold exceeds the experiment bound" + ); + assert!( + input.witness.measurement >= input.statement.threshold, + "private measurement is below the public threshold" + ); + assert!( + input.statement.subject_digest != [0_u8; 32], + "subject digest must be explicit" + ); + assert!( + input.statement.policy_digest != [0_u8; 32], + "policy digest must be explicit" + ); + assert!( + input.statement.challenge != [0_u8; 32], + "challenge must be explicit" + ); + + let mut commitment_input = Vec::with_capacity( + COMMITMENT_DOMAIN.len() + 4 + input.witness.evidence.len() + 32 + 8, + ); + commitment_input.extend_from_slice(COMMITMENT_DOMAIN); + commitment_input.extend_from_slice(&(input.witness.evidence.len() as u32).to_be_bytes()); + commitment_input.extend_from_slice(&input.witness.evidence); + commitment_input.extend_from_slice(&input.witness.salt); + commitment_input.extend_from_slice(&input.witness.measurement.to_be_bytes()); + + let journal = PublicJournal { + profile_digest: digest_bytes(PROFILE_LABEL), + predicate_digest: digest_bytes(PREDICATE_TEXT), + subject_digest: input.statement.subject_digest, + policy_digest: input.statement.policy_digest, + challenge: input.statement.challenge, + threshold: input.statement.threshold, + evidence_commitment: digest_bytes(&commitment_input), + predicate_satisfied: true, + }; + + env::commit(&journal); +} diff --git a/experiments/zizk_vstd/zero_knowledge/methods/src/lib.rs b/experiments/zizk_vstd/zero_knowledge/methods/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/methods/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs")); diff --git a/experiments/zizk_vstd/zero_knowledge/rust-toolchain.toml b/experiments/zizk_vstd/zero_knowledge/rust-toolchain.toml new file mode 100644 index 0000000..c6096c7 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.97" +components = ["rust-src"] +profile = "minimal" diff --git a/experiments/zizk_vstd/zero_knowledge/scripts/run_real_proof.sh b/experiments/zizk_vstd/zero_knowledge/scripts/run_real_proof.sh new file mode 100755 index 0000000..d2a7874 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/scripts/run_real_proof.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +EXPERIMENT_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)" + +export PATH="${HOME}/.risc0/bin:${HOME}/.cargo/bin:${PATH}" +export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-${HOME}/.cache/vstd-zk-target}" +export RISC0_DEV_MODE=0 + +cd "${EXPERIMENT_DIR}" +cargo run --locked --release -p vstd-zk-host -- \ + self-test local-artifacts/self-test diff --git a/experiments/zizk_vstd/zero_knowledge/types/Cargo.toml b/experiments/zizk_vstd/zero_knowledge/types/Cargo.toml new file mode 100644 index 0000000..66ec870 --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/types/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "vstd-zk-types" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +serde = { version = "=1.0.228", features = ["derive"] } diff --git a/experiments/zizk_vstd/zero_knowledge/types/src/lib.rs b/experiments/zizk_vstd/zero_knowledge/types/src/lib.rs new file mode 100644 index 0000000..0822e1c --- /dev/null +++ b/experiments/zizk_vstd/zero_knowledge/types/src/lib.rs @@ -0,0 +1,60 @@ +//! Shared, experiment-local types for the ZIZK-VSTD zero-knowledge probe. + +use serde::{Deserialize, Serialize}; + +pub const PROFILE_LABEL: &[u8] = b"ZIZK-VSTD-ZK-EXPERIMENT-0.1"; +pub const PREDICATE_TEXT: &[u8] = b"A private bounded evidence payload has a nonempty byte string of at most 64 bytes, an experiment-local SUPPORTED input tag, and a private measurement greater than or equal to the public threshold."; +pub const COMMITMENT_DOMAIN: &[u8] = b"vstd-zk-evidence-commitment-v1\0"; +pub const MAX_EVIDENCE_LEN: usize = 64; +pub const MAX_THRESHOLD: u64 = 1_000_000; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum CandidateState { + Supported, + Unknown, + Conflicted, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct PrivateWitness { + pub evidence: Vec, + pub salt: [u8; 32], + pub measurement: u64, + pub candidate_state: CandidateState, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct PublicStatement { + pub subject_digest: [u8; 32], + pub policy_digest: [u8; 32], + pub challenge: [u8; 32], + pub threshold: u64, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct ProverInput { + pub statement: PublicStatement, + pub witness: PrivateWitness, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct PublicJournal { + pub profile_digest: [u8; 32], + pub predicate_digest: [u8; 32], + pub subject_digest: [u8; 32], + pub policy_digest: [u8; 32], + pub challenge: [u8; 32], + pub threshold: u64, + pub evidence_commitment: [u8; 32], + pub predicate_satisfied: bool, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct PublicEnvelope { + pub experiment_profile: String, + pub proof_system: String, + pub image_id: String, + pub receipt_sha256: String, + pub receipt_size: u64, + pub journal: PublicJournal, +} diff --git a/tests/test_zizk_zero_knowledge_experiment.py b/tests/test_zizk_zero_knowledge_experiment.py new file mode 100644 index 0000000..b6139cd --- /dev/null +++ b/tests/test_zizk_zero_knowledge_experiment.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from pathlib import Path +import subprocess + + +ROOT = Path(__file__).resolve().parents[1] +EXPERIMENT = ROOT / "experiments" / "zizk_vstd" / "zero_knowledge" + + +def test_zero_knowledge_experiment_is_optional_and_pinned() -> None: + host_manifest = (EXPERIMENT / "host" / "Cargo.toml").read_text(encoding="utf-8") + guest_manifest = ( + EXPERIMENT / "methods" / "guest" / "Cargo.toml" + ).read_text(encoding="utf-8") + methods_manifest = (EXPERIMENT / "methods" / "Cargo.toml").read_text( + encoding="utf-8" + ) + + assert 'version = "=3.0.6"' in host_manifest + assert 'features = ["disable-dev-mode"]' in host_manifest + assert 'version = "=3.0.6"' in guest_manifest + assert 'version = "=3.0.6"' in methods_manifest + assert "zizk" not in (ROOT / "pyproject.toml").read_text(encoding="utf-8").lower() + + +def test_zero_knowledge_claim_boundary_is_explicit() -> None: + boundary = (EXPERIMENT / "CLAIM_BOUNDARY.md").read_text(encoding="utf-8") + assert "does not prove" in boundary + assert "optional experiment" in boundary + assert "UNKNOWN" in boundary + assert "CONFLICTED" in boundary + + +def test_private_and_generated_artifacts_are_not_versioned() -> None: + ignore = (EXPERIMENT / ".gitignore").read_text(encoding="utf-8") + assert "private-*.json" in ignore + assert "local-artifacts/" in ignore + tracked = subprocess.run( + ["git", "ls-files", "--", "experiments/zizk_vstd/zero_knowledge"], + cwd=ROOT, + check=True, + capture_output=True, + text=True, + ).stdout.splitlines() + assert not any(path.endswith(".msgpack") for path in tracked) + assert not any("private-" in path and path.endswith(".json") for path in tracked) From 69006dfea6f9b2f541292900908f15472acbbd36 Mon Sep 17 00:00:00 2001 From: TimeLordRaps Date: Sun, 23 Aug 2026 05:25:02 -0700 Subject: [PATCH 02/32] Add experimental ZIZK-VSTD bounded identity disclosure study Rejects "Zero Identity" as a public label: the construction withholds civil identity while retaining pseudonym, key, trust root, issuer, and revocation coordinates. Adds a non-normative semantic model, threat model, standard-library evaluator, 14 fixtures, and 39 tests that keep UNKNOWN and CONFLICTED intact. Coordinate: no layer, base 598c545, seam experiments/zizk_vstd/zero_identity. Falsification: any record reaching ACCEPTED_BOUNDED with a REFUTED property, or unlinkability reaching SUPPORTED, refutes the model. Compatibility: no wire identifier, schema $id, receipt digest, console alias, lifecycle token, dependency, or conformance behavior changed. Co-Authored-By: Claude Opus 5 --- experiments/zizk_vstd/zero_identity/README.md | 58 +++ .../ROUND1_ZERO_IDENTITY_REPORT.md | 165 ++++++++ .../zizk_vstd/zero_identity/SEMANTIC_MODEL.md | 92 ++++ .../zizk_vstd/zero_identity/THREAT_MODEL.md | 48 +++ .../zizk_vstd/zero_identity/evaluate.py | 393 ++++++++++++++++++ .../conflicted_identity_evidence.json | 77 ++++ .../positive_bounded_authorization.json | 69 +++ .../positive_minimized_boundary_narrowed.json | 85 ++++ .../fixtures/rejected_expired_authority.json | 69 +++ .../fixtures/rejected_key_compromise.json | 70 ++++ ...rejected_minimization_widens_boundary.json | 85 ++++ .../fixtures/rejected_missing_challenge.json | 67 +++ .../fixtures/rejected_replayed_challenge.json | 71 ++++ .../fixtures/rejected_revoked_authority.json | 69 +++ ...ejected_shared_pseudonym_independence.json | 74 ++++ ...ected_unlinkability_erases_trust_root.json | 84 ++++ .../fixtures/unknown_distinct_pseudonyms.json | 74 ++++ .../unknown_missing_authorization.json | 60 +++ .../fixtures/unknown_uniqueness_absent.json | 69 +++ .../model/zero_identity_model.json | 134 ++++++ .../zizk_vstd/zero_identity/run_validation.py | 48 +++ .../zero_identity/tests/test_zero_identity.py | 205 +++++++++ 22 files changed, 2166 insertions(+) create mode 100644 experiments/zizk_vstd/zero_identity/README.md create mode 100644 experiments/zizk_vstd/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md create mode 100644 experiments/zizk_vstd/zero_identity/SEMANTIC_MODEL.md create mode 100644 experiments/zizk_vstd/zero_identity/THREAT_MODEL.md create mode 100644 experiments/zizk_vstd/zero_identity/evaluate.py create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/conflicted_identity_evidence.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/positive_bounded_authorization.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/positive_minimized_boundary_narrowed.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/rejected_expired_authority.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/rejected_key_compromise.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/rejected_minimization_widens_boundary.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/rejected_missing_challenge.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/rejected_replayed_challenge.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/rejected_revoked_authority.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/rejected_shared_pseudonym_independence.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/rejected_unlinkability_erases_trust_root.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/unknown_distinct_pseudonyms.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/unknown_missing_authorization.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/unknown_uniqueness_absent.json create mode 100644 experiments/zizk_vstd/zero_identity/model/zero_identity_model.json create mode 100644 experiments/zizk_vstd/zero_identity/run_validation.py create mode 100644 experiments/zizk_vstd/zero_identity/tests/test_zero_identity.py diff --git a/experiments/zizk_vstd/zero_identity/README.md b/experiments/zizk_vstd/zero_identity/README.md new file mode 100644 index 0000000..4e751c3 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/README.md @@ -0,0 +1,58 @@ +# ZIZK-VSTD experiment: zero identity, examined + +**Status:** experimental. Not normative, not part of any VSTD layer, not implemented by +the `verifier` package, and not referenced by any receipt. Nothing here carries a wire +identifier, a schema `$id`, or a canonical digest. + +## The question + +Can "Zero Identity" be an operationally safe optional VSTD mode, or is the correct +mechanism something bounded — identity minimization, pseudonymity, selective disclosure? + +## The answer + +**The label is rejected for public use.** The construction it names does not remove +identity; it withholds *civil* identity while retaining a pseudonymous coordinate, a key +binding, a trust root, an issuer, and a revocation source — every one of which is an +identity coordinate and a correlation handle. Calling that "zero identity" overstates the +privacy achieved and hides the coordinates that remain. The mechanism this experiment +retains is **bounded identity disclosure**: civil identity withheld, authorization +reverifiable, and every other identity property reported honestly as `UNKNOWN`, +`CONFLICTED`, or `REFUTED` rather than assumed. + +Full reasoning and the exact claims that are and are not justified: +[`ROUND1_ZERO_IDENTITY_REPORT.md`](ROUND1_ZERO_IDENTITY_REPORT.md). + +## Contents + +| Path | What it is | +|---|---| +| [`SEMANTIC_MODEL.md`](SEMANTIC_MODEL.md) | term separation, statuses, minimum coordinates, prohibited inferences | +| [`THREAT_MODEL.md`](THREAT_MODEL.md) | thirteen threats, mitigations, residual risk, falsification conditions | +| [`model/zero_identity_model.json`](model/zero_identity_model.json) | the machine-readable model | +| [`evaluate.py`](evaluate.py) | standard-library evaluator over one disclosure record | +| [`fixtures/`](fixtures) | positive, negative, `UNKNOWN`, and `CONFLICTED` records with expected results | +| [`tests/test_zero_identity.py`](tests/test_zero_identity.py) | validation suite, one test per blocked inference | +| [`run_validation.py`](run_validation.py) | pytest-free runner for the same fixtures | + +## Running it + +```bash +python experiments/zizk_vstd/zero_identity/run_validation.py +python -m pytest experiments/zizk_vstd/zero_identity/tests -q +``` + +The repository suite (`python -m pytest -q`) sets `testpaths = ["tests"]` and does not +collect this directory, which is deliberate: an experiment must not gate conformance. + +## Constraints observed + +- No dependency added to `verifier-standard`; the evaluator is standard library only. +- No frozen wire identifier, schema `$id`, receipt digest, console alias, lifecycle token, + or conformance behavior is touched. See + [`../../../standard/WIRE_IDENTIFIERS.md`](../../../standard/WIRE_IDENTIFIERS.md). +- No cryptographic guarantee is invented. Signature and revocation results enter as + asserted evidence produced by some real protocol named by the deployment, and the model + decides only what may be concluded from them. +- `UNKNOWN` and `CONFLICTED` are preserved as results, per + [`../../../AGENTS.md`](../../../AGENTS.md) section 2. diff --git a/experiments/zizk_vstd/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md b/experiments/zizk_vstd/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md new file mode 100644 index 0000000..97b9c02 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md @@ -0,0 +1,165 @@ +# Round 1 report: zero identity in a ZIZK-VSTD profile + +**Status:** experimental result. Non-normative. No adoption is claimed or implied. + +## 1. Coordinates + +- Base commit: `598c545be3833d6d81bb7e252ca5837f3bb2a449` +- Branch: `claude/zizk-zero-identity` +- Worktree: `verifier-worktrees/zizk-zi-claude` (isolated; the primary checkout and the + separate ZIZK roadmap worktree were not modified) +- Remote: `github.com/TimeLordRaps/verifier` +- Layer: none. This experiment discharges no ladder rung. +- Seam: `experiments/zizk_vstd/zero_identity/` only. + +## 2. Terminology decision + +**"Zero Identity" is rejected as a public label.** It is retained only as the name of the +question this experiment answered, never as a description of what the profile provides. + +The falsification succeeded. A profile that "removes identity" was tested against its own +required coordinates and the requirement survived: bounded reverification needs a +pseudonymous coordinate, a key identifier, a trust root, an issuer, a grant, and a +revocation source. Those are identity coordinates. What is actually removed is *civil* +identity, and removing it changes nothing about correlation, uniqueness, or independence. + +Accepted term: **bounded identity disclosure**. Where a shorter phrase is needed, +*identity minimization* is accurate and *selective disclosure* is accurate only if a real +selective-disclosure protocol is actually deployed. "Anonymous" is rejected outright: the +profile is pseudonymous, and a stable pseudonym is a correlation handle. + +## 3. Identity properties this profile supports + +| Property | Best attainable | Basis | +|---|---|---| +| Authentication | `SUPPORTED` | asserted signature verification against a declared trust root | +| Authorization | `SUPPORTED` | grant covering the claim scope, from an active authority, by an authenticated key | +| Authority liveness | `SUPPORTED` / `REFUTED` | revocation state plus validity window against the evaluation instant | +| Freshness | `SUPPORTED` / `REFUTED` | challenge coordinate and verifier-held nonce history | +| Attribution | `ATTESTED` | binds a pseudonymous coordinate, never a person | +| Accountability | `ATTESTED` | a declared escalation authority that can act on the coordinate | +| Uniqueness / Sybil resistance | `ATTESTED` | only with an attested mechanism; default `UNKNOWN` | +| Verifier independence | `ATTESTED` / `REFUTED` | attested distinct trust roots; refuted by a shared pseudonym | +| Recovery | `ATTESTED` | a declared credential-loss mechanism; strength not evaluated | +| Unlinkability | `ASSUMED` | never `SUPPORTED`; assumptions must be declared | +| Confidentiality | `ASSUMED` | out of scope for the record | +| Civil identity | `UNSUPPORTED_BY_DESIGN` | withheld deliberately | + +`ACCEPTED_BOUNDED` means exactly: this key was authorized for this claim scope at this +instant. It means nothing about who the actor is, whether they are one actor, or whether +two records came from independent actors. + +## 4. Prohibited inferences + +Each is encoded in `model/zero_identity_model.json` and guarded by at least one test: + +1. absent civil identity implies anonymity; +2. absent civil identity implies unlinkability; +3. a pseudonym implies a distinct actor; +4. a shared pseudonym implies a single actor; +5. two distinct pseudonyms imply two independent actors; +6. a verified signature implies authorization; +7. a grant implies currently active authority; +8. absent revocation evidence implies active authority; +9. absent uniqueness evidence implies Sybil resistance; +10. hashing, redaction, encryption, omission, or pseudonymity alone implies zero identity; +11. disclosure minimization preserves the original claim boundary; +12. missing evidence implies safety. + +## 5. Trust roots and revocation dependencies + +The profile does not reduce trust-root dependence; it makes it explicit. A reader who +accepts an `ACCEPTED_BOUNDED` verdict is accepting, at minimum: + +- the issuer named in `authorization.issuer`; +- the trust root named in `actor.key_binding.trust_root`; +- the revocation service named in `revocation.source`, as of `revocation.checked_at`; +- whatever protocol produced `signature_verified`, which this model does not check. + +Revocation is a liveness dependency with a staleness bound, not a one-time check. A +record whose revocation source is absent is `UNKNOWN`; a record whose minimization request +deleted that source is `REJECTED` as unevaluable. Minimization is enforced by deletion +before evaluation, so a withheld coordinate cannot be silently read anyway. + +## 6. Privacy leak analysis + +Retained and observable in every conforming record: the pseudonymous coordinate, the key +identifier, the trust root, the issuer, the scope name, the validity window, the +evaluation instant, and the revocation source. Any two of these are joinable across +records. Publication timing and volume are not addressed at all. + +Consequence: an observer who sees two records under one pseudonym learns they share an +actor coordinate; an observer who sees two records under one issuer learns they share a +root. Withholding civil identity does not weaken either observation. Coercion risk is not +removed either — it moves to the issuer, which still holds the civil binding. This is a +displacement of risk, not a reduction, and the experiment reports it as such. + +## 7. Test results + +Both suites pass at the committed state. + +- `python experiments/zizk_vstd/zero_identity/run_validation.py` — 14 fixtures, 0 failures. +- `python -m pytest experiments/zizk_vstd/zero_identity/tests -q` — 39 passed. +- `python -m pytest -q` (repository suite) — unchanged and passing; the experiment is not + collected, because an experiment must not gate conformance. +- `python scripts/check_presentation.py` — passes. + +Fixture coverage, one per required case: + +| Fixture | Verdict | +|---|---| +| `positive_bounded_authorization` | `ACCEPTED_BOUNDED` | +| `positive_minimized_boundary_narrowed` | `ACCEPTED_BOUNDED` | +| `unknown_missing_authorization` | `UNKNOWN` | +| `unknown_distinct_pseudonyms` | `UNKNOWN` | +| `unknown_uniqueness_absent` | `UNKNOWN` | +| `conflicted_identity_evidence` | `CONFLICTED` | +| `rejected_revoked_authority` | `REJECTED` | +| `rejected_expired_authority` | `REJECTED` | +| `rejected_shared_pseudonym_independence` | `REJECTED` | +| `rejected_unlinkability_erases_trust_root` | `REJECTED` | +| `rejected_replayed_challenge` | `REJECTED` | +| `rejected_missing_challenge` | `REJECTED` | +| `rejected_minimization_widens_boundary` | `REJECTED` | +| `rejected_key_compromise` | `REJECTED` | + +No test failed. No assertion was weakened to obtain a green suite. + +## 8. Unresolved assumptions + +1. `signature_verified` and `revocation.state` are consumed as asserted evidence. No + protocol is bound yet, so no protocol's assumptions have been inherited or checked. +2. Attestation quality is unmodelled. `ATTESTED` records that someone said so. +3. Nonce history is verifier-held state that this model does not carry; replay detection + is only as good as that history. +4. No selective-disclosure or unlinkable-presentation scheme has been selected. Until one + is named, `unlinkability` stays `ASSUMED` at best. +5. Timing and volume side channels are out of scope and unmitigated. +6. Whether an issuer that grants many coordinates to one operator can be detected at all + from published records is open, and probably not decidable within one record. +7. Whether this profile should ever become normative is not decided here. Nothing in this + round argues that it should. + +## 9. Public claims currently justified + +- "Civil identity can be withheld while authorization for a bounded claim scope stays + reverifiable against a declared trust root." +- "Missing identity evidence yields `UNKNOWN`; conflicting identity evidence yields + `CONFLICTED`; revoked or expired authority yields a refutation." +- "The experiment enumerates the identity coordinates that remain, rather than implying + none remain." +- "The evaluator adds no dependency and touches no frozen wire identifier." + +## 10. Public claims still prohibited + +- "VSTD supports zero identity", or any use of "zero identity" without the qualification + that civil identity alone is withheld. +- "Anonymous", "untraceable", "uncorrelatable", or "privacy-preserving" as unqualified + descriptions of this profile. +- Any claim that hashing, redaction, encryption, omission, or a pseudonym provides + unlinkability. +- Any claim of Sybil resistance, actor uniqueness, or verifier independence that is not + backed by named attested evidence. +- Any claim that a zero-knowledge proof system is used, implemented, or relied upon. None + is present in this experiment. +- Any statement that this profile is production-ready, adopted, reviewed, or standardised. diff --git a/experiments/zizk_vstd/zero_identity/SEMANTIC_MODEL.md b/experiments/zizk_vstd/zero_identity/SEMANTIC_MODEL.md new file mode 100644 index 0000000..6dda00e --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/SEMANTIC_MODEL.md @@ -0,0 +1,92 @@ +# Semantic model: bounded identity disclosure + +**Status:** experimental, non-normative. No wire identifier, no schema route, no receipt digest. + +This document defines what the experiment means by each identity-adjacent term, which +properties a record can support, and which inferences are prohibited. The executable +form is [`model/zero_identity_model.json`](model/zero_identity_model.json) and +[`evaluate.py`](evaluate.py); where prose and code disagree, the code plus its fixtures +are the artifact under test and this document is the defect. + +## 1. Separated terms + +These are distinct properties. None implies another. + +| Term | Meaning here | Profile position | +|---|---|---| +| Civil or legal identity | a natural or legal person recognised by a jurisdiction | withheld; `UNSUPPORTED_BY_DESIGN` | +| Persistent public identity | a durable public name reused across contexts | out of scope; the profile uses a pseudonymous coordinate instead | +| Key or credential coordinate | `key_id`, its trust root, and the grant that references it | required | +| Authentication | evidence that a given key signed the record | evaluable, may be `SUPPORTED` | +| Authorization | evidence that the signer was permitted this claim scope | evaluable, may be `SUPPORTED` | +| Accountability | a named authority that can act on the pseudonymous coordinate | at best `ATTESTED` | +| Attribution | binding a record to a pseudonymous coordinate, never to a person | at best `ATTESTED` | +| Uniqueness / Sybil resistance | evidence that one coordinate corresponds to one actor | at best `ATTESTED`, default `UNKNOWN` | +| Verifier independence | evidence that two receipts came from actors that do not share a root | at best `ATTESTED`, refutable | +| Revocation and expiry | current liveness of a grant | evaluable, refutable | +| Confidentiality | protection of the record in transit and at rest | out of scope, at best `ASSUMED` | +| Unlinkability | inability of an observer to join two records to one actor | never `SUPPORTED`, at best `ASSUMED` | +| Anonymity / pseudonymity | absence of any actor coordinate versus a stable non-civil one | the profile is pseudonymous, never anonymous | + +## 2. Statuses + +`SUPPORTED` — decided from coordinates present in the record under stated rules. +`ATTESTED` — an external party asserts it; the assertion is recorded, not checked here. +`ASSUMED` — declared by the record as an assumption, carried forward as an assumption. +`UNKNOWN` — the coordinate needed to decide is absent. This is a result, not a gap to fill. +`CONFLICTED` — two retained pieces of evidence disagree. Terminal; never resolved by preference. +`REFUTED` — a positive negative result: the property is contradicted by evidence. +`UNSUPPORTED_BY_DESIGN` — the profile deliberately withholds the coordinate. + +Record verdicts are `ACCEPTED_BOUNDED`, `UNKNOWN`, `CONFLICTED`, `REJECTED`, in the +precedence `REJECTED > CONFLICTED > UNKNOWN > ACCEPTED_BOUNDED`. `ACCEPTED_BOUNDED` +asserts exactly one thing: authentication and authorization hold for the declared claim +scope at the declared instant. It asserts nothing about uniqueness, independence, +unlinkability, or the actor behind the coordinate. + +## 3. Minimum public actor coordinates + +Bounded reverification without civil identity needs all of: + +- `actor.pseudonym` — the coordinate a verdict attaches to; +- `actor.key_binding.key_id`, `.signature_verified`, `.trust_root`; +- `authorization.grant_id`, `.issuer`, `.scope`, `.not_before`, `.not_after`; +- `revocation.source`, `.state`, `.checked_at`; +- `trust_roots` — the roots the reader must already accept. + +Remove any of the trust-root coordinates and the dependent property becomes `UNKNOWN`; +remove `revocation.source`, `authorization.issuer`, or `actor.key_binding.trust_root` +under a minimization request and the record is `REJECTED` as unevaluable. Minimization is +enforced, not trusted: `evaluate.py` deletes each withheld coordinate before evaluating, +so a coordinate an actor asked to withhold cannot quietly still be read. + +## 4. Prohibited inferences + +Encoded in the model and each guarded by a test: + +1. Absent civil identity implies anonymity or unlinkability. +2. A pseudonym implies a distinct actor. +3. A shared pseudonym implies a single actor. +4. Two distinct pseudonyms imply two independent actors. +5. A verified signature implies authorization. +6. A grant implies that the authority is currently active. +7. Absent revocation evidence implies active authority. +8. Absent uniqueness evidence implies Sybil resistance. +9. Hashing, redaction, encryption, omission, or pseudonymity alone implies zero identity. +10. Disclosure minimization preserves the original claim boundary. +11. Missing evidence implies safety. + +## 5. Relationship to cryptography + +This model contains no cryptographic construction and asserts no cryptographic guarantee. +`signature_verified`, `state`, and any proof result are *inputs*: a deployment obtains them +from a real protocol and the model decides what may be concluded from them. If a +deployment wants selective disclosure or unlinkable presentation, it must name the actual +scheme it uses, state that scheme's assumptions, and record the outcome as evidence here. +Nothing in this experiment substitutes for that. + +## 6. Relationship to VSTD + +Nothing here changes a frozen wire identifier, a schema `$id`, a console alias, a lifecycle +token, or any conformance behavior. See [`../../../standard/WIRE_IDENTIFIERS.md`](../../../standard/WIRE_IDENTIFIERS.md). +The profile adds no dependency: `evaluate.py` is standard library only. diff --git a/experiments/zizk_vstd/zero_identity/THREAT_MODEL.md b/experiments/zizk_vstd/zero_identity/THREAT_MODEL.md new file mode 100644 index 0000000..302f4bf --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/THREAT_MODEL.md @@ -0,0 +1,48 @@ +# Threat model: bounded identity disclosure + +**Status:** experimental, non-normative. + +Scope: one bounded disclosure record and the conclusions a reader may draw from it. +Out of scope: transport security, storage security, the correctness of any cryptographic +protocol, and the honesty of an issuer's internal process. + +The adversary is assumed to be able to read every published record, to submit records of +their own, to create as many pseudonymous coordinates as an issuer will grant, and to +observe timing and volume of publication. The adversary is not assumed to break signature +schemes; where a key fails, it fails by compromise or misuse, not by cryptanalysis. + +| # | Threat | What the model does | Residual risk | +|---|---|---|---| +| T1 | Correlation across receipts | Records nothing that resolves to civil identity; `unlinkability` is never `SUPPORTED`, at best `ASSUMED` under declared assumptions | Real. A stable pseudonym, a stable key, a stable issuer, and publication timing are all joinable. The model cannot see the adversary's side information | +| T2 | Replay | When `freshness.required` is set, an absent challenge fails closed and a previously observed challenge is `REFUTED` | A verifier that never requires freshness gets `UNKNOWN`, which is honest but not protective. Nonce history must be kept by the verifier | +| T3 | Key compromise | `key_compromised_during_interval` refutes authentication and therefore authorization | The model learns of compromise only when someone reports it. Silent compromise is indistinguishable from normal signing | +| T4 | Revoked or expired authority | Revocation state `revoked`, or an evaluation instant outside the validity window, is `REFUTED`, never `UNKNOWN`; a missing revocation source is `UNKNOWN`, never active | Revocation freshness is bounded by `revocation.checked_at`; the model does not fetch status | +| T5 | One actor presenting as many independent actors | Independence requires attested evidence with distinct trust roots; distinct pseudonyms alone leave it `UNKNOWN` | An issuer that grants many credentials to one operator can produce evidence that looks distinct. Independence is `ATTESTED` at best, never proven here | +| T6 | Many actors sharing one credential | A shared pseudonymous coordinate refutes independence; `uniqueness` stays `UNKNOWN` without attested evidence | The model cannot detect sharing from a single record. Attribution binds a coordinate, never a person | +| T7 | Coerced identity disclosure | The profile never stores civil identity, so there is nothing in the record to coerce out of the verifier | Coercion moves to the issuer, which does hold the binding. This displaces the risk rather than removing it | +| T8 | Metadata and timing leakage | Not mitigated. Declared as out of scope and reported as such | Publication time, volume, scope names, and issuer choice remain observable | +| T9 | Colluding issuers or verifiers | Trust roots must be declared explicitly, so a reader can see that two records share one root | Collusion between a declared issuer and a declared verifier defeats the profile. The model surfaces the shared root; it cannot rule collusion out | +| T10 | Unverifiable claims of independence | `verifier_independence` never becomes `SUPPORTED`; a claim of it that lacks evidence downgrades the record verdict to `UNKNOWN` | Attestation quality is outside the model | +| T11 | Missing authorization | A record with no grant is `UNKNOWN`; it never fails open | A verifier that treats `UNKNOWN` as permission defeats this. The verdict is honest; the deployment must respect it | +| T12 | Recovery after credential loss | `recovery` is `ATTESTED` only when a mechanism is declared, otherwise `UNKNOWN` | Any recovery path is also an impersonation path. The model records that a path exists; it does not evaluate its strength | +| T13 | Privacy laundering through minimization | A minimization request that removes a required trust root makes the record `REJECTED`; a request that widens the claim boundary is `REJECTED` | An actor can still choose to publish less and accept a weaker verdict, which is the intended trade | + +## Falsification conditions + +This experiment is refuted if any of the following can be demonstrated: + +- a record reaches `ACCEPTED_BOUNDED` while any property is `REFUTED`; +- a `CONFLICTED` property is resolved to a favourable status by adding no new evidence; +- `unlinkability` reaches `SUPPORTED`; +- absence of a coordinate produces anything other than `UNKNOWN`, `REFUTED`, or `REJECTED`; +- a minimization request removes a required trust-root coordinate and the record still evaluates. + +The first four are asserted directly in +[`tests/test_zero_identity.py`](tests/test_zero_identity.py); the fifth is +[`fixtures/rejected_unlinkability_erases_trust_root.json`](fixtures/rejected_unlinkability_erases_trust_root.json). + +## What this threat model does not claim + +It does not claim that the profile provides anonymity, that it defeats correlation, or +that it is safe to deploy. It claims only that the evaluator refuses to convert missing +identity information into a favourable conclusion. diff --git a/experiments/zizk_vstd/zero_identity/evaluate.py b/experiments/zizk_vstd/zero_identity/evaluate.py new file mode 100644 index 0000000..96ac99a --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/evaluate.py @@ -0,0 +1,393 @@ +#!/usr/bin/env python3 +"""Experimental evaluator for the ZIZK-VSTD bounded identity disclosure profile. + +Discharges nothing on the VSTD ladder. This module is experimental scaffolding for +the terminology and safety question recorded in ``SEMANTIC_MODEL.md``: it decides +which identity-adjacent properties a bounded disclosure record can support, and it +fails closed everywhere else. + +The evaluator never verifies a signature, a revocation list, or a proof. It consumes +*asserted* evidence coordinates and decides what may be concluded from them. Any +cryptographic verification happens outside this module and enters here as evidence. +""" + +from __future__ import annotations + +import copy +from dataclasses import dataclass, field +import json +from pathlib import Path +from typing import Any + +MODEL_FILE = Path(__file__).resolve().parent / "model" / "zero_identity_model.json" + +SUPPORTED = "SUPPORTED" +ATTESTED = "ATTESTED" +ASSUMED = "ASSUMED" +UNKNOWN = "UNKNOWN" +CONFLICTED = "CONFLICTED" +REFUTED = "REFUTED" +UNSUPPORTED_BY_DESIGN = "UNSUPPORTED_BY_DESIGN" + +ACCEPTED_BOUNDED = "ACCEPTED_BOUNDED" +REJECTED = "REJECTED" + +VERDICT_PRECEDENCE = (REJECTED, CONFLICTED, UNKNOWN, ACCEPTED_BOUNDED) + +REQUIRED_TRUST_ROOT_COORDINATES = ( + "actor.key_binding.trust_root", + "authorization.issuer", + "revocation.source", +) + + +def load_model() -> dict[str, Any]: + """Return the experimental machine-readable model.""" + + return json.loads(MODEL_FILE.read_text(encoding="utf-8")) + + +@dataclass(frozen=True) +class Evaluation: + """Result of evaluating one bounded disclosure record.""" + + verdict: str + properties: dict[str, str] + reasons: list[str] = field(default_factory=list) + + def to_dict(self) -> dict[str, Any]: + return { + "verdict": self.verdict, + "properties": dict(self.properties), + "reasons": list(self.reasons), + } + + +def _get(record: dict[str, Any], dotted: str) -> Any: + node: Any = record + for part in dotted.split("."): + if not isinstance(node, dict) or part not in node: + return None + node = node[part] + return node + + +def _conflicted(record: dict[str, Any], prop: str) -> bool: + for entry in record.get("conflicts", []) or []: + if entry.get("property") == prop: + return True + return False + + +def _evaluate_civil_identity(record: dict[str, Any], reasons: list[str]) -> str: + if _conflicted(record, "civil_identity"): + reasons.append("civil_identity: conflicting evidence retained as CONFLICTED") + return CONFLICTED + disclosed = _get(record, "actor.civil_identity") + if disclosed not in (None, "withheld"): + reasons.append("civil_identity: a disclosed value is outside this profile") + return CONFLICTED + reasons.append( + "civil_identity: withheld by profile; absence is neither anonymity nor unlinkability" + ) + return UNSUPPORTED_BY_DESIGN + + +def _evaluate_authentication(record: dict[str, Any], reasons: list[str]) -> str: + if _conflicted(record, "authentication"): + reasons.append("authentication: conflicting evidence retained as CONFLICTED") + return CONFLICTED + binding = _get(record, "actor.key_binding") + if not isinstance(binding, dict): + reasons.append("authentication: no key binding coordinate") + return UNKNOWN + if binding.get("key_compromised_during_interval") is True: + reasons.append("authentication: signing key reported compromised for the interval") + return REFUTED + verified = binding.get("signature_verified") + if verified is False: + reasons.append("authentication: asserted signature verification failed") + return REFUTED + if verified is not True: + reasons.append("authentication: signature verification result absent") + return UNKNOWN + root = binding.get("trust_root") + if root not in (record.get("trust_roots") or []): + reasons.append("authentication: key trust root is not among the declared trust roots") + return UNKNOWN + return SUPPORTED + + +def _evaluate_authority_active(record: dict[str, Any], reasons: list[str]) -> str: + if _conflicted(record, "authority_active"): + reasons.append("authority_active: conflicting evidence retained as CONFLICTED") + return CONFLICTED + grant = record.get("authorization") + revocation = record.get("revocation") + if not isinstance(grant, dict): + reasons.append("authority_active: no authorization grant to evaluate") + return UNKNOWN + if not isinstance(revocation, dict) or not revocation.get("source"): + reasons.append("authority_active: no revocation source; absence is not liveness") + return UNKNOWN + state = revocation.get("state") + if state == "revoked": + reasons.append("authority_active: authority is revoked") + return REFUTED + if state != "active": + reasons.append("authority_active: revocation state is not asserted active") + return UNKNOWN + evaluated_at = record.get("evaluated_at") + not_before = grant.get("not_before") + not_after = grant.get("not_after") + if not (evaluated_at and not_before and not_after): + reasons.append("authority_active: validity window or evaluation instant absent") + return UNKNOWN + if not (not_before <= evaluated_at <= not_after): + reasons.append("authority_active: evaluation instant is outside the validity window") + return REFUTED + if not revocation.get("checked_at"): + reasons.append("authority_active: revocation check instant absent") + return UNKNOWN + return SUPPORTED + + +def _evaluate_authorization( + record: dict[str, Any], authentication: str, authority: str, reasons: list[str] +) -> str: + if _conflicted(record, "authorization"): + reasons.append("authorization: conflicting evidence retained as CONFLICTED") + return CONFLICTED + grant = record.get("authorization") + if not isinstance(grant, dict) or not grant.get("grant_id"): + reasons.append("authorization: no grant coordinate; missing authorization stays UNKNOWN") + return UNKNOWN + if authority == REFUTED: + reasons.append("authorization: refuted because the authority is not active") + return REFUTED + if authentication == REFUTED: + reasons.append("authorization: refuted because authentication is refuted") + return REFUTED + if authentication != SUPPORTED or authority != SUPPORTED: + reasons.append("authorization: preconditions are not both SUPPORTED") + return UNKNOWN + scope = grant.get("scope") or [] + claim_scope = record.get("claim_scope") + if not claim_scope: + reasons.append("authorization: record declares no claim scope to cover") + return UNKNOWN + if claim_scope not in scope: + reasons.append("authorization: grant scope does not cover the claim scope") + return REFUTED + return SUPPORTED + + +def _evaluate_freshness(record: dict[str, Any], reasons: list[str]) -> str: + freshness = record.get("freshness") or {} + if not freshness.get("required"): + reasons.append("freshness: not required by this record; replay is not excluded") + return UNKNOWN + nonce = freshness.get("nonce") + if not nonce or not freshness.get("challenge_source"): + reasons.append("freshness: required but the challenge coordinate is absent; fails closed") + return REFUTED + if nonce in (freshness.get("previously_observed_nonces") or []): + reasons.append("freshness: challenge value was previously observed; replay detected") + return REFUTED + return SUPPORTED + + +def _evaluate_uniqueness(record: dict[str, Any], reasons: list[str]) -> str: + if _conflicted(record, "uniqueness"): + reasons.append("uniqueness: conflicting evidence retained as CONFLICTED") + return CONFLICTED + evidence = record.get("uniqueness_evidence") or [] + if not [entry for entry in evidence if entry.get("attested_by")]: + reasons.append( + "uniqueness: no attested mechanism; absence does not imply Sybil resistance" + ) + return UNKNOWN + return ATTESTED + + +def _evaluate_independence(record: dict[str, Any], reasons: list[str]) -> str: + if _conflicted(record, "verifier_independence"): + reasons.append("verifier_independence: conflicting evidence retained as CONFLICTED") + return CONFLICTED + peers = record.get("peer_receipts") or [] + if not peers: + reasons.append("verifier_independence: no peer receipt to compare; independence UNKNOWN") + return UNKNOWN + own = _get(record, "actor.pseudonym") + for peer in peers: + if peer.get("pseudonym") == own: + reasons.append( + "verifier_independence: peer shares this pseudonymous coordinate; not independent" + ) + return REFUTED + evidence = record.get("independence_evidence") or [] + attested = [ + entry + for entry in evidence + if entry.get("attested_by") and entry.get("distinct_trust_root") + ] + if not attested: + reasons.append( + "verifier_independence: distinct pseudonyms are not evidence of distinct actors" + ) + return UNKNOWN + return ATTESTED + + +def _evaluate_unlinkability(record: dict[str, Any], reasons: list[str]) -> str: + request = record.get("disclosure_minimization") or {} + if not request: + reasons.append("unlinkability: not requested") + return UNKNOWN + if not request.get("declared_assumptions"): + reasons.append("unlinkability: requested without declared assumptions") + return UNKNOWN + reasons.append( + "unlinkability: ASSUMED under declared assumptions only; this model cannot observe " + "the correlation surface available to an adversary" + ) + return ASSUMED + + +def _evaluate_accountability(record: dict[str, Any], reasons: list[str]) -> str: + if not record.get("escalation_authority"): + reasons.append("accountability: no escalation authority bound to the pseudonym") + return UNKNOWN + return ATTESTED + + +def _evaluate_recovery(record: dict[str, Any], reasons: list[str]) -> str: + recovery = record.get("recovery") or {} + if not recovery.get("mechanism"): + reasons.append("recovery: no credential-loss recovery mechanism declared") + return UNKNOWN + return ATTESTED + + +def _apply_minimization(record: dict[str, Any]) -> dict[str, Any]: + """Return a copy of the record with every withheld coordinate actually removed. + + Minimization is enforced rather than trusted: a coordinate the actor asked to + withhold is deleted before evaluation, so a removed trust root really does make + the dependent property unevaluable instead of quietly remaining available. + """ + + request = record.get("disclosure_minimization") or {} + withheld = request.get("withheld_coordinates") or [] + if not withheld: + return record + reduced = copy.deepcopy(record) + for dotted in withheld: + parts = dotted.split(".") + node: Any = reduced + for part in parts[:-1]: + if not isinstance(node, dict) or part not in node: + node = None + break + node = node[part] + if isinstance(node, dict): + node.pop(parts[-1], None) + return reduced + + +def _check_structural_rejections(record: dict[str, Any], reasons: list[str]) -> list[str]: + """Return the reasons that make a record unevaluable, that is, REJECTED outright.""" + + fatal: list[str] = [] + request = record.get("disclosure_minimization") or {} + withheld = set(request.get("withheld_coordinates") or []) + for coordinate in REQUIRED_TRUST_ROOT_COORDINATES: + if coordinate in withheld: + fatal.append( + f"minimization removed required trust-root coordinate {coordinate}; " + "an unlinkability request does not erase trust roots" + ) + before = request.get("claim_boundary_before") + after = request.get("claim_boundary_after") + if before is not None and after is not None: + before_set = set(before if isinstance(before, list) else [before]) + after_set = set(after if isinstance(after, list) else [after]) + if not after_set <= before_set: + fatal.append( + "minimization widened the claim boundary; minimization may only narrow it" + ) + reasons.extend(fatal) + return fatal + + +def evaluate(record: dict[str, Any]) -> Evaluation: + """Evaluate one bounded disclosure record, failing closed on missing coordinates.""" + + reasons: list[str] = [] + record = _apply_minimization(record) + fatal = _check_structural_rejections(record, reasons) + + properties: dict[str, str] = {} + properties["civil_identity"] = _evaluate_civil_identity(record, reasons) + properties["authentication"] = _evaluate_authentication(record, reasons) + properties["authority_active"] = _evaluate_authority_active(record, reasons) + properties["authorization"] = _evaluate_authorization( + record, properties["authentication"], properties["authority_active"], reasons + ) + properties["freshness"] = _evaluate_freshness(record, reasons) + properties["uniqueness"] = _evaluate_uniqueness(record, reasons) + properties["verifier_independence"] = _evaluate_independence(record, reasons) + properties["unlinkability"] = _evaluate_unlinkability(record, reasons) + properties["accountability"] = _evaluate_accountability(record, reasons) + properties["recovery"] = _evaluate_recovery(record, reasons) + + if fatal: + return Evaluation(REJECTED, properties, reasons) + + values = set(properties.values()) + if REFUTED in values: + verdict = REJECTED + elif CONFLICTED in values: + verdict = CONFLICTED + elif properties["authorization"] == SUPPORTED and properties["authentication"] == SUPPORTED: + verdict = ACCEPTED_BOUNDED + else: + verdict = UNKNOWN + + unmet = [ + name + for name in (record.get("claimed_properties") or []) + if properties.get(name, UNKNOWN) not in (SUPPORTED, ATTESTED) + ] + if unmet and verdict == ACCEPTED_BOUNDED: + reasons.append( + "verdict: claimed properties " + + ", ".join(sorted(unmet)) + + " are not supported; the record stays UNKNOWN rather than widening" + ) + verdict = UNKNOWN + return Evaluation(verdict, properties, reasons) + + +def evaluate_file(path: Path) -> Evaluation: + """Evaluate the ``record`` object stored in a fixture file.""" + + fixture = json.loads(Path(path).read_text(encoding="utf-8")) + return evaluate(fixture["record"]) + + +def main(argv: list[str] | None = None) -> int: + import sys + + args = list(sys.argv[1:] if argv is None else argv) + if not args: + print("usage: evaluate.py FIXTURE [FIXTURE ...]") + return 2 + for raw in args: + result = evaluate_file(Path(raw)) + print(json.dumps({"fixture": raw, **result.to_dict()}, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/zizk_vstd/zero_identity/fixtures/conflicted_identity_evidence.json b/experiments/zizk_vstd/zero_identity/fixtures/conflicted_identity_evidence.json new file mode 100644 index 0000000..bac8e14 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/conflicted_identity_evidence.json @@ -0,0 +1,77 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "civil_identity": "CONFLICTED", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "CONFLICTED" + }, + "falsification_question": "Can a conflict be resolved by preferring the convenient source?", + "fixture_id": "conflicted_identity_evidence", + "intent": "Conflicting identity evidence is retained as CONFLICTED.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [ + { + "evidence": [ + "issuer directory binds this pseudonym to one subject", + "operator attestation binds the same pseudonym to a different subject" + ], + "property": "civil_identity" + } + ], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-conflicted", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/positive_bounded_authorization.json b/experiments/zizk_vstd/zero_identity/fixtures/positive_bounded_authorization.json new file mode 100644 index 0000000..c42bdd5 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/positive_bounded_authorization.json @@ -0,0 +1,69 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "ACCEPTED_BOUNDED" + }, + "falsification_question": "Does withholding civil identity remove the ability to reverify authorization?", + "fixture_id": "positive_bounded_authorization", + "intent": "Civil identity is withheld while a bounded authorization coordinate stays verifiable.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-positive-1", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/positive_minimized_boundary_narrowed.json b/experiments/zizk_vstd/zero_identity/fixtures/positive_minimized_boundary_narrowed.json new file mode 100644 index 0000000..5d85e9a --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/positive_minimized_boundary_narrowed.json @@ -0,0 +1,85 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "ASSUMED", + "verifier_independence": "UNKNOWN" + }, + "verdict": "ACCEPTED_BOUNDED" + }, + "falsification_question": "Does narrowing disclosure silently weaken the retained claim?", + "fixture_id": "positive_minimized_boundary_narrowed", + "intent": "Minimization that narrows the boundary keeps the bounded authorization result.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "disclosure_minimization": { + "claim_boundary_after": [ + "vstd4-refutation-run" + ], + "claim_boundary_before": [ + "vstd4-refutation-run", + "vstd4-availability-run" + ], + "declared_assumptions": [ + "issuer does not collude with the verifier" + ], + "requested_by": "actor", + "withheld_coordinates": [ + "actor.civil_identity" + ] + }, + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-minimized-narrowed", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_expired_authority.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_expired_authority.json new file mode 100644 index 0000000..0fa4af3 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_expired_authority.json @@ -0,0 +1,69 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "REFUTED", + "authorization": "REFUTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "REJECTED" + }, + "falsification_question": "Does an expired window silently remain usable?", + "fixture_id": "rejected_expired_authority", + "intent": "An evaluation instant outside the validity window refutes the authority.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2027-02-01T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-expired", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2027-02-01T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_key_compromise.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_key_compromise.json new file mode 100644 index 0000000..fdcf4ea --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_key_compromise.json @@ -0,0 +1,70 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "REFUTED", + "authority_active": "SUPPORTED", + "authorization": "REFUTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "REJECTED" + }, + "falsification_question": "Does a syntactically valid signature survive key compromise?", + "fixture_id": "rejected_key_compromise", + "intent": "A key reported compromised for the signing interval refutes authentication.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_compromised_during_interval": true, + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-key-compromise", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_minimization_widens_boundary.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_minimization_widens_boundary.json new file mode 100644 index 0000000..a98c181 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_minimization_widens_boundary.json @@ -0,0 +1,85 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "ASSUMED", + "verifier_independence": "UNKNOWN" + }, + "verdict": "REJECTED" + }, + "falsification_question": "Can redaction be used to enlarge what a receipt asserts?", + "fixture_id": "rejected_minimization_widens_boundary", + "intent": "Disclosure minimization may only narrow the claim boundary.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "disclosure_minimization": { + "claim_boundary_after": [ + "vstd4-refutation-run", + "vstd4-availability-run" + ], + "claim_boundary_before": [ + "vstd4-refutation-run" + ], + "declared_assumptions": [ + "issuer does not collude with the verifier" + ], + "requested_by": "actor", + "withheld_coordinates": [ + "actor.civil_identity" + ] + }, + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-boundary-widened", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_missing_challenge.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_missing_challenge.json new file mode 100644 index 0000000..08843eb --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_missing_challenge.json @@ -0,0 +1,67 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "REFUTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "REJECTED" + }, + "falsification_question": "Does an absent nonce read as freshness?", + "fixture_id": "rejected_missing_challenge", + "intent": "Required freshness with no challenge coordinate fails closed.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-missing-challenge", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_replayed_challenge.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_replayed_challenge.json new file mode 100644 index 0000000..e4a97fb --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_replayed_challenge.json @@ -0,0 +1,71 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "REFUTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "REJECTED" + }, + "falsification_question": "Is a reused challenge indistinguishable from a fresh one?", + "fixture_id": "rejected_replayed_challenge", + "intent": "A previously observed challenge value is a detected replay.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [ + "challenge:0001" + ], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-replay", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_revoked_authority.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_revoked_authority.json new file mode 100644 index 0000000..8b883a9 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_revoked_authority.json @@ -0,0 +1,69 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "REFUTED", + "authorization": "REFUTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "REJECTED" + }, + "falsification_question": "Can a revoked grant still be treated as active?", + "fixture_id": "rejected_revoked_authority", + "intent": "Revoked authority is refuted rather than degraded to UNKNOWN.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-revoked", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "revoked" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_shared_pseudonym_independence.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_shared_pseudonym_independence.json new file mode 100644 index 0000000..5c01037 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_shared_pseudonym_independence.json @@ -0,0 +1,74 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "REFUTED" + }, + "verdict": "REJECTED" + }, + "falsification_question": "Does a repeated pseudonym supply independent corroboration?", + "fixture_id": "rejected_shared_pseudonym_independence", + "intent": "Two receipts under one pseudonymous coordinate are not independent.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "verifier_independence" + ], + "conflicts": [], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [ + { + "pseudonym": "pseudonym:alpha", + "receipt_id": "peer:1" + } + ], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-independence-shared", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_unlinkability_erases_trust_root.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_unlinkability_erases_trust_root.json new file mode 100644 index 0000000..5f73689 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_unlinkability_erases_trust_root.json @@ -0,0 +1,84 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "UNKNOWN", + "authorization": "UNKNOWN", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "ASSUMED", + "verifier_independence": "UNKNOWN" + }, + "verdict": "REJECTED" + }, + "falsification_question": "Can privacy be bought by deleting the revocation source?", + "fixture_id": "rejected_unlinkability_erases_trust_root", + "intent": "An unlinkability request may not remove a required trust-root coordinate.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "disclosure_minimization": { + "claim_boundary_after": [ + "vstd4-refutation-run" + ], + "claim_boundary_before": [ + "vstd4-refutation-run" + ], + "declared_assumptions": [ + "issuer does not collude with the verifier" + ], + "requested_by": "actor", + "withheld_coordinates": [ + "revocation.source" + ] + }, + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-minimization-trust-root", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/unknown_distinct_pseudonyms.json b/experiments/zizk_vstd/zero_identity/fixtures/unknown_distinct_pseudonyms.json new file mode 100644 index 0000000..1f84fff --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/unknown_distinct_pseudonyms.json @@ -0,0 +1,74 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "UNKNOWN" + }, + "falsification_question": "Do two pseudonyms establish two actors?", + "fixture_id": "unknown_distinct_pseudonyms", + "intent": "Two distinct pseudonyms are not evidence of two distinct actors.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "verifier_independence" + ], + "conflicts": [], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [ + { + "pseudonym": "pseudonym:beta", + "receipt_id": "peer:2" + } + ], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-independence-distinct", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/unknown_missing_authorization.json b/experiments/zizk_vstd/zero_identity/fixtures/unknown_missing_authorization.json new file mode 100644 index 0000000..cdb4d8b --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/unknown_missing_authorization.json @@ -0,0 +1,60 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "UNKNOWN", + "authorization": "UNKNOWN", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "UNKNOWN" + }, + "falsification_question": "Can a missing grant be read as permission?", + "fixture_id": "unknown_missing_authorization", + "intent": "A record with no authorization grant stays UNKNOWN and never fails open.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-unknown-authorization", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/unknown_uniqueness_absent.json b/experiments/zizk_vstd/zero_identity/fixtures/unknown_uniqueness_absent.json new file mode 100644 index 0000000..2f5112e --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/unknown_uniqueness_absent.json @@ -0,0 +1,69 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "UNKNOWN" + }, + "falsification_question": "Does the absence of duplicates prove there are none?", + "fixture_id": "unknown_uniqueness_absent", + "intent": "Absent uniqueness evidence does not imply Sybil resistance.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "uniqueness" + ], + "conflicts": [], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-uniqueness-absent", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/model/zero_identity_model.json b/experiments/zizk_vstd/zero_identity/model/zero_identity_model.json new file mode 100644 index 0000000..7035792 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/model/zero_identity_model.json @@ -0,0 +1,134 @@ +{ + "model_id": "zizk-vstd/zero-identity/experimental-0", + "status": "EXPERIMENTAL", + "normative": false, + "wire_identifier": null, + "notes": [ + "This model is experimental and carries no wire identifier, no schema $id route, and no receipt digest.", + "It does not alter, extend, or reinterpret any frozen VSTD wire identifier.", + "Nothing in this model asserts a cryptographic guarantee. Cryptographic mechanisms named here are inputs whose verification is performed elsewhere and asserted as evidence." + ], + "terminology_decision": { + "public_label_zero_identity": "REJECTED_AS_UNQUALIFIED_PUBLIC_LABEL", + "accepted_label": "bounded identity disclosure", + "rationale": "The profile never removes identity; it withholds civil identity while retaining cryptographic and authorization coordinates. Calling that 'zero identity' overstates the privacy property and understates the retained coordinates." + }, + "identity_dimensions": [ + "civil_identity", + "persistent_public_identity", + "key_or_credential_coordinate", + "authentication", + "authorization", + "accountability", + "attribution", + "uniqueness", + "verifier_independence", + "revocation_or_expiry", + "confidentiality", + "unlinkability", + "anonymity_or_pseudonymity" + ], + "property_statuses": [ + "SUPPORTED", + "ATTESTED", + "ASSUMED", + "UNKNOWN", + "CONFLICTED", + "REFUTED", + "UNSUPPORTED_BY_DESIGN" + ], + "verdicts": ["ACCEPTED_BOUNDED", "UNKNOWN", "CONFLICTED", "REJECTED"], + "verdict_precedence": ["REJECTED", "CONFLICTED", "UNKNOWN", "ACCEPTED_BOUNDED"], + "minimum_public_actor_coordinates": [ + "actor.pseudonym", + "actor.key_binding.key_id", + "actor.key_binding.signature_verified", + "actor.key_binding.trust_root", + "authorization.grant_id", + "authorization.issuer", + "authorization.scope", + "authorization.not_before", + "authorization.not_after", + "revocation.source", + "revocation.state", + "revocation.checked_at", + "trust_roots" + ], + "prohibited_inferences": [ + "absent civil identity implies anonymity", + "absent civil identity implies unlinkability", + "a pseudonym implies a distinct actor", + "a shared pseudonym implies a single actor", + "two distinct pseudonyms imply two independent actors", + "a verified signature implies authorization", + "an authorization grant implies that authority is currently active", + "absent revocation evidence implies active authority", + "absent uniqueness evidence implies Sybil resistance", + "hashing, redaction, encryption, omission, or pseudonymity alone implies zero identity", + "disclosure minimization preserves the original claim boundary", + "missing evidence implies safety" + ], + "properties": { + "civil_identity": { + "profile_intent": "withheld", + "attainable_statuses": ["UNSUPPORTED_BY_DESIGN", "CONFLICTED"] + }, + "authentication": { + "attainable_statuses": ["SUPPORTED", "REFUTED", "UNKNOWN", "CONFLICTED"], + "requires": ["actor.key_binding.signature_verified", "resolvable trust_root"] + }, + "authority_active": { + "attainable_statuses": ["SUPPORTED", "REFUTED", "UNKNOWN", "CONFLICTED"], + "requires": ["revocation.state", "revocation.source", "validity window containing evaluated_at"] + }, + "authorization": { + "attainable_statuses": ["SUPPORTED", "REFUTED", "UNKNOWN", "CONFLICTED"], + "requires": ["authentication SUPPORTED", "authority_active SUPPORTED", "scope covers claim_scope"] + }, + "attribution": { + "attainable_statuses": ["ATTESTED", "UNKNOWN", "CONFLICTED"], + "bound_to": "pseudonymous coordinate only, never civil identity" + }, + "uniqueness": { + "attainable_statuses": ["ATTESTED", "UNKNOWN", "CONFLICTED"], + "default_when_absent": "UNKNOWN" + }, + "verifier_independence": { + "attainable_statuses": ["ATTESTED", "REFUTED", "UNKNOWN", "CONFLICTED"], + "default_when_absent": "UNKNOWN" + }, + "freshness": { + "attainable_statuses": ["SUPPORTED", "REFUTED", "UNKNOWN"], + "fail_closed_when_required_and_absent": true + }, + "unlinkability": { + "attainable_statuses": ["ASSUMED", "UNKNOWN", "REFUTED"], + "never": "SUPPORTED", + "reason": "This model observes one record at a time and cannot observe the adversary's full correlation surface." + }, + "accountability": { + "attainable_statuses": ["ATTESTED", "UNKNOWN"], + "requires": ["a named escalation authority that can act on the pseudonymous coordinate"] + }, + "confidentiality": { + "attainable_statuses": ["ASSUMED", "UNKNOWN"], + "reason": "Transport and storage confidentiality are outside this record." + }, + "recovery": { + "attainable_statuses": ["ATTESTED", "UNKNOWN"], + "default_when_absent": "UNKNOWN" + } + }, + "rules": [ + {"id": "ZI-R1", "statement": "A missing coordinate yields UNKNOWN, never a favourable status."}, + {"id": "ZI-R2", "statement": "CONFLICTED is terminal for the property and propagates to the record verdict."}, + {"id": "ZI-R3", "statement": "Revoked or expired authority is REFUTED, never UNKNOWN."}, + {"id": "ZI-R4", "statement": "A shared pseudonymous coordinate across two receipts REFUTES independence."}, + {"id": "ZI-R5", "statement": "Distinct pseudonymous coordinates leave both independence and actor-distinctness UNKNOWN."}, + {"id": "ZI-R6", "statement": "A minimization request that removes a required trust-root coordinate makes the record unevaluable and is REJECTED."}, + {"id": "ZI-R7", "statement": "When freshness is required, an absent challenge coordinate fails closed and a replayed challenge is REFUTED."}, + {"id": "ZI-R8", "statement": "A claim boundary may only narrow under minimization; widening is REJECTED."}, + {"id": "ZI-R9", "statement": "A key marked compromised for the signing interval REFUTES authentication."}, + {"id": "ZI-R10", "statement": "unlinkability is never SUPPORTED by this model; at best it is ASSUMED under declared assumptions."} + ] +} diff --git a/experiments/zizk_vstd/zero_identity/run_validation.py b/experiments/zizk_vstd/zero_identity/run_validation.py new file mode 100644 index 0000000..8b77e9d --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/run_validation.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Run the complete validation suite for this experiment. + +Uses the standard library only, so it runs without pytest. When pytest is present, +``python -m pytest experiments/zizk_vstd/zero_identity/tests -q`` runs the same +fixtures plus the inference-blocking assertions. +""" + +from __future__ import annotations + +import json +from pathlib import Path +import sys + +HERE = Path(__file__).resolve().parent +if str(HERE) not in sys.path: + sys.path.insert(0, str(HERE)) + +from evaluate import evaluate # noqa: E402 + + +def main() -> int: + failures: list[str] = [] + fixtures = sorted((HERE / "fixtures").glob("*.json")) + if not fixtures: + print("no fixtures found") + return 1 + for path in fixtures: + fixture = json.loads(path.read_text(encoding="utf-8")) + outcome = evaluate(fixture["record"]) + expected = fixture["expected"] + if outcome.verdict != expected["verdict"]: + failures.append( + f"{path.name}: verdict {outcome.verdict} != {expected['verdict']}" + ) + for name, want in expected["properties"].items(): + got = outcome.properties.get(name) + if got != want: + failures.append(f"{path.name}: {name} {got} != {want}") + print(f"{outcome.verdict:<17} {path.stem}") + for failure in failures: + print(f"FAIL {failure}") + print(f"{len(fixtures)} fixtures, {len(failures)} failures") + return 1 if failures else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/zizk_vstd/zero_identity/tests/test_zero_identity.py b/experiments/zizk_vstd/zero_identity/tests/test_zero_identity.py new file mode 100644 index 0000000..bddca70 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/tests/test_zero_identity.py @@ -0,0 +1,205 @@ +"""Validation suite for the experimental bounded identity disclosure profile. + +Each test names the inference it exists to block. A test that starts passing because +a status was upgraded to something more favourable is a defect, not a fix. +""" + +from __future__ import annotations + +import json +from pathlib import Path +import sys + +import pytest + +EXPERIMENT = Path(__file__).resolve().parents[1] +if str(EXPERIMENT) not in sys.path: + sys.path.insert(0, str(EXPERIMENT)) + +from evaluate import ( # noqa: E402 + ACCEPTED_BOUNDED, + ATTESTED, + CONFLICTED, + REFUTED, + REJECTED, + SUPPORTED, + UNKNOWN, + evaluate, + load_model, +) + +FIXTURES = sorted((EXPERIMENT / "fixtures").glob("*.json")) + + +def load(name: str) -> dict: + return json.loads((EXPERIMENT / "fixtures" / f"{name}.json").read_text(encoding="utf-8")) + + +def result(name: str): + return evaluate(load(name)["record"]) + + +def test_fixture_corpus_is_non_empty() -> None: + assert FIXTURES, "the fixture corpus must not be empty" + + +@pytest.mark.parametrize("path", FIXTURES, ids=lambda p: p.stem) +def test_fixture_matches_declared_expectation(path: Path) -> None: + fixture = json.loads(path.read_text(encoding="utf-8")) + outcome = evaluate(fixture["record"]) + assert outcome.verdict == fixture["expected"]["verdict"] + assert outcome.properties == fixture["expected"]["properties"] + assert outcome.reasons, "every evaluation must carry at least one stated reason" + + +def test_civil_identity_withheld_keeps_authorization_verifiable() -> None: + outcome = result("positive_bounded_authorization") + assert outcome.verdict == ACCEPTED_BOUNDED + assert outcome.properties["civil_identity"] == "UNSUPPORTED_BY_DESIGN" + assert outcome.properties["authorization"] == SUPPORTED + + +def test_bounded_acceptance_does_not_imply_uniqueness_or_independence() -> None: + outcome = result("positive_bounded_authorization") + assert outcome.properties["uniqueness"] == UNKNOWN + assert outcome.properties["verifier_independence"] == UNKNOWN + assert outcome.properties["unlinkability"] == UNKNOWN + + +def test_missing_authorization_stays_unknown() -> None: + outcome = result("unknown_missing_authorization") + assert outcome.verdict == UNKNOWN + assert outcome.properties["authorization"] == UNKNOWN + + +def test_revoked_authority_is_refuted_not_unknown() -> None: + outcome = result("rejected_revoked_authority") + assert outcome.verdict == REJECTED + assert outcome.properties["authority_active"] == REFUTED + + +def test_expired_authority_is_refuted() -> None: + outcome = result("rejected_expired_authority") + assert outcome.properties["authority_active"] == REFUTED + + +def test_shared_pseudonym_refutes_independence() -> None: + outcome = result("rejected_shared_pseudonym_independence") + assert outcome.properties["verifier_independence"] == REFUTED + assert outcome.verdict == REJECTED + + +def test_distinct_pseudonyms_do_not_establish_distinct_actors() -> None: + outcome = result("unknown_distinct_pseudonyms") + assert outcome.properties["verifier_independence"] == UNKNOWN + assert outcome.verdict == UNKNOWN + + +def test_minimization_cannot_delete_a_required_trust_root() -> None: + outcome = result("rejected_unlinkability_erases_trust_root") + assert outcome.verdict == REJECTED + assert outcome.properties["authority_active"] == UNKNOWN + assert any("trust-root" in reason for reason in outcome.reasons) + + +def test_replayed_challenge_is_detected() -> None: + outcome = result("rejected_replayed_challenge") + assert outcome.properties["freshness"] == REFUTED + assert outcome.verdict == REJECTED + + +def test_required_freshness_without_a_challenge_fails_closed() -> None: + outcome = result("rejected_missing_challenge") + assert outcome.properties["freshness"] == REFUTED + + +def test_absent_uniqueness_evidence_is_not_sybil_resistance() -> None: + outcome = result("unknown_uniqueness_absent") + assert outcome.properties["uniqueness"] == UNKNOWN + assert outcome.verdict == UNKNOWN + + +def test_conflicting_identity_evidence_stays_conflicted() -> None: + outcome = result("conflicted_identity_evidence") + assert outcome.properties["civil_identity"] == CONFLICTED + assert outcome.verdict == CONFLICTED + + +def test_minimization_may_not_widen_the_claim_boundary() -> None: + outcome = result("rejected_minimization_widens_boundary") + assert outcome.verdict == REJECTED + assert any("widened" in reason for reason in outcome.reasons) + + +def test_minimization_that_narrows_keeps_the_bounded_result() -> None: + outcome = result("positive_minimized_boundary_narrowed") + assert outcome.verdict == ACCEPTED_BOUNDED + assert outcome.properties["unlinkability"] == "ASSUMED" + + +def test_key_compromise_refutes_authentication() -> None: + outcome = result("rejected_key_compromise") + assert outcome.properties["authentication"] == REFUTED + assert outcome.verdict == REJECTED + + +def test_unlinkability_is_never_supported() -> None: + for path in FIXTURES: + fixture = json.loads(path.read_text(encoding="utf-8")) + assert evaluate(fixture["record"]).properties["unlinkability"] != SUPPORTED + + +def test_no_fixture_reaches_acceptance_with_a_refuted_property() -> None: + for path in FIXTURES: + outcome = evaluate(json.loads(path.read_text(encoding="utf-8"))["record"]) + if REFUTED in outcome.properties.values(): + assert outcome.verdict == REJECTED + + +def test_accountability_requires_a_bound_escalation_authority() -> None: + record = load("positive_bounded_authorization")["record"] + assert evaluate(record).properties["accountability"] == ATTESTED + record.pop("escalation_authority") + assert evaluate(record).properties["accountability"] == UNKNOWN + + +def test_recovery_absence_stays_unknown() -> None: + record = load("positive_bounded_authorization")["record"] + record.pop("recovery") + assert evaluate(record).properties["recovery"] == UNKNOWN + + +def test_unknown_trust_root_does_not_authenticate() -> None: + record = load("positive_bounded_authorization")["record"] + record["trust_roots"] = ["root:other"] + outcome = evaluate(record) + assert outcome.properties["authentication"] == UNKNOWN + assert outcome.verdict == UNKNOWN + + +def test_scope_mismatch_is_refuted() -> None: + record = load("positive_bounded_authorization")["record"] + record["claim_scope"] = "vstd4-availability-run" + outcome = evaluate(record) + assert outcome.properties["authorization"] == REFUTED + + +def test_model_declares_the_terminology_decision_and_prohibited_inferences() -> None: + model = load_model() + assert model["status"] == "EXPERIMENTAL" + assert model["wire_identifier"] is None + decision = model["terminology_decision"]["public_label_zero_identity"] + assert decision == "REJECTED_AS_UNQUALIFIED_PUBLIC_LABEL" + assert len(model["prohibited_inferences"]) >= 10 + + +def test_model_never_lists_unlinkability_as_supported() -> None: + model = load_model() + assert SUPPORTED not in model["properties"]["unlinkability"]["attainable_statuses"] + + +def test_experiment_declares_no_new_wire_identifier() -> None: + for path in (EXPERIMENT / "fixtures").glob("*.json"): + text = path.read_text(encoding="utf-8") + for frozen in ("VSTD-0.1", "VSTD-0.2", "VSTD-3.0", "VSTD-DATA-0.1"): + assert frozen not in text, f"{path.name} must not bind a frozen wire identifier" From 5caf57e24599d87ad00a827674d9d09b45ac0bc2 Mon Sep 17 00:00:00 2001 From: TimeLordRaps Date: Sun, 23 Aug 2026 05:33:50 -0700 Subject: [PATCH 03/32] Add authorship degree and credential ancestry to the identity experiment Authorization alone cannot tell a first-party claim from a relayed one, so the profile now keeps three questions apart: whether a key was permitted a scope, who is speaking and at what remove, and how the key obtained its authority. Authorship degree covers originator, delegate, relay, and aggregator roles; an absent role stays UNKNOWN rather than defaulting to origination, and a non-originator claiming origination is REFUTED. Credential ancestry records issuance, delegation, and rotation links, mirroring the recorded-lineage discipline of VSTD-Graph-1: a revoked ancestor or a scope-widening delegation is REFUTED, while an unattested link, a chain missing its trust root, and an unattested rotation stay UNKNOWN. Neither property can reach SUPPORTED. Adds seven fixtures (21 total) and eighteen tests (57 total). Records that ancestry enlarges the correlation surface, so authorship provenance and unlinkability are in direct tension, resolved toward provenance with the cost stated. Coordinate: no layer, base 598c545, seam experiments/zizk_vstd/zero_identity. Falsification: authorship_degree or credential_ancestry reaching SUPPORTED, a relayed role read as first-party authorship, or a revoked ancestor evaluating as anything other than a refutation. Compatibility: no wire identifier, schema $id, receipt digest, console alias, lifecycle token, dependency, or conformance behavior changed. Co-Authored-By: Claude Opus 5 --- experiments/zizk_vstd/zero_identity/README.md | 2 +- .../ROUND1_ZERO_IDENTITY_REPORT.md | 88 ++++++- .../zizk_vstd/zero_identity/SEMANTIC_MODEL.md | 24 +- .../zizk_vstd/zero_identity/THREAT_MODEL.md | 13 +- .../zizk_vstd/zero_identity/evaluate.py | 115 +++++++++ ...conflicted_authorship_degree_vs_chain.json | 96 ++++++++ .../conflicted_identity_evidence.json | 15 ++ .../positive_bounded_authorization.json | 15 ++ .../positive_minimized_boundary_narrowed.json | 15 ++ .../rejected_delegation_widens_scope.json | 97 ++++++++ .../fixtures/rejected_expired_authority.json | 15 ++ .../fixtures/rejected_key_compromise.json | 15 ++ ...rejected_minimization_widens_boundary.json | 15 ++ .../fixtures/rejected_missing_challenge.json | 15 ++ .../rejected_relay_claims_origination.json | 96 ++++++++ .../fixtures/rejected_replayed_challenge.json | 15 ++ .../fixtures/rejected_revoked_ancestor.json | 97 ++++++++ .../fixtures/rejected_revoked_authority.json | 15 ++ ...ejected_shared_pseudonym_independence.json | 15 ++ ...ected_unlinkability_erases_trust_root.json | 15 ++ .../fixtures/unknown_absent_authorship.json | 79 ++++++ .../fixtures/unknown_distinct_pseudonyms.json | 15 ++ .../unknown_missing_authorization.json | 15 ++ .../unknown_unattested_ancestry_link.json | 95 +++++++ .../fixtures/unknown_unattested_rotation.json | 90 +++++++ .../fixtures/unknown_uniqueness_absent.json | 15 ++ .../model/zero_identity_model.json | 233 +++++++++++++++--- .../zero_identity/tests/test_zero_identity.py | 64 +++++ 28 files changed, 1351 insertions(+), 48 deletions(-) create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/conflicted_authorship_degree_vs_chain.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/rejected_delegation_widens_scope.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/rejected_relay_claims_origination.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/rejected_revoked_ancestor.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/unknown_absent_authorship.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/unknown_unattested_ancestry_link.json create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/unknown_unattested_rotation.json diff --git a/experiments/zizk_vstd/zero_identity/README.md b/experiments/zizk_vstd/zero_identity/README.md index 4e751c3..536f075 100644 --- a/experiments/zizk_vstd/zero_identity/README.md +++ b/experiments/zizk_vstd/zero_identity/README.md @@ -28,7 +28,7 @@ Full reasoning and the exact claims that are and are not justified: | Path | What it is | |---|---| | [`SEMANTIC_MODEL.md`](SEMANTIC_MODEL.md) | term separation, statuses, minimum coordinates, prohibited inferences | -| [`THREAT_MODEL.md`](THREAT_MODEL.md) | thirteen threats, mitigations, residual risk, falsification conditions | +| [`THREAT_MODEL.md`](THREAT_MODEL.md) | sixteen threats, mitigations, residual risk, falsification conditions | | [`model/zero_identity_model.json`](model/zero_identity_model.json) | the machine-readable model | | [`evaluate.py`](evaluate.py) | standard-library evaluator over one disclosure record | | [`fixtures/`](fixtures) | positive, negative, `UNKNOWN`, and `CONFLICTED` records with expected results | diff --git a/experiments/zizk_vstd/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md b/experiments/zizk_vstd/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md index 97b9c02..709af32 100644 --- a/experiments/zizk_vstd/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md +++ b/experiments/zizk_vstd/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md @@ -37,6 +37,8 @@ profile is pseudonymous, and a stable pseudonym is a correlation handle. | Authority liveness | `SUPPORTED` / `REFUTED` | revocation state plus validity window against the evaluation instant | | Freshness | `SUPPORTED` / `REFUTED` | challenge coordinate and verifier-held nonce history | | Attribution | `ATTESTED` | binds a pseudonymous coordinate, never a person | +| Authorship degree | `ATTESTED` / `REFUTED` | declared role and remove, checked against the recorded delegation hops | +| Credential ancestry | `ATTESTED` / `REFUTED` | recorded chain from a declared trust root to the signing key | | Accountability | `ATTESTED` | a declared escalation authority that can act on the coordinate | | Uniqueness / Sybil resistance | `ATTESTED` | only with an attested mechanism; default `UNKNOWN` | | Verifier independence | `ATTESTED` / `REFUTED` | attested distinct trust roots; refuted by a shared pseudonym | @@ -46,8 +48,17 @@ profile is pseudonymous, and a stable pseudonym is a correlation handle. | Civil identity | `UNSUPPORTED_BY_DESIGN` | withheld deliberately | `ACCEPTED_BOUNDED` means exactly: this key was authorized for this claim scope at this -instant. It means nothing about who the actor is, whether they are one actor, or whether -two records came from independent actors. +instant. It means nothing about who the actor is, whether they are one actor, whether two +records came from independent actors, or whether the signer authored what it signed. + +Authorship degree and credential ancestry were added after the first round, on the +observation that authorization alone cannot tell a first-party claim from a relayed one. +Three questions are now kept apart: authorization asks whether this key was permitted this +scope; authorship degree asks who is speaking and at what remove; credential ancestry asks +how the key came to hold the authority. A record can be fully authorized with `UNKNOWN` +authorship, and that pairing is reported rather than merged. Neither new property can ever +reach `SUPPORTED`: both are assertions about the world outside the record, so `ATTESTED` is +their ceiling. ## 4. Prohibited inferences @@ -64,7 +75,19 @@ Each is encoded in `model/zero_identity_model.json` and guarded by at least one 9. absent uniqueness evidence implies Sybil resistance; 10. hashing, redaction, encryption, omission, or pseudonymity alone implies zero identity; 11. disclosure minimization preserves the original claim boundary; -12. missing evidence implies safety. +12. missing evidence implies safety; +13. a signer is the author of the claim; +14. a relayed, delegated, or aggregated claim is first-party authorship; +15. an absent authorship role means degree zero; +16. a recorded ancestry chain establishes that authority survived every hop; +17. no ancestor marked revoked means every ancestor is valid; +18. a rotation link merges two key coordinates into one actor; +19. a delegation may carry a scope its ancestor did not hold. + +Inferences 16 and 17 are the credential-side form of the recorded-lineage discipline +already normative in `standard/VSTD-Graph-1.md`, which states that an edge records ancestry +without establishing influence, and that no ancestor being marked revoked does not +establish that every ancestor is valid. ## 5. Trust roots and revocation dependencies @@ -74,7 +97,15 @@ accepts an `ACCEPTED_BOUNDED` verdict is accepting, at minimum: - the issuer named in `authorization.issuer`; - the trust root named in `actor.key_binding.trust_root`; - the revocation service named in `revocation.source`, as of `revocation.checked_at`; -- whatever protocol produced `signature_verified`, which this model does not check. +- whatever protocol produced `signature_verified`, which this model does not check; +- every attestor named in the recorded credential ancestry, one per link. + +Recorded ancestry increases the number of parties a reader depends on rather than reducing +it, and the report states that plainly: each delegation hop adds an attestor whose honesty +is assumed. A chain is refused when an ancestor is revoked or when a delegation carries a +scope its ancestor never held; it stays `UNKNOWN` when any link is unattested, when it does +not begin at a declared trust root, or when it does not terminate at the signing key. A +truncated chain therefore cannot be laundered into a clean one. Revocation is a liveness dependency with a staleness bound, not a one-time check. A record whose revocation source is absent is `UNKNOWN`; a record whose minimization request @@ -88,6 +119,14 @@ identifier, the trust root, the issuer, the scope name, the validity window, the evaluation instant, and the revocation source. Any two of these are joinable across records. Publication timing and volume are not addressed at all. +Recorded credential ancestry makes this strictly worse, and the trade is deliberate. Every +link publishes a parent coordinate, a child coordinate, a link type, and an attestor, so a +chain is a durable join key across every record that carries it: two records sharing one +delegation hop are linkable even when their pseudonyms differ, and a rotation link is an +explicit statement that two key coordinates are related. Authorship provenance and +unlinkability are therefore in direct tension. This experiment resolves the tension toward +provenance and reports the cost rather than claiming both. + Consequence: an observer who sees two records under one pseudonym learns they share an actor coordinate; an observer who sees two records under one issuer learns they share a root. Withholding civil identity does not weaken either observation. Coercion risk is not @@ -98,8 +137,8 @@ displacement of risk, not a reduction, and the experiment reports it as such. Both suites pass at the committed state. -- `python experiments/zizk_vstd/zero_identity/run_validation.py` — 14 fixtures, 0 failures. -- `python -m pytest experiments/zizk_vstd/zero_identity/tests -q` — 39 passed. +- `python experiments/zizk_vstd/zero_identity/run_validation.py` — 21 fixtures, 0 failures. +- `python -m pytest experiments/zizk_vstd/zero_identity/tests -q` — 57 passed. - `python -m pytest -q` (repository suite) — unchanged and passing; the experiment is not collected, because an experiment must not gate conformance. - `python scripts/check_presentation.py` — passes. @@ -122,6 +161,13 @@ Fixture coverage, one per required case: | `rejected_missing_challenge` | `REJECTED` | | `rejected_minimization_widens_boundary` | `REJECTED` | | `rejected_key_compromise` | `REJECTED` | +| `unknown_absent_authorship` | `UNKNOWN` | +| `unknown_unattested_ancestry_link` | `UNKNOWN` | +| `unknown_unattested_rotation` | `UNKNOWN` | +| `conflicted_authorship_degree_vs_chain` | `CONFLICTED` | +| `rejected_relay_claims_origination` | `REJECTED` | +| `rejected_revoked_ancestor` | `REJECTED` | +| `rejected_delegation_widens_scope` | `REJECTED` | No test failed. No assertion was weakened to obtain a green suite. @@ -129,15 +175,25 @@ No test failed. No assertion was weakened to obtain a green suite. 1. `signature_verified` and `revocation.state` are consumed as asserted evidence. No protocol is bound yet, so no protocol's assumptions have been inherited or checked. -2. Attestation quality is unmodelled. `ATTESTED` records that someone said so. -3. Nonce history is verifier-held state that this model does not carry; replay detection +2. Attestation quality is unmodelled. `ATTESTED` records that someone said so. This now + carries more weight than it did in the first round, because every ancestry link and + every authorship role rests on it. +3. An internally consistent but dishonest authorship role is undetectable from the record. + The model catches a relay that contradicts its own chain; it cannot catch a relay that + lies consistently. +4. Chain truncation before publication is only partially addressed. A chain that does not + reach a declared trust root stays `UNKNOWN`, but a chain trimmed to a plausible shorter + root is not distinguishable from an honest short chain. +5. Rotation is treated conservatively in one direction only: an unattested rotation does + not merge two coordinates. An actor rotating keys to shed a history is not detected. +6. Nonce history is verifier-held state that this model does not carry; replay detection is only as good as that history. -4. No selective-disclosure or unlinkable-presentation scheme has been selected. Until one +7. No selective-disclosure or unlinkable-presentation scheme has been selected. Until one is named, `unlinkability` stays `ASSUMED` at best. -5. Timing and volume side channels are out of scope and unmitigated. -6. Whether an issuer that grants many coordinates to one operator can be detected at all +8. Timing and volume side channels are out of scope and unmitigated. +9. Whether an issuer that grants many coordinates to one operator can be detected at all from published records is open, and probably not decidable within one record. -7. Whether this profile should ever become normative is not decided here. Nothing in this +10. Whether this profile should ever become normative is not decided here. Nothing in this round argues that it should. ## 9. Public claims currently justified @@ -148,6 +204,11 @@ No test failed. No assertion was weakened to obtain a green suite. `CONFLICTED`; revoked or expired authority yields a refutation." - "The experiment enumerates the identity coordinates that remain, rather than implying none remain." +- "Authorship degree and credential ancestry are recorded and checked for internal + consistency; a relayed claim cannot be read as first-party authorship, and a chain from a + revoked ancestor is refused." +- "A recorded ancestry chain is recorded ancestry, not proof that authority survived every + hop." - "The evaluator adds no dependency and touches no frozen wire identifier." ## 10. Public claims still prohibited @@ -162,4 +223,7 @@ No test failed. No assertion was weakened to obtain a green suite. backed by named attested evidence. - Any claim that a zero-knowledge proof system is used, implemented, or relied upon. None is present in this experiment. +- "Provenance is verified", or any phrasing that reads recorded ancestry as established + authority, established influence, or a verified chain of custody. +- Any claim that authorship is proven. Authorship degree is `ATTESTED` at its ceiling. - Any statement that this profile is production-ready, adopted, reviewed, or standardised. diff --git a/experiments/zizk_vstd/zero_identity/SEMANTIC_MODEL.md b/experiments/zizk_vstd/zero_identity/SEMANTIC_MODEL.md index 6dda00e..2ee822d 100644 --- a/experiments/zizk_vstd/zero_identity/SEMANTIC_MODEL.md +++ b/experiments/zizk_vstd/zero_identity/SEMANTIC_MODEL.md @@ -21,6 +21,8 @@ These are distinct properties. None implies another. | Authorization | evidence that the signer was permitted this claim scope | evaluable, may be `SUPPORTED` | | Accountability | a named authority that can act on the pseudonymous coordinate | at best `ATTESTED` | | Attribution | binding a record to a pseudonymous coordinate, never to a person | at best `ATTESTED` | +| Authorship degree | how far the signing party sits from the origin of the claim: originator, delegate, relay, aggregator | at best `ATTESTED`, default `UNKNOWN` | +| Credential ancestry | the recorded chain of issuance, delegation, and rotation links from a trust root to the signing key | at best `ATTESTED`, refutable | | Uniqueness / Sybil resistance | evidence that one coordinate corresponds to one actor | at best `ATTESTED`, default `UNKNOWN` | | Verifier independence | evidence that two receipts came from actors that do not share a root | at best `ATTESTED`, refutable | | Revocation and expiry | current liveness of a grant | evaluable, refutable | @@ -52,7 +54,15 @@ Bounded reverification without civil identity needs all of: - `actor.key_binding.key_id`, `.signature_verified`, `.trust_root`; - `authorization.grant_id`, `.issuer`, `.scope`, `.not_before`, `.not_after`; - `revocation.source`, `.state`, `.checked_at`; -- `trust_roots` — the roots the reader must already accept. +- `trust_roots` — the roots the reader must already accept; +- `authorship.role`, `.degree`, `.attested_by` — who authored the claim and at what remove; +- `credential_ancestry[].parent`, `.child`, `.link_type`, `.attested_by` — how the signing + key obtained its authority. + +Authorship degree and credential ancestry are distinct from authorization. Authorization +asks whether this key was permitted this scope; authorship asks who is speaking and at what +remove; ancestry asks how the key came to hold the authority at all. A record can be fully +authorized while its authorship is `UNKNOWN`, and that combination is reported, not merged. Remove any of the trust-root coordinates and the dependent property becomes `UNKNOWN`; remove `revocation.source`, `authorization.issuer`, or `actor.key_binding.trust_root` @@ -75,6 +85,18 @@ Encoded in the model and each guarded by a test: 9. Hashing, redaction, encryption, omission, or pseudonymity alone implies zero identity. 10. Disclosure minimization preserves the original claim boundary. 11. Missing evidence implies safety. +12. A signer is the author of the claim. +13. A relayed, delegated, or aggregated claim is first-party authorship. +14. An absent authorship role means degree zero. +15. A recorded ancestry chain establishes that authority survived every hop. +16. No ancestor marked revoked means every ancestor is valid. +17. A rotation link merges two key coordinates into one actor. +18. A delegation may carry a scope its ancestor did not hold. + +Inferences 15 and 16 mirror the recorded-lineage discipline of +[`../../../standard/VSTD-Graph-1.md`](../../../standard/VSTD-Graph-1.md): an edge records +ancestry, and a clean-ancestor policy must require validity explicitly rather than reading +it out of the absence of a revocation mark. ## 5. Relationship to cryptography diff --git a/experiments/zizk_vstd/zero_identity/THREAT_MODEL.md b/experiments/zizk_vstd/zero_identity/THREAT_MODEL.md index 302f4bf..ce53805 100644 --- a/experiments/zizk_vstd/zero_identity/THREAT_MODEL.md +++ b/experiments/zizk_vstd/zero_identity/THREAT_MODEL.md @@ -25,7 +25,10 @@ schemes; where a key fails, it fails by compromise or misuse, not by cryptanalys | T10 | Unverifiable claims of independence | `verifier_independence` never becomes `SUPPORTED`; a claim of it that lacks evidence downgrades the record verdict to `UNKNOWN` | Attestation quality is outside the model | | T11 | Missing authorization | A record with no grant is `UNKNOWN`; it never fails open | A verifier that treats `UNKNOWN` as permission defeats this. The verdict is honest; the deployment must respect it | | T12 | Recovery after credential loss | `recovery` is `ATTESTED` only when a mechanism is declared, otherwise `UNKNOWN` | Any recovery path is also an impersonation path. The model records that a path exists; it does not evaluate its strength | -| T13 | Privacy laundering through minimization | A minimization request that removes a required trust root makes the record `REJECTED`; a request that widens the claim boundary is `REJECTED` | An actor can still choose to publish less and accept a weaker verdict, which is the intended trade | +| T13 | Authorship inflation: a relay or aggregator presenting a claim as its own | Role and degree are asserted and checked for internal consistency; a non-originator that claims origination is `REFUTED`; an absent role stays `UNKNOWN` | The role itself is an assertion about the world. A dishonest originator claim that is internally consistent is not detectable from the record | +| T14 | Delegation laundering: manufacturing authority the issuer never granted | A delegation whose scope exceeds its ancestor scope is `REFUTED`; a chain from a revoked ancestor is `REFUTED`; an unattested link stays `UNKNOWN` | Ancestor state is as fresh as the evidence supplied. A chain can be truncated before publication, which is why a chain that misses a declared trust root stays `UNKNOWN` | +| T15 | Identity merge through key rotation | An unattested rotation leaves the chain `UNKNOWN`; two key coordinates are not merged into one actor without attestation | The inverse also holds and is unaddressed: an actor can rotate to escape a reputation history, which this model cannot detect | +| T16 | Privacy laundering through minimization | A minimization request that removes a required trust root makes the record `REJECTED`; a request that widens the claim boundary is `REJECTED` | An actor can still choose to publish less and accept a weaker verdict, which is the intended trade | ## Falsification conditions @@ -33,12 +36,14 @@ This experiment is refuted if any of the following can be demonstrated: - a record reaches `ACCEPTED_BOUNDED` while any property is `REFUTED`; - a `CONFLICTED` property is resolved to a favourable status by adding no new evidence; -- `unlinkability` reaches `SUPPORTED`; +- `unlinkability`, `authorship_degree`, or `credential_ancestry` reaches `SUPPORTED`; +- a non-originator role is read as first-party authorship; +- a chain containing a revoked ancestor evaluates as anything other than a refutation; - absence of a coordinate produces anything other than `UNKNOWN`, `REFUTED`, or `REJECTED`; - a minimization request removes a required trust-root coordinate and the record still evaluates. -The first four are asserted directly in -[`tests/test_zero_identity.py`](tests/test_zero_identity.py); the fifth is +All but the last are asserted directly in +[`tests/test_zero_identity.py`](tests/test_zero_identity.py); the last is [`fixtures/rejected_unlinkability_erases_trust_root.json`](fixtures/rejected_unlinkability_erases_trust_root.json). ## What this threat model does not claim diff --git a/experiments/zizk_vstd/zero_identity/evaluate.py b/experiments/zizk_vstd/zero_identity/evaluate.py index 96ac99a..0f485dd 100644 --- a/experiments/zizk_vstd/zero_identity/evaluate.py +++ b/experiments/zizk_vstd/zero_identity/evaluate.py @@ -239,6 +239,119 @@ def _evaluate_independence(record: dict[str, Any], reasons: list[str]) -> str: return ATTESTED +AUTHORSHIP_ROLES = ("ORIGINATOR", "DELEGATE", "RELAY", "AGGREGATOR") + + +def _evaluate_authorship_degree(record: dict[str, Any], reasons: list[str]) -> str: + """Decide how far the signing party sits from the origin of the claim. + + Degree is asserted, never inferred. An absent role does not default to + ORIGINATOR, and a relay is never readable as first-party authorship. + """ + + if _conflicted(record, "authorship_degree"): + reasons.append("authorship_degree: conflicting evidence retained as CONFLICTED") + return CONFLICTED + authorship = record.get("authorship") + if not isinstance(authorship, dict): + reasons.append( + "authorship_degree: no authorship coordinate; a signer is not assumed to be an author" + ) + return UNKNOWN + role = authorship.get("role") + degree = authorship.get("degree") + if role not in AUTHORSHIP_ROLES or not isinstance(degree, int): + reasons.append("authorship_degree: role or degree absent or unrecognised") + return UNKNOWN + if (role == "ORIGINATOR") != (degree == 0): + reasons.append("authorship_degree: declared role and declared degree disagree") + return CONFLICTED + chain = record.get("credential_ancestry") or [] + delegations = [link for link in chain if link.get("link_type") == "delegation"] + if chain and degree != len(delegations): + reasons.append( + "authorship_degree: declared degree disagrees with the number of recorded " + "delegation hops" + ) + return CONFLICTED + if role != "ORIGINATOR" and "authorship_origination" in ( + record.get("claimed_properties") or [] + ): + reasons.append( + f"authorship_degree: a {role} record claims origination; relayed authorship " + "is not first-party authorship" + ) + return REFUTED + if not authorship.get("attested_by"): + reasons.append("authorship_degree: role is declared but not attested") + return UNKNOWN + return ATTESTED + + +def _evaluate_credential_ancestry(record: dict[str, Any], reasons: list[str]) -> str: + """Decide what the recorded chain from a trust root to this credential supports. + + The chain records ancestry; it does not by itself establish that authority + survived every hop. An unattested link stays UNKNOWN, and a revoked ancestor + refutes the chain rather than leaving it merely uncertain. + """ + + if _conflicted(record, "credential_ancestry"): + reasons.append("credential_ancestry: conflicting evidence retained as CONFLICTED") + return CONFLICTED + chain = record.get("credential_ancestry") + if not chain: + reasons.append( + "credential_ancestry: no recorded chain; an authority origin is not assumed" + ) + return UNKNOWN + for link in chain: + if link.get("parent_state") == "revoked": + reasons.append( + "credential_ancestry: a recorded ancestor is revoked; authority does not " + "survive delegation from a revoked ancestor" + ) + return REFUTED + parent_scope = link.get("parent_scope") + child_scope = link.get("child_scope") + if parent_scope is not None and child_scope is not None: + if not set(child_scope) <= set(parent_scope): + reasons.append( + "credential_ancestry: a delegation widens scope beyond its ancestor" + ) + return REFUTED + if not all(link.get("attested_by") for link in chain): + reasons.append( + "credential_ancestry: a recorded link is unattested; an unattested chain is " + "not a verified chain" + ) + return UNKNOWN + if chain[0].get("parent") not in (record.get("trust_roots") or []): + reasons.append( + "credential_ancestry: the chain does not begin at a declared trust root" + ) + return UNKNOWN + for older, newer in zip(chain, chain[1:]): + if older.get("child") != newer.get("parent"): + reasons.append("credential_ancestry: the recorded chain is not contiguous") + return CONFLICTED + if chain[-1].get("child") != _get(record, "actor.key_binding.key_id"): + reasons.append( + "credential_ancestry: the chain does not terminate at the signing key" + ) + return UNKNOWN + if any( + link.get("link_type") == "rotation" and not link.get("same_actor_attested_by") + for link in chain + ): + reasons.append( + "credential_ancestry: an unattested rotation does not merge two key " + "coordinates into one actor" + ) + return UNKNOWN + return ATTESTED + + def _evaluate_unlinkability(record: dict[str, Any], reasons: list[str]) -> str: request = record.get("disclosure_minimization") or {} if not request: @@ -338,6 +451,8 @@ def evaluate(record: dict[str, Any]) -> Evaluation: properties["uniqueness"] = _evaluate_uniqueness(record, reasons) properties["verifier_independence"] = _evaluate_independence(record, reasons) properties["unlinkability"] = _evaluate_unlinkability(record, reasons) + properties["authorship_degree"] = _evaluate_authorship_degree(record, reasons) + properties["credential_ancestry"] = _evaluate_credential_ancestry(record, reasons) properties["accountability"] = _evaluate_accountability(record, reasons) properties["recovery"] = _evaluate_recovery(record, reasons) diff --git a/experiments/zizk_vstd/zero_identity/fixtures/conflicted_authorship_degree_vs_chain.json b/experiments/zizk_vstd/zero_identity/fixtures/conflicted_authorship_degree_vs_chain.json new file mode 100644 index 0000000..5ab1863 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/conflicted_authorship_degree_vs_chain.json @@ -0,0 +1,96 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "authorship_degree": "CONFLICTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "CONFLICTED" + }, + "falsification_question": "Is the more convenient of two disagreeing degree claims preferred?", + "fixture_id": "conflicted_authorship_degree_vs_chain", + "intent": "A declared degree that disagrees with the recorded chain stays CONFLICTED.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:delegate-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 2, + "role": "DELEGATE" + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + }, + { + "attested_by": "root:issuer-a", + "child": "key:delegate-1", + "child_scope": [ + "vstd4-refutation-run" + ], + "link_type": "delegation", + "parent": "key:alpha-1", + "parent_scope": [ + "vstd4-refutation-run" + ] + } + ], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-authorship-degree-conflict", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/conflicted_identity_evidence.json b/experiments/zizk_vstd/zero_identity/fixtures/conflicted_identity_evidence.json index bac8e14..99faa03 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/conflicted_identity_evidence.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/conflicted_identity_evidence.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "SUPPORTED", "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", "civil_identity": "CONFLICTED", + "credential_ancestry": "ATTESTED", "freshness": "SUPPORTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -36,6 +38,11 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "authorization" @@ -49,6 +56,14 @@ "property": "civil_identity" } ], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "escalation_authority": "root:issuer-a", "evaluated_at": "2026-08-23T00:00:00Z", "freshness": { diff --git a/experiments/zizk_vstd/zero_identity/fixtures/positive_bounded_authorization.json b/experiments/zizk_vstd/zero_identity/fixtures/positive_bounded_authorization.json index c42bdd5..7c43fb9 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/positive_bounded_authorization.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/positive_bounded_authorization.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "SUPPORTED", "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "SUPPORTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -36,11 +38,24 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "authorization" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "escalation_authority": "root:issuer-a", "evaluated_at": "2026-08-23T00:00:00Z", "freshness": { diff --git a/experiments/zizk_vstd/zero_identity/fixtures/positive_minimized_boundary_narrowed.json b/experiments/zizk_vstd/zero_identity/fixtures/positive_minimized_boundary_narrowed.json index 5d85e9a..a710e62 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/positive_minimized_boundary_narrowed.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/positive_minimized_boundary_narrowed.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "SUPPORTED", "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "SUPPORTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -36,11 +38,24 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "authorization" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "disclosure_minimization": { "claim_boundary_after": [ "vstd4-refutation-run" diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_delegation_widens_scope.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_delegation_widens_scope.json new file mode 100644 index 0000000..f7dcbe2 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_delegation_widens_scope.json @@ -0,0 +1,97 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "REFUTED", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "REJECTED" + }, + "falsification_question": "Can delegation manufacture authority the issuer never granted?", + "fixture_id": "rejected_delegation_widens_scope", + "intent": "A delegation may not carry a scope its ancestor did not hold.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:delegate-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 1, + "role": "DELEGATE" + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + }, + { + "attested_by": "root:issuer-a", + "child": "key:delegate-1", + "child_scope": [ + "vstd4-refutation-run", + "vstd4-availability-run" + ], + "link_type": "delegation", + "parent": "key:alpha-1", + "parent_scope": [ + "vstd4-refutation-run" + ] + } + ], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-ancestry-scope-escalation", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_expired_authority.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_expired_authority.json index 0fa4af3..c55b43a 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/rejected_expired_authority.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_expired_authority.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "REFUTED", "authorization": "REFUTED", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "SUPPORTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -36,11 +38,24 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "authorization" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "escalation_authority": "root:issuer-a", "evaluated_at": "2027-02-01T00:00:00Z", "freshness": { diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_key_compromise.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_key_compromise.json index fdcf4ea..3b23026 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/rejected_key_compromise.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_key_compromise.json @@ -5,7 +5,9 @@ "authentication": "REFUTED", "authority_active": "SUPPORTED", "authorization": "REFUTED", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "SUPPORTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -37,11 +39,24 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "authorization" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "escalation_authority": "root:issuer-a", "evaluated_at": "2026-08-23T00:00:00Z", "freshness": { diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_minimization_widens_boundary.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_minimization_widens_boundary.json index a98c181..ff5dacb 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/rejected_minimization_widens_boundary.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_minimization_widens_boundary.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "SUPPORTED", "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "SUPPORTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -36,11 +38,24 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "authorization" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "disclosure_minimization": { "claim_boundary_after": [ "vstd4-refutation-run", diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_missing_challenge.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_missing_challenge.json index 08843eb..b451e39 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/rejected_missing_challenge.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_missing_challenge.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "SUPPORTED", "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "REFUTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -36,11 +38,24 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "authorization" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "escalation_authority": "root:issuer-a", "evaluated_at": "2026-08-23T00:00:00Z", "freshness": { diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_relay_claims_origination.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_relay_claims_origination.json new file mode 100644 index 0000000..ec1de97 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_relay_claims_origination.json @@ -0,0 +1,96 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "authorship_degree": "REFUTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "REJECTED" + }, + "falsification_question": "Can a relay present a claim as its own?", + "fixture_id": "rejected_relay_claims_origination", + "intent": "A relayed claim is not first-party authorship.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:delegate-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 1, + "role": "RELAY" + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorship_origination" + ], + "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + }, + { + "attested_by": "root:issuer-a", + "child": "key:delegate-1", + "child_scope": [ + "vstd4-refutation-run" + ], + "link_type": "delegation", + "parent": "key:alpha-1", + "parent_scope": [ + "vstd4-refutation-run" + ] + } + ], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-authorship-relay", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_replayed_challenge.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_replayed_challenge.json index e4a97fb..12bfcd5 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/rejected_replayed_challenge.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_replayed_challenge.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "SUPPORTED", "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "REFUTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -36,11 +38,24 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "authorization" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "escalation_authority": "root:issuer-a", "evaluated_at": "2026-08-23T00:00:00Z", "freshness": { diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_revoked_ancestor.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_revoked_ancestor.json new file mode 100644 index 0000000..89755cf --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_revoked_ancestor.json @@ -0,0 +1,97 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "REFUTED", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "REJECTED" + }, + "falsification_question": "Does a revoked ancestor leave its descendants merely uncertain?", + "fixture_id": "rejected_revoked_ancestor", + "intent": "Authority does not survive delegation from a revoked ancestor.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:delegate-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 1, + "role": "DELEGATE" + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + }, + { + "attested_by": "root:issuer-a", + "child": "key:delegate-1", + "child_scope": [ + "vstd4-refutation-run" + ], + "link_type": "delegation", + "parent": "key:alpha-1", + "parent_scope": [ + "vstd4-refutation-run" + ], + "parent_state": "revoked" + } + ], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-ancestry-revoked", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_revoked_authority.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_revoked_authority.json index 8b883a9..75daad4 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/rejected_revoked_authority.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_revoked_authority.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "REFUTED", "authorization": "REFUTED", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "SUPPORTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -36,11 +38,24 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "authorization" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "escalation_authority": "root:issuer-a", "evaluated_at": "2026-08-23T00:00:00Z", "freshness": { diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_shared_pseudonym_independence.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_shared_pseudonym_independence.json index 5c01037..6f6c694 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/rejected_shared_pseudonym_independence.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_shared_pseudonym_independence.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "SUPPORTED", "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "SUPPORTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -36,11 +38,24 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "verifier_independence" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "escalation_authority": "root:issuer-a", "evaluated_at": "2026-08-23T00:00:00Z", "freshness": { diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_unlinkability_erases_trust_root.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_unlinkability_erases_trust_root.json index 5f73689..9893613 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/rejected_unlinkability_erases_trust_root.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_unlinkability_erases_trust_root.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "UNKNOWN", "authorization": "UNKNOWN", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "SUPPORTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -36,11 +38,24 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "authorization" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "disclosure_minimization": { "claim_boundary_after": [ "vstd4-refutation-run" diff --git a/experiments/zizk_vstd/zero_identity/fixtures/unknown_absent_authorship.json b/experiments/zizk_vstd/zero_identity/fixtures/unknown_absent_authorship.json new file mode 100644 index 0000000..6811842 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/unknown_absent_authorship.json @@ -0,0 +1,79 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "authorship_degree": "UNKNOWN", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "UNKNOWN" + }, + "falsification_question": "Does signing a record make you its author?", + "fixture_id": "unknown_absent_authorship", + "intent": "A signer is not assumed to be the author of the claim.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorship_degree" + ], + "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-authorship-absent", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/unknown_distinct_pseudonyms.json b/experiments/zizk_vstd/zero_identity/fixtures/unknown_distinct_pseudonyms.json index 1f84fff..2ddcb41 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/unknown_distinct_pseudonyms.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/unknown_distinct_pseudonyms.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "SUPPORTED", "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "SUPPORTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -36,11 +38,24 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "verifier_independence" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "escalation_authority": "root:issuer-a", "evaluated_at": "2026-08-23T00:00:00Z", "freshness": { diff --git a/experiments/zizk_vstd/zero_identity/fixtures/unknown_missing_authorization.json b/experiments/zizk_vstd/zero_identity/fixtures/unknown_missing_authorization.json index cdb4d8b..f294ac7 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/unknown_missing_authorization.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/unknown_missing_authorization.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "UNKNOWN", "authorization": "UNKNOWN", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "SUPPORTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -27,11 +29,24 @@ }, "pseudonym": "pseudonym:alpha" }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "authorization" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "escalation_authority": "root:issuer-a", "evaluated_at": "2026-08-23T00:00:00Z", "freshness": { diff --git a/experiments/zizk_vstd/zero_identity/fixtures/unknown_unattested_ancestry_link.json b/experiments/zizk_vstd/zero_identity/fixtures/unknown_unattested_ancestry_link.json new file mode 100644 index 0000000..306ff97 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/unknown_unattested_ancestry_link.json @@ -0,0 +1,95 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "UNKNOWN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "UNKNOWN" + }, + "falsification_question": "Does a written-down chain establish that authority survived every hop?", + "fixture_id": "unknown_unattested_ancestry_link", + "intent": "An unattested link in a recorded chain is not a verified chain.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:delegate-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 1, + "role": "DELEGATE" + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "credential_ancestry" + ], + "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + }, + { + "child": "key:delegate-1", + "child_scope": [ + "vstd4-refutation-run" + ], + "link_type": "delegation", + "parent": "key:alpha-1", + "parent_scope": [ + "vstd4-refutation-run" + ] + } + ], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-ancestry-unattested", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/unknown_unattested_rotation.json b/experiments/zizk_vstd/zero_identity/fixtures/unknown_unattested_rotation.json new file mode 100644 index 0000000..bf7449d --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/unknown_unattested_rotation.json @@ -0,0 +1,90 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "SUPPORTED", + "authority_active": "SUPPORTED", + "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "UNKNOWN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "UNKNOWN", + "verifier_independence": "UNKNOWN" + }, + "verdict": "UNKNOWN" + }, + "falsification_question": "Do two keys become one actor because a rotation was recorded?", + "fixture_id": "unknown_unattested_rotation", + "intent": "An unattested rotation does not merge two key coordinates into one actor.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-2", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "credential_ancestry" + ], + "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + }, + { + "attested_by": "root:issuer-a", + "child": "key:alpha-2", + "link_type": "rotation", + "parent": "key:alpha-1" + } + ], + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-ancestry-rotation", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/unknown_uniqueness_absent.json b/experiments/zizk_vstd/zero_identity/fixtures/unknown_uniqueness_absent.json index 2f5112e..e2dd5db 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/unknown_uniqueness_absent.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/unknown_uniqueness_absent.json @@ -5,7 +5,9 @@ "authentication": "SUPPORTED", "authority_active": "SUPPORTED", "authorization": "SUPPORTED", + "authorship_degree": "ATTESTED", "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "ATTESTED", "freshness": "SUPPORTED", "recovery": "ATTESTED", "uniqueness": "UNKNOWN", @@ -36,11 +38,24 @@ "vstd4-refutation-run" ] }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, "claim_scope": "vstd4-refutation-run", "claimed_properties": [ "uniqueness" ], "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], "escalation_authority": "root:issuer-a", "evaluated_at": "2026-08-23T00:00:00Z", "freshness": { diff --git a/experiments/zizk_vstd/zero_identity/model/zero_identity_model.json b/experiments/zizk_vstd/zero_identity/model/zero_identity_model.json index 7035792..3eafb9a 100644 --- a/experiments/zizk_vstd/zero_identity/model/zero_identity_model.json +++ b/experiments/zizk_vstd/zero_identity/model/zero_identity_model.json @@ -21,6 +21,8 @@ "authorization", "accountability", "attribution", + "authorship_degree", + "credential_ancestry", "uniqueness", "verifier_independence", "revocation_or_expiry", @@ -37,8 +39,18 @@ "REFUTED", "UNSUPPORTED_BY_DESIGN" ], - "verdicts": ["ACCEPTED_BOUNDED", "UNKNOWN", "CONFLICTED", "REJECTED"], - "verdict_precedence": ["REJECTED", "CONFLICTED", "UNKNOWN", "ACCEPTED_BOUNDED"], + "verdicts": [ + "ACCEPTED_BOUNDED", + "UNKNOWN", + "CONFLICTED", + "REJECTED" + ], + "verdict_precedence": [ + "REJECTED", + "CONFLICTED", + "UNKNOWN", + "ACCEPTED_BOUNDED" + ], "minimum_public_actor_coordinates": [ "actor.pseudonym", "actor.key_binding.key_id", @@ -52,7 +64,14 @@ "revocation.source", "revocation.state", "revocation.checked_at", - "trust_roots" + "trust_roots", + "authorship.role", + "authorship.degree", + "authorship.attested_by", + "credential_ancestry[].parent", + "credential_ancestry[].child", + "credential_ancestry[].link_type", + "credential_ancestry[].attested_by" ], "prohibited_inferences": [ "absent civil identity implies anonymity", @@ -66,69 +85,223 @@ "absent uniqueness evidence implies Sybil resistance", "hashing, redaction, encryption, omission, or pseudonymity alone implies zero identity", "disclosure minimization preserves the original claim boundary", - "missing evidence implies safety" + "missing evidence implies safety", + "a signer is the author of the claim", + "a relayed or delegated claim is first-party authorship", + "an absent authorship role means degree zero", + "a recorded ancestry chain establishes that authority survived every hop", + "no ancestor marked revoked means every ancestor is valid", + "a key rotation link merges two key coordinates into one actor", + "a delegation may carry a scope its ancestor did not hold" ], "properties": { "civil_identity": { "profile_intent": "withheld", - "attainable_statuses": ["UNSUPPORTED_BY_DESIGN", "CONFLICTED"] + "attainable_statuses": [ + "UNSUPPORTED_BY_DESIGN", + "CONFLICTED" + ] }, "authentication": { - "attainable_statuses": ["SUPPORTED", "REFUTED", "UNKNOWN", "CONFLICTED"], - "requires": ["actor.key_binding.signature_verified", "resolvable trust_root"] + "attainable_statuses": [ + "SUPPORTED", + "REFUTED", + "UNKNOWN", + "CONFLICTED" + ], + "requires": [ + "actor.key_binding.signature_verified", + "resolvable trust_root" + ] }, "authority_active": { - "attainable_statuses": ["SUPPORTED", "REFUTED", "UNKNOWN", "CONFLICTED"], - "requires": ["revocation.state", "revocation.source", "validity window containing evaluated_at"] + "attainable_statuses": [ + "SUPPORTED", + "REFUTED", + "UNKNOWN", + "CONFLICTED" + ], + "requires": [ + "revocation.state", + "revocation.source", + "validity window containing evaluated_at" + ] }, "authorization": { - "attainable_statuses": ["SUPPORTED", "REFUTED", "UNKNOWN", "CONFLICTED"], - "requires": ["authentication SUPPORTED", "authority_active SUPPORTED", "scope covers claim_scope"] + "attainable_statuses": [ + "SUPPORTED", + "REFUTED", + "UNKNOWN", + "CONFLICTED" + ], + "requires": [ + "authentication SUPPORTED", + "authority_active SUPPORTED", + "scope covers claim_scope" + ] }, "attribution": { - "attainable_statuses": ["ATTESTED", "UNKNOWN", "CONFLICTED"], + "attainable_statuses": [ + "ATTESTED", + "UNKNOWN", + "CONFLICTED" + ], "bound_to": "pseudonymous coordinate only, never civil identity" }, "uniqueness": { - "attainable_statuses": ["ATTESTED", "UNKNOWN", "CONFLICTED"], + "attainable_statuses": [ + "ATTESTED", + "UNKNOWN", + "CONFLICTED" + ], "default_when_absent": "UNKNOWN" }, "verifier_independence": { - "attainable_statuses": ["ATTESTED", "REFUTED", "UNKNOWN", "CONFLICTED"], + "attainable_statuses": [ + "ATTESTED", + "REFUTED", + "UNKNOWN", + "CONFLICTED" + ], "default_when_absent": "UNKNOWN" }, "freshness": { - "attainable_statuses": ["SUPPORTED", "REFUTED", "UNKNOWN"], + "attainable_statuses": [ + "SUPPORTED", + "REFUTED", + "UNKNOWN" + ], "fail_closed_when_required_and_absent": true }, "unlinkability": { - "attainable_statuses": ["ASSUMED", "UNKNOWN", "REFUTED"], + "attainable_statuses": [ + "ASSUMED", + "UNKNOWN", + "REFUTED" + ], "never": "SUPPORTED", "reason": "This model observes one record at a time and cannot observe the adversary's full correlation surface." }, "accountability": { - "attainable_statuses": ["ATTESTED", "UNKNOWN"], - "requires": ["a named escalation authority that can act on the pseudonymous coordinate"] + "attainable_statuses": [ + "ATTESTED", + "UNKNOWN" + ], + "requires": [ + "a named escalation authority that can act on the pseudonymous coordinate" + ] }, "confidentiality": { - "attainable_statuses": ["ASSUMED", "UNKNOWN"], + "attainable_statuses": [ + "ASSUMED", + "UNKNOWN" + ], "reason": "Transport and storage confidentiality are outside this record." }, "recovery": { - "attainable_statuses": ["ATTESTED", "UNKNOWN"], + "attainable_statuses": [ + "ATTESTED", + "UNKNOWN" + ], "default_when_absent": "UNKNOWN" + }, + "authorship_degree": { + "attainable_statuses": [ + "ATTESTED", + "REFUTED", + "UNKNOWN", + "CONFLICTED" + ], + "never": "SUPPORTED", + "roles": [ + "ORIGINATOR", + "DELEGATE", + "RELAY", + "AGGREGATOR" + ], + "default_when_absent": "UNKNOWN", + "reason": "Authorship distance is an assertion about the world outside the record; this model can check it for internal consistency but cannot observe who wrote a claim." + }, + "credential_ancestry": { + "attainable_statuses": [ + "ATTESTED", + "REFUTED", + "UNKNOWN", + "CONFLICTED" + ], + "never": "SUPPORTED", + "default_when_absent": "UNKNOWN", + "reason": "The chain records ancestry. It does not by itself establish that authority survived every hop, mirroring the recorded-lineage discipline of VSTD-Graph-1." } }, "rules": [ - {"id": "ZI-R1", "statement": "A missing coordinate yields UNKNOWN, never a favourable status."}, - {"id": "ZI-R2", "statement": "CONFLICTED is terminal for the property and propagates to the record verdict."}, - {"id": "ZI-R3", "statement": "Revoked or expired authority is REFUTED, never UNKNOWN."}, - {"id": "ZI-R4", "statement": "A shared pseudonymous coordinate across two receipts REFUTES independence."}, - {"id": "ZI-R5", "statement": "Distinct pseudonymous coordinates leave both independence and actor-distinctness UNKNOWN."}, - {"id": "ZI-R6", "statement": "A minimization request that removes a required trust-root coordinate makes the record unevaluable and is REJECTED."}, - {"id": "ZI-R7", "statement": "When freshness is required, an absent challenge coordinate fails closed and a replayed challenge is REFUTED."}, - {"id": "ZI-R8", "statement": "A claim boundary may only narrow under minimization; widening is REJECTED."}, - {"id": "ZI-R9", "statement": "A key marked compromised for the signing interval REFUTES authentication."}, - {"id": "ZI-R10", "statement": "unlinkability is never SUPPORTED by this model; at best it is ASSUMED under declared assumptions."} + { + "id": "ZI-R1", + "statement": "A missing coordinate yields UNKNOWN, never a favourable status." + }, + { + "id": "ZI-R2", + "statement": "CONFLICTED is terminal for the property and propagates to the record verdict." + }, + { + "id": "ZI-R3", + "statement": "Revoked or expired authority is REFUTED, never UNKNOWN." + }, + { + "id": "ZI-R4", + "statement": "A shared pseudonymous coordinate across two receipts REFUTES independence." + }, + { + "id": "ZI-R5", + "statement": "Distinct pseudonymous coordinates leave both independence and actor-distinctness UNKNOWN." + }, + { + "id": "ZI-R6", + "statement": "A minimization request that removes a required trust-root coordinate makes the record unevaluable and is REJECTED." + }, + { + "id": "ZI-R7", + "statement": "When freshness is required, an absent challenge coordinate fails closed and a replayed challenge is REFUTED." + }, + { + "id": "ZI-R8", + "statement": "A claim boundary may only narrow under minimization; widening is REJECTED." + }, + { + "id": "ZI-R9", + "statement": "A key marked compromised for the signing interval REFUTES authentication." + }, + { + "id": "ZI-R10", + "statement": "unlinkability is never SUPPORTED by this model; at best it is ASSUMED under declared assumptions." + }, + { + "id": "ZI-R11", + "statement": "Authorship degree is asserted, never inferred; an absent role stays UNKNOWN and never defaults to ORIGINATOR." + }, + { + "id": "ZI-R12", + "statement": "A relay, delegate, or aggregator that claims origination is REFUTED." + }, + { + "id": "ZI-R13", + "statement": "A revoked recorded ancestor REFUTES the chain; authority does not survive delegation from a revoked ancestor." + }, + { + "id": "ZI-R14", + "statement": "A delegation whose scope exceeds its ancestor scope is REFUTED." + }, + { + "id": "ZI-R15", + "statement": "An unattested link, a chain that misses a declared trust root, or a chain that misses the signing key stays UNKNOWN." + }, + { + "id": "ZI-R16", + "statement": "An unattested rotation does not merge two key coordinates into one actor." + }, + { + "id": "ZI-R17", + "statement": "A declared degree that disagrees with the recorded chain length is CONFLICTED." + } ] } diff --git a/experiments/zizk_vstd/zero_identity/tests/test_zero_identity.py b/experiments/zizk_vstd/zero_identity/tests/test_zero_identity.py index bddca70..df107b7 100644 --- a/experiments/zizk_vstd/zero_identity/tests/test_zero_identity.py +++ b/experiments/zizk_vstd/zero_identity/tests/test_zero_identity.py @@ -184,6 +184,70 @@ def test_scope_mismatch_is_refuted() -> None: assert outcome.properties["authorization"] == REFUTED +def test_signing_is_not_authorship() -> None: + outcome = result("unknown_absent_authorship") + assert outcome.properties["authorship_degree"] == UNKNOWN + assert outcome.verdict == UNKNOWN + + +def test_relayed_claim_is_not_first_party_authorship() -> None: + outcome = result("rejected_relay_claims_origination") + assert outcome.properties["authorship_degree"] == REFUTED + assert outcome.verdict == REJECTED + + +def test_declared_degree_must_agree_with_recorded_delegation_hops() -> None: + outcome = result("conflicted_authorship_degree_vs_chain") + assert outcome.properties["authorship_degree"] == CONFLICTED + assert outcome.verdict == CONFLICTED + + +def test_unattested_ancestry_link_is_not_a_verified_chain() -> None: + outcome = result("unknown_unattested_ancestry_link") + assert outcome.properties["credential_ancestry"] == UNKNOWN + + +def test_authority_does_not_survive_a_revoked_ancestor() -> None: + outcome = result("rejected_revoked_ancestor") + assert outcome.properties["credential_ancestry"] == REFUTED + assert outcome.verdict == REJECTED + + +def test_delegation_may_not_widen_scope_beyond_its_ancestor() -> None: + outcome = result("rejected_delegation_widens_scope") + assert outcome.properties["credential_ancestry"] == REFUTED + + +def test_unattested_rotation_does_not_merge_two_key_coordinates() -> None: + outcome = result("unknown_unattested_rotation") + assert outcome.properties["credential_ancestry"] == UNKNOWN + + +def test_absent_ancestry_chain_stays_unknown() -> None: + record = load("positive_bounded_authorization")["record"] + record.pop("credential_ancestry") + assert evaluate(record).properties["credential_ancestry"] == UNKNOWN + + +def test_chain_must_terminate_at_the_signing_key() -> None: + record = load("positive_bounded_authorization")["record"] + record["credential_ancestry"][0]["child"] = "key:someone-else" + assert evaluate(record).properties["credential_ancestry"] == UNKNOWN + + +def test_chain_must_begin_at_a_declared_trust_root() -> None: + record = load("positive_bounded_authorization")["record"] + record["credential_ancestry"][0]["parent"] = "root:undeclared" + assert evaluate(record).properties["credential_ancestry"] == UNKNOWN + + +def test_authorship_and_ancestry_are_never_supported() -> None: + for path in FIXTURES: + outcome = evaluate(json.loads(path.read_text(encoding="utf-8"))["record"]) + assert outcome.properties["authorship_degree"] != SUPPORTED + assert outcome.properties["credential_ancestry"] != SUPPORTED + + def test_model_declares_the_terminology_decision_and_prohibited_inferences() -> None: model = load_model() assert model["status"] == "EXPERIMENTAL" From 48fab87b05ad5ddaf24d08b6391cde99d05fc8f1 Mon Sep 17 00:00:00 2001 From: TimeLordRaps Date: Sun, 23 Aug 2026 06:14:27 -0700 Subject: [PATCH 04/32] Complete round 1 of the bounded identity disclosure experiment Records the round-1 result and tightens the evaluator where review found it inferring more than the evidence carried. Required public coordinates now cover the pseudonym, the key identifier, and the declared trust roots, and a minimization request that removes any of them is REJECTED whether it names the leaf or a parent path. Authentication requires a pseudonym and a key identifier bound to a declared root; authorization requires an issuer that is itself declared. Authorship degree must be a non-negative integer, so a boolean or a negative remove stays UNKNOWN rather than being read as origination. A shared pseudonymous coordinate no longer refutes independence. It refutes independent corroboration from that coordinate, which the record already carries, but actor independence is a claim about people and stays UNKNOWN from a single record. The fixture moves from rejected_ to unknown_ to say so. The round-1 report separates four evidence classes and states a ceiling for each: semantic results, external attestations, declared assumptions, and protocol guarantees. It records the terminology decision, the claims currently justified, the claims still prohibited, and the failed tests, of which there are none. 22 fixtures and 65 tests, all passing; repository suite 255 passed, 3 skipped; presentation gate clean. Coordinate: no layer, base 598c545, seam experiments/zizk_vstd/zero_identity. Falsification: absence of a required coordinate producing a favourable result, a parent-path deletion bypassing a protected leaf, or unlinkability, authorship_degree, or credential_ancestry reaching SUPPORTED. Compatibility: no wire identifier, schema $id, receipt digest, console alias, lifecycle token, dependency, or conformance behavior changed. Co-Authored-By: Claude Opus 5 --- experiments/zizk_vstd/zero_identity/README.md | 9 +- .../ROUND1_ZERO_IDENTITY_REPORT.md | 140 ++++++++++++++---- .../zizk_vstd/zero_identity/SEMANTIC_MODEL.md | 38 +++-- .../zizk_vstd/zero_identity/THREAT_MODEL.md | 16 +- .../zizk_vstd/zero_identity/evaluate.py | 58 ++++++-- ...ected_minimization_erases_key_binding.json | 99 +++++++++++++ ...nknown_shared_pseudonym_independence.json} | 8 +- .../model/zero_identity_model.json | 24 +-- .../zero_identity/tests/test_zero_identity.py | 47 +++++- 9 files changed, 352 insertions(+), 87 deletions(-) create mode 100644 experiments/zizk_vstd/zero_identity/fixtures/rejected_minimization_erases_key_binding.json rename experiments/zizk_vstd/zero_identity/fixtures/{rejected_shared_pseudonym_independence.json => unknown_shared_pseudonym_independence.json} (89%) diff --git a/experiments/zizk_vstd/zero_identity/README.md b/experiments/zizk_vstd/zero_identity/README.md index 536f075..0c7d496 100644 --- a/experiments/zizk_vstd/zero_identity/README.md +++ b/experiments/zizk_vstd/zero_identity/README.md @@ -17,7 +17,8 @@ binding, a trust root, an issuer, and a revocation source — every one of which identity coordinate and a correlation handle. Calling that "zero identity" overstates the privacy achieved and hides the coordinates that remain. The mechanism this experiment retains is **bounded identity disclosure**: civil identity withheld, authorization -reverifiable, and every other identity property reported honestly as `UNKNOWN`, +semantically reevaluable from public coordinates conditional on declared external checks, +and every other identity property reported honestly as `UNKNOWN`, `CONFLICTED`, or `REFUTED` rather than assumed. Full reasoning and the exact claims that are and are not justified: @@ -51,8 +52,8 @@ collect this directory, which is deliberate: an experiment must not gate conform - No frozen wire identifier, schema `$id`, receipt digest, console alias, lifecycle token, or conformance behavior is touched. See [`../../../standard/WIRE_IDENTIFIERS.md`](../../../standard/WIRE_IDENTIFIERS.md). -- No cryptographic guarantee is invented. Signature and revocation results enter as - asserted evidence produced by some real protocol named by the deployment, and the model - decides only what may be concluded from them. +- No cryptographic guarantee is invented. Signature and revocation results are fixture + inputs here. A deployment would have to produce them through a named real protocol; the + model decides only what may be concluded from the asserted results. - `UNKNOWN` and `CONFLICTED` are preserved as results, per [`../../../AGENTS.md`](../../../AGENTS.md) section 2. diff --git a/experiments/zizk_vstd/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md b/experiments/zizk_vstd/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md index 709af32..0972e13 100644 --- a/experiments/zizk_vstd/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md +++ b/experiments/zizk_vstd/zero_identity/ROUND1_ZERO_IDENTITY_REPORT.md @@ -2,12 +2,18 @@ **Status:** experimental result. Non-normative. No adoption is claimed or implied. +Reading rule for this report: where evidence is insufficient the result is `UNKNOWN`, and +where evidence contradicts itself the result is `CONFLICTED`. Both are retained as results. +Neither is a gap to be filled, and neither may be read as authorization, independence, +uniqueness, Sybil resistance, privacy, or safety. + ## 1. Coordinates - Base commit: `598c545be3833d6d81bb7e252ca5837f3bb2a449` - Branch: `claude/zizk-zero-identity` -- Worktree: `verifier-worktrees/zizk-zi-claude` (isolated; the primary checkout and the - separate ZIZK roadmap worktree were not modified) +- Worktree label: `zizk-zi-claude` (isolated; its absolute host path is intentionally + excluded from this public report; the primary checkout and separate ZIZK roadmap + worktree were not modified) - Remote: `github.com/TimeLordRaps/verifier` - Layer: none. This experiment discharges no ladder rung. - Seam: `experiments/zizk_vstd/zero_identity/` only. @@ -30,21 +36,21 @@ profile is pseudonymous, and a stable pseudonym is a correlation handle. ## 3. Identity properties this profile supports -| Property | Best attainable | Basis | +| Property | Best attainable here | Basis and boundary | |---|---|---| -| Authentication | `SUPPORTED` | asserted signature verification against a declared trust root | -| Authorization | `SUPPORTED` | grant covering the claim scope, from an active authority, by an authenticated key | -| Authority liveness | `SUPPORTED` / `REFUTED` | revocation state plus validity window against the evaluation instant | +| Authentication | `SUPPORTED` | semantic result over an asserted external signature check and a declared trust root; no signature is verified here | +| Authorization | `SUPPORTED` | semantic result over authentication, an asserted grant, liveness inputs, and scope coverage | +| Authority liveness | `SUPPORTED` / `REFUTED` | semantic result over asserted revocation state plus validity window against the evaluation instant | | Freshness | `SUPPORTED` / `REFUTED` | challenge coordinate and verifier-held nonce history | -| Attribution | `ATTESTED` | binds a pseudonymous coordinate, never a person | +| Attribution | not separately evaluated | the record binds a pseudonymous coordinate; any real-world actor binding is `ATTESTED` at best, never inferred | | Authorship degree | `ATTESTED` / `REFUTED` | declared role and remove, checked against the recorded delegation hops | | Credential ancestry | `ATTESTED` / `REFUTED` | recorded chain from a declared trust root to the signing key | | Accountability | `ATTESTED` | a declared escalation authority that can act on the coordinate | | Uniqueness / Sybil resistance | `ATTESTED` | only with an attested mechanism; default `UNKNOWN` | -| Verifier independence | `ATTESTED` / `REFUTED` | attested distinct trust roots; refuted by a shared pseudonym | +| Verifier independence | `ATTESTED` | only from named attested evidence; shared or distinct pseudonyms alone leave actor independence `UNKNOWN` | | Recovery | `ATTESTED` | a declared credential-loss mechanism; strength not evaluated | | Unlinkability | `ASSUMED` | never `SUPPORTED`; assumptions must be declared | -| Confidentiality | `ASSUMED` | out of scope for the record | +| Confidentiality | not evaluated | out of scope; any declaration remains an assumption, not an evaluator result | | Civil identity | `UNSUPPORTED_BY_DESIGN` | withheld deliberately | `ACCEPTED_BOUNDED` means exactly: this key was authorized for this claim scope at this @@ -60,6 +66,25 @@ authorship, and that pairing is reported rather than merged. Neither new propert reach `SUPPORTED`: both are assertions about the world outside the record, so `ATTESTED` is their ceiling. +### 3.1 Evidence classes, kept separate + +The four classes below are never merged, and no verdict promotes one into another. A +reader who collapses them recovers exactly the overclaim this experiment exists to block. + +| Class | What it means | Handling in this experiment | Ceiling in this model | +|---|---|---|---| +| Semantic result | decided by the stated rules from coordinates present in the record | any reader running `evaluate.py` on the record | `SUPPORTED`, `REFUTED`, `UNKNOWN`, `CONFLICTED` | +| External attestation | a named third party asserts a fact this model records but does not check | a deployment may authenticate it under an external protocol; this evaluator does neither that nor truth validation | `ATTESTED` | +| Declared assumption | the record states a condition it needs and cannot demonstrate | carried unchanged and never established by this record | `ASSUMED` | +| Protocol guarantee | whatever an actual named cryptographic protocol provides | absent here; it would be checked under that protocol outside this evaluator | not represented; enters only as an input | + +Concretely: `authentication` is a semantic result *about an asserted signature check*, not +a cryptographic guarantee — this model never verifies a signature. `uniqueness`, +`verifier_independence`, `authorship_degree`, and `credential_ancestry` are attestations at +their ceiling. `unlinkability` is an assumption at its ceiling; `confidentiality` is not an +evaluator output at all. No protocol guarantee is claimed anywhere, because no protocol is +bound yet. + ## 4. Prohibited inferences Each is encoded in `model/zero_identity_model.json` and guarded by at least one test: @@ -102,19 +127,21 @@ accepts an `ACCEPTED_BOUNDED` verdict is accepting, at minimum: Recorded ancestry increases the number of parties a reader depends on rather than reducing it, and the report states that plainly: each delegation hop adds an attestor whose honesty -is assumed. A chain is refused when an ancestor is revoked or when a delegation carries a -scope its ancestor never held; it stays `UNKNOWN` when any link is unattested, when it does -not begin at a declared trust root, or when it does not terminate at the signing key. A -truncated chain therefore cannot be laundered into a clean one. +is assumed. A chain is refused when an ancestor is recorded as revoked or when a delegation +carries a scope its ancestor never held; it stays `UNKNOWN` when any link is unattested, +when it does not begin at a declared trust root, or when it does not terminate at the +signing key. A truncated chain therefore cannot be laundered into a clean one without also +declaring the shorter root as trusted; the model cannot establish whether that declaration +is honest. Revocation is a liveness dependency with a staleness bound, not a one-time check. A record whose revocation source is absent is `UNKNOWN`; a record whose minimization request deleted that source is `REJECTED` as unevaluable. Minimization is enforced by deletion before evaluation, so a withheld coordinate cannot be silently read anyway. -## 6. Privacy leak analysis +## 6. Privacy and correlation leak analysis -Retained and observable in every conforming record: the pseudonymous coordinate, the key +Retained and observable in every `ACCEPTED_BOUNDED` record: the pseudonymous coordinate, the key identifier, the trust root, the issuer, the scope name, the validity window, the evaluation instant, and the revocation source. Any two of these are joinable across records. Publication timing and volume are not addressed at all. @@ -128,20 +155,67 @@ unlinkability are therefore in direct tension. This experiment resolves the tens provenance and reports the cost rather than claiming both. Consequence: an observer who sees two records under one pseudonym learns they share an -actor coordinate; an observer who sees two records under one issuer learns they share a -root. Withholding civil identity does not weaken either observation. Coercion risk is not -removed either — it moves to the issuer, which still holds the civil binding. This is a -displacement of risk, not a reduction, and the experiment reports it as such. +actor coordinate, not that they share one natural person. An observer who sees two records +under one issuer learns that they name the same issuer, not necessarily the same trust +root. Withholding civil identity does not remove either correlation handle. Coercion risk +is not removed either — it may move to an issuer that holds a civil binding. This is a +displacement of risk, not a demonstrated reduction, and the experiment reports it as such. ## 7. Test results -Both suites pass at the committed state. +All required checks pass at the committed state. **Failed tests: none.** No assertion was +weakened, skipped, or marked expected-failure to reach this state. -- `python experiments/zizk_vstd/zero_identity/run_validation.py` — 21 fixtures, 0 failures. -- `python -m pytest experiments/zizk_vstd/zero_identity/tests -q` — 57 passed. -- `python -m pytest -q` (repository suite) — unchanged and passing; the experiment is not - collected, because an experiment must not gate conformance. -- `python scripts/check_presentation.py` — passes. +| Check | Result | +|---|---| +| `python experiments/zizk_vstd/zero_identity/run_validation.py` | 22 fixtures, 0 failures | +| `python -m pytest experiments/zizk_vstd/zero_identity/tests -q` | 65 passed | +| `python -m pytest -q` (repository suite) | 255 passed, 3 skipped | +| `python scripts/check_presentation.py` | passes | + +The repository suite sets `testpaths = ["tests"]` and does not collect this directory. That +is deliberate: an experiment must not gate conformance. The 3 skips are pre-existing and +unrelated to this work. On a machine where another checkout of the package is installed, +the repository suite needs the `PYTHONPATH=src` prefix described in `AGENTS.md` section 3; +that is an environment condition, not a repository defect. + +### 7.1 Diff inspection + +The complete diff against the base is confined to `experiments/zizk_vstd/zero_identity/`: +30 files, 3734 added lines, **zero files changed outside that directory**. A pattern scan +over every added line reports: + +| Category | Findings | +|---|---| +| Private filesystem paths | none | +| Private model identifiers | none | +| Credentials or secrets | none | +| Email addresses | none | +| Business plans | none | +| Unsupported adoption claims | none | +| Unsupported privacy or anonymity claims | none in assertion position | +| Recorded ancestry described as causal | none | +| CRLF line endings | none | + +Literal pattern hits were adjudicated and retained deliberately, because each occurs +in negating or guarding position rather than as a claim: the word *untraceable* appears +only in section 10 as a prohibited claim; the four frozen wire identifiers appear only in a +test asserting that no fixture may bind one; and `$id` appears only in prose stating that +none is introduced. + +### 7.2 Non-regression of frozen surfaces + +Verified directly against the base commit, not assumed: + +- `pyproject.toml` is byte-unchanged, and `dependencies = []` still holds. The evaluator + imports only `copy`, `dataclasses`, `json`, `pathlib`, and `typing`; `pytest` appears + only in the experiment's own tests, which the repository suite does not collect. +- Zero files changed under `standard/`, `receipts/schema/`, `src/`, `examples/`, or + `scripts/`. No frozen wire identifier, schema `$id`, receipt digest, console alias, or + lifecycle token is added, renamed, or rebound. +- The stdlib-purity smoke check (`python -S -c "import verifier; ..."`) reports `1.1.3`. +- Existing conformance behavior is untouched: this experiment adds no code path that any + shipped module imports. Fixture coverage, one per required case: @@ -155,11 +229,12 @@ Fixture coverage, one per required case: | `conflicted_identity_evidence` | `CONFLICTED` | | `rejected_revoked_authority` | `REJECTED` | | `rejected_expired_authority` | `REJECTED` | -| `rejected_shared_pseudonym_independence` | `REJECTED` | +| `unknown_shared_pseudonym_independence` | `UNKNOWN` | | `rejected_unlinkability_erases_trust_root` | `REJECTED` | | `rejected_replayed_challenge` | `REJECTED` | | `rejected_missing_challenge` | `REJECTED` | | `rejected_minimization_widens_boundary` | `REJECTED` | +| `rejected_minimization_erases_key_binding` | `REJECTED` | | `rejected_key_compromise` | `REJECTED` | | `unknown_absent_authorship` | `UNKNOWN` | | `unknown_unattested_ancestry_link` | `UNKNOWN` | @@ -169,7 +244,10 @@ Fixture coverage, one per required case: | `rejected_revoked_ancestor` | `REJECTED` | | `rejected_delegation_widens_scope` | `REJECTED` | -No test failed. No assertion was weakened to obtain a green suite. +No final test failed. No assertion was weakened to obtain a green suite. Validation instead +closed two fail-open surfaces: a minimizer cannot evade a protected leaf by deleting its +parent object, and a shared pseudonym no longer becomes a claim about how many actors use +that credential. ## 8. Unresolved assumptions @@ -198,8 +276,9 @@ No test failed. No assertion was weakened to obtain a green suite. ## 9. Public claims currently justified -- "Civil identity can be withheld while authorization for a bounded claim scope stays - reverifiable against a declared trust root." +- "Civil identity can be withheld while the evaluator can recompute a bounded + authorization result from public coordinates, conditional on asserted external checks + and declared trust roots." - "Missing identity evidence yields `UNKNOWN`; conflicting identity evidence yields `CONFLICTED`; revoked or expired authority yields a refutation." - "The experiment enumerates the identity coordinates that remain, rather than implying @@ -209,7 +288,8 @@ No test failed. No assertion was weakened to obtain a green suite. revoked ancestor is refused." - "A recorded ancestry chain is recorded ancestry, not proof that authority survived every hop." -- "The evaluator adds no dependency and touches no frozen wire identifier." +- "The experiment adds no required package dependency and the complete base-to-branch diff + does not modify a frozen wire identifier or conformance implementation." ## 10. Public claims still prohibited diff --git a/experiments/zizk_vstd/zero_identity/SEMANTIC_MODEL.md b/experiments/zizk_vstd/zero_identity/SEMANTIC_MODEL.md index 2ee822d..d41a026 100644 --- a/experiments/zizk_vstd/zero_identity/SEMANTIC_MODEL.md +++ b/experiments/zizk_vstd/zero_identity/SEMANTIC_MODEL.md @@ -40,35 +40,43 @@ These are distinct properties. None implies another. `REFUTED` — a positive negative result: the property is contradicted by evidence. `UNSUPPORTED_BY_DESIGN` — the profile deliberately withholds the coordinate. -Record verdicts are `ACCEPTED_BOUNDED`, `UNKNOWN`, `CONFLICTED`, `REJECTED`, in the -precedence `REJECTED > CONFLICTED > UNKNOWN > ACCEPTED_BOUNDED`. `ACCEPTED_BOUNDED` -asserts exactly one thing: authentication and authorization hold for the declared claim -scope at the declared instant. It asserts nothing about uniqueness, independence, -unlinkability, or the actor behind the coordinate. +Record verdicts are `ACCEPTED_BOUNDED`, `UNKNOWN`, `CONFLICTED`, and `REJECTED`. They are +aggregated without erasing property-level uncertainty: any `REFUTED` property makes the +record `REJECTED`; otherwise any `CONFLICTED` property makes it `CONFLICTED`. +`ACCEPTED_BOUNDED` requires `SUPPORTED` authentication and authorization plus satisfaction +of every explicitly claimed property. An `UNKNOWN` ancillary property remains visible but +does not widen or erase that bounded authorization result. Every other record is `UNKNOWN`. +`ACCEPTED_BOUNDED` therefore asserts exactly one thing: authentication and authorization +hold for the declared claim scope at the declared instant. It asserts nothing about +uniqueness, independence, unlinkability, or the actor behind the coordinate. ## 3. Minimum public actor coordinates -Bounded reverification without civil identity needs all of: +Bounded authorization reverification without civil identity needs all of: - `actor.pseudonym` — the coordinate a verdict attaches to; - `actor.key_binding.key_id`, `.signature_verified`, `.trust_root`; - `authorization.grant_id`, `.issuer`, `.scope`, `.not_before`, `.not_after`; - `revocation.source`, `.state`, `.checked_at`; -- `trust_roots` — the roots the reader must already accept; -- `authorship.role`, `.degree`, `.attested_by` — who authored the claim and at what remove; -- `credential_ancestry[].parent`, `.child`, `.link_type`, `.attested_by` — how the signing - key obtained its authority. +- `trust_roots` — the roots the reader must already accept. + +The provenance extension may additionally disclose: + +- `authorship.role`, `.degree`, `.attested_by` — the asserted author role and remove; +- `credential_ancestry[].parent`, `.child`, `.link_type`, `.attested_by` — the recorded path + by which the signing key obtained its authority. Authorship degree and credential ancestry are distinct from authorization. Authorization asks whether this key was permitted this scope; authorship asks who is speaking and at what remove; ancestry asks how the key came to hold the authority at all. A record can be fully authorized while its authorship is `UNKNOWN`, and that combination is reported, not merged. -Remove any of the trust-root coordinates and the dependent property becomes `UNKNOWN`; -remove `revocation.source`, `authorization.issuer`, or `actor.key_binding.trust_root` -under a minimization request and the record is `REJECTED` as unevaluable. Minimization is -enforced, not trusted: `evaluate.py` deletes each withheld coordinate before evaluating, -so a coordinate an actor asked to withhold cannot quietly still be read. +Remove a required coordinate from an ordinary record and the dependent property becomes +`UNKNOWN`. Remove a required coordinate under a minimization request — whether by naming +the leaf or a parent path — and the record is `REJECTED` as unevaluable. Minimization is +enforced, not trusted: `evaluate.py` checks the requested paths and then deletes every +withheld coordinate before evaluating, so a coordinate an actor asked to withhold cannot +quietly still be read. ## 4. Prohibited inferences diff --git a/experiments/zizk_vstd/zero_identity/THREAT_MODEL.md b/experiments/zizk_vstd/zero_identity/THREAT_MODEL.md index ce53805..7b139d2 100644 --- a/experiments/zizk_vstd/zero_identity/THREAT_MODEL.md +++ b/experiments/zizk_vstd/zero_identity/THREAT_MODEL.md @@ -13,13 +13,13 @@ schemes; where a key fails, it fails by compromise or misuse, not by cryptanalys | # | Threat | What the model does | Residual risk | |---|---|---|---| -| T1 | Correlation across receipts | Records nothing that resolves to civil identity; `unlinkability` is never `SUPPORTED`, at best `ASSUMED` under declared assumptions | Real. A stable pseudonym, a stable key, a stable issuer, and publication timing are all joinable. The model cannot see the adversary's side information | +| T1 | Correlation across receipts | Omits a civil-identity field; `unlinkability` is never `SUPPORTED`, at best `ASSUMED` under declared assumptions | Real. Remaining coordinates or side information may resolve to civil identity. A stable pseudonym, key, issuer, and publication timing are all joinable | | T2 | Replay | When `freshness.required` is set, an absent challenge fails closed and a previously observed challenge is `REFUTED` | A verifier that never requires freshness gets `UNKNOWN`, which is honest but not protective. Nonce history must be kept by the verifier | | T3 | Key compromise | `key_compromised_during_interval` refutes authentication and therefore authorization | The model learns of compromise only when someone reports it. Silent compromise is indistinguishable from normal signing | | T4 | Revoked or expired authority | Revocation state `revoked`, or an evaluation instant outside the validity window, is `REFUTED`, never `UNKNOWN`; a missing revocation source is `UNKNOWN`, never active | Revocation freshness is bounded by `revocation.checked_at`; the model does not fetch status | | T5 | One actor presenting as many independent actors | Independence requires attested evidence with distinct trust roots; distinct pseudonyms alone leave it `UNKNOWN` | An issuer that grants many credentials to one operator can produce evidence that looks distinct. Independence is `ATTESTED` at best, never proven here | -| T6 | Many actors sharing one credential | A shared pseudonymous coordinate refutes independence; `uniqueness` stays `UNKNOWN` without attested evidence | The model cannot detect sharing from a single record. Attribution binds a coordinate, never a person | -| T7 | Coerced identity disclosure | The profile never stores civil identity, so there is nothing in the record to coerce out of the verifier | Coercion moves to the issuer, which does hold the binding. This displaces the risk rather than removing it | +| T6 | Many actors sharing one credential | A shared pseudonymous coordinate cannot supply independent corroboration, but actor independence and `uniqueness` stay `UNKNOWN` | The model cannot detect sharing from a single record. Attribution binds a coordinate, never a person | +| T7 | Coerced identity disclosure | The profile omits a civil-identity field and explicitly retains the remaining correlation coordinates | Side information may still identify an actor. Coercion also moves to the issuer, which may hold a civil binding. This displaces risk rather than removing it | | T8 | Metadata and timing leakage | Not mitigated. Declared as out of scope and reported as such | Publication time, volume, scope names, and issuer choice remain observable | | T9 | Colluding issuers or verifiers | Trust roots must be declared explicitly, so a reader can see that two records share one root | Collusion between a declared issuer and a declared verifier defeats the profile. The model surfaces the shared root; it cannot rule collusion out | | T10 | Unverifiable claims of independence | `verifier_independence` never becomes `SUPPORTED`; a claim of it that lacks evidence downgrades the record verdict to `UNKNOWN` | Attestation quality is outside the model | @@ -39,12 +39,12 @@ This experiment is refuted if any of the following can be demonstrated: - `unlinkability`, `authorship_degree`, or `credential_ancestry` reaches `SUPPORTED`; - a non-originator role is read as first-party authorship; - a chain containing a revoked ancestor evaluates as anything other than a refutation; -- absence of a coordinate produces anything other than `UNKNOWN`, `REFUTED`, or `REJECTED`; -- a minimization request removes a required trust-root coordinate and the record still evaluates. +- absence of a required evidence coordinate produces a favourable property result; +- a minimization request removes a required public coordinate, directly or through a + parent path, and the record still evaluates as anything other than `REJECTED`. -All but the last are asserted directly in -[`tests/test_zero_identity.py`](tests/test_zero_identity.py); the last is -[`fixtures/rejected_unlinkability_erases_trust_root.json`](fixtures/rejected_unlinkability_erases_trust_root.json). +These conditions are asserted in [`tests/test_zero_identity.py`](tests/test_zero_identity.py), +including leaf-path and parent-path minimization fixtures. ## What this threat model does not claim diff --git a/experiments/zizk_vstd/zero_identity/evaluate.py b/experiments/zizk_vstd/zero_identity/evaluate.py index 0f485dd..b9e2600 100644 --- a/experiments/zizk_vstd/zero_identity/evaluate.py +++ b/experiments/zizk_vstd/zero_identity/evaluate.py @@ -32,9 +32,10 @@ ACCEPTED_BOUNDED = "ACCEPTED_BOUNDED" REJECTED = "REJECTED" -VERDICT_PRECEDENCE = (REJECTED, CONFLICTED, UNKNOWN, ACCEPTED_BOUNDED) - -REQUIRED_TRUST_ROOT_COORDINATES = ( +REQUIRED_PUBLIC_COORDINATES = ( + "trust_roots", + "actor.pseudonym", + "actor.key_binding.key_id", "actor.key_binding.trust_root", "authorization.issuer", "revocation.source", @@ -101,6 +102,12 @@ def _evaluate_authentication(record: dict[str, Any], reasons: list[str]) -> str: if not isinstance(binding, dict): reasons.append("authentication: no key binding coordinate") return UNKNOWN + if not _get(record, "actor.pseudonym"): + reasons.append("authentication: no pseudonymous actor coordinate") + return UNKNOWN + if not binding.get("key_id"): + reasons.append("authentication: no signing-key coordinate") + return UNKNOWN if binding.get("key_compromised_during_interval") is True: reasons.append("authentication: signing key reported compromised for the interval") return REFUTED @@ -162,6 +169,13 @@ def _evaluate_authorization( if not isinstance(grant, dict) or not grant.get("grant_id"): reasons.append("authorization: no grant coordinate; missing authorization stays UNKNOWN") return UNKNOWN + issuer = grant.get("issuer") + if not issuer: + reasons.append("authorization: no issuer coordinate") + return UNKNOWN + if issuer not in (record.get("trust_roots") or []): + reasons.append("authorization: issuer is not among the declared trust roots") + return UNKNOWN if authority == REFUTED: reasons.append("authorization: refuted because the authority is not active") return REFUTED @@ -222,9 +236,11 @@ def _evaluate_independence(record: dict[str, Any], reasons: list[str]) -> str: for peer in peers: if peer.get("pseudonym") == own: reasons.append( - "verifier_independence: peer shares this pseudonymous coordinate; not independent" + "verifier_independence: peer shares this pseudonymous coordinate; the " + "coordinate cannot supply independent corroboration, but credential sharing " + "means actor independence remains UNKNOWN" ) - return REFUTED + return UNKNOWN evidence = record.get("independence_evidence") or [] attested = [ entry @@ -260,7 +276,7 @@ def _evaluate_authorship_degree(record: dict[str, Any], reasons: list[str]) -> s return UNKNOWN role = authorship.get("role") degree = authorship.get("degree") - if role not in AUTHORSHIP_ROLES or not isinstance(degree, int): + if role not in AUTHORSHIP_ROLES or type(degree) is not int or degree < 0: reasons.append("authorship_degree: role or degree absent or unrecognised") return UNKNOWN if (role == "ORIGINATOR") != (degree == 0): @@ -408,17 +424,37 @@ def _apply_minimization(record: dict[str, Any]) -> dict[str, Any]: return reduced +def _removes_coordinate(withheld: str, required: str) -> bool: + """Return whether withholding a path removes a required coordinate. + + Withholding ``actor.key_binding`` removes its ``trust_root`` child just as surely as + naming the leaf itself. Descendant paths do not remove their parent coordinate. + """ + + return withheld == required or required.startswith(withheld + ".") + + def _check_structural_rejections(record: dict[str, Any], reasons: list[str]) -> list[str]: """Return the reasons that make a record unevaluable, that is, REJECTED outright.""" fatal: list[str] = [] request = record.get("disclosure_minimization") or {} withheld = set(request.get("withheld_coordinates") or []) - for coordinate in REQUIRED_TRUST_ROOT_COORDINATES: - if coordinate in withheld: + for coordinate in REQUIRED_PUBLIC_COORDINATES: + removing_path = next( + ( + path + for path in withheld + if isinstance(path, str) and _removes_coordinate(path, coordinate) + ), + None, + ) + if removing_path is not None: fatal.append( - f"minimization removed required trust-root coordinate {coordinate}; " - "an unlinkability request does not erase trust roots" + f"minimization path {removing_path} removed required public coordinate " + f"{coordinate}; " + "disclosure minimization cannot erase coordinates required for bounded " + "reverification" ) before = request.get("claim_boundary_before") after = request.get("claim_boundary_after") @@ -437,8 +473,8 @@ def evaluate(record: dict[str, Any]) -> Evaluation: """Evaluate one bounded disclosure record, failing closed on missing coordinates.""" reasons: list[str] = [] - record = _apply_minimization(record) fatal = _check_structural_rejections(record, reasons) + record = _apply_minimization(record) properties: dict[str, str] = {} properties["civil_identity"] = _evaluate_civil_identity(record, reasons) diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_minimization_erases_key_binding.json b/experiments/zizk_vstd/zero_identity/fixtures/rejected_minimization_erases_key_binding.json new file mode 100644 index 0000000..2171214 --- /dev/null +++ b/experiments/zizk_vstd/zero_identity/fixtures/rejected_minimization_erases_key_binding.json @@ -0,0 +1,99 @@ +{ + "expected": { + "properties": { + "accountability": "ATTESTED", + "authentication": "UNKNOWN", + "authority_active": "SUPPORTED", + "authorization": "UNKNOWN", + "authorship_degree": "ATTESTED", + "civil_identity": "UNSUPPORTED_BY_DESIGN", + "credential_ancestry": "UNKNOWN", + "freshness": "SUPPORTED", + "recovery": "ATTESTED", + "uniqueness": "UNKNOWN", + "unlinkability": "ASSUMED", + "verifier_independence": "UNKNOWN" + }, + "verdict": "REJECTED" + }, + "falsification_question": "Can minimization bypass a protected leaf by deleting its parent object?", + "fixture_id": "rejected_minimization_erases_key_binding", + "intent": "Withholding actor.key_binding removes required key coordinates and is rejected.", + "record": { + "actor": { + "civil_identity": "withheld", + "key_binding": { + "key_id": "key:alpha-1", + "signature_verified": true, + "trust_root": "root:issuer-a" + }, + "pseudonym": "pseudonym:alpha" + }, + "authorization": { + "grant_id": "grant:alpha-1", + "issuer": "root:issuer-a", + "not_after": "2026-12-31T00:00:00Z", + "not_before": "2026-01-01T00:00:00Z", + "scope": [ + "vstd4-refutation-run" + ] + }, + "authorship": { + "attested_by": "root:issuer-a", + "degree": 0, + "role": "ORIGINATOR" + }, + "claim_scope": "vstd4-refutation-run", + "claimed_properties": [ + "authorization" + ], + "conflicts": [], + "credential_ancestry": [ + { + "attested_by": "root:issuer-a", + "child": "key:alpha-1", + "link_type": "issuance", + "parent": "root:issuer-a" + } + ], + "disclosure_minimization": { + "claim_boundary_after": [ + "vstd4-refutation-run" + ], + "claim_boundary_before": [ + "vstd4-refutation-run" + ], + "declared_assumptions": [ + "issuer does not collude with the verifier" + ], + "requested_by": "actor", + "withheld_coordinates": [ + "actor.key_binding" + ] + }, + "escalation_authority": "root:issuer-a", + "evaluated_at": "2026-08-23T00:00:00Z", + "freshness": { + "challenge_source": "verifier:v1", + "nonce": "challenge:0001", + "previously_observed_nonces": [], + "required": true + }, + "independence_evidence": [], + "peer_receipts": [], + "profile": "zizk-vstd/zero-identity/experimental-0", + "record_id": "zi-minimization-parent-path", + "recovery": { + "mechanism": "issuer reissue on quorum of two custodians" + }, + "revocation": { + "checked_at": "2026-08-23T00:00:00Z", + "source": "root:issuer-a/status", + "state": "active" + }, + "trust_roots": [ + "root:issuer-a" + ], + "uniqueness_evidence": [] + } +} diff --git a/experiments/zizk_vstd/zero_identity/fixtures/rejected_shared_pseudonym_independence.json b/experiments/zizk_vstd/zero_identity/fixtures/unknown_shared_pseudonym_independence.json similarity index 89% rename from experiments/zizk_vstd/zero_identity/fixtures/rejected_shared_pseudonym_independence.json rename to experiments/zizk_vstd/zero_identity/fixtures/unknown_shared_pseudonym_independence.json index 6f6c694..a52bb62 100644 --- a/experiments/zizk_vstd/zero_identity/fixtures/rejected_shared_pseudonym_independence.json +++ b/experiments/zizk_vstd/zero_identity/fixtures/unknown_shared_pseudonym_independence.json @@ -12,13 +12,13 @@ "recovery": "ATTESTED", "uniqueness": "UNKNOWN", "unlinkability": "UNKNOWN", - "verifier_independence": "REFUTED" + "verifier_independence": "UNKNOWN" }, - "verdict": "REJECTED" + "verdict": "UNKNOWN" }, "falsification_question": "Does a repeated pseudonym supply independent corroboration?", - "fixture_id": "rejected_shared_pseudonym_independence", - "intent": "Two receipts under one pseudonymous coordinate are not independent.", + "fixture_id": "unknown_shared_pseudonym_independence", + "intent": "A shared pseudonymous coordinate cannot supply independent corroboration and does not establish how many actors use it.", "record": { "actor": { "civil_identity": "withheld", diff --git a/experiments/zizk_vstd/zero_identity/model/zero_identity_model.json b/experiments/zizk_vstd/zero_identity/model/zero_identity_model.json index 3eafb9a..7095965 100644 --- a/experiments/zizk_vstd/zero_identity/model/zero_identity_model.json +++ b/experiments/zizk_vstd/zero_identity/model/zero_identity_model.json @@ -45,12 +45,15 @@ "CONFLICTED", "REJECTED" ], - "verdict_precedence": [ - "REJECTED", - "CONFLICTED", - "UNKNOWN", - "ACCEPTED_BOUNDED" - ], + "verdict_aggregation": { + "terminal_property_results": [ + "any REFUTED property makes the record REJECTED", + "otherwise any CONFLICTED property makes the record CONFLICTED" + ], + "acceptance_boundary": "otherwise authentication and authorization must both be SUPPORTED and every explicitly claimed property must be SUPPORTED or ATTESTED", + "ancillary_unknowns": "UNKNOWN on an unclaimed ancillary property remains visible and does not widen the ACCEPTED_BOUNDED authorization result", + "otherwise": "UNKNOWN" + }, "minimum_public_actor_coordinates": [ "actor.pseudonym", "actor.key_binding.key_id", @@ -64,7 +67,9 @@ "revocation.source", "revocation.state", "revocation.checked_at", - "trust_roots", + "trust_roots" + ], + "optional_provenance_coordinates": [ "authorship.role", "authorship.degree", "authorship.attested_by", @@ -159,7 +164,6 @@ "verifier_independence": { "attainable_statuses": [ "ATTESTED", - "REFUTED", "UNKNOWN", "CONFLICTED" ], @@ -249,7 +253,7 @@ }, { "id": "ZI-R4", - "statement": "A shared pseudonymous coordinate across two receipts REFUTES independence." + "statement": "A shared pseudonymous coordinate cannot supply independent corroboration, but it leaves actor independence UNKNOWN because multiple actors may share one credential." }, { "id": "ZI-R5", @@ -257,7 +261,7 @@ }, { "id": "ZI-R6", - "statement": "A minimization request that removes a required trust-root coordinate makes the record unevaluable and is REJECTED." + "statement": "A minimization request that removes a required public coordinate, whether directly or through a parent path, makes the record unevaluable and is REJECTED." }, { "id": "ZI-R7", diff --git a/experiments/zizk_vstd/zero_identity/tests/test_zero_identity.py b/experiments/zizk_vstd/zero_identity/tests/test_zero_identity.py index df107b7..ca211a5 100644 --- a/experiments/zizk_vstd/zero_identity/tests/test_zero_identity.py +++ b/experiments/zizk_vstd/zero_identity/tests/test_zero_identity.py @@ -83,10 +83,10 @@ def test_expired_authority_is_refuted() -> None: assert outcome.properties["authority_active"] == REFUTED -def test_shared_pseudonym_refutes_independence() -> None: - outcome = result("rejected_shared_pseudonym_independence") - assert outcome.properties["verifier_independence"] == REFUTED - assert outcome.verdict == REJECTED +def test_shared_pseudonym_does_not_establish_actor_independence_or_nonindependence() -> None: + outcome = result("unknown_shared_pseudonym_independence") + assert outcome.properties["verifier_independence"] == UNKNOWN + assert outcome.verdict == UNKNOWN def test_distinct_pseudonyms_do_not_establish_distinct_actors() -> None: @@ -99,7 +99,14 @@ def test_minimization_cannot_delete_a_required_trust_root() -> None: outcome = result("rejected_unlinkability_erases_trust_root") assert outcome.verdict == REJECTED assert outcome.properties["authority_active"] == UNKNOWN - assert any("trust-root" in reason for reason in outcome.reasons) + assert any("revocation.source" in reason for reason in outcome.reasons) + + +def test_minimization_cannot_bypass_a_protected_leaf_by_deleting_its_parent() -> None: + outcome = result("rejected_minimization_erases_key_binding") + assert outcome.verdict == REJECTED + assert outcome.properties["authentication"] == UNKNOWN + assert any("actor.key_binding" in reason for reason in outcome.reasons) def test_replayed_challenge_is_detected() -> None: @@ -177,6 +184,27 @@ def test_unknown_trust_root_does_not_authenticate() -> None: assert outcome.verdict == UNKNOWN +@pytest.mark.parametrize("coordinate", ["pseudonym", "key_id", "issuer"]) +def test_required_public_identity_coordinates_cannot_be_omitted(coordinate: str) -> None: + record = load("positive_bounded_authorization")["record"] + if coordinate == "pseudonym": + record["actor"].pop("pseudonym") + elif coordinate == "key_id": + record["actor"]["key_binding"].pop("key_id") + else: + record["authorization"].pop("issuer") + outcome = evaluate(record) + assert outcome.verdict == UNKNOWN + + +def test_undeclared_issuer_does_not_authorize() -> None: + record = load("positive_bounded_authorization")["record"] + record["authorization"]["issuer"] = "root:undeclared" + outcome = evaluate(record) + assert outcome.properties["authorization"] == UNKNOWN + assert outcome.verdict == UNKNOWN + + def test_scope_mismatch_is_refuted() -> None: record = load("positive_bounded_authorization")["record"] record["claim_scope"] = "vstd4-availability-run" @@ -202,6 +230,13 @@ def test_declared_degree_must_agree_with_recorded_delegation_hops() -> None: assert outcome.verdict == CONFLICTED +@pytest.mark.parametrize("degree", [True, -1]) +def test_authorship_degree_must_be_a_nonnegative_integer(degree: object) -> None: + record = load("positive_bounded_authorization")["record"] + record["authorship"]["degree"] = degree + assert evaluate(record).properties["authorship_degree"] == UNKNOWN + + def test_unattested_ancestry_link_is_not_a_verified_chain() -> None: outcome = result("unknown_unattested_ancestry_link") assert outcome.properties["credential_ancestry"] == UNKNOWN @@ -254,6 +289,8 @@ def test_model_declares_the_terminology_decision_and_prohibited_inferences() -> assert model["wire_identifier"] is None decision = model["terminology_decision"]["public_label_zero_identity"] assert decision == "REJECTED_AS_UNQUALIFIED_PUBLIC_LABEL" + assert "verdict_aggregation" in model + assert "verdict_precedence" not in model assert len(model["prohibited_inferences"]) >= 10 From 29044d06f5bc4bccd45b0cf14cdde4c3cc19521f Mon Sep 17 00:00:00 2001 From: TimeLordRaps Date: Sun, 23 Aug 2026 09:14:43 -0700 Subject: [PATCH 05/32] Document VSTD intellectual precedents --- README.md | 1 + ROADMAP.md | 2 + docs/CLAIMS_AND_LIMITS.md | 2 + docs/CONCEPTS_AND_PRECEDENTS.md | 86 +++++++++++++++++++ docs/ECOSYSTEM.md | 2 + docs/QUICKSTART.md | 2 + docs/layers/vstd-3/compatibility.md | 2 + docs/layers/vstd-3/references.md | 2 + docs/layers/vstd-3/threat-model.md | 2 + docs/layers/vstd-3/vendor-integration.md | 2 + docs/profiles/competition-evaluation.md | 2 + .../specimens/honest-unknown.json | 4 +- .../specimens/inflated-tier.json | 4 +- .../specimens/poisoned-ancestor.json | 6 +- .../specimens/wrong-artifact.json | 4 +- src/verifier/specifications/LADDER.md | 60 +++++++++++-- src/verifier/specifications/VSTD-3.md | 2 + src/verifier/specifications/VSTD-4.md | 2 + standard/LADDER.md | 60 +++++++++++-- standard/VSTD-1.md | 2 + standard/VSTD-2.md | 2 + standard/VSTD-3.md | 2 + standard/VSTD-4.md | 2 + standard/VSTD-5.md | 2 + standard/VSTD-Graph-1.md | 2 + standard/VSTD-Graph-2.md | 2 + standard/VSTD-Graph-3.md | 2 + standard/VSTD-Graph-4.md | 2 + standard/VSTD-Graph-5.md | 2 + 29 files changed, 246 insertions(+), 21 deletions(-) create mode 100644 docs/CONCEPTS_AND_PRECEDENTS.md diff --git a/README.md b/README.md index ed461e0..8a5f7e4 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ evidence, inside which bounds, and how somebody else can prove it wrong.* [Run the demo](#see-it-fail-correctly) · [Read the quickstart](docs/QUICKSTART.md) · [Inspect the standard](standard/LADDER.md) · +[Explore concepts and precedents](docs/CONCEPTS_AND_PRECEDENTS.md) · [Challenge a claim](https://github.com/TimeLordRaps/verifier/discussions/8) · [See the roadmap](ROADMAP.md) diff --git a/ROADMAP.md b/ROADMAP.md index 1f9300c..080d9a4 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,6 +4,8 @@ **Scope:** the public specification, reference implementation, and interoperability surface only +**Reader context:** [`Concept guide and intellectual precedents`](docs/CONCEPTS_AND_PRECEDENTS.md) + ## The near-term problem “Speed superintelligence” is used here as an operational condition, not as a model diff --git a/docs/CLAIMS_AND_LIMITS.md b/docs/CLAIMS_AND_LIMITS.md index 5533a9e..ca90903 100644 --- a/docs/CLAIMS_AND_LIMITS.md +++ b/docs/CLAIMS_AND_LIMITS.md @@ -1,5 +1,7 @@ # Claims and limits in plain language +> Reader aid: [concept glossary and primary precedents](CONCEPTS_AND_PRECEDENTS.md). + **Status:** normative interpretation guide for the VSTD object and Graph ladders This guide translates VSTD claim language into ordinary language. When a short claim diff --git a/docs/CONCEPTS_AND_PRECEDENTS.md b/docs/CONCEPTS_AND_PRECEDENTS.md new file mode 100644 index 0000000..a0f32d9 --- /dev/null +++ b/docs/CONCEPTS_AND_PRECEDENTS.md @@ -0,0 +1,86 @@ +# Concept guide and intellectual precedents + +**Status:** non-normative reader aid + +VSTD did not arise in a vacuum, but it also does not inherit another system's +guarantees merely by citing it. This guide separates two kinds of link: + +1. **Orientation links** point to Wikipedia for a quick definition. The links use + ordinary Markdown title text, which some browsers expose as a small hover tooltip. + GitHub does not run Wikipedia's Page Previews code, so a full infobox-style hover card + is not portable in repository Markdown. +2. **Primary references** point to standards, specifications, or original papers. These + establish the neighboring precedent described here. They do not prove that VSTD is + correct, adopted, interoperable, accredited, or conformant to the referenced system. + +When an orientation summary and a primary source differ, use the primary source. When a +primary source and a VSTD requirement differ, the VSTD document controls VSTD conformance +and the difference must remain explicit. + +## Orientation glossary + +| Concept | Quick orientation | How VSTD uses or bounds it | +|---|---|---| +| Assurance | [Information assurance](https://en.wikipedia.org/wiki/Information_assurance "Wikipedia orientation; not a VSTD authority") | VSTD reports evidence-bounded results, not universal confidence or institutional accreditation. | +| Layered controls | [Defense in depth](https://en.wikipedia.org/wiki/Defense_in_depth_%28computing%29 "Wikipedia orientation; not a VSTD authority") | The analogy is multiple failure classes. VSTD adds the stricter rule that evidence for one layer never supplies another. | +| Fail-closed decisions | [Fail-safe](https://en.wikipedia.org/wiki/Fail-safe "Wikipedia orientation; not a VSTD authority") | Missing or exhausted evidence stays `UNKNOWN`, `INDETERMINATE`, or `UNSUPPORTED`; it does not become a pass. | +| Trusted computing base | [Trusted computing base](https://en.wikipedia.org/wiki/Trusted_computing_base "Wikipedia orientation; not a VSTD authority") | Every result must expose the mechanism and trust roots on which it depends. | +| Zero trust | [Zero trust architecture](https://en.wikipedia.org/wiki/Zero_trust_architecture "Wikipedia orientation; not a VSTD authority") | VSTD borrows no product architecture wholesale; it uses explicit verification rather than identity or location as an automatic correctness signal. | +| Canonicalization | [Canonicalization](https://en.wikipedia.org/wiki/Canonicalization "Wikipedia orientation; not a VSTD authority") | Stable fields need one declared byte representation before hashing. VSTD's formats are not thereby RFC 8785 implementations. | +| Content addressing | [Content-addressable storage](https://en.wikipedia.org/wiki/Content-addressable_storage "Wikipedia orientation; not a VSTD authority") | Artifact and receipt coordinates bind declared bytes through digests; a digest alone does not establish origin or truth. | +| Cryptographic digest | [Cryptographic hash function](https://en.wikipedia.org/wiki/Cryptographic_hash_function "Wikipedia orientation; not a VSTD authority") | Hash observations can establish byte identity within an algorithm and observation boundary, not semantic correctness. | +| Provenance | [Data provenance](https://en.wikipedia.org/wiki/Data_provenance "Wikipedia orientation; not a VSTD authority") | VSTD-Graph records declared entities, transformations, and ancestry while preserving incomplete or unauthenticated history as such. | +| Hypergraph | [Hypergraph](https://en.wikipedia.org/wiki/Hypergraph "Wikipedia orientation; not a VSTD authority") | N-ary transformation edges preserve many-input and many-output structure without flattening it into ambiguous binary links. | +| Attestation | [Attestation](https://en.wikipedia.org/wiki/Attestation "Wikipedia orientation; not a VSTD authority") | VSTD-3 records who or what supplied evidence, the mechanism used, and the resulting evidence ceiling. | +| Trust root | [Trust anchor](https://en.wikipedia.org/wiki/Trust_anchor "Wikipedia orientation; not a VSTD authority") | A declared root is an explicit dependency and stopping boundary, not evidence that the root is honest. | +| Reproducibility | [Reproducibility](https://en.wikipedia.org/wiki/Reproducibility "Wikipedia orientation; not a VSTD authority") | VSTD binds the exact mechanism, inputs, environment, and equivalence relation required by the claim rather than treating the word as self-defining. | +| Reproducible build | [Reproducible builds](https://en.wikipedia.org/wiki/Reproducible_builds "Wikipedia orientation; not a VSTD authority") | Independently recreating identical artifacts is an important special case of portable checking, not a proof of every property of the artifact. | +| Falsifiability | [Falsifiability](https://en.wikipedia.org/wiki/Falsifiability "Wikipedia orientation; not a VSTD authority") | VSTD-4 requires an explicit, bounded way for an outside checker to refute the exact claim. It does not turn Popper's philosophy into a software theorem. | +| Proof-carrying artifact | [Proof-carrying code](https://en.wikipedia.org/wiki/Proof-carrying_code "Wikipedia orientation; not a VSTD authority") | The engineering precedent is that an untrusted producer can ship a result with a smaller independently checkable certificate under a declared policy. | +| SAT | [Boolean satisfiability problem](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem "Wikipedia orientation; not a VSTD authority") | The reference subset encodes finite admission questions; SAT success establishes only the encoded formula. | +| CNF | [Conjunctive normal form](https://en.wikipedia.org/wiki/Conjunctive_normal_form "Wikipedia orientation; not a VSTD authority") | VSTD's bounded policy encodings use finite CNF and do not equate arbitrary CNF with 3-SAT. | +| Resolution | [Resolution](https://en.wikipedia.org/wiki/Resolution_%28logic%29 "Wikipedia orientation; not a VSTD authority") | Clausal refutations provide checkable evidence for an unsatisfiable result within the implemented proof format. | +| Unit propagation | [Unit propagation](https://en.wikipedia.org/wiki/Unit_propagation "Wikipedia orientation; not a VSTD authority") | The minimal trusted checker validates the supported reverse-unit-propagation certificate path rather than trusting the producer's solver. | +| Three-valued result | [Three-valued logic](https://en.wikipedia.org/wiki/Three-valued_logic "Wikipedia orientation; not a VSTD authority") | `UNKNOWN` is a first-class refusal to overstate, not a Boolean false and never a pass. VSTD's statuses are not claimed to implement one historical three-valued logic. | +| Append-only transparency | [Certificate Transparency](https://en.wikipedia.org/wiki/Certificate_Transparency "Wikipedia orientation; not a VSTD authority") | Immutable receipts and additive corrections share an auditability goal with append-only logs; VSTD is not a Certificate Transparency implementation. | +| Update freshness | [The Update Framework](https://en.wikipedia.org/wiki/The_Update_Framework "Wikipedia orientation; not a VSTD authority") | Staleness, rollback, revocation, and key compromise are separate from content integrity and require explicit current-state evidence. | +| Semantic versioning | [Semantic Versioning](https://en.wikipedia.org/wiki/Software_versioning#Semantic_versioning "Wikipedia orientation; not a VSTD authority") | Repository releases use semantic versions independently of the VSTD object and Graph layer numbers. | +| Object language and metalanguage | [Metalogic](https://en.wikipedia.org/wiki/Metalogic "Wikipedia orientation; not a VSTD authority") | VSTD uses this only as a design analogy for examining a verification surface; it does not claim that every adjacent layer is a formal metalanguage. | +| Undefinability of truth | [Tarski's undefinability theorem](https://en.wikipedia.org/wiki/Tarski%27s_undefinability_theorem "Wikipedia orientation; not a VSTD authority") | The ladder expressly does not derive its architecture or observational limits from Tarski's theorem. | + +## Primary reference map + +| VSTD design seam | Primary or official reference | Relevant precedent and explicit limit | +|---|---|---| +| Separate failure controls and fail-safe defaults | Saltzer and Schroeder, [*The Protection of Information in Computer Systems*](https://web.mit.edu/Saltzer/www/publications/pubs.html) (1975) | Classic security-design principles include fail-safe defaults, complete mediation, separation of privilege, least privilege, and least common mechanism. They motivate separating failure surfaces; they do not derive VSTD's five layers. | +| Security-assurance components and packages | Common Criteria, [Part 3: Security assurance components](https://www.commoncriteriaportal.org/files/ccfiles/CC2022PART3R1.pdf) (CC:2022 Revision 1) | Established precedent for decomposing assurance into named components and packages. VSTD is not Common Criteria, accredited evaluation, or an Evaluation Assurance Level. | +| Canonical JSON as a cryptographic wire input | IETF Independent Stream, [RFC 8785: JSON Canonicalization Scheme](https://www.rfc-editor.org/rfc/rfc8785.html) | Shows why cryptographic operations over JSON require invariant representation. VSTD uses its own declared canonicalization rules and must not claim RFC 8785 conformance unless a format actually implements it. | +| Provenance entities, activities, and agents | W3C, [PROV-DM: The PROV Data Model](https://www.w3.org/TR/prov-dm/) | Standardized vocabulary and constraints for interoperable provenance. VSTD-Graph's artifact and transformation model is adjacent, not a PROV implementation or complete history claim. | +| Supply-chain step and artifact attestations | [in-toto specification v1.0](https://in-toto.io/docs/specs/) and [SLSA v1.2](https://slsa.dev/spec/v1.2/) | Established formats and levels for materials, products, builders, steps, and provenance. VSTD may bind their outputs as evidence but does not manufacture their authorization or assurance level. | +| Release preservation and provenance integrity | NIST, [SP 800-218: Secure Software Development Framework 1.1](https://doi.org/10.6028/NIST.SP.800-218) | Practices PS.3.1 and PS.3.2 cover archiving releases, maintaining provenance, protecting its integrity, and enabling recipient verification. This is operational precedent, not VSTD certification. | +| Independent recreation of artifacts | Reproducible Builds, [formal definition](https://reproducible-builds.org/docs/definition/) | Defines the source, environment, instruction, and artifact relationship needed for bit-for-bit recreation. VSTD permits other explicitly declared equivalence relations and does not infer truth from reproducibility alone. | +| Producer-supplied, consumer-checked certificates | Necula, [*Proof-Carrying Code*](https://doi.org/10.1145/263699.263712) (POPL 1997) | Primary precedent for an untrusted producer supplying a proof checked under a defined policy by the consumer. VSTD generalizes the receipt pattern but does not inherit PCC's safety theorem. | +| Checkable SAT refutations | Wetzler, Heule, and Hunt, [*DRAT-trim: Efficient Checking and Trimming Using Expressive Clausal Proofs*](https://www.cs.cmu.edu/~mheule/publications/drat-trim.pdf) (2014) | Demonstrates independently checking unsatisfiability proofs rather than trusting a SAT solver's answer. VSTD's implemented certificate is a narrower declared RUP path, not arbitrary DRAT. | +| Explicit indeterminate solver results | [SMT-LIB Standard 2.7](https://smt-lib.org/papers/smt-lib-reference-v2.7-r2025-04-09.pdf) | The standard response grammar includes `sat`, `unsat`, and `unknown`. VSTD's richer status vocabulary is independently defined, but the refusal to fabricate a Boolean answer has established solver precedent. | +| Append-only evidence and independently detectable equivocation | IETF, [RFC 9162: Certificate Transparency Version 2.0](https://www.rfc-editor.org/rfc/rfc9162.html) | Merkle inclusion and consistency proofs support auditing an append-only log, while the RFC also names split-view limitations. VSTD's additive history is analogous but not a CT log. | +| Freshness, rollback, freeze, and key-compromise boundaries | [The Update Framework specification](https://theupdateframework.github.io/specification/latest/) | Separates current-version metadata, expiration, delegated roles, and compromise recovery from artifact bytes. VSTD does not implement TUF, but shares the requirement that old authentic data is not automatically current data. | + +## How to cite these precedents + +Use language such as: + +- "VSTD's portable-certificate design is adjacent to proof-carrying code." +- "VSTD-Graph overlaps W3C PROV, in-toto, and SLSA at the provenance boundary." +- "The refusal to convert resource exhaustion into a false result has precedent in the + `unknown` response of SMT-LIB." + +Do not write: + +- "Saltzer and Schroeder prove the VSTD ladder." +- "VSTD implements PROV, SLSA, in-toto, TUF, Common Criteria, or Certificate + Transparency," unless separately demonstrated by a named conformance mechanism. +- "These citations establish VSTD's security, completeness, adoption, or novelty." + +The point of the map is traceable intellectual context: which established problem a VSTD +rule resembles, where the design deliberately differs, and what remains original project +architecture rather than inherited authority. diff --git a/docs/ECOSYSTEM.md b/docs/ECOSYSTEM.md index 62ef450..85d62ed 100644 --- a/docs/ECOSYSTEM.md +++ b/docs/ECOSYSTEM.md @@ -1,5 +1,7 @@ # Ecosystem boundary map +> Reader aid: [concept glossary and primary precedents](CONCEPTS_AND_PRECEDENTS.md). + **Status:** non-normative positioning note **Reviewed:** 2026-08-22 diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index a77c09a..84d7244 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -1,5 +1,7 @@ # VSTD quickstart +> Reader aid: [concept glossary and primary precedents](CONCEPTS_AND_PRECEDENTS.md). + ## 1. Install the public source VSTD requires Python 3.10–3.13. The base runtime has no required third-party diff --git a/docs/layers/vstd-3/compatibility.md b/docs/layers/vstd-3/compatibility.md index 01f9ba8..5b0987a 100644 --- a/docs/layers/vstd-3/compatibility.md +++ b/docs/layers/vstd-3/compatibility.md @@ -1,5 +1,7 @@ # VSTD-3 implementation compatibility +> Reader aid: [concept glossary and primary precedents](../../CONCEPTS_AND_PRECEDENTS.md). + VSTD-3 is additive. It does not reinterpret earlier receipt wire formats. For the historical filename and wire-identifier table, see `../../../standard/WIRE_IDENTIFIERS.md`. diff --git a/docs/layers/vstd-3/references.md b/docs/layers/vstd-3/references.md index f59b215..6222677 100644 --- a/docs/layers/vstd-3/references.md +++ b/docs/layers/vstd-3/references.md @@ -1,5 +1,7 @@ # VSTD-3 official public references +> Reader aid: [cross-layer concept glossary and primary precedents](../../CONCEPTS_AND_PRECEDENTS.md). + **Retrieved:** 2026-08-21 These sources inform adapter boundaries and interoperability vocabulary. They are not diff --git a/docs/layers/vstd-3/threat-model.md b/docs/layers/vstd-3/threat-model.md index b697e40..8e1ab92 100644 --- a/docs/layers/vstd-3/threat-model.md +++ b/docs/layers/vstd-3/threat-model.md @@ -1,5 +1,7 @@ # VSTD-3 threat model +> Reader aid: [concept glossary and primary precedents](../../CONCEPTS_AND_PRECEDENTS.md). + **Layer:** VSTD-3; historical receipt wire identifier `VSTD-3.0` **Purpose:** defensive verification and conformance; not offensive exploit guidance diff --git a/docs/layers/vstd-3/vendor-integration.md b/docs/layers/vstd-3/vendor-integration.md index 91d9671..126fbdf 100644 --- a/docs/layers/vstd-3/vendor-integration.md +++ b/docs/layers/vstd-3/vendor-integration.md @@ -1,5 +1,7 @@ # VSTD-3 accelerator vendor integration kit +> Reader aid: [concept glossary and primary precedents](../../CONCEPTS_AND_PRECEDENTS.md). + This is the minimum review surface for a firmware or silicon security team evaluating VSTD-3. It does not require adopting VSTD product names in firmware. diff --git a/docs/profiles/competition-evaluation.md b/docs/profiles/competition-evaluation.md index 44f63cd..f1b466f 100644 --- a/docs/profiles/competition-evaluation.md +++ b/docs/profiles/competition-evaluation.md @@ -1,5 +1,7 @@ # Competition evaluation profile +> Reader aid: [concept glossary and primary precedents](../CONCEPTS_AND_PRECEDENTS.md). + **Status:** non-normative VSTD-1/VSTD-Graph integration profile **Version:** 0.1 **Date:** 2026-08-21 diff --git a/examples/flagship_demo/specimens/honest-unknown.json b/examples/flagship_demo/specimens/honest-unknown.json index 834134f..61d2709 100644 --- a/examples/flagship_demo/specimens/honest-unknown.json +++ b/examples/flagship_demo/specimens/honest-unknown.json @@ -30,7 +30,7 @@ "format_fragment": "UP,WIDTH-K,RES", "implementation_hash": "sha256:49afe96d327f99b12a518529d47b1b68c16228c4688be14eca279ea3e5b14d5b", "parser_hash": "sha256:a49fc58d15ec1b925b4e2bd48f5dfb350d9e9fca28044c5a0fa9ff051dc70c01", - "specification_hash": "sha256:f2420f7826531ffdfadb1fcb9c0d3317d330d47a1cc367328ebd1c6921af36eb" + "specification_hash": "sha256:b8dd949cfff9044a48cd5c02426afebc9024c1efe6e2266773c6f73053e25c20" } }, "certificate": { @@ -157,7 +157,7 @@ ] }, "header": { - "binding": "aee206fd7bc450c01bda6c54a4e26ba228ebf752b4085df56f1d250055a7c9cd", + "binding": "288cd85ec791e73a5839ab8576bcc67905a008668111f21f9c1e9941cf808f76", "clause_count": 3, "format": "VSTD4-GDC-1", "literal_count": 4, diff --git a/examples/flagship_demo/specimens/inflated-tier.json b/examples/flagship_demo/specimens/inflated-tier.json index 481fe4d..4bf1fc9 100644 --- a/examples/flagship_demo/specimens/inflated-tier.json +++ b/examples/flagship_demo/specimens/inflated-tier.json @@ -30,7 +30,7 @@ "format_fragment": "UP,WIDTH-K,RES", "implementation_hash": "sha256:49afe96d327f99b12a518529d47b1b68c16228c4688be14eca279ea3e5b14d5b", "parser_hash": "sha256:a49fc58d15ec1b925b4e2bd48f5dfb350d9e9fca28044c5a0fa9ff051dc70c01", - "specification_hash": "sha256:f2420f7826531ffdfadb1fcb9c0d3317d330d47a1cc367328ebd1c6921af36eb" + "specification_hash": "sha256:b8dd949cfff9044a48cd5c02426afebc9024c1efe6e2266773c6f73053e25c20" } }, "certificate": { @@ -154,7 +154,7 @@ ] }, "header": { - "binding": "aee206fd7bc450c01bda6c54a4e26ba228ebf752b4085df56f1d250055a7c9cd", + "binding": "288cd85ec791e73a5839ab8576bcc67905a008668111f21f9c1e9941cf808f76", "clause_count": 3, "format": "VSTD4-GDC-1", "literal_count": 4, diff --git a/examples/flagship_demo/specimens/poisoned-ancestor.json b/examples/flagship_demo/specimens/poisoned-ancestor.json index e0ded0b..d47a4cb 100644 --- a/examples/flagship_demo/specimens/poisoned-ancestor.json +++ b/examples/flagship_demo/specimens/poisoned-ancestor.json @@ -30,7 +30,7 @@ "format_fragment": "UP,WIDTH-K,RES", "implementation_hash": "sha256:49afe96d327f99b12a518529d47b1b68c16228c4688be14eca279ea3e5b14d5b", "parser_hash": "sha256:a49fc58d15ec1b925b4e2bd48f5dfb350d9e9fca28044c5a0fa9ff051dc70c01", - "specification_hash": "sha256:f2420f7826531ffdfadb1fcb9c0d3317d330d47a1cc367328ebd1c6921af36eb" + "specification_hash": "sha256:b8dd949cfff9044a48cd5c02426afebc9024c1efe6e2266773c6f73053e25c20" } }, "collection": { @@ -63,7 +63,7 @@ "explanation": "collection:demo holds at graph level 0. Level 1 is refuted by: STATUS_ADMISSIBILITY: artifact:source is REVOKED.", "level": 0, "max_level": 5, - "refutation_digest": "87e9b1889e745c83c4a2dfc3968eadc9a4015146884a99f892e70b2d04d27eed", + "refutation_digest": "b0012642de01377d91eaa35ab471b11399380c063c23a9010f2c645a9c857497", "witness_digest": null }, "hypergraph": { @@ -589,7 +589,7 @@ ] }, "header": { - "binding": "23c087d7ef52a1995c2f54f51940f82faef60dc5262627fcee68dd9c9d78eb8e", + "binding": "0f90447073393ef43080efb11453ada842ac0078b32341f20038ab151f0d70aa", "clause_count": 17, "format": "VSTD4-GDC-1", "literal_count": 25, diff --git a/examples/flagship_demo/specimens/wrong-artifact.json b/examples/flagship_demo/specimens/wrong-artifact.json index c419d74..e39c557 100644 --- a/examples/flagship_demo/specimens/wrong-artifact.json +++ b/examples/flagship_demo/specimens/wrong-artifact.json @@ -30,7 +30,7 @@ "format_fragment": "UP,WIDTH-K,RES", "implementation_hash": "sha256:49afe96d327f99b12a518529d47b1b68c16228c4688be14eca279ea3e5b14d5b", "parser_hash": "sha256:a49fc58d15ec1b925b4e2bd48f5dfb350d9e9fca28044c5a0fa9ff051dc70c01", - "specification_hash": "sha256:f2420f7826531ffdfadb1fcb9c0d3317d330d47a1cc367328ebd1c6921af36eb" + "specification_hash": "sha256:b8dd949cfff9044a48cd5c02426afebc9024c1efe6e2266773c6f73053e25c20" } }, "certificate": { @@ -162,7 +162,7 @@ ] }, "header": { - "binding": "aee206fd7bc450c01bda6c54a4e26ba228ebf752b4085df56f1d250055a7c9cd", + "binding": "288cd85ec791e73a5839ab8576bcc67905a008668111f21f9c1e9941cf808f76", "clause_count": 3, "format": "VSTD4-GDC-1", "literal_count": 4, diff --git a/src/verifier/specifications/LADDER.md b/src/verifier/specifications/LADDER.md index be23b3d..d0bd09b 100644 --- a/src/verifier/specifications/LADDER.md +++ b/src/verifier/specifications/LADDER.md @@ -4,6 +4,8 @@ **Editor:** TimeLordRaps **License:** Apache-2.0 +**Reader context:** [`Concept guide and intellectual precedents`](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md) + VSTD specification numbers are **layers of verification depth**, not revisions of a single document. VSTD-3 does not supersede VSTD-1 any more than a floor supersedes its foundation. @@ -15,14 +17,23 @@ foundation. Each layer names a distinct verification question and a distinct failure class. The ordering is a composition rule, not logical entailment between layers. +The nearest familiar security analogy is +[defense in depth](https://en.wikipedia.org/wiki/Defense_in_depth_%28computing%29 "Wikipedia orientation; primary references are mapped below"), +but the analogy is limited: VSTD layers are separately evidenced questions, not +interchangeable controls whose mere quantity establishes assurance. Decomposing assurance +into named components also has precedent in the Common Criteria, while VSTD deliberately +uses different layers, evidence rules, and conformance semantics. + **Evidence for one layer never supplies evidence for another layer.** In particular, layer-4 evidence does not supply, imply, upgrade, or repair layer 3, 2, or 1. A reported depth of `N` is only shorthand for `N` separately checked results, one for each layer from 1 through `N`. -Reflection and metalanguage are useful design analogies for asking what a given +Reflection and [metalanguage](https://en.wikipedia.org/wiki/Metalogic "Wikipedia orientation; not a proof of the VSTD ladder") +are useful design analogies for asking what a given verification surface leaves unexamined. VSTD does not claim that Tarski's -undefinability theorem proves this ladder, that adjacent layers form formal +[undefinability theorem](https://en.wikipedia.org/wiki/Tarski%27s_undefinability_theorem "Wikipedia orientation; the theorem does not derive this ladder") +proves this ladder, that adjacent layers form formal metalanguages, or that a lower-layer implementation is logically incapable of describing another layer's failure. The normative requirement is narrower: an implementation MUST NOT treat success on one question as evidence for a different @@ -65,7 +76,10 @@ VSTD-Graph governs the verification of a **collection** of objects. Call this verification *dynamics*. The two axes are parallel but coupled: a collection's dynamics are constrained by its -members' mechanics, and by the provenance edges between them. +members' mechanics, and by the +[provenance](https://en.wikipedia.org/wiki/Data_provenance "Wikipedia orientation; see W3C PROV-DM and supply-chain references below") +edges between them. The implemented N-ary representation is a +[hypergraph](https://en.wikipedia.org/wiki/Hypergraph "Wikipedia orientation; not a claim of complete real-world lineage"). | Layer | Name | Collection-level closure | |---|---|---| @@ -118,7 +132,10 @@ computation, VSTD preserves that artifact and its verification bounds. ### 4.2 Bounded admission uses CNF -The reference admission procedures encode finite, bounded policy questions as CNF. +The reference admission procedures encode finite, bounded policy questions as +[conjunctive normal form](https://en.wikipedia.org/wiki/Conjunctive_normal_form "Wikipedia orientation; the implemented format is finite CNF") +(CNF) for the +[Boolean satisfiability problem](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem "Wikipedia orientation; SAT success establishes only the encoded formula"). CNF is not identical to 3-SAT. A finite CNF satisfiability instance can be transformed in polynomial time into an equisatisfiable 3-CNF instance, using auxiliary variables where required. VSTD does not need that transformation for every checker and does not @@ -155,7 +172,11 @@ An unsatisfiable result, by default, carries nothing but the solver's word. For a fail-closed standard, **refusals are the most consequential output**. A standard whose passes are checkable and whose refusals are not has its assurance backwards. Layer 4 therefore requires a refutation certificate — a clausal proof, verifiable by -reverse unit propagation, checkable without re-solving. +[reverse unit propagation](https://en.wikipedia.org/wiki/Unit_propagation "Wikipedia orientation; VSTD implements a bounded RUP checker"), +checkable without re-solving. This follows the same producer-certificate/consumer-checker +engineering asymmetry as +[proof-carrying code](https://en.wikipedia.org/wiki/Proof-carrying_code "Wikipedia orientation; VSTD does not inherit PCC's safety theorem"), +while using a narrower certificate language. Resolution proofs have exponential lower bounds for some formula families. A conforming implementation therefore MUST declare a bound and MUST answer `UNKNOWN` @@ -204,7 +225,34 @@ closed. It never means the lower layers became unnecessary. ## 7. Numbering - **Specification layers are integers**: VSTD-1 … VSTD-5, VSTD-Graph-1 … VSTD-Graph-5. -- **Repository releases use semantic versioning** and are independent of layer numbers. +- **Repository releases use [semantic versioning](https://semver.org/)** and are independent + of layer numbers. A release version never implies a layer, and a layer never implies a release. See `WIRE_IDENTIFIERS.md` for frozen wire identifiers and the historical public filenames. + +--- + +## 8. Intellectual lineage and adjacent precedents + +The ladder is VSTD project architecture; no cited work proves that these five layers are +necessary, sufficient, complete, or uniquely ordered. The references below show that its +individual design pressures have established precedents in security engineering, +provenance, reproducible systems, and proof checking. The +[`concept guide`](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md) provides definitions, additional +sources, and explicit non-equivalences. + +| VSTD pressure | Adjacent precedent | What the precedent contributes—and does not | +|---|---|---| +| Separate failure surfaces and fail-closed defaults | Saltzer and Schroeder, [*The Protection of Information in Computer Systems*](https://web.mit.edu/Saltzer/www/publications/pubs.html) | Classic principles include fail-safe defaults, complete mediation, separation of privilege, and least common mechanism. They motivate separation; they do not derive VSTD's layer count. | +| Named assurance components | Common Criteria, [Part 3: Security assurance components](https://www.commoncriteriaportal.org/files/ccfiles/CC2022PART3R1.pdf) | Demonstrates established componentized assurance and assurance packages. VSTD is not a Common Criteria evaluation or an Evaluation Assurance Level. | +| Stable cryptographic representations | [RFC 8785: JSON Canonicalization Scheme](https://www.rfc-editor.org/rfc/rfc8785.html) | Shows why JSON used as cryptographic input needs invariant representation. VSTD formats retain their own declared canonicalization rules. | +| Recorded entities, activities, and agents | W3C [PROV-DM](https://www.w3.org/TR/prov-dm/) | Supplies an interoperable provenance model adjacent to the Graph axis. VSTD-Graph is not a PROV implementation and does not infer complete history. | +| Software materials, builders, steps, and products | [in-toto specification v1.0](https://in-toto.io/docs/specs/) and [SLSA v1.2](https://slsa.dev/spec/v1.2/) | Establish supply-chain provenance and attestation precedents. VSTD may bind their evidence but cannot manufacture their authorization or assurance level. | +| Preserved release and provenance evidence | NIST [SP 800-218 SSDF 1.1](https://doi.org/10.6028/NIST.SP.800-218) | Practices PS.3.1 and PS.3.2 call for preserving releases and provenance and enabling integrity verification. They do not certify a VSTD receipt. | +| Independent recreation | Reproducible Builds, [formal definition](https://reproducible-builds.org/docs/definition/) | Grounds the special case where another party recreates specified artifacts from declared inputs and instructions. Reproducibility does not establish every semantic claim. | +| Producer-supplied portable certificates | Necula, [*Proof-Carrying Code*](https://doi.org/10.1145/263699.263712) | Establishes the pattern of an untrusted producer supplying a proof checked under a declared policy. VSTD uses the pattern beyond code safety without inheriting PCC's theorem. | +| Independently checked UNSAT results | Wetzler, Heule, and Hunt, [*DRAT-trim*](https://www.cs.cmu.edu/~mheule/publications/drat-trim.pdf) | Establishes practical checking of clausal unsatisfiability proofs rather than trusting solver output. VSTD's implemented RUP format is narrower than DRAT. | +| A first-class refusal to fabricate a Boolean answer | [SMT-LIB Standard 2.7](https://smt-lib.org/papers/smt-lib-reference-v2.7-r2025-04-09.pdf) | Its response grammar includes `sat`, `unsat`, and `unknown`. VSTD independently defines a richer status system with the same fail-closed pressure. | +| Append-only public evidence and detectable equivocation | [RFC 9162: Certificate Transparency Version 2.0](https://www.rfc-editor.org/rfc/rfc9162.html) | Merkle proofs make log inclusion and consistency auditable while preserving explicit split-view limitations. VSTD additive receipts are analogous, not a CT implementation. | +| Freshness, rollback, freeze, and compromise recovery | [The Update Framework specification](https://theupdateframework.github.io/specification/latest/) | Demonstrates that authentic old data is not automatically current data. VSTD does not implement TUF, but likewise keeps freshness and revocation distinct from byte identity. | diff --git a/src/verifier/specifications/VSTD-3.md b/src/verifier/specifications/VSTD-3.md index cd15da2..962a12a 100644 --- a/src/verifier/specifications/VSTD-3.md +++ b/src/verifier/specifications/VSTD-3.md @@ -1,5 +1,7 @@ # VSTD-3 — Substrate Accountability +> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md). + **Layer:** 3 of 5 on the object axis (see `LADDER.md`) **Receipt wire format:** `schema_version = "VSTD-3.0"` — frozen; see `WIRE_IDENTIFIERS.md` **Status:** implemented project specification diff --git a/src/verifier/specifications/VSTD-4.md b/src/verifier/specifications/VSTD-4.md index dd4def4..872d165 100644 --- a/src/verifier/specifications/VSTD-4.md +++ b/src/verifier/specifications/VSTD-4.md @@ -1,5 +1,7 @@ # VSTD-4 — Refutability +> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md). + **Layer:** 4 of 5 on the object axis (see `LADDER.md`) **Certificate format:** `VSTD4-GDC-1` **Status:** implemented project specification diff --git a/standard/LADDER.md b/standard/LADDER.md index be23b3d..d0bd09b 100644 --- a/standard/LADDER.md +++ b/standard/LADDER.md @@ -4,6 +4,8 @@ **Editor:** TimeLordRaps **License:** Apache-2.0 +**Reader context:** [`Concept guide and intellectual precedents`](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md) + VSTD specification numbers are **layers of verification depth**, not revisions of a single document. VSTD-3 does not supersede VSTD-1 any more than a floor supersedes its foundation. @@ -15,14 +17,23 @@ foundation. Each layer names a distinct verification question and a distinct failure class. The ordering is a composition rule, not logical entailment between layers. +The nearest familiar security analogy is +[defense in depth](https://en.wikipedia.org/wiki/Defense_in_depth_%28computing%29 "Wikipedia orientation; primary references are mapped below"), +but the analogy is limited: VSTD layers are separately evidenced questions, not +interchangeable controls whose mere quantity establishes assurance. Decomposing assurance +into named components also has precedent in the Common Criteria, while VSTD deliberately +uses different layers, evidence rules, and conformance semantics. + **Evidence for one layer never supplies evidence for another layer.** In particular, layer-4 evidence does not supply, imply, upgrade, or repair layer 3, 2, or 1. A reported depth of `N` is only shorthand for `N` separately checked results, one for each layer from 1 through `N`. -Reflection and metalanguage are useful design analogies for asking what a given +Reflection and [metalanguage](https://en.wikipedia.org/wiki/Metalogic "Wikipedia orientation; not a proof of the VSTD ladder") +are useful design analogies for asking what a given verification surface leaves unexamined. VSTD does not claim that Tarski's -undefinability theorem proves this ladder, that adjacent layers form formal +[undefinability theorem](https://en.wikipedia.org/wiki/Tarski%27s_undefinability_theorem "Wikipedia orientation; the theorem does not derive this ladder") +proves this ladder, that adjacent layers form formal metalanguages, or that a lower-layer implementation is logically incapable of describing another layer's failure. The normative requirement is narrower: an implementation MUST NOT treat success on one question as evidence for a different @@ -65,7 +76,10 @@ VSTD-Graph governs the verification of a **collection** of objects. Call this verification *dynamics*. The two axes are parallel but coupled: a collection's dynamics are constrained by its -members' mechanics, and by the provenance edges between them. +members' mechanics, and by the +[provenance](https://en.wikipedia.org/wiki/Data_provenance "Wikipedia orientation; see W3C PROV-DM and supply-chain references below") +edges between them. The implemented N-ary representation is a +[hypergraph](https://en.wikipedia.org/wiki/Hypergraph "Wikipedia orientation; not a claim of complete real-world lineage"). | Layer | Name | Collection-level closure | |---|---|---| @@ -118,7 +132,10 @@ computation, VSTD preserves that artifact and its verification bounds. ### 4.2 Bounded admission uses CNF -The reference admission procedures encode finite, bounded policy questions as CNF. +The reference admission procedures encode finite, bounded policy questions as +[conjunctive normal form](https://en.wikipedia.org/wiki/Conjunctive_normal_form "Wikipedia orientation; the implemented format is finite CNF") +(CNF) for the +[Boolean satisfiability problem](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem "Wikipedia orientation; SAT success establishes only the encoded formula"). CNF is not identical to 3-SAT. A finite CNF satisfiability instance can be transformed in polynomial time into an equisatisfiable 3-CNF instance, using auxiliary variables where required. VSTD does not need that transformation for every checker and does not @@ -155,7 +172,11 @@ An unsatisfiable result, by default, carries nothing but the solver's word. For a fail-closed standard, **refusals are the most consequential output**. A standard whose passes are checkable and whose refusals are not has its assurance backwards. Layer 4 therefore requires a refutation certificate — a clausal proof, verifiable by -reverse unit propagation, checkable without re-solving. +[reverse unit propagation](https://en.wikipedia.org/wiki/Unit_propagation "Wikipedia orientation; VSTD implements a bounded RUP checker"), +checkable without re-solving. This follows the same producer-certificate/consumer-checker +engineering asymmetry as +[proof-carrying code](https://en.wikipedia.org/wiki/Proof-carrying_code "Wikipedia orientation; VSTD does not inherit PCC's safety theorem"), +while using a narrower certificate language. Resolution proofs have exponential lower bounds for some formula families. A conforming implementation therefore MUST declare a bound and MUST answer `UNKNOWN` @@ -204,7 +225,34 @@ closed. It never means the lower layers became unnecessary. ## 7. Numbering - **Specification layers are integers**: VSTD-1 … VSTD-5, VSTD-Graph-1 … VSTD-Graph-5. -- **Repository releases use semantic versioning** and are independent of layer numbers. +- **Repository releases use [semantic versioning](https://semver.org/)** and are independent + of layer numbers. A release version never implies a layer, and a layer never implies a release. See `WIRE_IDENTIFIERS.md` for frozen wire identifiers and the historical public filenames. + +--- + +## 8. Intellectual lineage and adjacent precedents + +The ladder is VSTD project architecture; no cited work proves that these five layers are +necessary, sufficient, complete, or uniquely ordered. The references below show that its +individual design pressures have established precedents in security engineering, +provenance, reproducible systems, and proof checking. The +[`concept guide`](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md) provides definitions, additional +sources, and explicit non-equivalences. + +| VSTD pressure | Adjacent precedent | What the precedent contributes—and does not | +|---|---|---| +| Separate failure surfaces and fail-closed defaults | Saltzer and Schroeder, [*The Protection of Information in Computer Systems*](https://web.mit.edu/Saltzer/www/publications/pubs.html) | Classic principles include fail-safe defaults, complete mediation, separation of privilege, and least common mechanism. They motivate separation; they do not derive VSTD's layer count. | +| Named assurance components | Common Criteria, [Part 3: Security assurance components](https://www.commoncriteriaportal.org/files/ccfiles/CC2022PART3R1.pdf) | Demonstrates established componentized assurance and assurance packages. VSTD is not a Common Criteria evaluation or an Evaluation Assurance Level. | +| Stable cryptographic representations | [RFC 8785: JSON Canonicalization Scheme](https://www.rfc-editor.org/rfc/rfc8785.html) | Shows why JSON used as cryptographic input needs invariant representation. VSTD formats retain their own declared canonicalization rules. | +| Recorded entities, activities, and agents | W3C [PROV-DM](https://www.w3.org/TR/prov-dm/) | Supplies an interoperable provenance model adjacent to the Graph axis. VSTD-Graph is not a PROV implementation and does not infer complete history. | +| Software materials, builders, steps, and products | [in-toto specification v1.0](https://in-toto.io/docs/specs/) and [SLSA v1.2](https://slsa.dev/spec/v1.2/) | Establish supply-chain provenance and attestation precedents. VSTD may bind their evidence but cannot manufacture their authorization or assurance level. | +| Preserved release and provenance evidence | NIST [SP 800-218 SSDF 1.1](https://doi.org/10.6028/NIST.SP.800-218) | Practices PS.3.1 and PS.3.2 call for preserving releases and provenance and enabling integrity verification. They do not certify a VSTD receipt. | +| Independent recreation | Reproducible Builds, [formal definition](https://reproducible-builds.org/docs/definition/) | Grounds the special case where another party recreates specified artifacts from declared inputs and instructions. Reproducibility does not establish every semantic claim. | +| Producer-supplied portable certificates | Necula, [*Proof-Carrying Code*](https://doi.org/10.1145/263699.263712) | Establishes the pattern of an untrusted producer supplying a proof checked under a declared policy. VSTD uses the pattern beyond code safety without inheriting PCC's theorem. | +| Independently checked UNSAT results | Wetzler, Heule, and Hunt, [*DRAT-trim*](https://www.cs.cmu.edu/~mheule/publications/drat-trim.pdf) | Establishes practical checking of clausal unsatisfiability proofs rather than trusting solver output. VSTD's implemented RUP format is narrower than DRAT. | +| A first-class refusal to fabricate a Boolean answer | [SMT-LIB Standard 2.7](https://smt-lib.org/papers/smt-lib-reference-v2.7-r2025-04-09.pdf) | Its response grammar includes `sat`, `unsat`, and `unknown`. VSTD independently defines a richer status system with the same fail-closed pressure. | +| Append-only public evidence and detectable equivocation | [RFC 9162: Certificate Transparency Version 2.0](https://www.rfc-editor.org/rfc/rfc9162.html) | Merkle proofs make log inclusion and consistency auditable while preserving explicit split-view limitations. VSTD additive receipts are analogous, not a CT implementation. | +| Freshness, rollback, freeze, and compromise recovery | [The Update Framework specification](https://theupdateframework.github.io/specification/latest/) | Demonstrates that authentic old data is not automatically current data. VSTD does not implement TUF, but likewise keeps freshness and revocation distinct from byte identity. | diff --git a/standard/VSTD-1.md b/standard/VSTD-1.md index 86ed48f..c1a20dd 100644 --- a/standard/VSTD-1.md +++ b/standard/VSTD-1.md @@ -1,5 +1,7 @@ # VSTD-1 — Claim Mechanics +> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md). + **Layer:** 1 of 5 on the object axis (see `LADDER.md`) **Receipt wire format:** `schema_version = "VSTD-0.1"` — frozen; see `WIRE_IDENTIFIERS.md` **Status:** Project Specification with Implemented Reference Subset diff --git a/standard/VSTD-2.md b/standard/VSTD-2.md index 32b94ea..69d462d 100644 --- a/standard/VSTD-2.md +++ b/standard/VSTD-2.md @@ -1,5 +1,7 @@ # VSTD-2 — Verification Surface +> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md). + **Layer:** 2 of 5 on the object axis (see `LADDER.md`) **Receipt wire format:** `schema_version = "VSTD-0.2"` — frozen; see `WIRE_IDENTIFIERS.md` **Status:** Additive experimental standard with an implemented vertical slice diff --git a/standard/VSTD-3.md b/standard/VSTD-3.md index cd15da2..962a12a 100644 --- a/standard/VSTD-3.md +++ b/standard/VSTD-3.md @@ -1,5 +1,7 @@ # VSTD-3 — Substrate Accountability +> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md). + **Layer:** 3 of 5 on the object axis (see `LADDER.md`) **Receipt wire format:** `schema_version = "VSTD-3.0"` — frozen; see `WIRE_IDENTIFIERS.md` **Status:** implemented project specification diff --git a/standard/VSTD-4.md b/standard/VSTD-4.md index dd4def4..872d165 100644 --- a/standard/VSTD-4.md +++ b/standard/VSTD-4.md @@ -1,5 +1,7 @@ # VSTD-4 — Refutability +> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md). + **Layer:** 4 of 5 on the object axis (see `LADDER.md`) **Certificate format:** `VSTD4-GDC-1` **Status:** implemented project specification diff --git a/standard/VSTD-5.md b/standard/VSTD-5.md index 889b93c..9f66f00 100644 --- a/standard/VSTD-5.md +++ b/standard/VSTD-5.md @@ -1,5 +1,7 @@ # VSTD-5 — Witness Corroboration +> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md). + **Layer:** 5 of 5 on the object axis (see `LADDER.md`) **Status:** DRAFT — not implemented **Editor:** TimeLordRaps diff --git a/standard/VSTD-Graph-1.md b/standard/VSTD-Graph-1.md index a06b9cb..cb9cd1a 100644 --- a/standard/VSTD-Graph-1.md +++ b/standard/VSTD-Graph-1.md @@ -1,5 +1,7 @@ # VSTD-Graph-1 — Recorded Lineage +> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md). + **Layer:** 1 of 5 on the graph axis (see `LADDER.md`) **Receipt wire format:** `schema_version = "VSTD-DATA-0.1"` — frozen; see `WIRE_IDENTIFIERS.md` **Status:** Project Specification with Implemented Reference Subset diff --git a/standard/VSTD-Graph-2.md b/standard/VSTD-Graph-2.md index 9a714cc..4889a86 100644 --- a/standard/VSTD-Graph-2.md +++ b/standard/VSTD-Graph-2.md @@ -1,5 +1,7 @@ # VSTD-Graph-2 — Bounded Collection Surface +> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md). + **Layer:** 2 of 5 on the graph axis (see `LADDER.md`) **Status:** implemented computed profile **License:** Apache-2.0 diff --git a/standard/VSTD-Graph-3.md b/standard/VSTD-Graph-3.md index 710a34f..8679dc3 100644 --- a/standard/VSTD-Graph-3.md +++ b/standard/VSTD-Graph-3.md @@ -1,5 +1,7 @@ # VSTD-Graph-3 — Accountable Provenance Closure +> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md). + **Layer:** 3 of 5 on the graph axis (see `LADDER.md`) **Status:** implemented computed profile **License:** Apache-2.0 diff --git a/standard/VSTD-Graph-4.md b/standard/VSTD-Graph-4.md index 7fe4e93..9159bfd 100644 --- a/standard/VSTD-Graph-4.md +++ b/standard/VSTD-Graph-4.md @@ -1,5 +1,7 @@ # VSTD-Graph-4 — Refutable Transformation Closure +> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md). + **Layer:** 4 of 5 on the graph axis (see `LADDER.md`) **Status:** implemented computed profile **License:** Apache-2.0 diff --git a/standard/VSTD-Graph-5.md b/standard/VSTD-Graph-5.md index bdcf96b..702e662 100644 --- a/standard/VSTD-Graph-5.md +++ b/standard/VSTD-Graph-5.md @@ -1,5 +1,7 @@ # VSTD-Graph-5 — Corroborated Verification Network +> Reader aid: [concept glossary and primary precedents](https://github.com/TimeLordRaps/verifier/blob/main/docs/CONCEPTS_AND_PRECEDENTS.md). + **Layer:** 5 of 5 on the graph axis (see `LADDER.md`) **Status:** DRAFT profile; computation is implemented, witness protocol is not **License:** Apache-2.0 From 14148d453d7285f1b72beb6e71d390b4b62c3665 Mon Sep 17 00:00:00 2001 From: TimeLordRaps Date: Sun, 23 Aug 2026 20:04:46 -0700 Subject: [PATCH 06/32] Add audited experimental VSTD SCITT interlingua --- .gitattributes | 1 + README.md | 11 +- docs/ECOSYSTEM.md | 28 +- docs/standards/ADJACENT_STANDARDS_MATRIX.md | 39 + docs/standards/IETF_SCITT_ENGAGEMENT.md | 150 ++++ docs/standards/SCITT_INTEROP_AUDIT.md | 409 +++++++++ docs/standards/SCITT_SEMANTIC_BOUNDARY.md | 196 +++++ docs/standards/VSTD_SCITT_CROSSWALK.md | 181 ++++ examples/scitt_interop/README.md | 112 +++ examples/scitt_interop/artifact.txt | 1 + examples/scitt_interop/demo.py | 487 +++++++++++ .../scitt_interop/generated/issuer_public.pem | 3 + .../scitt_interop/generated/log_public.pem | 3 + examples/scitt_interop/generated/receipt.cose | Bin 0 -> 250 bytes .../generated/registration_template.json | 174 ++++ .../generated/signed_statement.cose | Bin 0 -> 5062 bytes .../generated/transparent_statement.cose | Bin 0 -> 5318 bytes .../generated/verification_result.json | 67 ++ .../scitt_interop/generated/vstd_receipt.json | 131 +++ .../generated/vstd_scitt_payload.json | 1 + pyproject.toml | 6 + src/verifier/interoperability/__init__.py | 1 + .../interoperability/scitt/__init__.py | 45 + .../interoperability/scitt/adapter.py | 793 ++++++++++++++++++ tests/test_scitt_crypto_example.py | 111 +++ tests/test_scitt_interop.py | 421 ++++++++++ 26 files changed, 3367 insertions(+), 4 deletions(-) create mode 100644 docs/standards/ADJACENT_STANDARDS_MATRIX.md create mode 100644 docs/standards/IETF_SCITT_ENGAGEMENT.md create mode 100644 docs/standards/SCITT_INTEROP_AUDIT.md create mode 100644 docs/standards/SCITT_SEMANTIC_BOUNDARY.md create mode 100644 docs/standards/VSTD_SCITT_CROSSWALK.md create mode 100644 examples/scitt_interop/README.md create mode 100644 examples/scitt_interop/artifact.txt create mode 100644 examples/scitt_interop/demo.py create mode 100644 examples/scitt_interop/generated/issuer_public.pem create mode 100644 examples/scitt_interop/generated/log_public.pem create mode 100644 examples/scitt_interop/generated/receipt.cose create mode 100644 examples/scitt_interop/generated/registration_template.json create mode 100644 examples/scitt_interop/generated/signed_statement.cose create mode 100644 examples/scitt_interop/generated/transparent_statement.cose create mode 100644 examples/scitt_interop/generated/verification_result.json create mode 100644 examples/scitt_interop/generated/vstd_receipt.json create mode 100644 examples/scitt_interop/generated/vstd_scitt_payload.json create mode 100644 src/verifier/interoperability/__init__.py create mode 100644 src/verifier/interoperability/scitt/__init__.py create mode 100644 src/verifier/interoperability/scitt/adapter.py create mode 100644 tests/test_scitt_crypto_example.py create mode 100644 tests/test_scitt_interop.py diff --git a/.gitattributes b/.gitattributes index 58ceb9b..00c11c1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,4 @@ *.py text eol=lf *.toml text eol=lf *.yml text eol=lf +*.cose binary diff --git a/README.md b/README.md index ed461e0..ab661f0 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,15 @@ Ordinary computational results often omit machine-readable answers to four quest 3. **Where does the verdict stop?** Explicit coordinates and resource bounds. 4. **How can it change?** Reproduction, counterexample, challenge, and degradation rules. -VSTD stores those answers in receipts and provenance hypergraphs. The reference +VSTD is designed as a **standard domain language for verification**: a verification +interlingua into which domain verifiers, proof engines, signature systems, identity +systems, transparency logs, provenance formats, and verification pipelines can map +their bounded claims and native results. VSTD is the operator-language class; those +systems are the orchestrated verification substrata. It does not replace or absorb +their native semantics. Explicit, loss-declared adapters map results into and through +VSTD without silently increasing their justified strength. + +VSTD stores that interoperable boundary in receipts and provenance hypergraphs. The reference implementation can validate stable receipt content, reproduce declared mechanisms, check grounded decision certificates, and compute collection-level ceilings from recorded ancestry and caller-supplied object and edge ratings. @@ -94,6 +102,7 @@ separately in [`standard/WIRE_IDENTIFIERS.md`](standard/WIRE_IDENTIFIERS.md). | Model a provenance collection | [`standard/VSTD-Graph-1.md`](standard/VSTD-Graph-1.md) | | Integrate accelerator evidence | [`docs/layers/vstd-3/vendor-integration.md`](docs/layers/vstd-3/vendor-integration.md) | | Use VSTD beside existing supply-chain/provenance systems | [`docs/ECOSYSTEM.md`](docs/ECOSYSTEM.md) | +| Inspect the experimental IETF SCITT interoperability profile | [`docs/standards/VSTD_SCITT_CROSSWALK.md`](docs/standards/VSTD_SCITT_CROSSWALK.md) | | Review exact public claim limits | [`docs/CLAIMS_AND_LIMITS.md`](docs/CLAIMS_AND_LIMITS.md) | ## Capture a generic computation diff --git a/docs/ECOSYSTEM.md b/docs/ECOSYSTEM.md index 62ef450..05ca31b 100644 --- a/docs/ECOSYSTEM.md +++ b/docs/ECOSYSTEM.md @@ -1,14 +1,24 @@ # Ecosystem boundary map **Status:** non-normative positioning note -**Reviewed:** 2026-08-22 +**Reviewed:** 2026-08-23 VSTD is designed to compose with established provenance, software-supply-chain, and artifact-authentication systems. It does not rename their guarantees as its own and does not claim to replace them. +VSTD's intended role is a **standard domain language for verification**. A native +verifier, prover, framework, or evidence system retains its own semantics and emits +its own result. A loss-declared adapter maps the claim boundary, native result, +evidence, bounds, assumptions, and trust coordinates into VSTD's portable result +semantics. Another consumer can then interpret or route that VSTD representation +without needing the producer's private orchestration logic. VSTD is therefore the +operator-language class; the mapped native systems are orchestrated verification +substrata. + | System | Its documented center of gravity | What VSTD may bind or add | What VSTD must not claim | |---|---|---|---| +| [IETF SCITT RFC 9943](https://datatracker.ietf.org/doc/html/rfc9943) and [COSE Receipts RFC 9942](https://datatracker.ietf.org/doc/html/rfc9942) | Signed Statements, registration policy, append-only/non-equivocating transparency services, and portable VDS receipts. | Carry a complete VSTD receipt as an application payload; consume independently verified registration/inclusion as narrowly typed transparency evidence. See the [experimental crosswalk](standards/VSTD_SCITT_CROSSWALK.md). | That registration establishes computational truth, or that VSTD replaces COSE, a Transparency Service, VDS proof profiles, or SCITT trust policy. | | [SLSA v1.2](https://slsa.dev/spec/v1.2/) | Levels and tracks for incrementally improving software supply-chain security, including recommended provenance and verification-summary formats. | A SLSA statement or verification summary as evidence under an explicit VSTD claim coordinate; separate refutation and degradation conditions. | That a VSTD receipt establishes a SLSA level without satisfying and assessing the relevant SLSA requirements. | | [in-toto](https://in-toto.io/docs/getting-started/) | Signed layouts and link metadata describing authorized supply-chain steps, functionaries, materials, and products. | in-toto layout/link bytes as named evidence; graph edges that point to checked step metadata. | That VSTD re-authorizes a functionary or repairs a missing/invalid in-toto chain. | | [Sigstore](https://docs.sigstore.dev/) | Artifact signing associated with identity, short-lived certificates, and transparency-log evidence. | Sigstore bundle, certificate identity, trust root, and verification result as explicit evidence and trust-root fields. | That a digest alone authenticates a signer, or that VSTD reference-kernel acceptance substitutes for signature and transparency-log verification. | @@ -23,15 +33,23 @@ that result and which information remains outside the mapping. ```text native object ──native verifier──> native result │ │ - └──── preserved bytes + identity ──┴──> VSTD evidence reference + └──── preserved bytes + identity ──┴──> loss-declared adapter + │ + ▼ + VSTD claim boundary + portable result │ - └── bounded VSTD claim + ▼ + another verifier, framework, or relying party ``` The VSTD claim does not flow backward and strengthen the native result. If the native verifier returns an unknown, unsupported, expired, or invalid outcome, the adapter must preserve it rather than translating it into a clean VSTD result. +Mapping through VSTD is not automatic semantic equivalence. Every adapter must state +what was preserved, what was omitted, what was transformed, and which native +assumptions remain authoritative. + ## Adapter acceptance checklist An ecosystem adapter is not ready until it declares and tests: @@ -47,3 +65,7 @@ An ecosystem adapter is not ready until it declares and tests: No adapter is included merely to populate a compatibility list. Each adapter increases the trusted and maintained surface and therefore needs its own evidence and tests. + +The current SCITT adapter is explicitly experimental and non-normative. Its exact +claim boundary is documented in +[`standards/SCITT_SEMANTIC_BOUNDARY.md`](standards/SCITT_SEMANTIC_BOUNDARY.md). diff --git a/docs/standards/ADJACENT_STANDARDS_MATRIX.md b/docs/standards/ADJACENT_STANDARDS_MATRIX.md new file mode 100644 index 0000000..5aac413 --- /dev/null +++ b/docs/standards/ADJACENT_STANDARDS_MATRIX.md @@ -0,0 +1,39 @@ +# Adjacent standards interoperability matrix + +> **Status:** non-normative scope control. SCITT is the primary interoperability +> target. This matrix identifies mechanisms VSTD should reuse rather than reinvent. + +| Standard/system | What it already supplies | Reuse | Reference | Consume | Do not replace | +|---|---|---|---|---|---| +| [IETF SCITT RFC 9943](https://datatracker.ietf.org/doc/html/rfc9943) + [COSE Receipts RFC 9942](https://datatracker.ietf.org/doc/html/rfc9942) | COSE Signed Statements, issuer/subject binding, registration policy, transparency services, VDS guarantees, portable inclusion/consistency receipts. | Signed Statement and Receipt envelopes, VDS identifiers, and attachment rules. | Exact RFCs, selected VDS profile, TS policy, and keys. | Independently verified registration/inclusion as narrowly typed transparency evidence. | Signature envelopes, transparency services, VDS registries, receipt attachment, or registration APIs. | +| [in-toto specification v1.0](https://in-toto.io/docs/specs/) | Signed layouts, authorized functionaries, step links, materials/products, artifact rules, and supply-chain verification. | Native layouts, links, and artifact-rule processing. | Exact metadata bytes, specification version, functionary keys, and layout policy. | Native verification result and bound material/product digests. | Software-supply-chain step authorization, layout processing, or artifact-rule semantics. | +| [SLSA v1.2](https://slsa.dev/spec/v1.2/provenance) | Build/source provenance, attestation predicates, build levels, producer expectations, and verification procedures. | SLSA predicates and verification-summary conventions. | Claimed track/level, predicate version, producer, and verification procedure. | Schema-valid provenance and native verifier output. | SLSA levels, build-platform threat model, provenance schemas, or ecosystem expectations. | +| [Sigstore](https://docs.sigstore.dev/) / [Rekor](https://docs.sigstore.dev/logging/overview/) | Identity-bound signing, short-lived certificates, signature transparency, inclusion proofs, and public-log monitoring. | Sigstore bundles and native verification workflow. | Fulcio/Rekor trust roots, certificate identity policy, integration time, and log coordinates. | Verified signature/certificate/Rekor results and exact bundle bytes. | Fulcio, Rekor, identity federation, keyless signing, or transparency monitoring. | +| [C2PA 2.4](https://spec.c2pa.org/specifications/) | Content Credentials, signed manifests, hard/soft asset bindings, assertions, ingredient/action history, validation statuses, and trust lists. | Native manifest, assertion, ingredient, and asset-binding semantics. | Exact C2PA version, validation algorithm, trust list, asset, and manifest. | Native validation output and exact asset/manifest bytes for bounded media claims. | Media embedding, content binding, assertion vocabulary, or the C2PA trust model. | +| [W3C PROV-O Recommendation](https://www.w3.org/TR/prov-o/) | Interoperable provenance vocabulary for entities, activities, agents, and relations in RDF/OWL. | PROV entity/activity/agent relations where semantic-web interchange is required. | PROV-O Recommendation, ontology IRIs, serialization, and namespaces. | Identified PROV graphs under an explicit mapping and completeness policy. | General provenance ontology, RDF, or OWL semantics. | +| [SPDX 3.0.1](https://spdx.github.io/spdx-spec/) | BOM data model and serializations for software, builds, AI models, datasets, vulnerabilities, licenses, relationships, provenance, and integrity. | SPDX element and relationship vocabulary. | Exact model/specification version, profile, namespace, and serialization. | Schema-valid SPDX elements, relationships, and native validation output. | SBOM/AI BOM vocabulary, license conclusions, vulnerability model, or SPDX conformance. | +| [IETF RATS Architecture RFC 9334](https://datatracker.ietf.org/doc/html/rfc9334) | Attester, Verifier, Relying Party roles; Evidence, Endorsements, Reference Values, Attestation Results, appraisal policy, and freshness models. | Standard attestation roles and trust terminology. | Appraisal policy, trust anchors, freshness method, endorsements, and reference values. | Native Attestation Results and the verifier's declared trust coordinates. | Remote-attestation architecture, freshness methods, role semantics, or authorization decisions. | +| [Entity Attestation Token RFC 9711](https://www.rfc-editor.org/rfc/rfc9711.html) | CWT/JWT attestation claim framework, profiles, submodules, and authenticity/integrity protection. | Registered EAT claim and profile semantics. | Exact EAT profile, token envelope, trust anchors, and verification algorithm. | Profile-validated EAT claims or Attestation Results. | EAT registries, token envelopes, device profiles, or key protection. | +| [CoRIM draft-ietf-rats-corim-11](https://datatracker.ietf.org/doc/html/draft-ietf-rats-corim-11) | Active RATS WG Last Call work on CBOR reference integrity manifests, endorsements, and reference values. | CoRIM structures only under the selected revision. | Exact active draft revision and its evolving registries. | Native validation output and identified reference values under an explicit policy. | Reference-value or endorsement manifest schema. | +| [DSSE](https://github.com/secure-systems-lab/dsse/blob/master/envelope.md) and in-toto Attestation Framework | Payload-type-bound signing envelope and generic statement/predicate convention. | DSSE when an upstream ecosystem already emits it. | Payload type, envelope specification, signing keys, and predicate type. | Native signature-verification output and exact envelope bytes. | Another JSON signing envelope; SCITT interoperability uses COSE as specified by RFC 9943. | + +## Design rule + +VSTD is the standard domain language and general operator/result class through which +orchestrated native verifiers, proof engines, and evidence substrata can expose +interoperable claim boundaries and portable results. A loss-declared adapter records +native bytes, version/profile, trust roots, native result, VSTD claim coordinates, +and any omitted or transformed semantics. VSTD does not replace the native engine +or reinterpret its domain semantics, and it must not translate a native success into +a stronger VSTD result than the native evidence and VSTD checker jointly support. + +The result should be a smaller VSTD architecture: + +- SCITT/Sigstore provide transparency and signing where appropriate; +- in-toto/SLSA/SPDX provide software-supply-chain vocabulary and evidence; +- C2PA provides media provenance and content binding; +- W3C PROV provides general provenance interchange; +- RATS/EAT/CoRIM provide attestation roles, tokens, endorsements, and reference + values; +- VSTD standardizes the bounded claim boundary, portable result, UNKNOWN, and + refutation semantics across selected native evidence. diff --git a/docs/standards/IETF_SCITT_ENGAGEMENT.md b/docs/standards/IETF_SCITT_ENGAGEMENT.md new file mode 100644 index 0000000..e7219a6 --- /dev/null +++ b/docs/standards/IETF_SCITT_ENGAGEMENT.md @@ -0,0 +1,150 @@ +# Proposed IETF SCITT engagement package + +> **Do not send automatically.** Human review is required before any mailing-list, +> repository, author, meeting, or standards-body contact. This text does not claim +> WG interest, adoption, or endorsement. + +## One-paragraph introduction + +VSTD is an alpha, implementation-backed specification for bounded computational +verification receipts. A VSTD result names a computational claim coordinate, +evidence and policy roots, verifier implementation/fragment, resource bounds, and a +checkable PASS, FAIL, or UNKNOWN artifact. We have implemented an experimental +and optional SCITT profile in which a complete VSTD receipt is carried as the +payload of an RFC 9943 Signed Statement, while verified SCITT +registration/inclusion can be consumed as narrowly typed VSTD transparency +evidence. VSTD verification does not require a natural-person or persistent actor +identity merely to decide an artifact claim. Selecting SCITT adds an authenticated, +potentially linkable issuer and a transparency proposition. The adapter deliberately +prevents either property from being interpreted as proof that the VSTD computational +proposition passed. + +## Technical relationship + +SCITT already supplies an appropriate **optional accountability substrate** for +issuer authentication, statement registration, auditable registration policy, +append-only/non-equivocating VDSs, and portable COSE Receipts. VSTD does not propose +replacements for those functions or require them for every VSTD receipt. VSTD's +distinct role is a standard domain language for verification: an operator/result +interlingua that maps and preserves the claim boundary and portable result semantics +of native verifiers and proof engines, including explicit resource exhaustion and +refutation semantics, while leaving each domain engine's native semantics visible. + +This experiment is deliberately full disclosure. It does not claim zero knowledge, +zero identity, anonymity, unlinkability, or absolute trustlessness. An experimental +witness-private VSTD proof profile would need its own real proof-system guarantees +and privacy analysis; routing that profile through SCITT would require an explicit +decision about issuer correlation and public registration metadata. + +The implemented experiment demonstrates: + +- an actual VSTD-4 grounded PASS certificate checked by the reference kernel; +- deterministic VSTD application-payload serialization; +- a real EdDSA COSE Signed Statement carrying that payload; +- a real RFC9162 SHA-256 COSE inclusion receipt from a local one-entry test log; +- independent statement and receipt verification; +- adversarial cases for artifact substitution, valid-registration/invalid-claim, + missing/stale/revoked/superseded evidence, resource-bounded UNKNOWN, conflict, + tampering, unsupported versions, and wrong issuer/subject; +- explicit preservation of both native VSTD and native SCITT results; +- a separately digest-bound native VSTD checker observation, preventing an embedded + receipt's declared `PASS` from being treated as proof that checking occurred. + +The local test log is not a production TS or public anchor. The experiment is about +the wire/semantic boundary, not deployment assurance. + +## Three questions for SCITT participants + +1. **Optional payload profile boundary:** Is an application payload profile that + defines bounded computational-verification semantics, while leaving Signed + Statement, Registration, VDS, and COSE Receipt processing unchanged, consistent + with the intended SCITT extension model? Is it also consistent to treat SCITT as + an optional accountability publication path rather than a prerequisite for the + domain verifier? If so, should the profile identifier live only in the payload + media type, or also in a protected header/type coordinate? +2. **Composite-verification alignment:** For work resembling the individual + Composite Evidence Verification draft, should a domain verifier return its + native result as a separate typed statement/report, or should the composite + profile directly incorporate domain-result semantics? We want to prevent a graph + `pass` from being read as arbitrary payload truth. +3. **Historical inclusion vs current usability:** What representation pattern is + preferred when a COSE Receipt remains valid historical inclusion evidence but a + relying-party policy now considers a dependency stale, superseded, conflicted, + or revoked? The experiment preserves both states instead of invalidating history. + +## Concrete contribution to offer + +Offer the working implementation experiment first: + +1. a short VSTD/SCITT crosswalk; +2. a payload-profile specimen with COSE Signed Statement and Receipt bytes; +3. negative tests proving that SCITT integrity cannot be laundered into + computational truth; and +4. three focused design questions above. + +Ask participants whether the useful next artifact is: + +- an examples-repository contribution; +- a short implementation report; +- an application payload/profile document; +- alignment with composite-evidence/statement-graph exploration; or +- no standards document until more implementations exist. + +Do not arrive assuming the WG wants a VSTD statement type, registry entry, or draft. + +## Recommended first message shape + +Subject suggestion: + +> Experimental VSTD payload profile for SCITT: bounded computational results without registration-to-truth upgrade + +Body outline: + +1. One paragraph from the introduction above. +2. One sentence stating RFC 9943's accuracy boundary: registration proves an + issuer produced a statement, not that the payload is true. +3. Links to the crosswalk, semantic boundary, implementation, and adversarial tests. +4. State exactly what is cryptographically demonstrated and that the log is local. +5. Ask the three questions. +6. Invite correction of the decomposition before proposing any Internet-Draft. + +No adoption claims, deadline pressure, marketing language, or private plans belong +in the message. + +## Internet-Draft maturity decision + +**Recommendation: Option E now — implementation report and discussion first.** + +VSTD has enough executable substance to justify a technical conversation, but not +yet enough community input to choose among an application media-type profile, a +statement type, or a broader bounded-verification profile. The closest overlapping +work is an active **individual** draft, not adopted WG architecture, and its result +model still contains open design questions. Writing a draft now would prematurely +freeze vocabulary and could duplicate work the WG prefers elsewhere. + +Reassess **Option B, an informational VSTD/SCITT payload profile**, after: + +- SCITT participants confirm the layer boundary; +- at least one native TS or independent implementation verifies the VSTD specimen; +- the payload media type/profile identifier and protected-header strategy are + agreed; +- status/freshness/revocation behavior is reviewed; +- a second implementation can consume the profile without repository-specific + knowledge. + +Option C (new statement type) and Option D (general bounded-computational profile) +are premature. Option A (no engagement) is too conservative now that code and +negative tests exist. + +## Human pre-send checklist + +- Recheck all Datatracker statuses on the send date. +- Run the base and optional cryptographic test suites from a clean checkout. +- Confirm generated binary hashes match the checked-in report. +- Make public links resolve to the intended branch or merged commit. +- Confirm no private paths, identities, credentials, or operational plans appear. +- Ask one technical question per paragraph; do not ask for adoption. +- Describe individual Internet-Drafts as individual work in progress. +- State that the VSTD SCITT profile is experimental and non-normative. +- Do not imply that SCITT is required for VSTD or that either layer supplies zero + knowledge, zero identity, anonymity, unlinkability, or absolute trustlessness. diff --git a/docs/standards/SCITT_INTEROP_AUDIT.md b/docs/standards/SCITT_INTEROP_AUDIT.md new file mode 100644 index 0000000..45ed14c --- /dev/null +++ b/docs/standards/SCITT_INTEROP_AUDIT.md @@ -0,0 +1,409 @@ +# VSTD/SCITT integration sprint compliance audit + +> **Status:** experimental, non-normative audit of the isolated SCITT branch. +> **Audit date:** 2026-08-23. +> **Immutable comparison base:** `598c545be3833d6d81bb7e252ca5837f3bb2a449`. +> This report does not imply IETF or SCITT Working Group review, interest, +> adoption, or endorsement. + +## Audit method + +The original 19-section sprint prompt was decomposed into **248 atomic +checklist items**. Files, symbols, source status, Git diff, generated artifacts, +real COSE execution, and tests were rechecked rather than inferred from file +presence. `PARTIAL` is not counted as complete. + +## Defects found and fixed during this audit + +1. The first adapter could compose an embedded receipt's declared `PASS` without a + separately bound native VSTD checker observation. `compose_results` now requires + `VstdVerificationEvidence` for the exact receipt digest. +2. The first demo replayed a grounded certificate without passing the reconstructed + `ClaimBinding`. It now checks the exact claim, coordinate, roots, verifier + descriptor, and bounds. +3. Deterministic Ed25519 private seeds were removed. Production uses fresh + memory-only keys and emits only public trust coordinates. +4. Real cryptographic tests were added for resource exhaustion, a + SCITT-valid/VSTD-rejected result, malformed COSE, and tampering. +5. SCITT statuses were refreshed, and overlapping individual drafts are no longer + described as WG positions or ignored when discussing novelty. +6. The architecture was corrected: VSTD is the standard domain language and + operator/result interlingua through which orchestrated domain verifiers, proof + engines, signature/identity systems, + transparency logs, provenance formats, and other verification substrata. It does + not replace them. +7. The adjacent matrix now separates Reuse, Reference, Consume, and Do not replace. + +## Requirement-by-requirement compliance matrix + +| # | Original requirement | Status | Evidence | Gap / action | +|---:|---|---|---|---| +| 1 | Treat the original 19-section sprint prompt as the authoritative specification | COMPLETE | This matrix follows it section by section | None. | +| 2 | Determine exactly where VSTD and SCITT overlap | COMPLETE | `docs/standards/VSTD_SCITT_CROSSWALK.md` Result and Rigorous crosswalk | None. | +| 3 | Determine exactly where VSTD and SCITT differ | COMPLETE | Crosswalk Difference column and semantic-boundary document | None. | +| 4 | Implement the cleanest composition boundary without making VSTD compete with SCITT | COMPLETE | Optional bidirectional adapter with separate native results | None. | +| 5 | Read and obey AGENTS.md | COMPLETE | Repository identity, dependency, key-material, privacy, wire, and commit rules applied | None. | +| 6 | Read and obey standard/WIRE_IDENTIFIERS.md | COMPLETE | Normative and identifier diff is empty | None. | +| 7 | Inspect README.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `README.md`. | None. | +| 8 | Inspect standard/VSTD-1.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `standard/VSTD-1.md`. | None. | +| 9 | Inspect standard/VSTD-2.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `standard/VSTD-2.md`. | None. | +| 10 | Inspect standard/VSTD-3.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `standard/VSTD-3.md`. | None. | +| 11 | Inspect standard/VSTD-4.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `standard/VSTD-4.md`. | None. | +| 12 | Inspect standard/VSTD-5.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `standard/VSTD-5.md`. | None. | +| 13 | Inspect standard/VSTD-Graph-1.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `standard/VSTD-Graph-1.md`. | None. | +| 14 | Inspect standard/VSTD-Graph-2.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `standard/VSTD-Graph-2.md`. | None. | +| 15 | Inspect standard/VSTD-Graph-3.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `standard/VSTD-Graph-3.md`. | None. | +| 16 | Inspect standard/VSTD-Graph-4.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `standard/VSTD-Graph-4.md`. | None. | +| 17 | Inspect standard/VSTD-Graph-5.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `standard/VSTD-Graph-5.md`. | None. | +| 18 | Inspect standard/LADDER.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `standard/LADDER.md`. | None. | +| 19 | Inspect docs/CLAIMS_AND_LIMITS.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `docs/CLAIMS_AND_LIMITS.md`. | None. | +| 20 | Inspect docs/ECOSYSTEM.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `docs/ECOSYSTEM.md`. | None. | +| 21 | Inspect GOVERNANCE.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `GOVERNANCE.md`. | None. | +| 22 | Inspect ROADMAP.md. | COMPLETE | Read in this audit; findings reflected in crosswalk/boundary: `ROADMAP.md`. | None. | +| 23 | Inspect every receipt schema | COMPLETE | `receipts/schema/` inventory; VSTD-4 specimen schema validation test | None. | +| 24 | Inspect verifier/checker implementation | COMPLETE | `src/verifier/core/kernel.py`, core types, and SCITT adapter | None. | +| 25 | Inspect flagship examples | COMPLETE | Repository examples plus executed `examples/scitt_interop/` | None. | +| 26 | Inspect graph-validation tests | COMPLETE | Graph tests inventoried and included in full suite | None. | +| 27 | Inspect grounding and decision-certificate tests | COMPLETE | VSTD-4/kernel tests plus real demo certificate replay | None. | +| 28 | Inspect provenance behavior | COMPLETE | VSTD-3 provenance and VSTD-Graph implementation/tests | None. | +| 29 | Inspect revocation behavior | COMPLETE | Graph challenge/degradation and crosswalk lifecycle rows | None. | +| 30 | Identify current implementation instead of trusting prompt descriptions | COMPLETE | Boundary distinguishes implemented VSTD 1–4/Graph from draft VSTD-5/Graph-5 | None. | +| 31 | Do not modify normative semantics to ease SCITT integration | COMPLETE | Diff from base under `standard`, `receipts`, and core is empty | None. | +| 32 | Use current primary IETF sources | COMPLETE | Datatracker/RFC Editor rechecked on 2026-08-23 | None. | +| 33 | Inspect current primary material for RFC 9943 | COMPLETE | Proposed Standard; core architecture and accuracy boundary; exact status is in crosswalk source table | None. | +| 34 | Inspect current primary material for current SCITT architecture/API work | COMPLETE | SCRAPI -11, active WG draft in RFC Editor Queue; exact status is in crosswalk source table | None. | +| 35 | Inspect current primary material for COSE Receipt work | COMPLETE | RFC 9942 Proposed Standard and CCF profile -04; exact status is in crosswalk source table | None. | +| 36 | Inspect current primary material for Transparency Service semantics | COMPLETE | RFC 9943 sections 5–7; exact status is in crosswalk source table | None. | +| 37 | Inspect current primary material for registration policies | COMPLETE | RFC 9943 section 5.1.1; exact status is in crosswalk source table | None. | +| 38 | Inspect current primary material for statement-envelope semantics | COMPLETE | RFC 9943 Signed/Transparent Statements; exact status is in crosswalk source table | None. | +| 39 | Inspect current primary material for composite evidence verification | COMPLETE | composite-evidence -00; exact status is in crosswalk source table | None. | +| 40 | Inspect current primary material for SCITT statement graphs | COMPLETE | composite-evidence and protected-object-binding -00; exact status is in crosswalk source table | None. | +| 41 | Inspect current primary material for AI transparency receipts | COMPLETE | Article 50 -00 and AI Agent Receipt -01; exact status is in crosswalk source table | None. | +| 42 | Inspect current primary material for execution evidence | COMPLETE | agent-execution -00; exact status is in crosswalk source table | None. | +| 43 | Inspect current primary material for agent/action receipts | COMPLETE | AI Agent Receipt -01 and Action Capsule -02; exact status is in crosswalk source table | None. | +| 44 | Inspect current primary material for evidence bundles | COMPLETE | composite-evidence -00 sections 10–12; exact status is in crosswalk source table | None. | +| 45 | Inspect current primary material for revocation | COMPLETE | RFC 9943 key-compromise limits and composite proposal; exact status is in crosswalk source table | None. | +| 46 | Inspect current primary material for supersession | COMPLETE | RFC 9943 section 9.2 and composite proposal; exact status is in crosswalk source table | None. | +| 47 | Inspect current primary material for verification profiles | COMPLETE | RFC 9942 profiles and composite proposal; exact status is in crosswalk source table | None. | +| 48 | Classify Proposed Standard RFCs separately | COMPLETE | RFC 9942 and RFC 9943 labeled Proposed Standard | None. | +| 49 | Classify adopted Working Group drafts separately | COMPLETE | SCRAPI -11 and CCF profile -04 labeled active SCITT WG drafts | None. | +| 50 | Classify individual Internet-Drafts separately | COMPLETE | Each listed individual proposal is explicitly labeled no WG adoption/formal standing | None. | +| 51 | Classify expired drafts separately | COMPLETE | No expired draft relied upon; replaced/older revisions excluded | None. | +| 52 | Never blur RFC, WG draft, individual draft, and expired status | COMPLETE | Crosswalk status table and engagement pre-send checklist | None. | +| 53 | Test VSTD inside SCITT | COMPLETE | Complete receipt carried by real COSE Signed Statement | None. | +| 54 | Test SCITT evidence inside VSTD | COMPLETE | `consume_scitt_evidence` returns `SCITT_TRANSPARENCY/NOT_EVALUATED` | None. | +| 55 | Test bidirectional composition | COMPLETE | Both directions implemented and executed | None. | +| 56 | Test graph composition | COMPLETE | Crosswalk Architecture decision item 3; experimental only | None. | +| 57 | Determine the cleanest arrangement | COMPLETE | Optional bidirectional composition with separate native verdicts | None. | +| 58 | Confirm or falsify the provisional thesis | COMPLETE | Corrected: VSTD is the verification interlingua and operator/result language over orchestrated native engines, not a domain-engine replacement | None. | +| 59 | Create docs/standards/VSTD_SCITT_CROSSWALK.md | COMPLETE | Named substantive deliverable | None. | +| 60 | Use Concern, VSTD, SCITT, Overlap, Difference, and Composition columns | COMPLETE | Six-column rigorous crosswalk | None. | +| 61 | Crosswalk claim identity. | COMPLETE | Named crosswalk row: `claim identity`. | None. | +| 62 | Crosswalk subject identity. | COMPLETE | Named crosswalk row: `subject identity`. | None. | +| 63 | Crosswalk predicates. | COMPLETE | Named crosswalk row: `predicates`. | None. | +| 64 | Crosswalk parameters. | COMPLETE | Named crosswalk row: `parameters`. | None. | +| 65 | Crosswalk explicit limits. | COMPLETE | Named crosswalk row: `explicit limits`. | None. | +| 66 | Crosswalk issuer identity. | COMPLETE | Named crosswalk row: `issuer identity`. | None. | +| 67 | Crosswalk signatures. | COMPLETE | Named crosswalk row: `signatures`. | None. | +| 68 | Crosswalk artifact binding. | COMPLETE | Named crosswalk row: `artifact binding`. | None. | +| 69 | Crosswalk statement registration. | COMPLETE | Named crosswalk row: `statement registration`. | None. | +| 70 | Crosswalk transparency. | COMPLETE | Named crosswalk row: `transparency`. | None. | +| 71 | Crosswalk append-only logs. | COMPLETE | Named crosswalk row: `append-only logs`. | None. | +| 72 | Crosswalk portable receipts. | COMPLETE | Named crosswalk row: `portable receipts`. | None. | +| 73 | Crosswalk evidence bundles. | COMPLETE | Named crosswalk row: `evidence bundles`. | None. | +| 74 | Crosswalk provenance graphs. | COMPLETE | Named crosswalk row: `provenance graphs`. | None. | +| 75 | Crosswalk statement graphs. | COMPLETE | Named crosswalk row: `statement graphs`. | None. | +| 76 | Crosswalk dependencies. | COMPLETE | Named crosswalk row: `dependencies`. | None. | +| 77 | Crosswalk revocation. | COMPLETE | Named crosswalk row: `revocation`. | None. | +| 78 | Crosswalk supersession. | COMPLETE | Named crosswalk row: `supersession`. | None. | +| 79 | Crosswalk conflicts. | COMPLETE | Named crosswalk row: `conflicts`. | None. | +| 80 | Crosswalk freshness. | COMPLETE | Named crosswalk row: `freshness`. | None. | +| 81 | Crosswalk verification profiles. | COMPLETE | Named crosswalk row: `verification profiles`. | None. | +| 82 | Crosswalk resource bounds. | COMPLETE | Named crosswalk row: `resource bounds`. | None. | +| 83 | Crosswalk computational grounding. | COMPLETE | Named crosswalk row: `computational grounding`. | None. | +| 84 | Crosswalk reproduction. | COMPLETE | Named crosswalk row: `reproduction`. | None. | +| 85 | Crosswalk independent checking. | COMPLETE | Named crosswalk row: `independent checking`. | None. | +| 86 | Crosswalk counterexamples. | COMPLETE | Named crosswalk row: `counterexamples`. | None. | +| 87 | Crosswalk PASS. | COMPLETE | Named crosswalk row: `PASS`. | None. | +| 88 | Crosswalk FAIL. | COMPLETE | Named crosswalk row: `FAIL`. | None. | +| 89 | Crosswalk UNKNOWN. | COMPLETE | Named crosswalk row: `UNKNOWN`. | None. | +| 90 | Crosswalk warnings. | COMPLETE | Named crosswalk row: `warnings`. | None. | +| 91 | Crosswalk cost/work claims. | COMPLETE | Named crosswalk row: `cost/work claims`. | None. | +| 92 | Crosswalk graph degradation. | COMPLETE | Named crosswalk row: `graph degradation`. | None. | +| 93 | Crosswalk real-world truth versus evidence validity. | COMPLETE | Named crosswalk row: `real-world truth versus evidence validity`. | None. | +| 94 | Identify what SCITT already does well without relabeling it as VSTD | COMPLETE | Crosswalk section What SCITT already does well | None. | +| 95 | Claim only VSTD differentiation supported by normative text and implementation | COMPLETE | Crosswalk section Current overlap and narrower VSTD contribution | None. | +| 96 | Distinguish UNKNOWN-related condition: unavailable evidence. | COMPLETE | Crosswalk UNKNOWN taxonomy: `unavailable evidence`. | None. | +| 97 | Distinguish UNKNOWN-related condition: incomplete evidence. | COMPLETE | Crosswalk UNKNOWN taxonomy: `incomplete evidence`. | None. | +| 98 | Distinguish UNKNOWN-related condition: stale evidence. | COMPLETE | Crosswalk UNKNOWN taxonomy: `stale evidence`. | None. | +| 99 | Distinguish UNKNOWN-related condition: conflicting evidence. | COMPLETE | Crosswalk UNKNOWN taxonomy: `conflicting evidence`. | None. | +| 100 | Distinguish UNKNOWN-related condition: revoked evidence. | COMPLETE | Crosswalk UNKNOWN taxonomy: `revoked evidence`. | None. | +| 101 | Distinguish UNKNOWN-related condition: unsupported verification method. | COMPLETE | Crosswalk UNKNOWN taxonomy: `unsupported verification method`. | None. | +| 102 | Distinguish UNKNOWN-related condition: verifier resource-budget exhaustion. | COMPLETE | Crosswalk UNKNOWN taxonomy: `verifier resource-budget exhaustion`. | None. | +| 103 | Distinguish UNKNOWN-related condition: bounded inability to establish the predicate. | COMPLETE | Crosswalk UNKNOWN taxonomy: `bounded inability to establish the predicate`. | None. | +| 104 | Distinguish UNKNOWN-related condition: explicit failure. | COMPLETE | Crosswalk UNKNOWN taxonomy: `explicit failure`. | None. | +| 105 | Determine whether SCITT and VSTD UNKNOWN are semantically equivalent | COMPLETE | Explicit answer: no; SCITT core has no generic application verdict and individual-draft labels are profile-specific | None. | +| 106 | Identify the exact VSTD-shaped hole | COMPLETE | Operator/result-semantics boundary in crosswalk Result and Positioning | None. | +| 107 | Do not settle for an unsupported slogan | COMPLETE | Novelty language corrected after overlap research | None. | +| 108 | Implement a small isolated interoperability module | COMPLETE | `src/verifier/interoperability/scitt/adapter.py` | None. | +| 109 | Use repository-consistent location | COMPLETE | Existing `src/verifier/interoperability/` package | None. | +| 110 | Do not implement an entire Transparency Service | COMPLETE | Local one-entry cryptographic specimen only; explicit non-production limit | None. | +| 111 | Implement VSTD receipt/result to SCITT-compatible statement mapping | COMPLETE | `VstdCoordinates`, `VstdScittPayload`, registration template | None. | +| 112 | Implement SCITT receipt/statement to VSTD evidence mapping where supported | COMPLETE | `ScittVerificationEvidence` and `consume_scitt_evidence` | None. | +| 113 | Provide deterministic serialization where required | COMPLETE | `canonical_json_bytes` and deterministic payload test | None. | +| 114 | Provide explicit version identifiers | COMPLETE | mapping version, profile, content type; rejection tests | None. | +| 115 | Preserve VSTD claim coordinates | COMPLETE | closed coordinate projection and round-trip/mismatch tests | None. | +| 116 | Preserve artifact identities | COMPLETE | artifact digest mapping and substitution test | None. | +| 117 | Preserve bounds | COMPLETE | evidence bounds mapping and real budget test | None. | +| 118 | Preserve UNKNOWN | COMPLETE | indeterminate state and composition tests | None. | +| 119 | Preserve provenance references | COMPLETE | round-trip assertion | None. | +| 120 | Reject unsupported mappings rather than guessing | COMPLETE | closed shapes and version/profile/native-result rejections | None. | +| 121 | Prevent semantic upgrading | COMPLETE | composition requires exact digest-bound native VSTD observation plus verified SCITT evidence | None. | +| 122 | Create a self-contained end-to-end example | COMPLETE | `examples/scitt_interop/` includes artifact, demo, docs, public trust artifacts, and tests | None. | +| 123 | Make the complete cryptographic example byte-for-byte deterministic | PARTIAL | VSTD receipt/application payload is deterministic; newly produced COSE bytes use fresh memory-only Ed25519 keys | Full byte determinism conflicts with the repository rule forbidding committed test private-key material. Disclose limited determinism; do not weaken key hygiene. | +| 124 | Demonstrate pipeline step: real computational claim. | COMPLETE | Executed `examples/scitt_interop/demo.py` produce/verify path: `real computational claim`. | None. | +| 125 | Demonstrate pipeline step: VSTD evidence. | COMPLETE | Executed `examples/scitt_interop/demo.py` produce/verify path: `VSTD evidence`. | None. | +| 126 | Demonstrate pipeline step: VSTD verification. | COMPLETE | Executed `examples/scitt_interop/demo.py` produce/verify path: `VSTD verification`. | None. | +| 127 | Demonstrate pipeline step: VSTD result/receipt. | COMPLETE | Executed `examples/scitt_interop/demo.py` produce/verify path: `VSTD result/receipt`. | None. | +| 128 | Demonstrate pipeline step: SCITT-compatible statement. | COMPLETE | Executed `examples/scitt_interop/demo.py` produce/verify path: `SCITT-compatible statement`. | None. | +| 129 | Demonstrate pipeline step: transparency/receipt representation. | COMPLETE | Executed `examples/scitt_interop/demo.py` produce/verify path: `transparency/receipt representation`. | None. | +| 130 | Demonstrate pipeline step: independent consumption. | COMPLETE | Executed `examples/scitt_interop/demo.py` produce/verify path: `independent consumption`. | None. | +| 131 | Implement reverse-direction consumption if practical | COMPLETE | Demo emits REGISTERED SCITT transparency evidence with `computational_verdict=NOT_EVALUATED` | None. | +| 132 | Execute artifact substitution | COMPLETE | SCITT REGISTERED; original VSTD PASS; composition FAIL | None. | +| 133 | Execute valid SCITT registration with an invalid underlying computational claim | COMPLETE | real signature/receipt verified; VSTD REJECTED; composition FAIL | None. | +| 134 | Execute missing required evidence | COMPLETE | SCITT MISSING; VSTD PASS; composition UNKNOWN | None. | +| 135 | Execute revoked evidence | COMPLETE | SCITT REVOKED; historical state retained; composition UNKNOWN | None. | +| 136 | Execute superseded evidence | COMPLETE | SCITT SUPERSEDED; historical state retained; composition UNKNOWN | None. | +| 137 | Execute resource exhaustion | COMPLETE | real signature/receipt verified; VSTD REFUSED/UNKNOWN; composition UNKNOWN | None. | +| 138 | Test serialization | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: deterministic payload test | None. | +| 139 | Test round-trip identity | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: payload and evidence round-trip tests | None. | +| 140 | Test claim-coordinate preservation | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: coordinate mismatch rejection | None. | +| 141 | Test hash and artifact binding | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: substitution and payload-transplant tests | None. | +| 142 | Test unsupported mappings | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: unsupported result/profile/version tests | None. | +| 143 | Test SCITT-valid but VSTD-invalid inputs | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: unit and real-COSE rejected-claim tests | None. | +| 144 | Test VSTD-valid but SCITT-missing inputs | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: MISSING state test | None. | +| 145 | Test UNKNOWN preservation | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: native VSTD and SCITT noncurrent-state tests | None. | +| 146 | Test tampering | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: real statement and receipt tamper test | None. | +| 147 | Test revoked evidence | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: REVOKED state test | None. | +| 148 | Test malformed statements | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: real malformed COSE statement test | None. | +| 149 | Test version mismatches | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: mapping/profile version test | None. | +| 150 | Test bounded resource exhaustion | COMPLETE | `tests/test_scitt_interop.py` / `tests/test_scitt_crypto_example.py`: real budget-zero test | None. | +| 151 | Enforce that SCITT REGISTERED cannot create VSTD PASS without native VSTD verification | COMPLETE | exact no-upgrade, digest-binding, rejected-claim, and budget-exhaustion tests | None. | +| 152 | Run the relevant full repository test suite | COMPLETE | Final validation: 279 passed, 4 skipped | None. | +| 153 | Run the new SCITT interoperability tests | COMPLETE | Final validation: 36 passed | None. | +| 154 | Create docs/standards/SCITT_SEMANTIC_BOUNDARY.md | COMPLETE | Named substantive deliverable | None. | +| 155 | Explain only what current SCITT can establish | COMPLETE | Trust-coordinate-qualified SCITT section and RFC accuracy boundary | None. | +| 156 | Explain only what current VSTD can establish | COMPLETE | Operator-layer section distinguishes implemented and draft layers | None. | +| 157 | State that neither layer automatically establishes arbitrary real-world truth. | COMPLETE | Semantic-boundary Neither establishes automatically section: `arbitrary real-world truth`. | None. | +| 158 | State that neither layer automatically establishes causal correctness. | COMPLETE | Semantic-boundary Neither establishes automatically section: `causal correctness`. | None. | +| 159 | State that neither layer automatically establishes safety. | COMPLETE | Semantic-boundary Neither establishes automatically section: `safety`. | None. | +| 160 | State that neither layer automatically establishes completeness of undisclosed evidence. | COMPLETE | Semantic-boundary Neither establishes automatically section: `completeness of undisclosed evidence`. | None. | +| 161 | State that neither layer automatically establishes rights or authorization from provenance. | COMPLETE | Semantic-boundary Neither establishes automatically section: `rights or authorization from provenance`. | None. | +| 162 | State that neither layer automatically establishes provenance from integrity. | COMPLETE | Semantic-boundary Neither establishes automatically section: `provenance from integrity`. | None. | +| 163 | State that neither layer automatically establishes correctness from signature validity. | COMPLETE | Semantic-boundary Neither establishes automatically section: `correctness from signature validity`. | None. | +| 164 | Make the semantic-boundary document suitable for standards engineers | COMPLETE | Precise propositions, trust coordinates, status language, and non-endorsement disclaimer | None. | +| 165 | Create docs/standards/IETF_SCITT_ENGAGEMENT.md | COMPLETE | Named substantive deliverable | None. | +| 166 | Include a concise, non-hype VSTD introduction | COMPLETE | One-paragraph introduction | None. | +| 167 | Explain the technical VSTD/SCITT relationship | COMPLETE | Operator over orchestrated substrates in Technical relationship | None. | +| 168 | Include at least three substantive questions | COMPLETE | Three questions on payload boundary, composite result semantics, and historical/current state | None. | +| 169 | Suggest a concrete contribution | COMPLETE | Crosswalk, specimen, implementation, and negative tests offered first | None. | +| 170 | Do not assume which contribution the WG wants | COMPLETE | Contribution forms are posed as questions | None. | +| 171 | Do not send any external message without human review | COMPLETE | Prominent do-not-send rule; no external contact performed | None. | +| 172 | Consider Internet-Draft maturity option A — no draft yet | COMPLETE | Engagement maturity section | None. | +| 173 | Consider Internet-Draft maturity option B — informational interoperability/profile draft | COMPLETE | Engagement maturity section | None. | +| 174 | Consider Internet-Draft maturity option C — VSTD evidence statement type | COMPLETE | Engagement maturity section | None. | +| 175 | Consider Internet-Draft maturity option D — bounded computational-verification profile | COMPLETE | Engagement maturity section | None. | +| 176 | Consider Internet-Draft maturity option E — implementation report first | COMPLETE | Engagement maturity section | None. | +| 177 | Recommend the correct maturity level | COMPLETE | Option E now; reconsider Option B only after community and interoperability evidence | None. | +| 178 | Create a concise adjacent-standards matrix | COMPLETE | `docs/standards/ADJACENT_STANDARDS_MATRIX.md` | None. | +| 179 | For IETF SCITT, state what VSTD should reuse | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 180 | For IETF SCITT, state what VSTD should reference | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 181 | For IETF SCITT, state what VSTD can consume | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 182 | For IETF SCITT, state what VSTD should not replace | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 183 | For in-toto, state what VSTD should reuse | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 184 | For in-toto, state what VSTD should reference | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 185 | For in-toto, state what VSTD can consume | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 186 | For in-toto, state what VSTD should not replace | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 187 | For SLSA, state what VSTD should reuse | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 188 | For SLSA, state what VSTD should reference | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 189 | For SLSA, state what VSTD can consume | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 190 | For SLSA, state what VSTD should not replace | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 191 | For Sigstore, state what VSTD should reuse | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 192 | For Sigstore, state what VSTD should reference | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 193 | For Sigstore, state what VSTD can consume | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 194 | For Sigstore, state what VSTD should not replace | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 195 | For C2PA, state what VSTD should reuse | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 196 | For C2PA, state what VSTD should reference | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 197 | For C2PA, state what VSTD can consume | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 198 | For C2PA, state what VSTD should not replace | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 199 | For W3C PROV, state what VSTD should reuse | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 200 | For W3C PROV, state what VSTD should reference | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 201 | For W3C PROV, state what VSTD can consume | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 202 | For W3C PROV, state what VSTD should not replace | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 203 | For SPDX, state what VSTD should reuse | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 204 | For SPDX, state what VSTD should reference | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 205 | For SPDX, state what VSTD can consume | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 206 | For SPDX, state what VSTD should not replace | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 207 | For relevant attestation formats (RATS, EAT, CoRIM, DSSE), state what VSTD should reuse | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 208 | For relevant attestation formats (RATS, EAT, CoRIM, DSSE), state what VSTD should reference | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 209 | For relevant attestation formats (RATS, EAT, CoRIM, DSSE), state what VSTD can consume | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 210 | For relevant attestation formats (RATS, EAT, CoRIM, DSSE), state what VSTD should not replace | COMPLETE | Separate Reuse, Reference, Consume, and Do not replace matrix columns | None. | +| 211 | Use adjacent-standard analysis to prevent reinvention | COMPLETE | Design rule and Do not replace column | None. | +| 212 | Keep SCITT as the sprint's primary target | COMPLETE | Matrix status note and bounded scope | None. | +| 213 | Treat existing-standard composition as a design advantage | COMPLETE | Crosswalk and adjacent matrix shrink VSTD around native systems | None. | +| 214 | Prefer VSTD plus an existing standard when semantics are preserved | COMPLETE | Adapter preserves native bytes, identifiers, trust roots, and results | None. | +| 215 | Keep experimental SCITT work separate from normative VSTD | COMPLETE | All artifacts labeled experimental/non-normative and isolated in docs/examples/interoperability/tests | None. | +| 216 | Do not modify core VSTD semantics | COMPLETE | Normative diff empty | None. | +| 217 | Do not renumber wire identifiers | COMPLETE | Wire identifier files unchanged | None. | +| 218 | Do not change normative receipt structures | COMPLETE | Schemas unchanged; full receipt wrapped | None. | +| 219 | Do not make existing receipts incompatible | COMPLETE | Core/schema behavior unchanged; full suite passes | None. | +| 220 | Document a proposed normative change separately if necessary | NOT APPLICABLE — JUSTIFIED | No normative change proved necessary | Revisit only if review exposes a normative defect. | +| 221 | Do not say IETF supports VSTD | COMPLETE | Non-endorsement statements | None. | +| 222 | Do not say SCITT validates VSTD | COMPLETE | Two-receipts/two-propositions rule and `NOT_EVALUATED` | None. | +| 223 | Do not say VSTD is becoming an IETF standard | COMPLETE | Implementation-report-first recommendation | None. | +| 224 | Use precise experimental interoperability language | COMPLETE | Experimental/non-normative and conditional mapping wording | None. | +| 225 | Deliver docs/standards/VSTD_SCITT_CROSSWALK.md. | COMPLETE | Present and inspected: `docs/standards/VSTD_SCITT_CROSSWALK.md`. | None. | +| 226 | Deliver docs/standards/SCITT_SEMANTIC_BOUNDARY.md. | COMPLETE | Present and inspected: `docs/standards/SCITT_SEMANTIC_BOUNDARY.md`. | None. | +| 227 | Deliver docs/standards/IETF_SCITT_ENGAGEMENT.md. | COMPLETE | Present and inspected: `docs/standards/IETF_SCITT_ENGAGEMENT.md`. | None. | +| 228 | Deliver docs/standards/ADJACENT_STANDARDS_MATRIX.md. | COMPLETE | Present and inspected: `docs/standards/ADJACENT_STANDARDS_MATRIX.md`. | None. | +| 229 | Deliver examples/scitt_interop/. | COMPLETE | Present and inspected: `examples/scitt_interop/`. | None. | +| 230 | Deliver src/verifier/interoperability/scitt/. | COMPLETE | Present and inspected: `src/verifier/interoperability/scitt/`. | None. | +| 231 | Deliver tests/test_scitt_interop.py. | COMPLETE | Present and inspected: `tests/test_scitt_interop.py`. | None. | +| 232 | Deliver tests/test_scitt_crypto_example.py. | COMPLETE | Present and inspected: `tests/test_scitt_crypto_example.py`. | None. | +| 233 | Follow actual repository naming and layout conventions | COMPLETE | Source under `src/verifier`; examples/docs/tests use existing layout | None. | +| 234 | Answer final-report question 1: How close is SCITT to VSTD? | COMPLETE | Explicit numbered answer 1 below | None. | +| 235 | Answer final-report question 2: What does SCITT already solve that VSTD should stop trying to solve itself? | COMPLETE | Explicit numbered answer 2 below | None. | +| 236 | Answer final-report question 3: What important capability remains distinctively VSTD? | COMPLETE | Explicit numbered answer 3 below | None. | +| 237 | Answer final-report question 4: What is the cleanest VSTD-SCITT architecture? | COMPLETE | Explicit numbered answer 4 below | None. | +| 238 | Answer final-report question 5: Can VSTD receipts be carried as SCITT statements without semantic loss? | COMPLETE | Explicit numbered answer 5 below | None. | +| 239 | Answer final-report question 6: Can SCITT receipts serve as VSTD evidence, and under what policy? | COMPLETE | Explicit numbered answer 6 below | None. | +| 240 | Answer final-report question 7: What does VSTD add to composite-evidence verification? | COMPLETE | Explicit numbered answer 7 below | None. | +| 241 | Answer final-report question 8: Which current SCITT document is closest to VSTD, with exact status? | COMPLETE | Explicit numbered answer 8 below | None. | +| 242 | Answer final-report question 9: What interoperability work was implemented? | COMPLETE | Explicit numbered answer 9 below | None. | +| 243 | Answer final-report question 10: Which tests demonstrate no SCITT-integrity-to-VSTD-truth laundering? | COMPLETE | Explicit numbered answer 10 below | None. | +| 244 | Answer final-report question 11: Should the SCITT mailing list be engaged now, and how? | COMPLETE | Explicit numbered answer 11 below | None. | +| 245 | Answer final-report question 12: Should an Internet-Draft be prepared now, later, or not at all? | COMPLETE | Explicit numbered answer 12 below | None. | +| 246 | Answer final-report question 13: What is the strongest positioning sentence? | COMPLETE | Explicit numbered answer 13 below | None. | +| 247 | Answer final-report question 14: What is the strongest technical demo? | COMPLETE | Explicit numbered answer 14 below | None. | +| 248 | Answer final-report question 15: What is unfinished before engagement? | COMPLETE | Explicit numbered answer 15 below | None. | + + +## Original final-report questions — explicit answers + +1. **How close is SCITT to VSTD?** A percentage would be misleading because the + layers answer partly orthogonal questions. SCITT is standardized for signed + statement authenticity, registration policy, transparency/VDS properties, and + portable COSE receipts. VSTD is the domain language for portable claim boundaries + and results across native verification substrata. Active individual SCITT drafts + overlap some result and graph vocabulary but are not WG standards. +2. **What does SCITT already solve that VSTD should stop trying to solve itself?** + COSE envelopes, issuer/subject authentication, registration policy, Transparency + Service APIs, append-only/non-equivocating VDS behavior, COSE Receipt + attachment/verification, and TS-key discovery. +3. **What remains distinctively VSTD?** In this repository, a domain-general + verification interlingua that preserves a native engine's exact claim, evidence, + policy, verifier, budget, result, UNKNOWN, refutation, and degradation boundary. + This is an implementation distinction, not a universal novelty claim. +4. **What is the cleanest architecture?** Optional bidirectional composition with + separate native results: carry a complete VSTD receipt as a SCITT application + payload when transparency is wanted; consume verified SCITT registration as typed + transparency evidence; never upgrade either native result. +5. **Can VSTD receipts be carried as SCITT statements without semantic loss?** Yes + for the implemented full-receipt mapping, provided the receipt, coordinates, + bounds, artifact digests, provenance references, mapping version, payload digest, + and native checker result all remain visible. Selective projection needs its own + loss analysis. +6. **Can SCITT receipts serve as VSTD evidence? Under what policy?** Yes, narrowly, + after native statement and COSE Receipt verification under named issuer, subject, + payload digest, TS, VDS/profile, registration policy, key, time/freshness, and + lifecycle assumptions. The adapted result is `SCITT_TRANSPARENCY/NOT_EVALUATED`, + not computational PASS. +7. **What does VSTD add to composite-evidence verification?** A concrete portable + contract binding a native verifier's receipt/result to claim coordinates, + artifacts, roots, resource ceiling, and refutation surface. The individual + composite draft already proposes graphs, bundles, profiles, missing, stale, + conflict, warning, and unknown outcomes; VSTD does not claim those as unique. +8. **Which current SCITT document is closest?** + `draft-nobuo-scitt-composite-evidence-verification-00`, an **active individual + Internet-Draft with no WG adoption or formal standing**. AI Agent Receipt -01 is + also close on narrow-claim and validity/sufficiency boundaries and has the same + individual-draft status. +9. **What was implemented?** A strict adapter; canonical application payload; + normalized native VSTD and SCITT evidence; monotone composition; real EdDSA COSE + Signed Statement; real RFC9162-SHA256 inclusion receipt; reverse evidence adapter; + end-to-end example; documentation; and 36 focused tests. +10. **Which tests prove no integrity-to-truth laundering?** + `test_registered_scitt_cannot_create_pass_without_bound_vstd_verification`, + `test_vstd_checker_result_must_bind_exact_receipt_and_native_result`, + `test_rejected_vstd_receipt_cannot_be_repaired_by_scitt_registration`, + `test_real_scitt_registration_does_not_upgrade_vstd_budget_exhaustion`, and + `test_real_valid_scitt_registration_does_not_repair_rejected_vstd_claim`. +11. **Should the SCITT mailing list be engaged now?** Yes for initial technical + correction, not adoption. After human review and publication, send one compact + implementation-report message that states the RFC 9943 accuracy boundary, links + code/crosswalk/tests, discloses the local log and lack of an independent + implementation, and asks the three bounded questions in the engagement package. +12. **Should an Internet-Draft be prepared?** Later if discussion supports it. + Choose Option E now (implementation report first); reconsider Option B + (informational payload profile) after feedback and independent/native + interoperability. Options C and D are premature. +13. **Strongest positioning sentence:** “SCITT can authenticate and make a VSTD + receipt's registration transparently auditable; VSTD supplies the verification + interlingua that preserves the bounded claim boundary and portable result + semantics of the native verifier or proof engine that produced the result.” +14. **Strongest technical demo:** With real signatures and receipts, show an exact + VSTD claim composing to PASS, a binding-invalid VSTD witness composing to FAIL + while SCITT remains REGISTERED, and a budget-zero check composing to UNKNOWN + while SCITT remains REGISTERED. +15. **What remains before engagement?** No substantive blocker for an initial + technical inquiry. Disclose that the TS is a local one-entry log, there is no + external implementation, identifiers are experimental, and fresh memory-only + keys intentionally prevent byte-identical COSE regeneration. + +## Executed end-to-end path + +```powershell +python examples/scitt_interop/demo.py produce --output +python examples/scitt_interop/demo.py verify --output +``` + +Observed: native VSTD `ACCEPTED/PASS`; Signed Statement signature verified; COSE +Receipt verified; SCITT state `REGISTERED`; composition `PASS`; reverse adapter +`REGISTERED/NOT_EVALUATED`. Stable artifact digest: +`39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341`. +Deterministic application-payload digest: +`38a21c5d1a5aa9feb99626d7631a626d5b140f39486117a9954afb54ae2fb661`. + +## Executed adversarial outcomes + +| Case | SCITT layer | VSTD layer | Composition | Reason | +|---|---|---|---|---| +| Artifact substitution | REGISTERED | PASS for original artifact | FAIL | Observed artifact digest differs from bound digest. | +| Valid registration, invalid claim | REGISTERED; signature/receipt verified | REJECTED | FAIL | Registration does not establish the computational claim. | +| Missing required evidence | MISSING | PASS | UNKNOWN | Optional transparency proposition is unestablished. | +| Revoked evidence | REVOKED | PASS | UNKNOWN | Historical inclusion remains; current admissibility does not. | +| Superseded evidence | SUPERSEDED | PASS | UNKNOWN | Newer does not mean truer; selection needs policy. | +| Resource budget zero | REGISTERED; signature/receipt verified | REFUSED/UNKNOWN | UNKNOWN | SCITT cannot repair bounded inability to check. | + +## Validation record + +- Focused SCITT suite: **36 passed**. +- Full repository suite: **279 passed, 4 skipped**. +- `python scripts/check_presentation.py`: **OK**. +- `python -m compileall -q src examples/scitt_interop tests`: **OK**. +- Normative diff (`standard`, `receipts`, core verifier): **empty**. +- Base runtime dependencies: **unchanged**; SCITT dependencies are optional/pinned. +- Standard-library-only adapter import (`python -S` with `src` on `sys.path`): **OK**. +- Tracked-tree privacy/claims scan: **clean**. + +## Score + +- Total requirements: **248** +- Complete: **246** +- Partial: **1** +- Missing: **0** +- Justified N/A: **1** +- Blocked externally: **0** +- Applicable requirements: **247** +- Completion percentage: **99.60%** + (`COMPLETE / applicable`; PARTIAL is not counted as complete) diff --git a/docs/standards/SCITT_SEMANTIC_BOUNDARY.md b/docs/standards/SCITT_SEMANTIC_BOUNDARY.md new file mode 100644 index 0000000..3016f0b --- /dev/null +++ b/docs/standards/SCITT_SEMANTIC_BOUNDARY.md @@ -0,0 +1,196 @@ +# SCITT semantic boundary for VSTD interoperability + +> **Status:** experimental, non-normative. This boundary follows [RFC 9943](https://datatracker.ietf.org/doc/html/rfc9943), [RFC 9942](https://datatracker.ietf.org/doc/html/rfc9942), and the current repository's implemented VSTD specifications. It does not claim SCITT WG review. + +## SCITT can establish + +Subject to the named trust anchors, keys, algorithms, VDS profile, registration +policy, receipt validity period, and relying-party checks, SCITT can establish: + +- which exact Signed Statement bytes an issuer signed; +- the authenticated `iss` and `sub` protected claims and payload media type; +- that a TS applied its then-current registration policy before registration; +- that the Signed Statement was included in the TS's VDS state represented by a + valid COSE Receipt; +- the VDS proof properties implemented by the Receipt profile, such as inclusion + and, where supported, consistency; +- append-only/non-equivocation evidence and auditable registration history; +- enough registration collateral for authorized auditors to reproduce the + registration checks required by RFC 9943; +- historical relationships such as later same-issuer/same-subject statements that + a relying-party policy may treat as superseding earlier statements. + +RFC 9943 is explicit that an issuer can make a false statement and that registration +only proves the statement was produced by the issuer. A SCITT Receipt is therefore +not a generic certificate of payload truth. + +## VSTD can establish + +VSTD is not the domain verifier or proof engine. It is the standard domain language +and operator/result layer through which those orchestrated substrates expose +portable claim boundaries and results. Only for its declared claim, coordinate, +evidence, policy, native verifier fragment, and resource bounds, the implemented +VSTD layers can establish: + +- claim-mechanics and declared falsification conditions; +- an explicit verification surface and claim coordinate; +- substrate/accountability evidence within VSTD-3's implemented capability model; +- a VSTD-4 PASS, FAIL, or UNKNOWN carrying the required decision artifact; +- grounding between a bounded logical encoding and named artifact facts; +- independent checking of the VSTD-4 certificate without sharing verdict-producing + code; +- a bounded cost/memory/certificate-size ceiling and honest refusal when exceeded; +- graph-level lineage, closure, challenge, blast-radius, and degradation results over + the recorded VSTD-Graph evidence. + +The native solver, proof engine, signature checker, identity service, transparency +log, or provenance system retains its own semantics and result. A loss-declared +adapter maps that result into VSTD's verification interlingua and records the +boundary around its portable composition; VSTD does not absorb or reimplement the +substrate. + +VSTD-5 and VSTD-Graph-5 remain draft. A higher VSTD layer does not supply a missing +lower layer. + +## Identity, disclosure, trust, and reputation + +VSTD verification is claim-first. Deciding a bounded claim does not, merely by +being a VSTD check, require a natural-person identity, creator identity, or +persistent actor identity. Some VSTD layers and profiles name devices, verifier +implementations, evidence sources, or witnesses where those coordinates are part +of the claim. Such identifiers do not automatically establish authorship, +authority, independence, reputation, or real-world identity. + +SCITT composition is therefore **optional**, not a prerequisite for VSTD. An RFC +9943 Signed Statement introduces an authenticated issuer coordinate, and public +registration may expose stable identifiers, subjects, payload bytes or digests, +timing, and relationship metadata. A key or pseudonym need not identify a natural +person, but it can still be linkable. Wrapping a VSTD receipt in SCITT adds an +accountability/transparency proposition; it does not strengthen the native VSTD +computational proposition and can weaken an identity-minimizing privacy posture. + +The implemented VSTD core is disclosure-neutral, not a zero-knowledge proof +protocol. Full-disclosure receipts remain valid. Zero-knowledge and zero-identity +work belongs in separately reviewed experimental profiles, and no receipt may be +called zero knowledge without a real proof-system guarantee. “Trustless” must mean +trust-minimized and assumption-explicit: a relying party still depends on selected +algorithms, checker code, canonicalization, policy, input availability, and, when +used, proof-system parameters or trust roots. + +VSTD-Graph can preserve artifact history, challenges, lifecycle changes, and +refutations, but the current standard does not define a scalar artifact-reputation +score. A future reputation or “rust” view can be derived from that recorded history +only as a separate, time-indexed policy result. It must never overwrite a native +verdict or turn repeated registrations, signatures, or observations into truth. + +## Neither establishes automatically + +Neither a valid SCITT Receipt nor a valid VSTD receipt automatically establishes: + +- truth of arbitrary physical-world or historical propositions; +- completeness of evidence that was never disclosed or discoverable; +- causal correctness or causal influence merely from recorded lineage; +- safety, harmlessness, fitness for purpose, or regulatory compliance; +- authorization, rights, ownership, or permission merely from identity or + provenance; +- provenance merely from integrity or a matching digest; +- computational correctness merely from signature validity or registration; +- issuer independence, uniqueness, Sybil resistance, or lack of collusion; +- current validity merely from historical inclusion; +- correct policy selection merely because a policy identifier is present; +- privacy, anonymity, confidentiality, or unlinkability. + +## Two receipts, two propositions + +| Artifact | Native proposition | +|---|---| +| VSTD receipt | The declared bounded computational result and its evidence/refutation boundary. | +| SCITT COSE Receipt | A VDS property, normally inclusion of the exact Signed Statement under a TS identity and proof profile. | + +The experimental profile places the first inside the payload of a SCITT Signed +Statement and attaches the second to that statement. Implementations must name the +receipt type whenever “receipt” would be ambiguous. + +The unwrapped VSTD receipt remains independently checkable. Selecting the SCITT +profile deliberately adds issuer and transparency coordinates; it is not the +default wire path for an identity-independent or witness-private VSTD profile. + +## Trust coordinates that must remain visible + +### SCITT + +- issuer key/certificate and identity interpretation; +- TS receipt-verification key and TS identity; +- VDS/VDP profile and algorithm; +- registration policy and policy version/state; +- statement subject and content type; +- registration/receipt time and freshness policy; +- key-compromise, supersession, revocation, and discovery policy; +- external native verifier implementation/version. + +### VSTD + +- claim, subject, predicate, and parameters; +- policy root and evidence root; +- artifact identities and content digests; +- verifier specification, implementation, parser, and supported fragment; +- resource bounds and prior commitment; +- certificate format, verdict, reason, and native lifecycle status; +- evidence availability, challenges, and graph ancestors when applicable. + +## Composition rule + +A composed PASS is permitted only when all of the following hold: + +1. the native VSTD checker independently accepts a VSTD PASS; +2. the full VSTD payload digest matches the payload signed in the SCITT statement; +3. the SCITT statement signature is valid under an accepted issuer policy; +4. the SCITT Receipt is valid for that exact statement under an accepted TS/VDS + policy; +5. the SCITT subject equals the VSTD claim-coordinate subject; +6. the observed artifact digests equal the VSTD-bound artifact digests; +7. the required evidence is current and neither revoked, superseded, conflicted, + missing, nor unavailable under the declared relying-party policy. + +Any single failed condition prevents PASS. Registration never repairs a failed VSTD +claim. A VSTD PASS never fabricates missing SCITT transparency. + +## UNKNOWN and lifecycle behavior + +SCITT core does not define one application-level UNKNOWN verdict. The individual +[Composite Evidence Verification draft](https://datatracker.ietf.org/doc/draft-nobuo-scitt-composite-evidence-verification/) +proposes `unknown`, `missing`, `stale`, `conflict`, and `warning`, but it is not an +adopted WG standard and its result precedence remains draft work. + +VSTD UNKNOWN is bounded and reason-bearing. In VSTD-4, resource exhaustion, +unavailable dependencies, unavailable verifiers, and unretrievable artifacts have +distinct indeterminacy reasons. Therefore adapters must preserve both the native +SCITT condition and native VSTD reason. Label equality alone is not semantic +equivalence. + +Historical SCITT inclusion may remain valid while current VSTD usability falls. For +example, a receipt can still prove that a statement was registered in the past even +after a relying party considers its evidence stale or an ancestor revoked. The +adapter records both facts rather than deleting history or treating inclusion as +current computational validity. + +## Implementation boundary + +The module in `src/verifier/interoperability/scitt/`: + +- emits deterministic application payload bytes and a normalized registration + template; +- does **not** claim that JSON is the SCITT wire format; +- requires a native RFC 9943/COSE producer to create a real Signed Statement; +- requires a native RFC 9942 verifier to validate a COSE Receipt; +- consumes the native verifier's output only under explicit issuer, subject, payload, + policy, TS, and VDS coordinates; +- requires a separately bound native VSTD checker result for the exact embedded + receipt; a receipt's declared `PASS` is not evidence that it was checked; +- returns `computational_verdict = NOT_EVALUATED` when adapting SCITT evidence alone; +- rejects unknown mappings instead of guessing. + +The example uses pinned optional libraries to create and verify real COSE bytes and +an RFC9162 SHA-256 inclusion receipt in a local one-entry test log. That demonstrates +the cryptographic boundary but does not represent a production TS, public witness, +or public anchoring. diff --git a/docs/standards/VSTD_SCITT_CROSSWALK.md b/docs/standards/VSTD_SCITT_CROSSWALK.md new file mode 100644 index 0000000..69c3a1f --- /dev/null +++ b/docs/standards/VSTD_SCITT_CROSSWALK.md @@ -0,0 +1,181 @@ +# VSTD and IETF SCITT: experimental interoperability crosswalk + +> **Status:** experimental, non-normative, reviewed against public specifications on +> 2026-08-23. This document does not alter VSTD semantics and does not imply IETF, +> SCITT Working Group, or implementation-provider endorsement. + +## Result + +The working thesis survives with one important correction: + +> **SCITT authenticates statements and makes their policy-governed registration in a +> verifiable data structure transparent and portable. VSTD is a standard domain +> language for verification: an interlingua that standardizes the claim boundary and +> portable result semantics by which a domain verifier or proof engine's bounded +> result is represented, binding-checked, refuted, mapped, and composed with adjacent +> evidence.** + +SCITT is not merely transport. It already specifies issuer/subject binding, signed +statements, registration-policy evaluation, append-only and non-equivocating +transparency, portable COSE receipts, and replayable registration audits. VSTD must +not rename those mechanisms as VSTD inventions. Conversely, SCITT explicitly allows +false statements to be registered and leaves payload truth to application-domain +semantics. VSTD does not replace those application-domain semantics or engines. It +is the general operator-language class; native verifiers, proof engines, and other +evidence substrata are the orchestrated implementations whose own outputs and limits +remain authoritative and visible. Explicit adapters make the mapping and any loss +reviewable. That is the clean VSTD-shaped boundary. + +## Sources and exact status + +| Document | Status on 2026-08-23 | Relevance | +|---|---|---| +| [RFC 9943: SCITT Architecture](https://datatracker.ietf.org/doc/html/rfc9943) | IETF Standards Track RFC, **Proposed Standard**, June 2026 | Normative SCITT architecture, Signed Statements, Registration, Receipts, Transparent Statements, and security boundary. | +| [RFC 9942: COSE Receipts](https://datatracker.ietf.org/doc/html/rfc9942) | IETF Standards Track RFC, **Proposed Standard**, June 2026 | COSE Receipt wrapper, VDS/VDP registries, RFC9162 inclusion and consistency proof encodings. | +| [draft-ietf-scitt-scrapi-11](https://datatracker.ietf.org/doc/html/draft-ietf-scitt-scrapi-11) | **Active SCITT WG Internet-Draft**, intended Proposed Standard, in the RFC Editor Queue; not yet an RFC | HTTP registration, asynchronous completion, receipt resolution, and TS key discovery. | +| [draft-ietf-scitt-receipts-ccf-profile-04](https://datatracker.ietf.org/doc/html/draft-ietf-scitt-receipts-ccf-profile-04) | **Active SCITT WG Internet-Draft**, intended Proposed Standard, publication requested; not an RFC | CCF ledger VDS and inclusion-proof profile for COSE Receipts. | +| [draft-nobuo-scitt-composite-evidence-verification-00](https://datatracker.ietf.org/doc/draft-nobuo-scitt-composite-evidence-verification/) | **Active individual Internet-Draft**, no WG adoption or formal standing | Closest work: composite verification of statements, receipts, bindings, relationships, freshness, conflicts, and bundles under a named profile. | +| [draft-nobuo-scitt-protected-object-binding-00](https://datatracker.ietf.org/doc/draft-nobuo-scitt-protected-object-binding/) | **Active individual Internet-Draft**, no WG adoption or formal standing | Proposed object bindings and statement-graph relationships; explicitly does not establish payload truth. | +| [draft-emirdag-scitt-ai-agent-execution-00](https://datatracker.ietf.org/doc/html/draft-emirdag-scitt-ai-agent-execution-00) | **Active individual Internet-Draft**, no stream or WG adoption; its draft header says intended Informational | Agent-execution records, sequence completeness, evidence custody, and redaction receipts. | +| [draft-noa-scitt-ai-agent-receipt-01](https://datatracker.ietf.org/doc/html/draft-noa-scitt-ai-agent-receipt-01) | **Active individual Internet-Draft**, no stream or WG adoption; its draft header says Standards Track | Per-action receipt profile with narrow claims, validity/sufficiency separation, absence/indeterminacy semantics, and explicit external-world limits. | +| [draft-dawkins-scitt-ai-article50-00](https://datatracker.ietf.org/doc/html/draft-dawkins-scitt-ai-article50-00) | **Active individual Internet-Draft**, no stream or WG adoption | AI-transparency receipt profile for selected EU AI Act Article 50 disclosure claims. | +| [draft-mih-scitt-agent-action-capsule-02](https://datatracker.ietf.org/doc/html/draft-mih-scitt-agent-action-capsule-02) | **Active individual Internet-Draft**, no stream or WG adoption | Agent Action Capsule payload profile separating dispatched attempts, observed results, and human-in-the-loop records. | +| [draft-mih-scitt-agent-action-capsule-sel-disc-00](https://datatracker.ietf.org/doc/html/draft-mih-scitt-agent-action-capsule-sel-disc-00) | **Active individual Internet-Draft**, no stream or WG adoption | Selective-disclosure construction and missing-required-field behavior for Agent Action Capsules. | +| [draft-hillier-scitt-arp-03](https://datatracker.ietf.org/doc/html/draft-hillier-scitt-arp-03) | **Active individual Internet-Draft**, no stream or WG adoption | Attestation reconciliation, query binding, divergence axes, policy coordinates, and budget-exhaustion concerns. | +| [draft-dogru-scitt-disclosure-evidence-07](https://datatracker.ietf.org/doc/html/draft-dogru-scitt-disclosure-evidence-07) | **Active individual Internet-Draft**, no stream or WG adoption | Transformation evidence and coverage reconciliation, including excluded and indeterminate coverage outcomes. | +| [draft-le-scitt-derived-subjects-00](https://datatracker.ietf.org/doc/html/draft-le-scitt-derived-subjects-00) | **Active individual Internet-Draft**, no stream or WG adoption | Deterministic subject derivation across independently governed identifier schemes. | +| [draft-mih-sokolov-scitt-payload-binding-01](https://datatracker.ietf.org/doc/html/draft-mih-sokolov-scitt-payload-binding-01) | **Active individual Internet-Draft**, no stream or WG adoption | Canonical payload binding and cross-profile digest references; appraisal remains in consuming profiles. | + +Internet-Drafts are work in progress. The individual drafts above are proposals by +their authors, not IETF or SCITT WG positions. Earlier draft revisions that have been +replaced or expired were not used as current authority. None of the documents relied +on in this table is expired as of the review date. + +## Architecture decision + +The cleanest arrangement is **optional bidirectional composition with separate +verdicts**. SCITT is not a prerequisite for VSTD and is not the default publication +path for an identity-independent or witness-private VSTD profile: + +1. **VSTD inside SCITT:** a complete VSTD receipt is the application payload of an + RFC 9943 Signed Statement. The SCITT protected headers bind issuer, subject, + content type, and signature. A COSE Receipt proves registration/inclusion under + the selected TS, VDS, registration policy, key, and time assumptions. +2. **SCITT evidence inside VSTD:** output from a native SCITT verifier may be VSTD + evidence for a narrowly stated transparency proposition, such as “this exact + statement was signed by an accepted issuer and included in this TS VDS under this + policy.” It is not evidence that silently settles the statement's computational + payload. SCITT is one orchestrated substrate, not a privileged source of truth. +3. **Graph composition:** a SCITT statement-graph profile may identify registered + statements, object bindings, edges, supersession, and conflicts. VSTD-Graph can + evaluate bounded predicates over selected nodes and edges, but each graph's + native identifiers, status semantics, and policy remain visible. + +This is not recursive self-certification. SCITT and VSTD remain adjacent layers with +different trust roots and different questions. Selecting SCITT deliberately adds +issuer authentication, registration policy, transparency, and possible correlation; +omitting SCITT leaves those properties unclaimed rather than making them UNKNOWN +VSTD computational evidence. + +## Rigorous crosswalk + +| Concern | VSTD | SCITT | Overlap | Difference | Composition | +|---|---|---|---|---|---| +| Claim identity | Receipt and claim identifiers; VSTD-4 binds a claim string and coordinate. | Signed Statement bytes plus issuer/subject and payload media type identify a statement context. | Both bind an assertion to named coordinates. | SCITT identity is signed-statement identity; VSTD identity includes bounded computational semantics. | Carry the native VSTD receipt intact and bind its full payload digest in the SCITT statement. | +| Actor identity | A bounded artifact claim need not identify a natural person, creator, or persistent actor; layer-specific device/verifier/witness identifiers do not imply authorship or authority. | A Signed Statement authenticates a declared issuer under a relying-party trust policy; the issuer can be a key or pseudonym but may be linkable. | Both may bind identifiers when the declared proposition needs them. | SCITT issuer authentication is central to accountability; actor identity is not required for every VSTD computation. | Make SCITT wrapping optional and never copy issuer reputation into the native VSTD verdict. | +| Disclosure / zero knowledge | Core VSTD is disclosure-neutral; current receipts may disclose evidence, and experimental ZK profiles must supply real proof-system guarantees. | Registration makes signed statement material or commitments available under TS policy and can expose timing, subjects, and relationships. | Either can carry commitments or proofs defined by an application profile. | Neither RFC 9943 nor current VSTD core automatically provides witness confidentiality, anonymity, or unlinkability. | Treat privacy effects as an explicit profile property; do not label this full-disclosure example ZK or zero identity. | +| Artifact reputation / trust | Graph history can record challenges, staleness, supersession, revocation, and refutation; no normative scalar reputation score exists. | Logs provide durable registration history and issuer accountability, not payload reputation or truth. | Both can contribute time-indexed observations about one artifact. | Repetition and age do not increase epistemic strength by themselves. | A future reputation/rust view must be separately derived, policy-bound, and unable to upgrade native results. | +| Subject identity | VSTD-2/VSTD-4 coordinate `subject`. | Protected CWT `sub` claim; issuer-defined and usable to correlate statements. | Both name what a claim is about. | Equal spelling does not prove equal interpretation. | Require exact subject equality under the experimental profile; reject mismatch. | +| Predicates | Explicit VSTD predicate and parameters. | Payload/application profile defines predicate semantics; SCITT core is content-agnostic. | A VSTD predicate can be a SCITT payload predicate. | SCITT core does not define the VSTD predicate. | Preserve predicate and parameters in the payload projection and full receipt. | +| Parameters | Bound into VSTD claim coordinates and canonical receipt. | May appear in opaque payload or profile-defined protected fields. | Both can integrity-bind parameters. | SCITT has no generic computational-parameter semantics. | Keep parameters in VSTD payload; only promote selected values to protected headers after profile review. | +| Explicit limits | VSTD claim limitations, excluded claims, and refutation surface. | RFC 9943 states architectural/security limits; application payload profiles may add limits. | Both can document scope. | VSTD makes per-result bounds part of verification semantics. | Carry VSTD limits without translating them into SCITT registration-policy claims. | +| Issuer identity | May occur in provenance, but VSTD core does not replace signing identity infrastructure. | Protected `iss`; signature and trust-anchor validation are mandatory registration concerns. | Both may record a producer. | SCITT owns signed issuer authentication; VSTD ownership/authorship is not inferred from integrity. | Reuse SCITT issuer authentication and keep it separate from VSTD computational outcome. | +| Signatures | VSTD can consume signature evidence; it does not define a universal signing system. | COSE_Sign1 is normative for Signed Statements and Receipts. | VSTD can reference verified signature evidence. | SCITT already standardizes the envelope and signature placement. | When the SCITT profile is selected, use SCITT/COSE rather than inventing a competing envelope. | +| Artifact binding | VSTD binds content-addressed subjects/evidence roots and checks wrong-artifact cases. | `sub`, payload hashes/detached payloads, and signed envelope bind statements to declared artifacts. | Both defend substitution at different layers. | SCITT proves what bytes/subject the issuer signed, not that VSTD evaluated the intended artifact correctly. | Require exact VSTD artifact digests and SCITT payload digest; either mismatch fails composition. | +| Statement registration | Not a VSTD core function. | TS applies registration policy, inserts the statement, and issues a receipt. | None needed. | SCITT already owns this layer. | VSTD should consume the result, not recreate registration. | +| Transparency | VSTD can record published artifacts but defines no generic transparency service. | Core objective: auditable, accountable signed-content transparency. | VSTD receipts are suitable transparent payloads. | SCITT provides the standardized transparency machinery. | Register through SCITT when public accountability is desired; do not require it for identity-independent/private verification. | +| Append-only logs | VSTD-Graph records additive challenge history but is not a general public log protocol. | SCITT VDS must be append-only, non-equivocating, and replayable. | Both avoid rewriting history. | SCITT defines the log/VDS guarantees and receipts. | Use SCITT VDS rather than a VSTD-specific transparency log. | +| Portable receipts | VSTD receipts carry computational evidence and bounds. | COSE Receipts carry signed VDS proofs and attach to Transparent Statements. | Both produce portable evidence artifacts. | “Receipt” names different proof targets. | Name both explicitly: VSTD computational receipt inside a SCITT Signed Statement; SCITT COSE Receipt outside it. | +| Evidence bundles | VSTD receipts and graph collections may contain evidence references. | Core permits payloads; composite-evidence draft proposes bundles under profiles. | Both can package evidence sets. | The SCITT bundle model is currently an individual proposal, not a WG standard. | Use a VSTD payload now; discuss bundle alignment before standardizing graph exchange. | +| Provenance graphs | VSTD-Graph records typed artifact/transformation lineage and graph-level degradation. | RFC 9943 correlates statements by subject; individual drafts propose object bindings and statement graphs. | Both can connect evidence about shared subjects. | SCITT core does not standardize the proposed statement-graph vocabulary; VSTD lineage is not causal proof. | Reference native SCITT statement IDs from VSTD-Graph without rewriting either graph. | +| Statement graphs | VSTD-Graph has implemented graph levels and policy checks. | Proposed by individual object-binding/composite drafts. | Both need explicit edge semantics and policy. | Maturity and graph objects differ. | Experimental bridge only; no claim of SCITT WG alignment. | +| Dependencies | VSTD-4 can return `UNKNOWN/DEPENDENCY_UNAVAILABLE`; Graph evaluates transitive ancestors. | Composite draft proposes required statements and dependency edges. | Both surface unavailable dependencies. | SCITT core receipt validity does not settle application dependency completeness. | Preserve the native missing reason and let VSTD issue its own bounded indeterminacy certificate. | +| Revocation | VSTD challenge state can revoke artifacts; graph level degrades on revoked ancestors. | RFC 9943 discusses compromised-key handling but leaves revocation strategies out of scope; individual composite draft proposes revocation statements/checks. | Both can react to invalidated evidence. | No universal SCITT payload-revocation semantics exist in the core RFC. | A relying-party policy may cap a VSTD composition at UNKNOWN or FAIL, while preserving native state and reason. | +| Supersession | VSTD-Graph records `SUPERSEDED` without automatically making the older node inadmissible. | RFC 9943 permits later same-issuer/same-subject statements to supersede earlier ones; selection is relying-party policy. | Both preserve history. | Neither makes “newer” automatically “truer”; policy consequences differ. | Normalize `SUPERSEDED` without upgrading; require explicit current-evidence policy. | +| Conflicts | VSTD preserves `CONFLICTED` where defined and graph blockers. | RFC 9943 allows conflicting issuers; individual composite draft proposes `conflict`. | Both refuse silent reconciliation. | SCITT core delegates issuer selection; VSTD may express a bounded conflict result. | Preserve `CONFLICTED` as distinct from UNKNOWN and FAIL. | +| Freshness | VSTD bounds and evidence can include time/freshness; stale graph artifacts are inadmissible at higher graph levels. | Receipt state is true when issued; keys/policies can change; application policies determine freshness. SCRAPI can issue fresh receipts. | Both require time-indexed trust coordinates. | Inclusion is historical; it does not establish current payload validity. | Carry registration time, policy, key/VDS, and freshness decision separately. | +| Verification profiles | VSTD layers and verifier descriptors define supported fragments. | RFC 9942 defines VDS profiles; RFC 9943 permits application profiles; composite draft proposes named verification profiles. | Both use explicit capability/profile identifiers. | VDS proof profile is not computational predicate profile. | Bind both profile identifiers; never collapse them. | +| Resource bounds | VSTD-4 preflights verification cost, memory, and certificate size. | SCITT core has operational limits but no payload-domain computational-verdict resource model. | Both can reject over-limit inputs operationally. | SCRAPI 429/204 is protocol state, not epistemic UNKNOWN. | Keep VSTD bounds in payload and preserve resource exhaustion as VSTD UNKNOWN. | +| Computational grounding | VSTD-4 binds variables/clauses to facts, subjects, rules, policy/evidence roots, and verifier code. | SCITT can register such a payload but does not define those semantics. | SCITT can integrity-protect grounding artifacts. | Grounding correctness is distinctively VSTD here. | SCITT carries and makes the grounded certificate transparent; VSTD kernel checks it. | +| Reproduction | VSTD declares reproduction levels and executable falsification paths. | SCITT auditors reproduce registration checks from retained statements, collateral, policy, and trust anchors. | Both support independent replay. | They replay different decisions. | Report `VSTD_CHECK_REPLAY` and `SCITT_REGISTRATION_REPLAY` separately. | +| Independent checking | VSTD has a small checker isolated from verdict-producing code. | SCITT relying parties verify issuer signature and COSE Receipt offline; auditors check VDS consistency. | Both support independent verification. | The checked proposition differs. | Demonstrate both checkers in sequence, retaining both native results. | +| Counterexamples | VSTD FAIL can carry a counterexample or refutation certificate. | SCITT receipt invalidity can carry verification failure, but core does not define domain counterexamples. | Both can expose detected failure. | A bad inclusion proof is not a counterexample to payload truth. | Keep SCITT integrity failure and VSTD predicate refutation as typed failures. | +| PASS | Bounded proposition accepted with its required certificate/evidence. | Core SCITT has verified signature/receipt/registration, not a generic application `PASS`; the individual composite draft proposes profile `pass`. | Both can have successful checks. | The success domains are not equivalent. | Composed PASS requires native VSTD PASS and exact current SCITT verification; SCITT alone never creates it. | +| FAIL | Evidenced predicate violation or rejected certificate, depending on the VSTD result surface. | Signature, receipt, inclusion, policy, or profile verification can fail. | Both can detect concrete failures. | Failure reasons apply to different layers. | Preserve native reason codes and identify which layer failed. | +| UNKNOWN | Bounded inability to decide, with VSTD-4 indeterminacy evidence. | No core RFC application verdict; individual composite draft uses `unknown` for unavailable evidence or unrecognized profile and separates missing/stale/conflict. | Both reject guessing. | They are not semantically equivalent. | See the taxonomy below; map by reason, never by label alone. | +| Warnings | VSTD warnings cannot silently supply a missing layer or verdict. | Individual composite draft proposes `warning` when mandatory checks pass but a condition is surfaced. | Both can retain nonfatal findings. | A warning's acceptability is profile-specific. | Preserve warnings; do not map warning to VSTD PASS without full native VSTD verification. | +| Cost/work claims | VSTD binds/checks verification work and receipt size at VSTD-4. | SCITT proves VDS properties; its protocol latency/status does not prove application checking cost. | Receipts can carry cost claims as payload data. | SCITT has no generic proof of VSTD work. | Carry the VSTD bound and checker result as payload semantics. | +| Graph degradation | VSTD-Graph recomputes levels and blast radius without mutating history. | SCITT core preserves log history; individual graph draft proposes revocation/supersession/conflict checks. | Both favor additive history. | SCITT inclusion remains true even if a payload becomes disfavored; VSTD evidence ceiling may fall. | Keep historical inclusion true while lowering the current VSTD composition result. | +| Real-world truth vs evidence validity | VSTD explicitly limits arbitrary truth claims to its declared evidence and predicate. | RFC 9943 states registration only proves the statement was produced by an issuer; issuers may be false. | Strong agreement on non-upgrade. | VSTD additionally specifies a checkable bounded computational proposition. | This is the central composition boundary. | + +## UNKNOWN is not one shared enum + +| Condition | SCITT core / draft treatment | VSTD treatment | Composition | +|---|---|---|---| +| Evidence unavailable | Core receipt may remain historically valid; individual composite draft: `unknown` or `missing`. | `UNKNOWN/DEPENDENCY_UNAVAILABLE` or `ARTIFACT_UNRETRIEVABLE` when relevant. | UNKNOWN with both native reasons. | +| Incomplete bundle | Not a core RFC verdict; composite draft: `missing`. | UNKNOWN if required VSTD evidence is absent. | UNKNOWN, never PASS from registration alone. | +| Resource budget exhausted | SCRAPI 204/429 are protocol/operation states, not application truth. | `UNKNOWN/PROOF_BOUND_EXCEEDED` or `DEPTH_BOUND_EXCEEDED`. | Preserve VSTD UNKNOWN even if the statement is registered. | +| Predicate not established within the declared bound | SCITT core has no payload-domain undecidability result; the individual composite draft's `unknown` is profile/evidence-oriented. | A bounded VSTD check remains UNKNOWN with the native verifier reason; it is not proof that the predicate is globally undecidable. | Preserve the bounded inability to establish, without widening it into global undecidability or narrowing it into FAIL. | +| Unsupported verification method/profile | A relying party cannot verify; composite draft: `unknown` for unrecognized profile. | `UNSUPPORTED` or `UNKNOWN/VERIFIER_UNAVAILABLE`, depending on layer. | UNKNOWN or explicit UNSUPPORTED; no guess. | +| Conflicting evidence | RFC 9943 permits conflicting statements; relying-party selection is external. Composite draft: `conflict`. | `CONFLICTED` where applicable. | CONFLICTED, not generic UNKNOWN. | +| Stale evidence | Application policy; composite draft: `stale`. | `STALE` graph status or a bounded freshness failure. | Retain STALE and cap current composition. | +| Revoked ancestor/key | Key-compromise response is discussed; universal revocation strategy is out of scope. | Revoked ancestors lower graph level and expose blast radius. | Preserve historical inclusion while current VSTD evidence becomes UNKNOWN/FAIL under declared policy. | +| Failed proof | Invalid SCITT signature/receipt/inclusion is concrete integrity failure. | Invalid decision certificate or evidenced counterexample is FAIL/rejection. | FAIL at the failing layer, not UNKNOWN. | + +## What SCITT already does well + +- COSE Signed Statements and Receipt attachment. +- Protected issuer and subject coordinates. +- Registration policies and auditable policy history. +- Append-only, non-equivocating VDS requirements. +- Portable, offline-verifiable inclusion receipts. +- Registration/receipt APIs through the active SCRAPI WG draft. +- Multiple issuers, multiple TSs, and historical supersession without claiming + arbitrary payload truth. + +VSTD should reuse these mechanisms rather than define another signature envelope, +transparency log, receipt-attachment convention, or registration API. + +## Current overlap and the narrower VSTD contribution + +Several active **individual** SCITT drafts now address concerns that must not be +marketed as uniquely VSTD: narrow claim boundaries, validity versus sufficiency, +missing/stale/conflicted evidence, statement graphs, evidence bundles, selective +disclosure, canonical payload binding, coverage reconciliation, and typed +application-profile outcomes. They remain work in progress without WG adoption, but +their technical overlap is real. + +The narrower contribution demonstrated by the current VSTD implementation is not a +new domain prover. It is a standard domain language and operator/result layer over +orchestrated native verifier instances: + +- one domain-general claim coordinate for a computational predicate and parameters; +- an implemented `VSTD4-GDC-1` grounded decision certificate binding proof variables + and clauses to named facts, subjects, policy/evidence roots, verifier code, and + resource ceilings; +- a small independent checker that returns evidence-bearing PASS, FAIL, or bounded + UNKNOWN and refuses over-budget work before proof replay; +- a refutation surface and challenge-driven degradation integrated with the existing + VSTD object and graph axes; and +- an adapter that requires separately bound native VSTD and native SCITT verifier + results, so neither declared payload success nor registration can create PASS. + +These are implementation and composition distinctions, not a claim that nobody else +has proposed related semantics. + +## Positioning sentence + +> **SCITT can authenticate and make a VSTD receipt's registration transparently +> auditable; VSTD supplies the verification interlingua that preserves the bounded +> claim boundary and portable result semantics of the native verifier or proof engine +> that produced the result.** diff --git a/examples/scitt_interop/README.md b/examples/scitt_interop/README.md new file mode 100644 index 0000000..5f1b079 --- /dev/null +++ b/examples/scitt_interop/README.md @@ -0,0 +1,112 @@ +# VSTD/SCITT cryptographic interoperability example + +> **Experimental and non-normative.** This example creates real COSE signatures and +> an RFC9162 SHA-256 inclusion receipt in a local one-entry test log. +> It does not operate a production SCITT Transparency Service, publish to a public +> log, or demonstrate third-party monitoring. + +## What it proves + +The example executes this chain: + +```text +artifact bytes + -> grounded VSTD-4 digest predicate + -> independently checked VSTD PASS certificate + -> deterministic experimental VSTD/SCITT payload + -> RFC 9943-style EdDSA COSE Signed Statement + -> RFC 9942 / RFC9162_SHA256 signed inclusion receipt + -> independent statement and receipt verification + -> composed result preserving both native verdicts +``` + +It proves, under the emitted public keys and local test-log policy, that the exact +Signed Statement is authentic and included in the one-entry VDS, and that the exact +embedded VSTD certificate independently checks as PASS for the artifact digest +predicate. It does not prove artifact safety, production-service registration, +public witnessing, issuer authority outside the test, or arbitrary payload truth. + +## Identity and privacy boundary + +The VSTD receipt is produced and checkable before SCITT is applied. This example +then deliberately adds a fixed issuer, signature, subject, registration time, and +transparency-service coordinate because those are part of the selected SCITT +profile. It is therefore **not** a zero-identity or zero-knowledge example: the +payload is disclosed, and the issuer and statement can be correlated. SCITT is an +optional accountability wrapper here, not a prerequisite for VSTD verification. + +Before issuing the local receipt, the example policy verifies the statement +signature and requires the exact test issuer, VSTD subject, payload content type, +and experimental profile identifier. The policy identifier is retained in the +normalized SCITT observation. + +## Setup + +From the repository root: + +```bash +python -m pip install -e ".[scitt]" +``` + +The optional extra is pinned in `pyproject.toml`: + +- `scitt-cose==0.2.2` +- `cbor2==6.1.4` +- `cryptography==50.0.0` + +`scitt-cose` is an independent implementation, not an IETF publication or +endorsement. The normative wire references are [RFC 9943](https://datatracker.ietf.org/doc/html/rfc9943), [RFC 9942](https://datatracker.ietf.org/doc/html/rfc9942), RFC 9052/9053, and RFC 9162. + +## Produce and verify + +```bash +python examples/scitt_interop/demo.py produce +python examples/scitt_interop/demo.py verify +``` + +The producer writes a deterministic canonical VSTD payload plus real COSE artifacts +under `generated/`. Fresh ephemeral signing keys are generated on each production +run, so the public keys, signatures, and their hashes intentionally change. The +checked-in specimen remains deterministically verifiable, but producing a new +specimen is not byte-reproducible without externally managed fixed keys. The verifier reads +only those artifacts, the two public keys, the local artifact, and the documented +trust coordinates. No private key is written or committed. The ephemeral keys have +no authority outside this example. + +## Generated artifacts + +| File | Meaning | +|---|---| +| `vstd_receipt.json` | Native VSTD-4 receipt and grounded decision certificate. | +| `vstd_scitt_payload.json` | Canonical application payload bytes carried by SCITT. | +| `registration_template.json` | Human-readable normalized input; explicitly **not** COSE. | +| `signed_statement.cose` | Real COSE_Sign1 Signed Statement. | +| `receipt.cose` | Real signed RFC9162_SHA256 inclusion receipt. | +| `transparent_statement.cose` | Signed Statement with receipt attached at COSE header label 394. | +| `issuer_public.pem` | Public key for independent statement verification. | +| `log_public.pem` | Public key for independent receipt verification. | +| `verification_result.json` | Native VSTD result, native SCITT observation, composition, and hashes. | + +## Adversarial coverage + +`tests/test_scitt_interop.py` and `tests/test_scitt_crypto_example.py` cover: + +- deterministic serialization and round trips; +- identity, claim-coordinate, artifact, and payload binding; +- valid SCITT registration with VSTD FAIL or UNKNOWN; +- missing, stale, revoked, superseded, conflicted, and unsupported evidence; +- wrong issuer/subject and unaccepted policy coordinates; +- malformed payloads and version mismatches; +- corrupted COSE statement and receipt bytes; +- the invariant that SCITT-only evidence returns + `computational_verdict = NOT_EVALUATED`. +- the invariant that a composed PASS requires a native VSTD checker result bound to + the exact embedded receipt; +- the invariant that the native VSTD payload contains no SCITT issuer, transparency + service, registration policy, or registration time. + +Run: + +```bash +python -m pytest -q tests/test_scitt_interop.py tests/test_scitt_crypto_example.py +``` diff --git a/examples/scitt_interop/artifact.txt b/examples/scitt_interop/artifact.txt new file mode 100644 index 0000000..3518a08 --- /dev/null +++ b/examples/scitt_interop/artifact.txt @@ -0,0 +1 @@ +VSTD and SCITT compose without semantic upgrading. diff --git a/examples/scitt_interop/demo.py b/examples/scitt_interop/demo.py new file mode 100644 index 0000000..6328524 --- /dev/null +++ b/examples/scitt_interop/demo.py @@ -0,0 +1,487 @@ +"""Deterministic cryptographic VSTD/SCITT interoperability specimen. + +The optional ``scitt`` extra supplies COSE and RFC 9162 receipt primitives. A +one-entry local test log is used so the example is self-contained. This is a +real signed statement, signed inclusion receipt, and independent verification; +it is not a production Transparency Service, public anchoring, or endorsement. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from dataclasses import replace +from pathlib import Path +from typing import Any + +from verifier.core.certificate import ( + CertificateHeader, + ClaimBinding, + ClaimCoordinate, + ClauseGrounding, + CostTier, + DecisionBlock, + DecisionCertificate, + EncodingRule, + GroundedFact, + Grounding, + ResourceBounds, + VariableGrounding, + Verdict, + VerifierDescriptor, + canonical_bytes, + canonical_digest, + certificate_from_dict, +) +from verifier.core.kernel import KernelOutcome, check, reference_descriptor +from verifier.interoperability.scitt import ( + EXPERIMENTAL_CONTENT_TYPE, + EXPERIMENTAL_PROFILE, + ScittEvidenceState, + ScittVerificationEvidence, + VstdCoordinates, + VstdScittPayload, + VstdVerificationEvidence, + VstdVerificationState, + compose_results, + consume_scitt_evidence, + create_scitt_registration_template, +) + + +HERE = Path(__file__).resolve().parent +ARTIFACT = HERE / "artifact.txt" +ISSUER = "https://issuer.example/vstd-scitt-demo" +LOCAL_LOG = "urn:example:vstd-scitt-local-test-log" +POLICY = "urn:example:vstd-scitt-registration-policy:v1" + + +def _crypto(): + try: + import cbor2 + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric import ed25519 + from scitt_cose import ( + attach_receipts, + build_receipt, + build_signed_statement, + extract_receipts, + merkle_root, + parse_signed_statement, + sign_sign1, + verify_receipt, + ) + except ImportError as exc: # pragma: no cover - exercised in base environment + raise SystemExit( + "Install the pinned optional dependencies with: " + "python -m pip install -e '.[scitt]'" + ) from exc + return { + "cbor2": cbor2, + "serialization": serialization, + "ed25519": ed25519, + "attach_receipts": attach_receipts, + "build_receipt": build_receipt, + "build_signed_statement": build_signed_statement, + "extract_receipts": extract_receipts, + "merkle_root": merkle_root, + "parse_signed_statement": parse_signed_statement, + "sign_sign1": sign_sign1, + "verify_receipt": verify_receipt, + } + + +def _sha256(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def _public_key_pair(): + crypto = _crypto() + serialization = crypto["serialization"] + key = crypto["ed25519"].Ed25519PrivateKey.generate() + private_pem = key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + ) + public_pem = key.public_key().public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + return private_pem, public_pem + + +def _claim_binding_from_dict(value: dict[str, Any]) -> ClaimBinding: + """Reconstruct the exact receipt binding for independent kernel checking.""" + + coordinate = value["coordinate"] + bounds = value["bounds"] + verifier = value["verifier"] + return ClaimBinding( + claim=value["claim"], + coordinate=ClaimCoordinate( + coordinate["subject"], + coordinate["predicate"], + dict(coordinate["parameters"]), + ), + policy_root=value["policy_root"], + evidence_root=value["evidence_root"], + verifier=VerifierDescriptor( + specification_hash=verifier["specification_hash"], + implementation_hash=verifier["implementation_hash"], + parser_hash=verifier["parser_hash"], + certificate_format=verifier["certificate_format"], + format_fragment=verifier["format_fragment"], + dependencies=tuple(verifier["dependencies"]), + deterministic=verifier["deterministic"], + ), + bounds=ResourceBounds( + bounds["verification_cost_bound"], + bounds["memory_bound"], + bounds["certificate_size_bound"], + ), + prior_commitment=value["prior_commitment"], + ) + + +def _apply_local_registration_policy( + parsed: dict[str, Any], coordinates: VstdCoordinates +) -> None: + """Minimal explicit policy applied before the local log issues a receipt.""" + + if parsed.get("signature_verified") is not True: + raise RuntimeError("registration policy rejected an unverified statement") + if parsed.get("issuer") != ISSUER: + raise RuntimeError("registration policy rejected the issuer") + if parsed.get("subject") != coordinates.subject: + raise RuntimeError("registration policy rejected the subject") + if parsed.get("content_type") != EXPERIMENTAL_CONTENT_TYPE: + raise RuntimeError("registration policy rejected the payload content type") + if parsed.get("claims", {}).get("vstd_profile") != EXPERIMENTAL_PROFILE: + raise RuntimeError("registration policy rejected the VSTD profile") + + +def build_vstd_receipt() -> tuple[dict[str, Any], VstdCoordinates]: + artifact_digest = _sha256(ARTIFACT.read_bytes()) + subject = f"artifact:sha256:{artifact_digest}" + predicate = "content_digest_matches" + formula = ((1,),) + rule = EncodingRule("RULE:ASSERT_DIGEST_MATCH", ("artifact",), ((1, "artifact"),)) + grounding = Grounding( + variables=( + VariableGrounding( + 1, GroundedFact(subject, predicate, "MATCH") + ), + ), + clauses=( + ClauseGrounding(0, rule.rule_id, {"artifact": 1}, {"artifact": subject}), + ), + rules=(rule,), + ) + binding = ClaimBinding( + claim="the named artifact bytes have the declared SHA-256 digest", + coordinate=ClaimCoordinate( + subject, predicate, {"algorithm": "sha-256", "digest": artifact_digest} + ), + policy_root=canonical_digest( + {"algorithm": "sha-256", "predicate": predicate} + ), + evidence_root=artifact_digest, + verifier=reference_descriptor(), + bounds=ResourceBounds(100, 10, 20000), + ) + certificate = DecisionCertificate( + CertificateHeader( + Verdict.PASS, + CostTier.UP, + n_vars=1, + clause_count=1, + literal_count=1, + step_count=0, + binding=binding.digest(), + ), + formula, + grounding, + DecisionBlock(model={1: True}), + ) + result = check(certificate, budget=100, binding=binding) + if result.outcome is not KernelOutcome.ACCEPTED or result.verdict is not Verdict.PASS: + raise RuntimeError(f"VSTD kernel did not accept demo certificate: {result}") + + receipt = { + "schema_version": "VSTD-4", + "receipt_id": "VFY-4-scitt-interop-demo", + "claim_id": "SCITT-INTEROP-DEMO-DIGEST", + "binding": binding.to_dict(), + "vstd4_depth": 14, + "rung_evidence": { + f"4.{index}": f"decision_certificate:{certificate.digest()}#4.{index}" + for index in range(1, 15) + }, + "witness": certificate.to_dict(), + "ceiling_refutation": None, + "blocking_rungs": [], + "status": "VALID", + "refutation_surface": { + "admissible_refutations": [ + "artifact bytes hash to a value other than the bound digest", + "the VSTD decision certificate fails independent checking", + ], + "excluded_claims": [ + "artifact safety", + "issuer authorization", + "truth outside the bounded digest predicate", + ], + }, + } + receipt_digest = _sha256(canonical_bytes(receipt)) + coordinates = VstdCoordinates( + receipt_id=receipt["receipt_id"], + schema_version=receipt["schema_version"], + claim_id=receipt["claim_id"], + subject=subject, + predicate=predicate, + parameters={"algorithm": "sha-256", "digest": artifact_digest}, + native_result=result.verdict.value, + native_canonical_digest=receipt_digest, + evidence_bounds=binding.bounds.to_dict(), + artifact_digests={"primary": artifact_digest}, + provenance_references=("urn:example:vstd-scitt-demo:artifact",), + ) + return receipt, coordinates + + +def produce( + output: Path, *, vstd_binding_tamper: bool = False +) -> dict[str, Any]: + crypto = _crypto() + receipt, coordinates = build_vstd_receipt() + if vstd_binding_tamper: + receipt["witness"]["header"]["binding"] = "0" * 64 + coordinates = replace( + coordinates, + native_canonical_digest=_sha256(canonical_bytes(receipt)), + ) + template = create_scitt_registration_template( + receipt, coordinates, issuer=ISSUER, subject=coordinates.subject + ) + payload_bytes = template.payload.to_bytes() + + # Generate fresh, memory-only private keys. The public keys are emitted + # as explicit trust coordinates; private key material is never committed + # or written to the output directory. + issuer_private, issuer_public = _public_key_pair() + log_private, log_public = _public_key_pair() + issuer_kid = hashlib.sha256(issuer_public).digest() + log_kid = hashlib.sha256(log_public).digest() + statement = crypto["build_signed_statement"]( + payload_bytes, + alg="EdDSA", + private_key_pem=issuer_private, + issuer=ISSUER, + subject=coordinates.subject, + content_type=EXPERIMENTAL_CONTENT_TYPE, + extra_cwt_claims={"vstd_profile": EXPERIMENTAL_PROFILE}, + kid=issuer_kid, + ) + _apply_local_registration_policy( + crypto["parse_signed_statement"]( + statement, public_key_pem=issuer_public + ), + coordinates, + ) + tree_entries = [statement.hex()] + base_receipt = crypto["build_receipt"]( + leaf_entry_hex=statement.hex(), + leaf_index=0, + tree_entries_hex=tree_entries, + alg="EdDSA", + log_private_key_pem=log_private, + ) + # The generic RFC 9942 builder supplies the VDS proof. Re-sign the same + # detached root with RFC 9943's mandatory protected CWT issuer/subject + # claims so this specimen is also a SCITT Receipt, not only a COSE Receipt. + decoded_base = crypto["cbor2"].loads(base_receipt) + root = bytes.fromhex(crypto["merkle_root"](tree_entries)) + scitt_receipt = crypto["sign_sign1"]( + root, + alg="EdDSA", + private_key_pem=log_private, + protected={ + 4: log_kid, + 15: {1: LOCAL_LOG, 2: coordinates.subject}, + 395: 1, + }, + unprotected=decoded_base.value[1], + detached=True, + ) + transparent = crypto["attach_receipts"](statement, [scitt_receipt]) + + output.mkdir(parents=True, exist_ok=True) + (output / "vstd_receipt.json").write_text( + json.dumps(receipt, indent=2, sort_keys=True) + "\n", encoding="utf-8" + ) + (output / "vstd_scitt_payload.json").write_bytes(payload_bytes + b"\n") + (output / "registration_template.json").write_text( + json.dumps(template.to_dict(), indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + (output / "signed_statement.cose").write_bytes(statement) + (output / "receipt.cose").write_bytes(scitt_receipt) + (output / "transparent_statement.cose").write_bytes(transparent) + (output / "issuer_public.pem").write_bytes(issuer_public) + (output / "log_public.pem").write_bytes(log_public) + return verify(output) + + +def verify(output: Path, *, vstd_budget: int = 100) -> dict[str, Any]: + crypto = _crypto() + payload_bytes = (output / "vstd_scitt_payload.json").read_bytes().rstrip(b"\n") + payload = VstdScittPayload.from_bytes(payload_bytes) + statement = (output / "signed_statement.cose").read_bytes() + scitt_receipt = (output / "receipt.cose").read_bytes() + transparent = (output / "transparent_statement.cose").read_bytes() + issuer_public = (output / "issuer_public.pem").read_bytes() + log_public = (output / "log_public.pem").read_bytes() + + try: + parsed = crypto["parse_signed_statement"]( + statement, public_key_pem=issuer_public + ) + statement_structure = crypto["cbor2"].loads(statement) + statement_protected = crypto["cbor2"].loads(statement_structure.value[0]) + except Exception as exc: + raise RuntimeError("malformed SCITT Signed Statement") from exc + receipt_result = crypto["verify_receipt"]( + scitt_receipt, + leaf_entry_hex=statement.hex(), + log_public_key_pem=log_public, + ) + attached = crypto["extract_receipts"](transparent) + receipt_structure = crypto["cbor2"].loads(scitt_receipt) + receipt_protected = crypto["cbor2"].loads(receipt_structure.value[0]) + if parsed["signature_verified"] is not True: + raise RuntimeError("SCITT Signed Statement signature did not verify") + _apply_local_registration_policy(parsed, payload.coordinates) + if parsed["payload"] != payload_bytes: + raise RuntimeError("SCITT Signed Statement payload changed") + if parsed["issuer"] != ISSUER or parsed["subject"] != payload.coordinates.subject: + raise RuntimeError("SCITT Signed Statement identity coordinates changed") + if parsed["content_type"] != EXPERIMENTAL_CONTENT_TYPE: + raise RuntimeError("SCITT Signed Statement content type changed") + if statement_protected.get(4) != hashlib.sha256(issuer_public).digest(): + raise RuntimeError("SCITT Signed Statement key identifier changed") + if not receipt_result.ok: + raise RuntimeError(f"COSE Receipt failed: {receipt_result.errors}") + if receipt_protected.get(15) != { + 1: LOCAL_LOG, + 2: payload.coordinates.subject, + }: + raise RuntimeError("SCITT Receipt issuer/subject claims changed") + if receipt_protected.get(4) != hashlib.sha256(log_public).digest(): + raise RuntimeError("SCITT Receipt key identifier changed") + if attached != [scitt_receipt]: + raise RuntimeError("Transparent Statement did not preserve its receipt") + + native_receipt = json.loads((output / "vstd_receipt.json").read_text()) + certificate = certificate_from_dict(native_receipt["witness"]) + binding = _claim_binding_from_dict(native_receipt["binding"]) + vstd_result = check(certificate, budget=vstd_budget, binding=binding) + if vstd_result.outcome is KernelOutcome.ACCEPTED: + vstd_state = VstdVerificationState.VERIFIED + if vstd_result.verdict is None: + raise RuntimeError("independent VSTD checker returned no native verdict") + native_vstd_result = vstd_result.verdict.value + elif vstd_result.outcome is KernelOutcome.REFUSED: + vstd_state = VstdVerificationState.INDETERMINATE + native_vstd_result = "UNKNOWN" + else: + vstd_state = VstdVerificationState.REJECTED + native_vstd_result = "REJECTED" + + vstd_observation = VstdVerificationEvidence( + state=vstd_state, + receipt_sha256=_sha256(canonical_bytes(native_receipt)), + native_result=native_vstd_result, + checker="verifier.core.kernel.check", + verification_profile="VSTD4-GDC-1/reference-kernel", + reason=vstd_result.details, + ) + + observation = ScittVerificationEvidence( + state=ScittEvidenceState.REGISTERED, + statement_sha256=_sha256(statement), + payload_sha256=_sha256(payload_bytes), + issuer=parsed["issuer"], + subject=parsed["subject"], + signed_statement_verified=True, + receipt_verified=True, + verification_profile="RFC9943+RFC9942/RFC9162_SHA256", + registration_policy=POLICY, + transparency_service=LOCAL_LOG, + vds="RFC9162_SHA256", + native_result="SIGNED_STATEMENT_AND_INCLUSION_RECEIPT_VERIFIED", + reason=( + "local one-entry test log; cryptographic inclusion verified, " + "without public anchoring or production-service claims" + ), + registered_at="2026-08-23T00:00:00Z", + ) + composition = compose_results( + payload, + vstd_observation, + observation, + artifact_digests={"primary": _sha256(ARTIFACT.read_bytes())}, + accepted_issuers=[ISSUER], + ) + expected_composition = { + KernelOutcome.ACCEPTED: "PASS", + KernelOutcome.REFUSED: "UNKNOWN", + KernelOutcome.REJECTED: "FAIL", + }[vstd_result.outcome] + if composition.status.value != expected_composition: + raise RuntimeError(f"composition failed: {composition}") + + scitt_as_vstd_evidence = consume_scitt_evidence( + observation, + expected_payload_sha256=payload.payload_sha256(), + expected_subject=payload.coordinates.subject, + accepted_issuers=[ISSUER], + ) + + result = { + "vstd_kernel": vstd_result.to_dict(), + "vstd_observation": vstd_observation.to_dict(), + "scitt_observation": observation.to_dict(), + "scitt_as_vstd_evidence": scitt_as_vstd_evidence, + "composition": composition.to_dict(), + "artifact_sha256": _sha256(ARTIFACT.read_bytes()), + "payload_sha256": _sha256(payload_bytes), + "statement_sha256": _sha256(statement), + "receipt_sha256": _sha256(scitt_receipt), + "transparent_statement_sha256": _sha256(transparent), + } + (output / "verification_result.json").write_text( + json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8" + ) + return result + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("command", choices=("produce", "verify")) + parser.add_argument("--output", type=Path, default=HERE / "generated") + parser.add_argument("--vstd-budget", type=int, default=100) + args = parser.parse_args() + result = ( + produce(args.output) + if args.command == "produce" + else verify(args.output, vstd_budget=args.vstd_budget) + ) + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/examples/scitt_interop/generated/issuer_public.pem b/examples/scitt_interop/generated/issuer_public.pem new file mode 100644 index 0000000..e862c68 --- /dev/null +++ b/examples/scitt_interop/generated/issuer_public.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA0EqyMnQrtKs6E2i9RhXk5tAiSrcaAWuvhSCjMsl3hzc= +-----END PUBLIC KEY----- diff --git a/examples/scitt_interop/generated/log_public.pem b/examples/scitt_interop/generated/log_public.pem new file mode 100644 index 0000000..44fda8b --- /dev/null +++ b/examples/scitt_interop/generated/log_public.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAoJql9HpnWYAv+VX43C0qFKXJnSO+l/hkEn/5ODRVpPA= +-----END PUBLIC KEY----- diff --git a/examples/scitt_interop/generated/receipt.cose b/examples/scitt_interop/generated/receipt.cose new file mode 100644 index 0000000000000000000000000000000000000000..f3a8d5193f58de56d124a1855b342f0e8f7b0a8d GIT binary patch literal 250 zcmVN+6vk$f++*wcGm6Ep%mbbS-ReX99RoVRCe7W?^G=I&)}Y zGBq|jGdW{4G%`6jIASz1HDNJjG-G2oV>V)9G%;i}W@0lnFf=w~WnnfkVKri9Gh<|C zGdW>3I5J`|H8f>mWivD}83Bs{0Vkmu0gRy_fkcA=0D$&bK*4KBzu4Y@U2&>Rvw4hD zm%kmLz$uAEF!k$LwB)W7`>Djua2CI@%<}YrAzd}Oa!@Ln_zA!&CWxhb71{Jb07sx@ AjQ{`u literal 0 HcmV?d00001 diff --git a/examples/scitt_interop/generated/registration_template.json b/examples/scitt_interop/generated/registration_template.json new file mode 100644 index 0000000..3a8f383 --- /dev/null +++ b/examples/scitt_interop/generated/registration_template.json @@ -0,0 +1,174 @@ +{ + "payload": { + "mapping_version": "0.1", + "profile": "vstd-scitt-interop-experimental-0.1", + "receipt_media_type": "application/vnd.verifier.vstd-receipt+json", + "receipt_sha256": "10dace7e0fc4bbea53ee7e26965ae1e7b2f4b11e15e64b6d83ccf96742adbf41", + "vstd_coordinates": { + "artifact_digests": { + "primary": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341" + }, + "claim_coordinate": { + "parameters": { + "algorithm": "sha-256", + "digest": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341" + }, + "predicate": "content_digest_matches", + "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341" + }, + "claim_id": "SCITT-INTEROP-DEMO-DIGEST", + "evidence_bounds": { + "certificate_size_bound": 20000, + "memory_bound": 10, + "verification_cost_bound": 100 + }, + "native_canonical_digest": "10dace7e0fc4bbea53ee7e26965ae1e7b2f4b11e15e64b6d83ccf96742adbf41", + "native_result": "PASS", + "provenance_references": [ + "urn:example:vstd-scitt-demo:artifact" + ], + "receipt_id": "VFY-4-scitt-interop-demo", + "schema_version": "VSTD-4" + }, + "vstd_receipt": { + "binding": { + "bounds": { + "certificate_size_bound": 20000, + "memory_bound": 10, + "verification_cost_bound": 100 + }, + "claim": "the named artifact bytes have the declared SHA-256 digest", + "coordinate": { + "parameters": { + "algorithm": "sha-256", + "digest": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341" + }, + "predicate": "content_digest_matches", + "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341" + }, + "evidence_root": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341", + "policy_root": "418c69bf2c7e119d75936d599903f860acaf5d3689817ae4ab9881d4659e6b2a", + "prior_commitment": "", + "verifier": { + "certificate_format": "VSTD4-GDC-1", + "dependencies": [ + "python-stdlib" + ], + "deterministic": true, + "format_fragment": "UP,WIDTH-K,RES", + "implementation_hash": "sha256:49afe96d327f99b12a518529d47b1b68c16228c4688be14eca279ea3e5b14d5b", + "parser_hash": "sha256:a49fc58d15ec1b925b4e2bd48f5dfb350d9e9fca28044c5a0fa9ff051dc70c01", + "specification_hash": "sha256:f2420f7826531ffdfadb1fcb9c0d3317d330d47a1cc367328ebd1c6921af36eb" + } + }, + "blocking_rungs": [], + "ceiling_refutation": null, + "claim_id": "SCITT-INTEROP-DEMO-DIGEST", + "receipt_id": "VFY-4-scitt-interop-demo", + "refutation_surface": { + "admissible_refutations": [ + "artifact bytes hash to a value other than the bound digest", + "the VSTD decision certificate fails independent checking" + ], + "excluded_claims": [ + "artifact safety", + "issuer authorization", + "truth outside the bounded digest predicate" + ] + }, + "rung_evidence": { + "4.1": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.1", + "4.10": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.10", + "4.11": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.11", + "4.12": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.12", + "4.13": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.13", + "4.14": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.14", + "4.2": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.2", + "4.3": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.3", + "4.4": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.4", + "4.5": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.5", + "4.6": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.6", + "4.7": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.7", + "4.8": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.8", + "4.9": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.9" + }, + "schema_version": "VSTD-4", + "status": "VALID", + "vstd4_depth": 14, + "witness": { + "decision": { + "model": { + "1": true + }, + "propagation": null, + "resolution": null, + "transcript": null + }, + "formula": [ + [ + 1 + ] + ], + "grounding": { + "clauses": [ + { + "bindings": { + "artifact": 1 + }, + "clause_index": 0, + "rule_id": "RULE:ASSERT_DIGEST_MATCH", + "subjects": { + "artifact": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341" + } + } + ], + "rules": [ + { + "roles": [ + "artifact" + ], + "rule_id": "RULE:ASSERT_DIGEST_MATCH", + "template": [ + [ + 1, + "artifact" + ] + ] + } + ], + "variables": [ + { + "fact": { + "predicate": "content_digest_matches", + "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341", + "value": "MATCH" + }, + "var": 1 + } + ] + }, + "header": { + "binding": "0a544c944e0ecd14a40b74bacd3ffb3a443a978780e554c28cf9be0e657fda10", + "clause_count": 1, + "format": "VSTD4-GDC-1", + "literal_count": 1, + "n_vars": 1, + "step_count": 0, + "tier": "UP", + "verdict": "PASS", + "width": 0 + }, + "hints": {} + } + } + }, + "payload_sha256": "38a21c5d1a5aa9feb99626d7631a626d5b140f39486117a9954afb54ae2fb661", + "representation": "normalized-registration-input-not-cose", + "required_protected_header_projection": { + "content_type": "application/vnd.verifier.vstd-receipt+json", + "issuer": "https://issuer.example/vstd-scitt-demo", + "payload_hash_algorithm": "sha-256", + "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341", + "type": "vstd-scitt-interop-experimental-0.1" + } +} diff --git a/examples/scitt_interop/generated/signed_statement.cose b/examples/scitt_interop/generated/signed_statement.cose new file mode 100644 index 0000000000000000000000000000000000000000..2e070019bf6345516d0a081f4e9bf7b7061b7928 GIT binary patch literal 5062 zcmd^DO^78|6?RMzTnz>n;zBAFOfbHhsatjH?`4yAx--+5IPG-Lm|{yYJ=umXET!F0^1qSLMY;DK9Lb(Mm&eVbL1tH6TFO=F88t zReAmw=avu8jH%n{#f!RaJ6r@yHm?h~*e`B{HpY`MuRgilV~x>@2{UbHEQsQ1mkPU@>74hfqirW-Lkr?ne|Dgp?H4L|!+Q(giFZI%80m z2AZn&U|FN@b0`ffJpTf@|7qtt|N8CuAN(qMZU67y`PZMTzW3%UKM%Hk`TG|>cf#LU zq`&_9xkr9>^MU1wJ4Y0Bd6Z-DEdt?AU3}l2xT87zeHZp{{Rme+2dP2_Avp8ZvAmM?coDd)p#FWP}4TVryoX~(tuBeToM^02# zBX!9PwC?o8U8N_N`UqOn->VTl)*PeJkDDy_&cqc3)AM|7)>^Gu!{&hL8hj{@sz#gH z94%tJ9+nAuJ2-TYoTwYj1Xj1rgQ&1jN;5{B&zTW31Y_d19e)Od!6hqH?p4vQanKUC z-@mkbaNzBJ?O^BH)je-}=c%jS_U`4K{R4CY7Ft3nAm>$A${rs9wugH6^H$#;AGy=O z$G?d?$3|<8hxP81;GLm&hh|3PTeE)XV@5FfdI7m$WmV#?#fSttb`Oed+`NIdD=d_I zTl@PM7Ix4AN@fFVKtW?e#NckZT~kg+_L!b&F3TR%RT|vGbxZXC-gx4sN4?NOV7zW&G7v{lG;iJfrNUv3QSzWfmfGXtl@qLzZDB~z$C|PloLCgczm$>F(Cf;4? zsLkBjMpjjW%!6xeeNyl8_9f49w1gT;O8|OkkNVims?tNvD>S!#AT4dqb*WpUg*!D( z2NQSLELROX8ZBSnn|xz;`{0WA)ycIT+ko2Yb1#Zk;`146XJ~vBWtuSsSuDdKQCY@G zz#@`HK_+R!36E1j;vh%`jZwCNP!KFgGGHM@oKP8Y8$D}UXx_IkqnQ#>Dp8aK;aL!I z3IUfiMJ#)TR_?x#6W%@bXx^9@KurqaTF4zq(Z49 zO7KkhG7L$A|9lLA5h21j34;{4B$(!aFcrqY-8;w%yr{&ttmrpgd1NUGLB?UH=x>4Q zOg|I3)3Pgym70Adi?0TNk76t2!qGt9b`8!+C{QTMa*lJA=0#7IYW z@D>h=GuRXhe{ecKE*bw6~CL&_ZU%;}4sh@d;{Rhn(-;*m`nz8@;iU zG|f>d3=R>5PTXg;DWSzBC#O7ZujiG7!d@Wr*+~Vh;6b2fNAJ;8JS$X1XUCG2%9@6i zt!VIz!*B=Rvm;JdFx2N;+a z^@pIlao7Nkdi_xsp_-g||Ik`kutqbSECws~%Z$y>T7myJ=VR*^4lr0^1MHYBbkwHJ zP6RVx67bh)a&eu{A{-erO2G#q31!sh3FS=4P~m9FC=FSbq)FIs&V!B1xB-Ss`&C4gUA`tJ$j zA^2G>ZNu0z_zaJ&wxsUh-_wu(=#ftxKKh?m&VTWV+s}RB{#(EK??2mreB1l}-~RB@ zSN{0oOAq|@^WXgJW94ISTz!7=??1ix_NRaM=byZM`K?zU{LGKV?2Rw|@TZ@87uS_k Ax&QzG literal 0 HcmV?d00001 diff --git a/examples/scitt_interop/generated/transparent_statement.cose b/examples/scitt_interop/generated/transparent_statement.cose new file mode 100644 index 0000000000000000000000000000000000000000..7dd4306c886cfde386bc2068d27a1f75ea7e680f GIT binary patch literal 5318 zcmd^DU5FiL8P3`w^lC9gv4S$4(w4?^lKJ`O?-ZnKcAEqfvt~DOLs*CJ=l%A4?VOq8 z{G6W664=`MS9>A;5K>W$jTS2wi@h)+q{Sj$*qdI|28tJ=B2>N5TG}e;`_0VR-L`}l znu8ba#WH8+oA3R5-sgS3dG3KzGuK_bZoHQchl%2}R9U)zl!|>L$dy!(?;91R=;pvd z4a<+-U1aI(BQxV$`(-&Sdi(dQqNs2YjOk#Q!2Wu1!Btsy1q`zHj2CHMDoJ_SEBe&& z{GJ{BzhUAS@P!TVNTf9`>mM=#J3lrz4c4q-+2F(m*;k!+CAPd2Z#cduEkjV0)b%mOw6Cz zG23+G?;UYoE#J7lg-bjDp=eyL4*_3&=Lp-eLxUQ8+qMMd5P&5)VGO9}0{(LR$oDAN z5HLp)X4_zU;1lMH(B)i4en1>5m?S!i8aX`6a-mX+^)Y)J<^-OxP-|GR`rZ)HL-Tbs z`fkB$p6!@Cp=uCso7Gl_G^YbVxHdi{Ygw+!{s1jvyj^S)^tN&68aXk{u@czbx(+jsNc{<#9|KUjt)M#v^?J>x&k%++t$=5WGcl@ zSgS9mmh2k-n1`|tMv9;p#w03(tz#1!eL4a|KNWy$5vRt|p*h_HM%w|{LR&ri|JpoT zU6E&*M%B9}CPZY0^L{;UHzan*{fJ422VmQg2)xMkg%?GU<;u{vD5ugBt{+CB9Z(>Y z;Rwf(?RydU%%L^JRhDDB4F;;z(-+;uyAvIuE4QXm!+(9f%MO;Zvk8O2^w-vU{cwI(!sX_X|CY4)}(J`n(K$5zON zt%10xa@2`9pl~RQ0qRP{lA0{r1vOi~g*%GAQDz1;Ml`9w$dE^JBoms}j8cz~Z5q)e zqtT|BMrnOx>;R-8sY(h1DX~RhX&{r z3*i(kgtdieiWVX*kFTbj@d?hr3OV0AId|mnJbI&*G>LIilqe!>(lPHDoGPb(F)f(L;iU3GXz~XU)}?q52T?P8==)rCxp1#Z+~6yuYd~ zjA*VXDvQR7{xYTOvps?TH|JyR7YZ1JSd(_F7CNfSrWHXSr~v%6Ra{(WQ4b|!Lq2$J6 z^7g_sM(;3#_)by6aPkBpRpKW@^zOu>?h?Q+E%o<=_7Hrp61rjZ8N83jRt>4M__uQB zH*eduy7%=bKS3A`PsuC|Hb)-cfatV&+NG)z2o^~Ul_gi e^!b0>@RMi1eevMmetFaT&hh^9w}0b1@BJ@_WxT!s literal 0 HcmV?d00001 diff --git a/examples/scitt_interop/generated/verification_result.json b/examples/scitt_interop/generated/verification_result.json new file mode 100644 index 0000000..05c2083 --- /dev/null +++ b/examples/scitt_interop/generated/verification_result.json @@ -0,0 +1,67 @@ +{ + "artifact_sha256": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341", + "composition": { + "native_scitt_result": "SIGNED_STATEMENT_AND_INCLUSION_RECEIPT_VERIFIED", + "native_vstd_result": "PASS", + "reason": "native VSTD PASS and exact current SCITT registration both verified", + "scitt_statement_sha256": "bf054204c40f1273c0abb260b0cdc91977585214ccaf59f937e7ecc21a4754dd", + "status": "PASS", + "vstd_receipt_sha256": "10dace7e0fc4bbea53ee7e26965ae1e7b2f4b11e15e64b6d83ccf96742adbf41" + }, + "payload_sha256": "38a21c5d1a5aa9feb99626d7631a626d5b140f39486117a9954afb54ae2fb661", + "receipt_sha256": "8e6c0d446c9c9bf52b5c0b05fda350bf192747d02f8f84973a8010da8fed12d6", + "scitt_as_vstd_evidence": { + "computational_verdict": "NOT_EVALUATED", + "evidence_kind": "SCITT_TRANSPARENCY", + "native_scitt_result": "SIGNED_STATEMENT_AND_INCLUSION_RECEIPT_VERIFIED", + "normalized_state": "REGISTERED", + "payload_sha256": "38a21c5d1a5aa9feb99626d7631a626d5b140f39486117a9954afb54ae2fb661", + "reason": "local one-entry test log; cryptographic inclusion verified, without public anchoring or production-service claims", + "registered_at": "2026-08-23T00:00:00Z", + "statement_sha256": "bf054204c40f1273c0abb260b0cdc91977585214ccaf59f937e7ecc21a4754dd", + "trust_coordinates": { + "accepted_issuers": [ + "https://issuer.example/vstd-scitt-demo" + ], + "registration_policy": "urn:example:vstd-scitt-registration-policy:v1", + "transparency_service": "urn:example:vstd-scitt-local-test-log", + "vds": "RFC9162_SHA256", + "verification_profile": "RFC9943+RFC9942/RFC9162_SHA256" + } + }, + "scitt_observation": { + "issuer": "https://issuer.example/vstd-scitt-demo", + "native_result": "SIGNED_STATEMENT_AND_INCLUSION_RECEIPT_VERIFIED", + "payload_sha256": "38a21c5d1a5aa9feb99626d7631a626d5b140f39486117a9954afb54ae2fb661", + "reason": "local one-entry test log; cryptographic inclusion verified, without public anchoring or production-service claims", + "receipt_verified": true, + "registered_at": "2026-08-23T00:00:00Z", + "registration_policy": "urn:example:vstd-scitt-registration-policy:v1", + "signed_statement_verified": true, + "state": "REGISTERED", + "statement_sha256": "bf054204c40f1273c0abb260b0cdc91977585214ccaf59f937e7ecc21a4754dd", + "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341", + "transparency_service": "urn:example:vstd-scitt-local-test-log", + "vds": "RFC9162_SHA256", + "verification_profile": "RFC9943+RFC9942/RFC9162_SHA256" + }, + "statement_sha256": "bf054204c40f1273c0abb260b0cdc91977585214ccaf59f937e7ecc21a4754dd", + "transparent_statement_sha256": "ab52c3d383df55ea8d00d19231f6fb356a4e8a14621b8ff3b4ab000bf08ae100", + "vstd_kernel": { + "details": "model satisfies all 1 grounded clauses", + "hints_present": false, + "literals_processed": 1, + "outcome": "ACCEPTED", + "reason": null, + "steps_checked": 0, + "verdict": "PASS" + }, + "vstd_observation": { + "checker": "verifier.core.kernel.check", + "native_result": "PASS", + "reason": "model satisfies all 1 grounded clauses", + "receipt_sha256": "10dace7e0fc4bbea53ee7e26965ae1e7b2f4b11e15e64b6d83ccf96742adbf41", + "state": "VERIFIED", + "verification_profile": "VSTD4-GDC-1/reference-kernel" + } +} diff --git a/examples/scitt_interop/generated/vstd_receipt.json b/examples/scitt_interop/generated/vstd_receipt.json new file mode 100644 index 0000000..4c1817c --- /dev/null +++ b/examples/scitt_interop/generated/vstd_receipt.json @@ -0,0 +1,131 @@ +{ + "binding": { + "bounds": { + "certificate_size_bound": 20000, + "memory_bound": 10, + "verification_cost_bound": 100 + }, + "claim": "the named artifact bytes have the declared SHA-256 digest", + "coordinate": { + "parameters": { + "algorithm": "sha-256", + "digest": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341" + }, + "predicate": "content_digest_matches", + "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341" + }, + "evidence_root": "39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341", + "policy_root": "418c69bf2c7e119d75936d599903f860acaf5d3689817ae4ab9881d4659e6b2a", + "prior_commitment": "", + "verifier": { + "certificate_format": "VSTD4-GDC-1", + "dependencies": [ + "python-stdlib" + ], + "deterministic": true, + "format_fragment": "UP,WIDTH-K,RES", + "implementation_hash": "sha256:49afe96d327f99b12a518529d47b1b68c16228c4688be14eca279ea3e5b14d5b", + "parser_hash": "sha256:a49fc58d15ec1b925b4e2bd48f5dfb350d9e9fca28044c5a0fa9ff051dc70c01", + "specification_hash": "sha256:f2420f7826531ffdfadb1fcb9c0d3317d330d47a1cc367328ebd1c6921af36eb" + } + }, + "blocking_rungs": [], + "ceiling_refutation": null, + "claim_id": "SCITT-INTEROP-DEMO-DIGEST", + "receipt_id": "VFY-4-scitt-interop-demo", + "refutation_surface": { + "admissible_refutations": [ + "artifact bytes hash to a value other than the bound digest", + "the VSTD decision certificate fails independent checking" + ], + "excluded_claims": [ + "artifact safety", + "issuer authorization", + "truth outside the bounded digest predicate" + ] + }, + "rung_evidence": { + "4.1": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.1", + "4.10": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.10", + "4.11": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.11", + "4.12": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.12", + "4.13": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.13", + "4.14": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.14", + "4.2": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.2", + "4.3": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.3", + "4.4": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.4", + "4.5": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.5", + "4.6": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.6", + "4.7": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.7", + "4.8": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.8", + "4.9": "decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.9" + }, + "schema_version": "VSTD-4", + "status": "VALID", + "vstd4_depth": 14, + "witness": { + "decision": { + "model": { + "1": true + }, + "propagation": null, + "resolution": null, + "transcript": null + }, + "formula": [ + [ + 1 + ] + ], + "grounding": { + "clauses": [ + { + "bindings": { + "artifact": 1 + }, + "clause_index": 0, + "rule_id": "RULE:ASSERT_DIGEST_MATCH", + "subjects": { + "artifact": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341" + } + } + ], + "rules": [ + { + "roles": [ + "artifact" + ], + "rule_id": "RULE:ASSERT_DIGEST_MATCH", + "template": [ + [ + 1, + "artifact" + ] + ] + } + ], + "variables": [ + { + "fact": { + "predicate": "content_digest_matches", + "subject": "artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341", + "value": "MATCH" + }, + "var": 1 + } + ] + }, + "header": { + "binding": "0a544c944e0ecd14a40b74bacd3ffb3a443a978780e554c28cf9be0e657fda10", + "clause_count": 1, + "format": "VSTD4-GDC-1", + "literal_count": 1, + "n_vars": 1, + "step_count": 0, + "tier": "UP", + "verdict": "PASS", + "width": 0 + }, + "hints": {} + } +} diff --git a/examples/scitt_interop/generated/vstd_scitt_payload.json b/examples/scitt_interop/generated/vstd_scitt_payload.json new file mode 100644 index 0000000..023e0a6 --- /dev/null +++ b/examples/scitt_interop/generated/vstd_scitt_payload.json @@ -0,0 +1 @@ +{"mapping_version":"0.1","profile":"vstd-scitt-interop-experimental-0.1","receipt_media_type":"application/vnd.verifier.vstd-receipt+json","receipt_sha256":"10dace7e0fc4bbea53ee7e26965ae1e7b2f4b11e15e64b6d83ccf96742adbf41","vstd_coordinates":{"artifact_digests":{"primary":"39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"claim_coordinate":{"parameters":{"algorithm":"sha-256","digest":"39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"predicate":"content_digest_matches","subject":"artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"claim_id":"SCITT-INTEROP-DEMO-DIGEST","evidence_bounds":{"certificate_size_bound":20000,"memory_bound":10,"verification_cost_bound":100},"native_canonical_digest":"10dace7e0fc4bbea53ee7e26965ae1e7b2f4b11e15e64b6d83ccf96742adbf41","native_result":"PASS","provenance_references":["urn:example:vstd-scitt-demo:artifact"],"receipt_id":"VFY-4-scitt-interop-demo","schema_version":"VSTD-4"},"vstd_receipt":{"binding":{"bounds":{"certificate_size_bound":20000,"memory_bound":10,"verification_cost_bound":100},"claim":"the named artifact bytes have the declared SHA-256 digest","coordinate":{"parameters":{"algorithm":"sha-256","digest":"39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"predicate":"content_digest_matches","subject":"artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"},"evidence_root":"39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341","policy_root":"418c69bf2c7e119d75936d599903f860acaf5d3689817ae4ab9881d4659e6b2a","prior_commitment":"","verifier":{"certificate_format":"VSTD4-GDC-1","dependencies":["python-stdlib"],"deterministic":true,"format_fragment":"UP,WIDTH-K,RES","implementation_hash":"sha256:49afe96d327f99b12a518529d47b1b68c16228c4688be14eca279ea3e5b14d5b","parser_hash":"sha256:a49fc58d15ec1b925b4e2bd48f5dfb350d9e9fca28044c5a0fa9ff051dc70c01","specification_hash":"sha256:f2420f7826531ffdfadb1fcb9c0d3317d330d47a1cc367328ebd1c6921af36eb"}},"blocking_rungs":[],"ceiling_refutation":null,"claim_id":"SCITT-INTEROP-DEMO-DIGEST","receipt_id":"VFY-4-scitt-interop-demo","refutation_surface":{"admissible_refutations":["artifact bytes hash to a value other than the bound digest","the VSTD decision certificate fails independent checking"],"excluded_claims":["artifact safety","issuer authorization","truth outside the bounded digest predicate"]},"rung_evidence":{"4.1":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.1","4.10":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.10","4.11":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.11","4.12":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.12","4.13":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.13","4.14":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.14","4.2":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.2","4.3":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.3","4.4":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.4","4.5":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.5","4.6":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.6","4.7":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.7","4.8":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.8","4.9":"decision_certificate:11b5f590075cfed4b999419d1b8dbe9d5bb6734b5617f8228d8e557dafbb89e2#4.9"},"schema_version":"VSTD-4","status":"VALID","vstd4_depth":14,"witness":{"decision":{"model":{"1":true},"propagation":null,"resolution":null,"transcript":null},"formula":[[1]],"grounding":{"clauses":[{"bindings":{"artifact":1},"clause_index":0,"rule_id":"RULE:ASSERT_DIGEST_MATCH","subjects":{"artifact":"artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341"}}],"rules":[{"roles":["artifact"],"rule_id":"RULE:ASSERT_DIGEST_MATCH","template":[[1,"artifact"]]}],"variables":[{"fact":{"predicate":"content_digest_matches","subject":"artifact:sha256:39c442988b425a1e4cc7c6bb41d4fb35046dea61a5be3cdf39a582b054eae341","value":"MATCH"},"var":1}]},"header":{"binding":"0a544c944e0ecd14a40b74bacd3ffb3a443a978780e554c28cf9be0e657fda10","clause_count":1,"format":"VSTD4-GDC-1","literal_count":1,"n_vars":1,"step_count":0,"tier":"UP","verdict":"PASS","width":0},"hints":{}}}} diff --git a/pyproject.toml b/pyproject.toml index bca87c6..3274fcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,11 @@ yaml = ["pyyaml>=6.0"] llguidance = ["llguidance==1.8.0"] torch = ["torch>=2.2"] jsonschema = ["jsonschema>=4.18"] +scitt = [ + "scitt-cose==0.2.2", + "cbor2==6.1.4", + "cryptography==50.0.0", +] test = ["pytest>=8.0", "pyyaml>=6.0", "jsonschema>=4.18"] release = ["build==1.5.0", "twine==7.0.0"] @@ -54,3 +59,4 @@ verifier = ["hardware/*.json", "specifications/*.md"] [tool.pytest.ini_options] testpaths = ["tests"] norecursedirs = ["artifacts_tmp", "build", "dist", ".git", ".venv"] +pythonpath = ["src"] diff --git a/src/verifier/interoperability/__init__.py b/src/verifier/interoperability/__init__.py new file mode 100644 index 0000000..6da7ccd --- /dev/null +++ b/src/verifier/interoperability/__init__.py @@ -0,0 +1 @@ +"""Experimental adapters to adjacent verification and transparency standards.""" diff --git a/src/verifier/interoperability/scitt/__init__.py b/src/verifier/interoperability/scitt/__init__.py new file mode 100644 index 0000000..7e81812 --- /dev/null +++ b/src/verifier/interoperability/scitt/__init__.py @@ -0,0 +1,45 @@ +"""Experimental, non-normative VSTD/SCITT interoperability surface. + +This package does not implement COSE or a SCITT Transparency Service. It +defines the application payload carried by a SCITT Signed Statement and the +strict boundary at which a native SCITT verifier's result can become bounded +VSTD evidence. +""" + +from .adapter import ( + EXPERIMENTAL_CONTENT_TYPE, + EXPERIMENTAL_PROFILE, + MAPPING_VERSION, + CompositionResult, + CompositionStatus, + InteropError, + ScittEvidenceState, + ScittRegistrationTemplate, + ScittVerificationEvidence, + VstdCoordinates, + VstdVerificationEvidence, + VstdVerificationState, + VstdScittPayload, + compose_results, + consume_scitt_evidence, + create_scitt_registration_template, +) + +__all__ = [ + "EXPERIMENTAL_CONTENT_TYPE", + "EXPERIMENTAL_PROFILE", + "MAPPING_VERSION", + "CompositionResult", + "CompositionStatus", + "InteropError", + "ScittEvidenceState", + "ScittRegistrationTemplate", + "ScittVerificationEvidence", + "VstdCoordinates", + "VstdVerificationEvidence", + "VstdVerificationState", + "VstdScittPayload", + "compose_results", + "consume_scitt_evidence", + "create_scitt_registration_template", +] diff --git a/src/verifier/interoperability/scitt/adapter.py b/src/verifier/interoperability/scitt/adapter.py new file mode 100644 index 0000000..28384a0 --- /dev/null +++ b/src/verifier/interoperability/scitt/adapter.py @@ -0,0 +1,793 @@ +"""Strict experimental mapping between VSTD's interlingua and IETF SCITT. + +The emitted registration template is a deterministic *input* to a native +SCITT/COSE implementation. It is not CBOR, COSE_Sign1, a signature, a COSE +Receipt, or proof that a Transparency Service registered anything. Likewise, +the reverse adapter accepts only the normalized output of an external SCITT +verifier. It never verifies COSE itself. + +VSTD does not replace SCITT or the payload's native verifier. It provides the +portable claim/result language through which those orchestrated substrates are +composed while their native semantics remain visible. + +The central invariant is monotonicity of epistemic strength: registration or +receipt integrity cannot manufacture a VSTD computational verdict. A composed +PASS requires both a native VSTD PASS and a current, verified SCITT registration +for the exact payload. Every other state is preserved or lowers the result. +""" + +from __future__ import annotations + +import hashlib +import json +import re +from dataclasses import dataclass +from enum import Enum +from types import MappingProxyType +from typing import Any, Mapping, Sequence + + +MAPPING_VERSION = "0.1" +EXPERIMENTAL_PROFILE = "vstd-scitt-interop-experimental-0.1" +EXPERIMENTAL_CONTENT_TYPE = "application/vnd.verifier.vstd-receipt+json" + +_SHA256 = re.compile(r"^[0-9a-f]{64}$") +_VSTD_PASS = frozenset({"PASS"}) +_VSTD_FAIL = frozenset({"FAIL", "FALSIFIED"}) +_VSTD_UNKNOWN = frozenset({"UNKNOWN", "INDETERMINATE", "UNSUPPORTED"}) + + +class InteropError(ValueError): + """Raised when a mapping is incomplete, ambiguous, or unsupported.""" + + +def canonical_json_bytes(value: Any) -> bytes: + """Serialize experimental mapping objects deterministically. + + This deliberately matches VSTD's existing sorted, compact, ASCII JSON + rules, while remaining a mapping-level serializer rather than a claim that + JSON is SCITT's COSE wire format. + """ + + try: + return json.dumps( + value, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=True, + allow_nan=False, + ).encode("utf-8") + except (TypeError, ValueError) as exc: + raise InteropError(f"value is not canonical-JSON serializable: {exc}") from exc + + +def _sha256(value: bytes) -> str: + return hashlib.sha256(value).hexdigest() + + +def _digest(value: str, label: str) -> str: + if not isinstance(value, str): + raise InteropError(f"{label} must be a lowercase SHA-256 digest") + normalized = value.removeprefix("sha256:") + if not _SHA256.fullmatch(normalized): + raise InteropError(f"{label} must be a lowercase SHA-256 digest") + return normalized + + +def _nonempty(value: Any, label: str) -> str: + if not isinstance(value, str) or not value: + raise InteropError(f"{label} must be a non-empty string") + return value + + +def _exact_keys(value: Mapping[str, Any], expected: set[str], label: str) -> None: + actual = set(value) + if actual != expected: + raise InteropError( + f"{label} keys mismatch; missing={sorted(expected - actual)}, " + f"extra={sorted(actual - expected)}" + ) + + +def _string_map(value: Mapping[str, Any], label: str) -> dict[str, str]: + result: dict[str, str] = {} + for key, item in value.items(): + result[_nonempty(key, f"{label} key")] = _nonempty( + item, f"{label}[{key!r}]" + ) + return dict(sorted(result.items())) + + +@dataclass(frozen=True) +class VstdCoordinates: + """Loss-sensitive projection of the VSTD semantics carried in SCITT. + + The full native receipt is embedded as the payload. This projection makes + the coordinates a SCITT registration policy or relying-party tool is most + likely to inspect explicit without pretending one generic adapter can infer + every VSTD receipt family's semantics. + """ + + receipt_id: str + schema_version: str + claim_id: str + subject: str + predicate: str + parameters: Mapping[str, str] + native_result: str + native_canonical_digest: str + evidence_bounds: Mapping[str, int] + artifact_digests: Mapping[str, str] + provenance_references: tuple[str, ...] = () + + def __post_init__(self) -> None: + for name in ( + "receipt_id", + "schema_version", + "claim_id", + "subject", + "predicate", + "native_result", + ): + _nonempty(getattr(self, name), name) + object.__setattr__( + self, + "native_canonical_digest", + _digest(self.native_canonical_digest, "native_canonical_digest"), + ) + params = _string_map(self.parameters, "parameters") + object.__setattr__(self, "parameters", MappingProxyType(params)) + + bounds: dict[str, int] = {} + for key, value in self.evidence_bounds.items(): + key = _nonempty(key, "evidence_bounds key") + if type(value) is not int or value < 0: + raise InteropError( + f"evidence_bounds[{key!r}] must be a non-negative integer" + ) + bounds[key] = value + object.__setattr__( + self, "evidence_bounds", MappingProxyType(dict(sorted(bounds.items()))) + ) + + artifacts = { + _nonempty(key, "artifact_digests key"): _digest( + value, f"artifact_digests[{key!r}]" + ) + for key, value in self.artifact_digests.items() + } + if not artifacts: + raise InteropError("at least one artifact digest is required") + object.__setattr__( + self, "artifact_digests", MappingProxyType(dict(sorted(artifacts.items()))) + ) + refs = tuple(_nonempty(item, "provenance reference") for item in self.provenance_references) + if len(set(refs)) != len(refs): + raise InteropError("provenance_references must be unique") + object.__setattr__(self, "provenance_references", refs) + + def to_dict(self) -> dict[str, Any]: + return { + "receipt_id": self.receipt_id, + "schema_version": self.schema_version, + "claim_id": self.claim_id, + "claim_coordinate": { + "subject": self.subject, + "predicate": self.predicate, + "parameters": dict(self.parameters), + }, + "native_result": self.native_result, + "native_canonical_digest": self.native_canonical_digest, + "evidence_bounds": dict(self.evidence_bounds), + "artifact_digests": dict(self.artifact_digests), + "provenance_references": list(self.provenance_references), + } + + @classmethod + def from_dict(cls, value: Mapping[str, Any]) -> "VstdCoordinates": + _exact_keys( + value, + { + "receipt_id", + "schema_version", + "claim_id", + "claim_coordinate", + "native_result", + "native_canonical_digest", + "evidence_bounds", + "artifact_digests", + "provenance_references", + }, + "vstd_coordinates", + ) + coordinate = value["claim_coordinate"] + if not isinstance(coordinate, Mapping): + raise InteropError("claim_coordinate must be an object") + _exact_keys( + coordinate, {"subject", "predicate", "parameters"}, "claim_coordinate" + ) + parameters = coordinate["parameters"] + bounds = value["evidence_bounds"] + artifacts = value["artifact_digests"] + refs = value["provenance_references"] + if not isinstance(parameters, Mapping): + raise InteropError("claim_coordinate.parameters must be an object") + if not isinstance(bounds, Mapping): + raise InteropError("evidence_bounds must be an object") + if not isinstance(artifacts, Mapping): + raise InteropError("artifact_digests must be an object") + if not isinstance(refs, list) or not all(isinstance(item, str) for item in refs): + raise InteropError("provenance_references must be an array of strings") + return cls( + receipt_id=value["receipt_id"], + schema_version=value["schema_version"], + claim_id=value["claim_id"], + subject=coordinate["subject"], + predicate=coordinate["predicate"], + parameters=parameters, + native_result=value["native_result"], + native_canonical_digest=value["native_canonical_digest"], + evidence_bounds=bounds, + artifact_digests=artifacts, + provenance_references=tuple(refs), + ) + + +@dataclass(frozen=True) +class VstdScittPayload: + """Experimental application payload for carriage in a SCITT statement.""" + + receipt: Mapping[str, Any] + coordinates: VstdCoordinates + receipt_sha256: str + mapping_version: str = MAPPING_VERSION + profile: str = EXPERIMENTAL_PROFILE + receipt_media_type: str = EXPERIMENTAL_CONTENT_TYPE + + def __post_init__(self) -> None: + if self.mapping_version != MAPPING_VERSION: + raise InteropError(f"unsupported mapping version {self.mapping_version!r}") + if self.profile != EXPERIMENTAL_PROFILE: + raise InteropError(f"unsupported profile {self.profile!r}") + if self.receipt_media_type != EXPERIMENTAL_CONTENT_TYPE: + raise InteropError( + f"unsupported receipt media type {self.receipt_media_type!r}" + ) + if not isinstance(self.receipt, Mapping): + raise InteropError("receipt must be an object") + _digest(self.receipt_sha256, "receipt_sha256") + # Break aliases to caller-owned nested dictionaries. ``to_dict`` also + # rechecks the digest, so even deliberate mutation through the exposed + # nested projection fails closed rather than changing signed bytes. + copied = json.loads(canonical_json_bytes(dict(self.receipt)).decode("utf-8")) + object.__setattr__(self, "receipt", MappingProxyType(copied)) + self.verify_integrity() + + @classmethod + def create( + cls, receipt: Mapping[str, Any], coordinates: VstdCoordinates + ) -> "VstdScittPayload": + copied = dict(receipt) + return cls( + receipt=copied, + coordinates=coordinates, + receipt_sha256=_sha256(canonical_json_bytes(copied)), + ) + + def verify_integrity(self) -> None: + observed = _sha256(canonical_json_bytes(dict(self.receipt))) + if observed != self.receipt_sha256: + raise InteropError("embedded VSTD receipt does not match receipt_sha256") + for field in ("receipt_id", "schema_version"): + native = self.receipt.get(field) + declared = getattr(self.coordinates, field) + if native != declared: + raise InteropError( + f"embedded receipt {field} {native!r} does not match " + f"declared coordinate {declared!r}" + ) + native_digest = self.receipt.get("canonical_digest") + if native_digest is not None: + if _digest(native_digest, "receipt.canonical_digest") != ( + self.coordinates.native_canonical_digest + ): + raise InteropError( + "embedded receipt canonical_digest does not match VSTD coordinates" + ) + elif observed != self.coordinates.native_canonical_digest: + raise InteropError( + "embedded receipt full canonical digest does not match VSTD coordinates" + ) + + native_claim_id = self.receipt.get("claim_id") + if native_claim_id is not None and native_claim_id != self.coordinates.claim_id: + raise InteropError( + "embedded receipt claim_id does not match VSTD coordinates" + ) + + binding = self.receipt.get("binding") + if isinstance(binding, Mapping): + coordinate = binding.get("coordinate") + if isinstance(coordinate, Mapping): + expected = { + "subject": self.coordinates.subject, + "predicate": self.coordinates.predicate, + "parameters": dict(self.coordinates.parameters), + } + if dict(coordinate) != expected: + raise InteropError( + "embedded VSTD binding coordinate does not match mapping coordinate" + ) + bounds = binding.get("bounds") + if isinstance(bounds, Mapping) and dict(bounds) != dict( + self.coordinates.evidence_bounds + ): + raise InteropError( + "embedded VSTD evidence bounds do not match mapping coordinates" + ) + + native_result = None + witness = self.receipt.get("witness") + if isinstance(witness, Mapping): + header = witness.get("header") + if isinstance(header, Mapping): + native_result = header.get("verdict") + decision = self.receipt.get("decision") + if native_result is None and isinstance(decision, Mapping): + native_result = decision.get("verdict") + if native_result is not None and native_result != self.coordinates.native_result: + raise InteropError( + "embedded VSTD native result does not match mapping coordinates" + ) + + def to_dict(self) -> dict[str, Any]: + self.verify_integrity() + return { + "mapping_version": self.mapping_version, + "profile": self.profile, + "receipt_media_type": self.receipt_media_type, + "receipt_sha256": self.receipt_sha256, + "vstd_coordinates": self.coordinates.to_dict(), + "vstd_receipt": dict(self.receipt), + } + + def to_bytes(self) -> bytes: + return canonical_json_bytes(self.to_dict()) + + def payload_sha256(self) -> str: + return _sha256(self.to_bytes()) + + @classmethod + def from_bytes(cls, value: bytes) -> "VstdScittPayload": + try: + decoded = json.loads(value.decode("utf-8")) + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise InteropError(f"SCITT payload is not canonical JSON: {exc}") from exc + if not isinstance(decoded, Mapping): + raise InteropError("SCITT payload must be an object") + if canonical_json_bytes(decoded) != value: + raise InteropError("SCITT payload bytes are not in canonical form") + _exact_keys( + decoded, + { + "mapping_version", + "profile", + "receipt_media_type", + "receipt_sha256", + "vstd_coordinates", + "vstd_receipt", + }, + "SCITT payload", + ) + coordinates = decoded["vstd_coordinates"] + receipt = decoded["vstd_receipt"] + if not isinstance(coordinates, Mapping) or not isinstance(receipt, Mapping): + raise InteropError("vstd_coordinates and vstd_receipt must be objects") + return cls( + mapping_version=decoded["mapping_version"], + profile=decoded["profile"], + receipt_media_type=decoded["receipt_media_type"], + receipt_sha256=decoded["receipt_sha256"], + coordinates=VstdCoordinates.from_dict(coordinates), + receipt=receipt, + ) + + +@dataclass(frozen=True) +class ScittRegistrationTemplate: + """Normalized input for a native RFC 9943/COSE statement producer.""" + + issuer: str + subject: str + payload: VstdScittPayload + + def __post_init__(self) -> None: + _nonempty(self.issuer, "issuer") + _nonempty(self.subject, "subject") + if self.subject != self.payload.coordinates.subject: + raise InteropError( + "SCITT subject must equal the VSTD claim-coordinate subject" + ) + + def to_dict(self) -> dict[str, Any]: + return { + "representation": "normalized-registration-input-not-cose", + "required_protected_header_projection": { + "content_type": EXPERIMENTAL_CONTENT_TYPE, + "issuer": self.issuer, + "payload_hash_algorithm": "sha-256", + "subject": self.subject, + "type": EXPERIMENTAL_PROFILE, + }, + "payload_sha256": self.payload.payload_sha256(), + "payload": self.payload.to_dict(), + } + + def to_bytes(self) -> bytes: + return canonical_json_bytes(self.to_dict()) + + +class ScittEvidenceState(str, Enum): + """Normalized relying-party state; not an IETF registry.""" + + REGISTERED = "REGISTERED" + MISSING = "MISSING" + STALE = "STALE" + CONFLICTED = "CONFLICTED" + REVOKED = "REVOKED" + SUPERSEDED = "SUPERSEDED" + UNKNOWN = "UNKNOWN" + INVALID = "INVALID" + + +class VstdVerificationState(str, Enum): + """Normalized state from a native VSTD checker, not a wire registry.""" + + VERIFIED = "VERIFIED" + REJECTED = "REJECTED" + INDETERMINATE = "INDETERMINATE" + NOT_EVALUATED = "NOT_EVALUATED" + + +@dataclass(frozen=True) +class VstdVerificationEvidence: + """Bound output from a native VSTD checker. + + The adapter cannot infer that an embedded receipt was checked merely + because the receipt declares ``PASS``. A caller must provide the native + check state for the exact embedded receipt and retain the checker trust + coordinates. This is deliberately symmetric with + :class:`ScittVerificationEvidence`, which is normalized output from a + native SCITT verifier rather than a replacement for one. + """ + + state: VstdVerificationState + receipt_sha256: str + native_result: str + checker: str + verification_profile: str + reason: str + + def __post_init__(self) -> None: + try: + state = VstdVerificationState(self.state) + except (TypeError, ValueError) as exc: + raise InteropError( + f"unsupported VSTD verification state {self.state!r}" + ) from exc + object.__setattr__(self, "state", state) + object.__setattr__( + self, "receipt_sha256", _digest(self.receipt_sha256, "receipt_sha256") + ) + for name in ("native_result", "checker", "verification_profile", "reason"): + _nonempty(getattr(self, name), name) + + def to_dict(self) -> dict[str, str]: + return { + "state": self.state.value, + "receipt_sha256": self.receipt_sha256, + "native_result": self.native_result, + "checker": self.checker, + "verification_profile": self.verification_profile, + "reason": self.reason, + } + + @classmethod + def from_dict(cls, value: Mapping[str, Any]) -> "VstdVerificationEvidence": + expected = { + "state", + "receipt_sha256", + "native_result", + "checker", + "verification_profile", + "reason", + } + _exact_keys(value, expected, "VSTD verification evidence") + try: + state = VstdVerificationState(value["state"]) + except ValueError as exc: + raise InteropError( + f"unsupported VSTD verification state {value['state']!r}" + ) from exc + return cls(state=state, **{key: value[key] for key in expected - {"state"}}) + + +@dataclass(frozen=True) +class ScittVerificationEvidence: + """Output supplied by a native SCITT verifier under an explicit policy. + + ``state`` is a local normalized policy result. RFC 9943 does not define + this enum, and callers must retain ``native_result`` and ``reason`` so that + the source verifier's semantics are not erased. + """ + + state: ScittEvidenceState + statement_sha256: str + payload_sha256: str + issuer: str + subject: str + signed_statement_verified: bool + receipt_verified: bool + verification_profile: str + registration_policy: str + transparency_service: str + vds: str + native_result: str + reason: str + registered_at: str | None = None + + def __post_init__(self) -> None: + try: + state = ScittEvidenceState(self.state) + except (TypeError, ValueError) as exc: + raise InteropError(f"unsupported SCITT evidence state {self.state!r}") from exc + object.__setattr__(self, "state", state) + object.__setattr__( + self, "statement_sha256", _digest(self.statement_sha256, "statement_sha256") + ) + object.__setattr__( + self, "payload_sha256", _digest(self.payload_sha256, "payload_sha256") + ) + for name in ( + "issuer", + "subject", + "verification_profile", + "registration_policy", + "transparency_service", + "vds", + "native_result", + "reason", + ): + _nonempty(getattr(self, name), name) + if type(self.signed_statement_verified) is not bool: + raise InteropError("signed_statement_verified must be boolean") + if type(self.receipt_verified) is not bool: + raise InteropError("receipt_verified must be boolean") + if self.registered_at is not None: + _nonempty(self.registered_at, "registered_at") + if self.state is ScittEvidenceState.REGISTERED and not ( + self.signed_statement_verified and self.receipt_verified + ): + raise InteropError( + "REGISTERED requires independently verified statement and receipt" + ) + + def to_dict(self) -> dict[str, Any]: + return { + "state": self.state.value, + "statement_sha256": self.statement_sha256, + "payload_sha256": self.payload_sha256, + "issuer": self.issuer, + "subject": self.subject, + "signed_statement_verified": self.signed_statement_verified, + "receipt_verified": self.receipt_verified, + "verification_profile": self.verification_profile, + "registration_policy": self.registration_policy, + "transparency_service": self.transparency_service, + "vds": self.vds, + "native_result": self.native_result, + "reason": self.reason, + "registered_at": self.registered_at, + } + + @classmethod + def from_dict(cls, value: Mapping[str, Any]) -> "ScittVerificationEvidence": + expected = { + "state", + "statement_sha256", + "payload_sha256", + "issuer", + "subject", + "signed_statement_verified", + "receipt_verified", + "verification_profile", + "registration_policy", + "transparency_service", + "vds", + "native_result", + "reason", + "registered_at", + } + _exact_keys(value, expected, "SCITT verification evidence") + try: + state = ScittEvidenceState(value["state"]) + except ValueError as exc: + raise InteropError(f"unsupported SCITT evidence state {value['state']!r}") from exc + return cls(state=state, **{key: value[key] for key in expected - {"state"}}) + + +class CompositionStatus(str, Enum): + PASS = "PASS" + FAIL = "FAIL" + UNKNOWN = "UNKNOWN" + CONFLICTED = "CONFLICTED" + + +@dataclass(frozen=True) +class CompositionResult: + status: CompositionStatus + native_vstd_result: str + native_scitt_result: str + reason: str + vstd_receipt_sha256: str + scitt_statement_sha256: str + + def to_dict(self) -> dict[str, str]: + return { + "status": self.status.value, + "native_vstd_result": self.native_vstd_result, + "native_scitt_result": self.native_scitt_result, + "reason": self.reason, + "vstd_receipt_sha256": self.vstd_receipt_sha256, + "scitt_statement_sha256": self.scitt_statement_sha256, + } + + +def create_scitt_registration_template( + receipt: Mapping[str, Any], + coordinates: VstdCoordinates, + *, + issuer: str, + subject: str, +) -> ScittRegistrationTemplate: + """Create deterministic inputs for an external SCITT/COSE producer.""" + + return ScittRegistrationTemplate( + issuer=issuer, + subject=subject, + payload=VstdScittPayload.create(receipt, coordinates), + ) + + +def consume_scitt_evidence( + evidence: ScittVerificationEvidence, + *, + expected_payload_sha256: str, + expected_subject: str, + accepted_issuers: Sequence[str], +) -> dict[str, Any]: + """Convert a native SCITT verifier result into bounded VSTD evidence. + + The returned object describes transparency evidence only. Its + ``computational_verdict`` is always ``NOT_EVALUATED``. + """ + + expected_digest = _digest(expected_payload_sha256, "expected_payload_sha256") + accepted = tuple(_nonempty(item, "accepted issuer") for item in accepted_issuers) + if not accepted: + raise InteropError("accepted_issuers cannot be empty") + + state = evidence.state + reason = evidence.reason + if evidence.payload_sha256 != expected_digest: + state = ScittEvidenceState.INVALID + reason = "SCITT statement payload does not bind the expected VSTD payload" + elif evidence.subject != expected_subject: + state = ScittEvidenceState.INVALID + reason = "SCITT subject does not match the VSTD claim subject" + elif evidence.issuer not in accepted: + state = ScittEvidenceState.INVALID + reason = "SCITT issuer is not accepted by the relying-party policy" + elif not evidence.signed_statement_verified or not evidence.receipt_verified: + state = ScittEvidenceState.INVALID + reason = "native SCITT statement or receipt verification did not succeed" + + return { + "evidence_kind": "SCITT_TRANSPARENCY", + "normalized_state": state.value, + "native_scitt_result": evidence.native_result, + "reason": reason, + "computational_verdict": "NOT_EVALUATED", + "trust_coordinates": { + "accepted_issuers": list(accepted), + "registration_policy": evidence.registration_policy, + "transparency_service": evidence.transparency_service, + "verification_profile": evidence.verification_profile, + "vds": evidence.vds, + }, + "statement_sha256": evidence.statement_sha256, + "payload_sha256": evidence.payload_sha256, + "registered_at": evidence.registered_at, + } + + +def compose_results( + payload: VstdScittPayload, + vstd: VstdVerificationEvidence, + scitt: ScittVerificationEvidence, + *, + artifact_digests: Mapping[str, str], + accepted_issuers: Sequence[str], +) -> CompositionResult: + """Compose exact VSTD and SCITT results without semantic upgrading.""" + + observed_artifacts = { + _nonempty(key, "artifact_digests key"): _digest( + value, f"artifact_digests[{key!r}]" + ) + for key, value in artifact_digests.items() + } + transparency = consume_scitt_evidence( + scitt, + expected_payload_sha256=payload.payload_sha256(), + expected_subject=payload.coordinates.subject, + accepted_issuers=accepted_issuers, + ) + scitt_state = ScittEvidenceState(transparency["normalized_state"]) + native_vstd = vstd.native_result + + if observed_artifacts != dict(payload.coordinates.artifact_digests): + status = CompositionStatus.FAIL + reason = "artifact binding mismatch" + elif vstd.receipt_sha256 != payload.receipt_sha256: + status = CompositionStatus.FAIL + reason = "native VSTD checker result does not bind the embedded receipt" + elif ( + vstd.state is VstdVerificationState.VERIFIED + and vstd.native_result != payload.coordinates.native_result + ): + status = CompositionStatus.FAIL + reason = "native VSTD checker result does not match the payload result" + elif vstd.state is VstdVerificationState.REJECTED: + status = CompositionStatus.FAIL + reason = f"native VSTD checker rejected the receipt: {vstd.reason}" + elif vstd.state is VstdVerificationState.NOT_EVALUATED: + status = CompositionStatus.UNKNOWN + reason = "native VSTD receipt was not evaluated" + elif vstd.state is VstdVerificationState.INDETERMINATE: + status = CompositionStatus.UNKNOWN + reason = f"native VSTD checker was unable to decide: {vstd.reason}" + elif native_vstd in _VSTD_FAIL: + status = CompositionStatus.FAIL + reason = "native VSTD verification failed" + elif scitt_state is ScittEvidenceState.INVALID: + status = CompositionStatus.FAIL + reason = transparency["reason"] + elif native_vstd == CompositionStatus.CONFLICTED.value: + status = CompositionStatus.CONFLICTED + reason = "native VSTD evidence is conflicted" + elif scitt_state is ScittEvidenceState.CONFLICTED: + status = CompositionStatus.CONFLICTED + reason = "SCITT evidence graph or relying-party policy reports a conflict" + elif native_vstd in _VSTD_UNKNOWN: + status = CompositionStatus.UNKNOWN + reason = "native VSTD verification is indeterminate or unsupported" + elif scitt_state is not ScittEvidenceState.REGISTERED: + status = CompositionStatus.UNKNOWN + reason = f"SCITT evidence state {scitt_state.value} does not establish a current registration" + elif native_vstd in _VSTD_PASS: + status = CompositionStatus.PASS + reason = "native VSTD PASS and exact current SCITT registration both verified" + else: + raise InteropError( + f"unsupported native VSTD result {native_vstd!r}; refusing to guess" + ) + + return CompositionResult( + status=status, + native_vstd_result=native_vstd, + native_scitt_result=scitt.native_result, + reason=reason, + vstd_receipt_sha256=payload.receipt_sha256, + scitt_statement_sha256=scitt.statement_sha256, + ) diff --git a/tests/test_scitt_crypto_example.py b/tests/test_scitt_crypto_example.py new file mode 100644 index 0000000..d4426ab --- /dev/null +++ b/tests/test_scitt_crypto_example.py @@ -0,0 +1,111 @@ +"""Optional real-COSE integration test for the self-contained example.""" + +from __future__ import annotations + +import importlib.util +import json +from pathlib import Path + +import pytest +from jsonschema import Draft202012Validator +from referencing import Registry, Resource + + +pytest.importorskip("scitt_cose") +pytest.importorskip("cryptography") + +REPO_ROOT = Path(__file__).resolve().parents[1] +DEMO = REPO_ROOT / "examples" / "scitt_interop" / "demo.py" + + +def _load_demo(): + spec = importlib.util.spec_from_file_location("vstd_scitt_demo", DEMO) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_real_signed_statement_receipt_and_independent_consumption(tmp_path): + result = _load_demo().produce(tmp_path) + assert result["vstd_kernel"]["outcome"] == "ACCEPTED" + assert result["vstd_kernel"]["verdict"] == "PASS" + assert result["scitt_observation"]["signed_statement_verified"] is True + assert result["scitt_observation"]["receipt_verified"] is True + assert result["composition"]["status"] == "PASS" + + schema_dir = REPO_ROOT / "receipts" / "schema" + receipt_schema = json.loads((schema_dir / "vstd4_receipt.json").read_text()) + certificate_schema = json.loads( + (schema_dir / "vstd4_certificate.json").read_text() + ) + registry = Registry().with_resource( + certificate_schema["$id"], Resource.from_contents(certificate_schema) + ) + Draft202012Validator(receipt_schema, registry=registry).validate( + json.loads((tmp_path / "vstd_receipt.json").read_text()) + ) + + +def test_application_payload_is_deterministic_but_ephemeral_cose_keys_are_not( + tmp_path, +): + demo = _load_demo() + first = tmp_path / "first" + second = tmp_path / "second" + demo.produce(first) + demo.produce(second) + + assert (first / "vstd_scitt_payload.json").read_bytes() == ( + second / "vstd_scitt_payload.json" + ).read_bytes() + assert (first / "signed_statement.cose").read_bytes() != ( + second / "signed_statement.cose" + ).read_bytes() + + +def test_real_statement_and_receipt_tampering_are_rejected(tmp_path): + demo = _load_demo() + demo.produce(tmp_path) + + statement = tmp_path / "signed_statement.cose" + statement_bytes = statement.read_bytes() + statement.write_bytes(statement_bytes[:-1] + bytes([statement_bytes[-1] ^ 1])) + with pytest.raises(RuntimeError, match="signature did not verify"): + demo.verify(tmp_path) + + demo.produce(tmp_path) + receipt = tmp_path / "receipt.cose" + receipt_bytes = receipt.read_bytes() + receipt.write_bytes(receipt_bytes[:-1] + bytes([receipt_bytes[-1] ^ 1])) + with pytest.raises(RuntimeError, match="COSE Receipt failed"): + demo.verify(tmp_path) + + +def test_real_malformed_scitt_statement_is_rejected_before_composition(tmp_path): + demo = _load_demo() + demo.produce(tmp_path) + (tmp_path / "signed_statement.cose").write_bytes(b"\x80") + + with pytest.raises(RuntimeError, match="malformed SCITT Signed Statement"): + demo.verify(tmp_path) + + +def test_real_scitt_registration_does_not_upgrade_vstd_budget_exhaustion(tmp_path): + demo = _load_demo() + demo.produce(tmp_path) + result = demo.verify(tmp_path, vstd_budget=0) + assert result["scitt_observation"]["signed_statement_verified"] is True + assert result["scitt_observation"]["receipt_verified"] is True + assert result["vstd_kernel"]["outcome"] == "REFUSED" + assert result["vstd_kernel"]["verdict"] == "UNKNOWN" + assert result["composition"]["status"] == "UNKNOWN" + + +def test_real_valid_scitt_registration_does_not_repair_rejected_vstd_claim(tmp_path): + result = _load_demo().produce(tmp_path, vstd_binding_tamper=True) + assert result["scitt_observation"]["signed_statement_verified"] is True + assert result["scitt_observation"]["receipt_verified"] is True + assert result["vstd_kernel"]["outcome"] == "REJECTED" + assert result["vstd_observation"]["state"] == "REJECTED" + assert result["composition"]["status"] == "FAIL" diff --git a/tests/test_scitt_interop.py b/tests/test_scitt_interop.py new file mode 100644 index 0000000..942ea2c --- /dev/null +++ b/tests/test_scitt_interop.py @@ -0,0 +1,421 @@ +"""Adversarial tests for the experimental VSTD/SCITT composition boundary.""" + +from __future__ import annotations + +import json + +import pytest + +from verifier.interoperability.scitt import ( + CompositionStatus, + InteropError, + ScittEvidenceState, + ScittVerificationEvidence, + VstdCoordinates, + VstdScittPayload, + VstdVerificationEvidence, + VstdVerificationState, + compose_results, + consume_scitt_evidence, + create_scitt_registration_template, +) + + +DIGEST_A = "a" * 64 +DIGEST_B = "b" * 64 +DIGEST_C = "c" * 64 +ISSUER = "https://issuer.example" +SUBJECT = "artifact:sha256:" + DIGEST_A + + +def _receipt(*, result: str = "PASS") -> dict: + return { + "schema_version": "VSTD-4", + "receipt_id": "VFY-4-scitt-interop-test", + "canonical_digest": DIGEST_B, + "claim_id": "SCITT-INTEROP-TEST", + "binding": { + "claim": "the bounded predicate holds for the named artifact", + "coordinate": { + "subject": SUBJECT, + "predicate": "bounded_predicate", + "parameters": {"policy": "test-policy-v1"}, + }, + "bounds": { + "verification_cost_bound": 100, + "memory_bound": 10, + "certificate_size_bound": 10000, + }, + }, + "decision": {"verdict": result, "certificate": "fixture-only"}, + } + + +def _coordinates(*, result: str = "PASS") -> VstdCoordinates: + return VstdCoordinates( + receipt_id="VFY-4-scitt-interop-test", + schema_version="VSTD-4", + claim_id="SCITT-INTEROP-TEST", + subject=SUBJECT, + predicate="bounded_predicate", + parameters={"policy": "test-policy-v1"}, + native_result=result, + native_canonical_digest=DIGEST_B, + evidence_bounds={ + "verification_cost_bound": 100, + "memory_bound": 10, + "certificate_size_bound": 10000, + }, + artifact_digests={"primary": DIGEST_A}, + provenance_references=("urn:example:provenance:1",), + ) + + +def _payload(*, result: str = "PASS") -> VstdScittPayload: + return VstdScittPayload.create(_receipt(result=result), _coordinates(result=result)) + + +def _scitt( + payload: VstdScittPayload, + *, + state: ScittEvidenceState = ScittEvidenceState.REGISTERED, + signed: bool = True, + receipt: bool = True, + payload_digest: str | None = None, + issuer: str = ISSUER, + subject: str = SUBJECT, +) -> ScittVerificationEvidence: + return ScittVerificationEvidence( + state=state, + statement_sha256=DIGEST_C, + payload_sha256=payload_digest or payload.payload_sha256(), + issuer=issuer, + subject=subject, + signed_statement_verified=signed, + receipt_verified=receipt, + verification_profile="RFC9943+RFC9942", + registration_policy="urn:example:registration-policy:v1", + transparency_service="https://transparency.example", + vds="RFC9162_SHA256", + native_result=state.value.lower(), + reason="native verifier fixture result", + registered_at="2026-08-23T00:00:00Z", + ) + + +def _vstd( + payload: VstdScittPayload, + *, + state: VstdVerificationState = VstdVerificationState.VERIFIED, + result: str | None = None, + receipt_digest: str | None = None, +) -> VstdVerificationEvidence: + return VstdVerificationEvidence( + state=state, + receipt_sha256=receipt_digest or payload.receipt_sha256, + native_result=result or payload.coordinates.native_result, + checker="verifier.core.kernel.check", + verification_profile="VSTD4-GDC-1/reference-kernel", + reason="native checker fixture result", + ) + + +def _compose( + payload: VstdScittPayload, + scitt: ScittVerificationEvidence, + *, + artifacts: dict[str, str] | None = None, +): + return compose_results( + payload, + _vstd(payload), + scitt, + artifact_digests=artifacts or {"primary": DIGEST_A}, + accepted_issuers=[ISSUER], + ) + + +def test_deterministic_serialization_and_round_trip_preserve_coordinates(): + payload = _payload() + encoded = payload.to_bytes() + assert encoded == payload.to_bytes() + assert b'": ' not in encoded + assert b", " not in encoded + + decoded = VstdScittPayload.from_bytes(encoded) + assert decoded.to_bytes() == encoded + assert decoded.coordinates.to_dict() == payload.coordinates.to_dict() + assert decoded.receipt_sha256 == payload.receipt_sha256 + assert decoded.coordinates.evidence_bounds["memory_bound"] == 10 + assert decoded.coordinates.provenance_references == ( + "urn:example:provenance:1", + ) + + +def test_native_vstd_payload_does_not_require_scitt_identity_or_log_coordinates(): + payload = _payload().to_dict() + serialized = json.dumps(payload, sort_keys=True) + for scitt_coordinate in ( + "issuer", + "transparency_service", + "registration_policy", + "registered_at", + ): + assert scitt_coordinate not in payload + assert f'"{scitt_coordinate}"' not in serialized + + template = create_scitt_registration_template( + _receipt(), _coordinates(), issuer=ISSUER, subject=SUBJECT + ).to_dict() + assert template["required_protected_header_projection"]["issuer"] == ISSUER + + +def test_noncanonical_or_extra_payload_fields_are_rejected(): + payload = _payload().to_dict() + payload["unexpected"] = True + with pytest.raises(InteropError, match="not in canonical form"): + VstdScittPayload.from_bytes(json.dumps(payload).encode()) + + canonical_with_extra = json.dumps( + payload, sort_keys=True, separators=(",", ":") + ).encode() + with pytest.raises(InteropError, match="keys mismatch"): + VstdScittPayload.from_bytes(canonical_with_extra) + + +def test_version_mismatch_and_unsupported_profile_fail_closed(): + payload = _payload().to_dict() + payload["mapping_version"] = "9.9" + encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() + with pytest.raises(InteropError, match="unsupported mapping version"): + VstdScittPayload.from_bytes(encoded) + + payload["mapping_version"] = "0.1" + payload["profile"] = "unknown-profile" + encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() + with pytest.raises(InteropError, match="unsupported profile"): + VstdScittPayload.from_bytes(encoded) + + +def test_receipt_identity_and_claim_coordinate_mismatch_are_rejected(): + receipt = _receipt() + receipt["receipt_id"] = "VFY-4-other" + with pytest.raises(InteropError, match="receipt_id"): + VstdScittPayload.create(receipt, _coordinates()) + + receipt = _receipt() + receipt["binding"]["coordinate"]["predicate"] = "other_predicate" + with pytest.raises(InteropError, match="binding coordinate"): + VstdScittPayload.create(receipt, _coordinates()) + + +def test_mutating_nested_receipt_after_creation_does_not_change_payload(): + receipt = _receipt() + payload = VstdScittPayload.create(receipt, _coordinates()) + before = payload.to_bytes() + receipt["binding"]["claim"] = "mutated by caller" + assert payload.to_bytes() == before + + +def test_registration_template_is_explicitly_not_cose_and_binds_subject(): + template = create_scitt_registration_template( + _receipt(), _coordinates(), issuer=ISSUER, subject=SUBJECT + ) + data = template.to_dict() + assert data["representation"] == "normalized-registration-input-not-cose" + assert data["payload_sha256"] == template.payload.payload_sha256() + assert data["required_protected_header_projection"]["issuer"] == ISSUER + assert data["required_protected_header_projection"]["subject"] == SUBJECT + + with pytest.raises(InteropError, match="subject must equal"): + create_scitt_registration_template( + _receipt(), _coordinates(), issuer=ISSUER, subject="artifact:other" + ) + + +def test_registered_vstd_pass_composes_to_pass_only_for_exact_artifact(): + payload = _payload() + result = _compose(payload, _scitt(payload)) + assert result.status is CompositionStatus.PASS + assert result.native_vstd_result == "PASS" + assert result.native_scitt_result == "registered" + + +def test_registered_scitt_cannot_create_pass_without_bound_vstd_verification(): + payload = _payload() + result = compose_results( + payload, + _vstd(payload, state=VstdVerificationState.NOT_EVALUATED), + _scitt(payload), + artifact_digests={"primary": DIGEST_A}, + accepted_issuers=[ISSUER], + ) + assert result.status is CompositionStatus.UNKNOWN + assert result.reason == "native VSTD receipt was not evaluated" + + +def test_vstd_checker_result_must_bind_exact_receipt_and_native_result(): + payload = _payload() + wrong_receipt = compose_results( + payload, + _vstd(payload, receipt_digest=DIGEST_C), + _scitt(payload), + artifact_digests={"primary": DIGEST_A}, + accepted_issuers=[ISSUER], + ) + assert wrong_receipt.status is CompositionStatus.FAIL + assert "embedded receipt" in wrong_receipt.reason + + wrong_result = compose_results( + payload, + _vstd(payload, result="UNKNOWN"), + _scitt(payload), + artifact_digests={"primary": DIGEST_A}, + accepted_issuers=[ISSUER], + ) + assert wrong_result.status is CompositionStatus.FAIL + assert "payload result" in wrong_result.reason + + +def test_rejected_vstd_receipt_cannot_be_repaired_by_scitt_registration(): + payload = _payload() + result = compose_results( + payload, + _vstd(payload, state=VstdVerificationState.REJECTED), + _scitt(payload), + artifact_digests={"primary": DIGEST_A}, + accepted_issuers=[ISSUER], + ) + assert result.status is CompositionStatus.FAIL + assert "checker rejected" in result.reason + + +def test_registered_scitt_preserves_vstd_resource_indeterminacy(): + payload = _payload() + result = compose_results( + payload, + _vstd( + payload, + state=VstdVerificationState.INDETERMINATE, + result="UNKNOWN", + ), + _scitt(payload), + artifact_digests={"primary": DIGEST_A}, + accepted_issuers=[ISSUER], + ) + assert result.status is CompositionStatus.UNKNOWN + assert result.native_vstd_result == "UNKNOWN" + assert "unable to decide" in result.reason + + +def test_artifact_substitution_fails_even_when_scitt_registration_is_valid(): + payload = _payload() + result = _compose(payload, _scitt(payload), artifacts={"primary": DIGEST_B}) + assert result.status is CompositionStatus.FAIL + assert result.reason == "artifact binding mismatch" + + +def test_valid_registration_does_not_upgrade_failed_vstd_claim(): + payload = _payload(result="FAIL") + result = _compose(payload, _scitt(payload)) + assert result.status is CompositionStatus.FAIL + assert result.native_vstd_result == "FAIL" + + +@pytest.mark.parametrize("native", ["UNKNOWN", "INDETERMINATE", "UNSUPPORTED"]) +def test_registered_statement_preserves_vstd_indeterminacy(native): + payload = _payload(result=native) + result = _compose(payload, _scitt(payload)) + assert result.status is CompositionStatus.UNKNOWN + assert result.native_vstd_result == native + + +@pytest.mark.parametrize( + "state", + [ + ScittEvidenceState.MISSING, + ScittEvidenceState.STALE, + ScittEvidenceState.REVOKED, + ScittEvidenceState.SUPERSEDED, + ScittEvidenceState.UNKNOWN, + ], +) +def test_noncurrent_scitt_evidence_caps_vstd_pass_at_unknown(state): + payload = _payload() + result = _compose(payload, _scitt(payload, state=state)) + assert result.status is CompositionStatus.UNKNOWN + assert state.value in result.reason + + +def test_conflicted_evidence_is_not_collapsed_to_unknown_or_pass(): + payload = _payload() + result = _compose( + payload, _scitt(payload, state=ScittEvidenceState.CONFLICTED) + ) + assert result.status is CompositionStatus.CONFLICTED + + +def test_payload_transplant_is_detected_despite_verified_scitt_receipt(): + payload = _payload() + evidence = _scitt(payload, payload_digest=DIGEST_B) + result = _compose(payload, evidence) + assert result.status is CompositionStatus.FAIL + assert "payload" in result.reason + + +def test_wrong_issuer_and_subject_fail_relying_party_policy(): + payload = _payload() + wrong_issuer = _scitt(payload, issuer="https://other.example") + assert _compose(payload, wrong_issuer).status is CompositionStatus.FAIL + + wrong_subject = _scitt(payload, subject="artifact:other") + assert _compose(payload, wrong_subject).status is CompositionStatus.FAIL + + +def test_unverified_statement_or_receipt_cannot_be_called_registered(): + payload = _payload() + with pytest.raises(InteropError, match="REGISTERED requires"): + _scitt(payload, signed=False) + with pytest.raises(InteropError, match="REGISTERED requires"): + _scitt(payload, receipt=False) + + +def test_scitt_evidence_adapter_never_emits_computational_verdict(): + payload = _payload() + evidence = consume_scitt_evidence( + _scitt(payload), + expected_payload_sha256=payload.payload_sha256(), + expected_subject=SUBJECT, + accepted_issuers=[ISSUER], + ) + assert evidence["normalized_state"] == "REGISTERED" + assert evidence["computational_verdict"] == "NOT_EVALUATED" + + +def test_malformed_evidence_and_unknown_vstd_result_are_rejected(): + payload = _payload() + malformed = _scitt(payload).to_dict() + malformed["extra"] = "guess me" + with pytest.raises(InteropError, match="keys mismatch"): + ScittVerificationEvidence.from_dict(malformed) + + unsupported = _payload(result="VALID") + with pytest.raises(InteropError, match="refusing to guess"): + _compose(unsupported, _scitt(unsupported)) + + +def test_scitt_verification_evidence_round_trip(): + payload = _payload() + evidence = _scitt(payload) + decoded = ScittVerificationEvidence.from_dict(evidence.to_dict()) + assert decoded == evidence + + +def test_vstd_verification_evidence_round_trip_and_closed_shape(): + evidence = _vstd(_payload()) + assert VstdVerificationEvidence.from_dict(evidence.to_dict()) == evidence + + malformed = evidence.to_dict() + malformed["extra"] = "guess me" + with pytest.raises(InteropError, match="keys mismatch"): + VstdVerificationEvidence.from_dict(malformed) From 1384f6685f7bb673f177f3a42bfaf94dfa6e942b Mon Sep 17 00:00:00 2001 From: TimeLordRaps Date: Mon, 24 Aug 2026 08:46:56 -0700 Subject: [PATCH 07/32] Document bounded verification allocation roadmap --- README.md | 8 ++++++ ROADMAP.md | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ed461e0..cf8ee1a 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,14 @@ *A PASS is not enough. Show what passed, under which meaning, against which evidence, inside which bounds, and how somebody else can prove it wrong.* +VSTD gives different verification tools a shared way to say **what they checked, +how they checked it, where the result stops, and what would overturn it**. Verification +time is limited, so the roadmap also asks a practical next question: **what should we +check first?** The intended direction is to record why verification work was selected, +spend bounded verification effort where it matters most, and make artifacts easier to +check and refute—without turning priority, confidence, or a tool's native result into a +stronger claim than the evidence supports. + [Run the demo](#see-it-fail-correctly) · [Read the quickstart](docs/QUICKSTART.md) · [Inspect the standard](standard/LADDER.md) · diff --git a/ROADMAP.md b/ROADMAP.md index 1f9300c..1d209b8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -23,6 +23,54 @@ checkable within stated bounds, and capable of being overturned. It is evidence infrastructure around fast systems—not proof that a system is aligned, safe, conscious, superintelligent, or fully observed. +## The next question: what should we check first? + +Verification is never free. A project can usually identify more claims, artifacts, and +dependencies worth checking than its available time, compute, evidence access, and human +attention can cover. Hiding that constraint does not remove it; it only makes the choice +of what went unchecked harder to inspect. + +The intended next direction is straightforward for a newcomer: + +1. record the available verification budget; +2. choose which check to run next under a declared policy; +3. record why that check was selected and what was deferred; +4. preserve the native verifier's actual result and VSTD claim boundary; and +5. observe whether the policy makes artifacts easier to check—or merely easier to game. + +This is **bounded verification allocation**. A priority is a scheduling result, not a +truth result. “Check this first” does not mean “this is false,” “this is important in +every context,” or “everything else is safe.” Budget exhaustion leaves the deferred +surface explicit and unresolved. + +The longer-term objective is a portable, verifier-independent way to: + +- allocate bounded verification work across different proof engines, domain verifiers, + tests, reproduction procedures, and challenge routes; +- bind the policy, evidence, expected cost, downstream blast radius, and recorded reason + for each allocation decision; +- measure **verification yield** without reducing it to solver time alone; +- make certificate-friendly, modular, replayable, and cheaply refutable artifacts easier + to select and deploy; and +- expose feedback loops in which artifacts or adaptive systems change their behavior + because they anticipate what will be checked. + +The allocation policy is itself a versioned software artifact. It can therefore be +tested, challenged, meta-verified, and represented in VSTD-Graph alongside the artifacts +and verifier actions it influences. A stable feedback loop is not automatically a true +one: randomized challenges, counterevidence searches, dependency-aware updates, and +explicit `UNKNOWN` outcomes remain necessary to resist self-confirming verification. + +This direction composes established work on +[bounded optimality](https://www.cs.cmu.edu/afs/cs/project/jair/pub/volume2/russell95a.pdf), +[active testing](https://proceedings.mlr.press/v139/kossen21a.html), +[cost-sensitive testing trees](https://proceedings.mlr.press/v32/cicalese14.html), +[proof-carrying code](https://people.eecs.berkeley.edu/~necula/papers.html), and +[certifying algorithms](https://www.sciencedirect.com/science/article/pii/S1574013710000560). +The roadmap does not claim those foundations as VSTD inventions. The research question +is whether VSTD can provide interoperable claim boundaries and portable result semantics +for their combined use across heterogeneous verification substrates. + ## Vision board ```text @@ -45,6 +93,27 @@ claim → evidence → bounded check → publish → challenge → adjudicate No arrow in that loop upgrades one VSTD layer with another layer's evidence. Each layer still requires its own evidence; the loop only carries results and challenges. +## Current branch-backed development tracks + +This dated register records substantive local work as of **2026-08-24**. These branches +are not merged into `main`, pushed for public review, normative, released, or evidence of +adoption merely because they exist. Branch names are operational coordinates, not +experiment identities; the experimental-workflow profile is intended to replace this +manual table with portable manifests and a generated index. + +| Track | Local branch or branches | Current boundary | Roadmap disposition | +|---|---|---|---| +| Public surface and reserved `.vstd` lockfile | `codex/public-surface-lockfile-audit` | Uncommitted local draft at `main`; mixed changes must be separated and audited before integration. | Review the specification as reserved and non-normative; do not imply an implemented lockfile. | +| Documentation lineage and precedents | `codex/documentation-lineage` | One signed local commit; unmerged and unpublished. | Review source accuracy and merge only compatibility-preserving documentation. | +| SCITT interoperability | `codex/scitt-interop` | One signed local commit; experimental adapter, examples, adversarial tests, and audit; unmerged and unpublished. | Preserve SCITT as an adjacent transparency/receipt substrate and VSTD as bounded verification semantics. | +| ZIZK experiments | `codex/zizk-zero-knowledge`, `claude/zizk-zero-identity`, `claude/zizk-reverification` | Signed local experiment chain; unmerged and unpublished. | Keep zero knowledge, identity minimization, and reverification experimental until their claim boundaries and trustless substrate survive joint review. | +| Verifier-guided SAT routing | `codex/verifier-guided-sat` | Real SAT/native-verifier baseline infrastructure exists locally, but the required live LM evaluation is hard-blocked and no result commit exists. | Preserve the blocker; do not substitute fake LM evidence or claim speed/generalization. | +| Verification allocation and experimental workflows | `codex/verification-allocation-roadmap` | Roadmap definition only; no allocation engine or profile conformance claim. | Specify the platform-independent profile, then a GitHub adapter, then dogfood it on the SCITT, ZIZK, and SAT tracks. | + +Historical release branches, pre-rename branches, and already-merged branches are +repository-maintenance concerns rather than active roadmap tracks. Their continued local +existence does not make their older semantics candidates for reintegration. + ## Milestone 1 — make refutation the front door **Exit evidence** @@ -73,11 +142,18 @@ layer still requires its own evidence; the loop only carries results and challen - disagreements are preserved as public interoperability failures until resolved; - no “independent” label is used merely because two entry points call shared logic. -## Milestone 3 — agent-work profile +## Milestone 3 — experimental-workflow and agent-work profiles **Build** -- a non-normative profile for observable user, agent, and tool messages; +- a platform-independent, non-normative experimental-workflow profile for questions, + hypotheses, preregistration, interventions, observations, native-verifier results, + budgets, amendments, challenges, and publication state; +- a GitHub adapter that maps issues, commits, workflow runs, artifacts, pull requests, + and merges without treating repository state as a verification verdict; +- bounded verification-allocation records that preserve the policy, reason, budget, + deferred surface, and native outcome without assigning truth by priority; +- an agent-harness specialization for observable user, agent, and tool messages; - bindings for repository state, patches, file reads, commands, outputs, tests, failures, retries, and final claims; - explicit serialization gaps for hidden prompts, inaccessible reasoning, and @@ -86,6 +162,9 @@ layer still requires its own evidence; the loop only carries results and challen **Exit evidence** +- the SCITT, ZIZK, and SAT tracks can be indexed through the same experimental-workflow + vocabulary without changing their native verifiers or erasing their blockers; +- a GitHub merge remains an integration event rather than becoming a VSTD pass; - the same trace can be checked by two independent consumers; - deleting or substituting a bound tool output changes the receipt digest or fails a declared rule; From fd8c5f33e4389c00617280c40fd3785336d8ba1d Mon Sep 17 00:00:00 2001 From: TimeLordRaps Date: Mon, 24 Aug 2026 09:23:08 -0700 Subject: [PATCH 08/32] Add experimental workflow profile --- AGENTS.md | 29 +- CLAUDE.md | 4 + CONTRIBUTING.md | 7 + README.md | 7 + ROADMAP.md | 24 +- docs/CLAIMS_AND_LIMITS.md | 7 +- docs/ECOSYSTEM.md | 6 + docs/assets/site.css | 23 + docs/index.html | 13 +- docs/layers/vstd-3/compatibility.md | 5 + docs/layers/vstd-3/vendor-integration.md | 5 + docs/profiles/competition-evaluation.md | 4 + docs/profiles/experimental-workflow.md | 155 +++ .../experimental-workflow.schema.json | 897 ++++++++++++++++++ docs/reference.html | 430 +++++++++ examples/experimental_workflow/README.md | 22 + examples/experimental_workflow/demo.py | 34 + .../github_snapshot.json | 46 + examples/experimental_workflow/manifest.json | 190 ++++ examples/generic_run/manifest.json | 8 +- examples/simulacrabench_synthetic/README.md | 5 + experiments/INDEX.md | 19 + experiments/zizk_vstd/experiment.json | 326 +++++++ pyproject.toml | 11 +- scripts/build_experiment_index.py | 118 +++ scripts/build_reference.py | 427 +++++++++ scripts/check_presentation.py | 60 +- .../experimental_workflow/__init__.py | 34 + src/verifier/experimental_workflow/github.py | 216 +++++ src/verifier/experimental_workflow/profile.py | 705 ++++++++++++++ src/verifier/experimental_workflow/schema.py | 273 ++++++ tests/test_experimental_workflow_profile.py | 273 ++++++ tests/test_presentation_surface.py | 42 + 33 files changed, 4394 insertions(+), 31 deletions(-) create mode 100644 CLAUDE.md create mode 100644 docs/profiles/experimental-workflow.md create mode 100644 docs/profiles/experimental-workflow.schema.json create mode 100644 docs/reference.html create mode 100644 examples/experimental_workflow/README.md create mode 100644 examples/experimental_workflow/demo.py create mode 100644 examples/experimental_workflow/github_snapshot.json create mode 100644 examples/experimental_workflow/manifest.json create mode 100644 experiments/INDEX.md create mode 100644 experiments/zizk_vstd/experiment.json create mode 100644 scripts/build_experiment_index.py create mode 100644 scripts/build_reference.py create mode 100644 src/verifier/experimental_workflow/__init__.py create mode 100644 src/verifier/experimental_workflow/github.py create mode 100644 src/verifier/experimental_workflow/profile.py create mode 100644 src/verifier/experimental_workflow/schema.py create mode 100644 tests/test_experimental_workflow_profile.py diff --git a/AGENTS.md b/AGENTS.md index 01d445e..4d1bd13 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,9 +4,11 @@ Working rules for automated contributors to VSTD. Read this before editing anyth ## 1. What this repository is -VSTD is a **specification** plus its **reference implementation** for portable, bounded, -refutable evidence about computational claims. The distribution is `verifier-standard`, -the import package is `verifier`, and `vstd` is the canonical command. +VSTD is a **verification domain language** plus its **reference implementation** for +portable, bounded, refutable evidence about computational claims. It standardizes claim +boundaries and portable result semantics across domain verifiers without replacing their +native work. The distribution is `verifier-standard`, the import package is `verifier`, +and `vstd` is the canonical command. Two independent axes: `VSTD-1..5` (object mechanics) and `VSTD-Graph-1..5` (collection dynamics). Layers 1-4 are implemented; **layer 5 is DRAFT**. An aggregate depth of `N` @@ -41,7 +43,9 @@ reason, not a pass. See [`CONTRIBUTING.md`](CONTRIBUTING.md). python -m pip install ".[test]" python -m pytest -q python scripts/check_presentation.py +python scripts/build_reference.py --check python -m compileall -q src scripts +PYTHONPATH=src python scripts/build_experiment_index.py --check ``` Stdlib-purity smoke, mirroring the `stdlib-smoke` CI job: @@ -80,7 +84,11 @@ If that path is not inside this repository, prefix commands with `PYTHONPATH=src - `src/verifier/runtime/` — `public_cli.py` (every CLI entry point) and `demo.py`. - `src/verifier/specifications/` — byte-identical copies of normative spec files. - `receipts/schema/` — JSON Schemas. `examples/` — runnable specimens. -- `scripts/` — `check_presentation.py`, `release_artifacts.py`, `build_pages.py`. +- `experiments/` — non-normative studies with profile manifests and explicit blockers. +- `src/verifier/experimental_workflow/` — optional workflow/profile interchange; it + records allocation but never grants a VSTD verdict from repository state. +- `scripts/` — `check_presentation.py`, `release_artifacts.py`, `build_pages.py`, + `build_reference.py`, and `build_experiment_index.py`. - `tests/` — flat `tests/test_*.py`, no `conftest.py`. ## 5. Invariants that must not be refactored away @@ -103,8 +111,9 @@ keeps import cost near zero. Do not convert these into eager imports. **Console scripts.** `vstd`, `verifier`, and `verifiable` all map to `verifier.runtime.public_cli:main`. `vstd` is canonical because an unqualified `verifier` on Windows commonly resolves to Windows Driver Verifier. `verifiable` is a **permanent** -alias: published receipts bind it in falsification instructions, so removing it would -render already-published refutation steps unrunnable. +alias: receipts in the `v0.1.0` and `v0.2.0` release artifacts bind it in falsification +instructions, so removing it would render already-published refutation steps unrunnable. +The evidence is the published releases, not a file in the current checkout. **Frozen wire identifiers.** `VSTD-0.1`, `VSTD-0.2`, `VSTD-3.0`, and `VSTD-DATA-0.1` are frozen; readers dispatch on them, not on filenames. Released artifacts are immutable and @@ -140,6 +149,7 @@ CRLF/LF equivalence as byte identity. This matters when working on Windows. disclosure, explicit non-goals). Do not reword those sentences casually; - a local Windows or home-directory path leaked into committed content; - a change to the overview asset dimensions or its accessibility role. +- a stale generated CLI/API reference or experiment index. The `conformance-gate` job requires `base`, `stdlib-smoke`, `release-integrity`, `installed-wheel-smoke`, and `presentation` to all succeed. @@ -167,8 +177,11 @@ assertion to make a suite green. Work lands via pull request into `main`. `.github/PULL_REQUEST_TEMPLATE.md` requires a Coordinate (layer, release, seam), a falsification condition, and compatibility plus -frozen-wire impact. Commit subjects are short and imperative. Do not run release or tag -workflows; [`RELEASING.md`](RELEASING.md) is a maintainer procedure. +frozen-wire impact. Commit subjects are short and imperative. Every commit is GPG-signed; +never bypass a signing failure with an unsigned commit. A signature binds commit bytes to +a key but does not establish identity, correctness, independence, authorization, or +safety. Do not run release or tag workflows; [`RELEASING.md`](RELEASING.md) is a +maintainer procedure. `.github/workflows/pages.yml` publishes the `scripts/build_pages.py` output to GitHub Pages on every push to `main`. Documentation and schema edits become public the moment they merge, diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c115e62 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,4 @@ +# CLAUDE.md + +See [AGENTS.md](AGENTS.md). It is the single source of working rules for this repository, +shared by every automated contributor regardless of harness. Read it before editing. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 370fb35..685538e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,6 +23,13 @@ Section 3 patent terms and Section 5 contribution terms. The project does not ye a separate contributor license agreement or standards-venue patent policy; this is a known boundary for future standards-venue work. +## Commits + +Commits in this repository are GPG-signed (`git commit -S`). Pull requests are expected to +carry signed commits, and automated contributors must never bypass signing. A commit +signature binds bytes to a signing key; it does not establish the signer's identity, the +change's correctness, independence, authorization, or safety. + ## Feedback that does not require a proposed patch Use the structured issue forms for specification ambiguities, counterexamples or diff --git a/README.md b/README.md index cf8ee1a..4277b7a 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,12 @@ emit each specimen. ## What VSTD adds +VSTD is a verification domain language and interchange layer. It does not replace domain +verifiers, proof engines, signatures, identity systems, transparency logs, or provenance +formats. Those systems keep producing their native results; VSTD standardizes the claim +boundary and portable result semantics used to map them across systems without silently +upgrading what they establish. + Ordinary computational results often omit machine-readable answers to four questions: 1. **What exactly was claimed?** The subject, predicate, parameters, and limits. @@ -100,6 +106,7 @@ separately in [`standard/WIRE_IDENTIFIERS.md`](standard/WIRE_IDENTIFIERS.md). | Inspect a disclosure-bounded closed evaluation | [`examples/simulacrabench_synthetic`](examples/simulacrabench_synthetic) | | Implement an independent checker | [`standard/VSTD-4.md`](standard/VSTD-4.md) and [`VSTD4-GDC-1` schema](receipts/schema/vstd4_certificate.json) | | Model a provenance collection | [`standard/VSTD-Graph-1.md`](standard/VSTD-Graph-1.md) | +| Record and allocate bounded experimental work | [`docs/profiles/experimental-workflow.md`](docs/profiles/experimental-workflow.md) and [`experiments/INDEX.md`](experiments/INDEX.md) | | Integrate accelerator evidence | [`docs/layers/vstd-3/vendor-integration.md`](docs/layers/vstd-3/vendor-integration.md) | | Use VSTD beside existing supply-chain/provenance systems | [`docs/ECOSYSTEM.md`](docs/ECOSYSTEM.md) | | Review exact public claim limits | [`docs/CLAIMS_AND_LIMITS.md`](docs/CLAIMS_AND_LIMITS.md) | diff --git a/ROADMAP.md b/ROADMAP.md index 1d209b8..582418e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -93,22 +93,23 @@ claim → evidence → bounded check → publish → challenge → adjudicate No arrow in that loop upgrades one VSTD layer with another layer's evidence. Each layer still requires its own evidence; the loop only carries results and challenges. -## Current branch-backed development tracks +## Current experimental development tracks -This dated register records substantive local work as of **2026-08-24**. These branches -are not merged into `main`, pushed for public review, normative, released, or evidence of -adoption merely because they exist. Branch names are operational coordinates, not -experiment identities; the experimental-workflow profile is intended to replace this -manual table with portable manifests and a generated index. +This dated register records substantive work as of **2026-08-24**. A committed experiment, +local branch, passing test, or generated index is not normative, released, independently +reproduced, or evidence of adoption merely because it exists. Branch names are operational +coordinates rather than experiment identities. Profile manifests and the generated +[`experiments/INDEX.md`](experiments/INDEX.md) are the portable experiment register when +intentional experiment artifacts are present. | Track | Local branch or branches | Current boundary | Roadmap disposition | |---|---|---|---| | Public surface and reserved `.vstd` lockfile | `codex/public-surface-lockfile-audit` | Uncommitted local draft at `main`; mixed changes must be separated and audited before integration. | Review the specification as reserved and non-normative; do not imply an implemented lockfile. | | Documentation lineage and precedents | `codex/documentation-lineage` | One signed local commit; unmerged and unpublished. | Review source accuracy and merge only compatibility-preserving documentation. | | SCITT interoperability | `codex/scitt-interop` | One signed local commit; experimental adapter, examples, adversarial tests, and audit; unmerged and unpublished. | Preserve SCITT as an adjacent transparency/receipt substrate and VSTD as bounded verification semantics. | -| ZIZK experiments | `codex/zizk-zero-knowledge`, `claude/zizk-zero-identity`, `claude/zizk-reverification` | Signed local experiment chain; unmerged and unpublished. | Keep zero knowledge, identity minimization, and reverification experimental until their claim boundaries and trustless substrate survive joint review. | +| ZIZK experiments | [`experiments/zizk_vstd/experiment.json`](experiments/zizk_vstd/experiment.json) | Integrated experimental code, reports, fixtures, bounded workflow manifest, and unresolved horizons; not a normative ZIZK profile or completed protocol. | Keep zero knowledge, identity minimization, and reverification experimental until their claim boundaries and trustless substrate survive joint review. | | Verifier-guided SAT routing | `codex/verifier-guided-sat` | Real SAT/native-verifier baseline infrastructure exists locally, but the required live LM evaluation is hard-blocked and no result commit exists. | Preserve the blocker; do not substitute fake LM evidence or claim speed/generalization. | -| Verification allocation and experimental workflows | `codex/verification-allocation-roadmap` | Roadmap definition only; no allocation engine or profile conformance claim. | Specify the platform-independent profile, then a GitHub adapter, then dogfood it on the SCITT, ZIZK, and SAT tracks. | +| Verification allocation and experimental workflows | [`docs/profiles/experimental-workflow.md`](docs/profiles/experimental-workflow.md) | Experimental profile 0.1, strict validator, schema, verdict-neutral GitHub adapter, generated index, and ZIZK dogfood manifest are implemented. No allocation engine, optimality claim, or independent consumer exists. | Add non-GitHub adapters and index SCITT and SAT only when their intentional artifacts and blockers are ready to bind. | Historical release branches, pre-rename branches, and already-merged branches are repository-maintenance concerns rather than active roadmap tracks. Their continued local @@ -144,7 +145,7 @@ existence does not make their older semantics candidates for reintegration. ## Milestone 3 — experimental-workflow and agent-work profiles -**Build** +**Implemented in experimental profile 0.1** - a platform-independent, non-normative experimental-workflow profile for questions, hypotheses, preregistration, interventions, observations, native-verifier results, @@ -153,6 +154,11 @@ existence does not make their older semantics candidates for reintegration. and merges without treating repository state as a verification verdict; - bounded verification-allocation records that preserve the policy, reason, budget, deferred surface, and native outcome without assigning truth by priority; +- deterministic canonicalization, repository-artifact binding, a generated experiment + index, adversarial tests, and a ZIZK dogfood manifest. + +**Still build** + - an agent-harness specialization for observable user, agent, and tool messages; - bindings for repository state, patches, file reads, commands, outputs, tests, failures, retries, and final claims; diff --git a/docs/CLAIMS_AND_LIMITS.md b/docs/CLAIMS_AND_LIMITS.md index 5533a9e..a96ad17 100644 --- a/docs/CLAIMS_AND_LIMITS.md +++ b/docs/CLAIMS_AND_LIMITS.md @@ -9,9 +9,9 @@ conflicts with the bounded wording here, the bounded wording controls. A VSTD result always has this form: -> For this identified subject snapshot, this declared verification surface passed this -> identified mechanism using this bound evidence, subject to these limitations, trust -> roots, and horizons. +> For this **identified subject snapshot**, this **identified mechanism** returned this +> **bounded result** over this **declared verification surface**, using this +> **bound evidence**, subject to these **limitations**, **trust roots**, and **horizons**. Omitting any bolded idea changes the claim. `VERIFIED` never means universally true, safe, complete, permanent, legally authorized, or endorsed. @@ -33,6 +33,7 @@ safe, complete, permanent, legally authorized, or endorsed. | “This was independently verified.” | **Only when the relevant independence seam is demonstrated.** | Independence requires separation from the producer's relevant state and logic plus a declared trusted computing base. | Producer/auditor boundary, TCB, source identities, isolation evidence, independent result. | Running the bundled verifier on its own output is automatically independent. | | “This verification surface is self-closed.” | **Only if every VSTD-2 self-closure condition passes.** | Self-closure requires ordinary closure, resolved material residuals, discharged valences, post-verified mechanisms, no unresolved trust-root horizon, and contiguous verification orders. | Complete geometry document and passing closure assessment with no blockers. | Universal truth, infinite regress closure, permanent validity, or verification outside the surface. | | “This competition submission and score are bound together.” | **Yes, conditionally.** | A receipt can bind identified submission bytes, evaluator version, raw metrics, and deterministic score derivation. | Submission digest, evaluator/scorer identity, environment, raw metrics, score rule, receipt. | Hidden-test integrity, no leakage, leaderboard ranking, prize eligibility, or organizer acceptance. | +| “This native verifier result was mapped into VSTD.” | **Yes, when the mapping preserves the native object, result, trust roots, bounds, and unsupported fields.** | VSTD can standardize the claim boundary and portable result semantics around a domain verifier without performing that verifier's native work. | Native object and version, native verifier implementation/version, native result, field-level mapping, information-loss declaration, VSTD coordinate, adapter tests. | VSTD replaced or reimplemented the native verifier, strengthened its result, inherited its authority, or established conformance to the source standard. | | “A challenge to this recorded ancestor affects these recorded descendants.” | **Yes.** | Blast radius is forward reachability over the stored graph. | Challenged artifact ID and bound hypergraph. | Historical receipts were automatically mutated or that unrecorded downstream systems were found. | ## VSTD-4 grounded-decision claim translations diff --git a/docs/ECOSYSTEM.md b/docs/ECOSYSTEM.md index 62ef450..8a650ca 100644 --- a/docs/ECOSYSTEM.md +++ b/docs/ECOSYSTEM.md @@ -7,6 +7,12 @@ VSTD is designed to compose with established provenance, software-supply-chain, artifact-authentication systems. It does not rename their guarantees as its own and does not claim to replace them. +In this composition, VSTD supplies the common operator vocabulary: claim coordinates, +evidence references, bounds, native outcomes, and degradation rules. A domain verifier +or proof system remains the orchestrated mechanism that performs the actual domain check. +An adapter maps between those roles; it does not transfer the native verifier's authority +to VSTD or let a VSTD result strengthen the native result. + | System | Its documented center of gravity | What VSTD may bind or add | What VSTD must not claim | |---|---|---|---| | [SLSA v1.2](https://slsa.dev/spec/v1.2/) | Levels and tracks for incrementally improving software supply-chain security, including recommended provenance and verification-summary formats. | A SLSA statement or verification summary as evidence under an explicit VSTD claim coordinate; separate refutation and degradation conditions. | That a VSTD receipt establishes a SLSA level without satisfying and assessing the relevant SLSA requirements. | diff --git a/docs/assets/site.css b/docs/assets/site.css index 68d667a..038930b 100644 --- a/docs/assets/site.css +++ b/docs/assets/site.css @@ -47,6 +47,11 @@ nav { .eyebrow { color: var(--teal); font-size: .78rem; font-weight: 780; letter-spacing: .16em; text-transform: uppercase; } h1 { max-width: 760px; margin: 12px 0 22px; font-size: clamp(2.8rem, 6vw, 5.6rem); line-height: .98; letter-spacing: -.055em; } .lead { color: #c6d6da; font-size: clamp(1.12rem, 2vw, 1.34rem); max-width: 670px; } +.lead-defs { margin: 18px 0 0; max-width: 670px; color: #94a9ae; font-size: 0.95rem; line-height: 1.55; border-left: 2px solid #24373c; padding-left: 16px; } +.lead-defs dt { color: #c6d6da; font-weight: 600; letter-spacing: 0.01em; } +.lead-defs dd { margin: 2px 0 12px; } +.lead-defs dd:last-child { margin-bottom: 0; } +.lead-close { margin-top: 18px; } .actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 30px; } .button { display: inline-flex; align-items: center; min-height: 46px; padding: 0 18px; border: 1px solid var(--line); border-radius: 9px; color: var(--ink); text-decoration: none; font-weight: 700; background: rgba(14, 39, 52, .72); } .button.primary { color: #061a1b; background: var(--teal); border-color: var(--teal); } @@ -72,6 +77,24 @@ pre { margin: 0; padding: 24px; color: #dcebed; font: 500 .92rem/1.75 ui-monospa .boundary ul { margin: 10px 0 0; padding-left: 20px; color: var(--muted); } .status { border-left: 3px solid var(--amber); padding: 3px 0 3px 18px; color: #d6e2e5; max-width: 850px; } +.ref-hero { padding: 54px 0 10px; } +.ref-hero h1 { font-size: clamp(2.4rem, 5vw, 4.2rem); margin-bottom: 18px; } +.ref-hero .status { margin-top: 20px; } +.ref-table, .ref-list { margin-top: 26px; } +.ref-table { overflow-x: auto; } +table { width: 100%; border-collapse: collapse; font-size: .93rem; } +th, td { text-align: left; vertical-align: top; padding: 10px 14px; border-bottom: 1px solid var(--line); } +th { color: var(--teal); font-size: .74rem; letter-spacing: .12em; text-transform: uppercase; } +td { color: var(--muted); } +td code, .ref-help code, .section-lead code, .lead code, li code, p code { color: #dcebed; background: rgba(9, 30, 41, .8); border: 1px solid var(--line); border-radius: 6px; padding: 1px 6px; font: 500 .86em/1.5 ui-monospace, SFMono-Regular, Consolas, monospace; } +.ref-item { border: 1px solid var(--line); border-radius: 15px; background: var(--panel); padding: 20px 22px; margin-bottom: 14px; overflow-x: auto; } +.ref-item h3 { margin: 0 0 8px; font-size: 1.05rem; } +.ref-item h3 code { background: none; border: none; padding: 0; color: var(--ink); font-size: 1em; } +.ref-tag { color: var(--amber); font-size: .7rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; } +.ref-help, .ref-none, .ref-source { color: var(--muted); font-size: .93rem; margin: 0 0 12px; } +.ref-source { font-size: .84rem; } +.ref-signature { padding: 14px 16px; margin: 0 0 12px; font-size: .84rem; border-radius: 10px; overflow-x: auto; } + footer { border-top: 1px solid var(--line); margin-top: 54px; padding: 28px 0 44px; color: var(--muted); font-size: .9rem; } @media (max-width: 900px) { diff --git a/docs/index.html b/docs/index.html index 8c24c75..1c28fc9 100644 --- a/docs/index.html +++ b/docs/index.html @@ -16,8 +16,10 @@