diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index ebda2273a..346196404 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -161,3 +161,63 @@ jobs: - name: Integer rANS + int_synth + JPEG-AI mANS goldens (one machine → every machine) working-directory: wai/byte-exact-conformance run: cargo test --release + + # The C2PA emitter is gated behind an off-by-default feature, so the four-platform + # job above — which builds a fixed feature list — never compiles it. A feature no + # workflow enables has no CI coverage at all: the suite passes, and the tests in it + # simply do not run. + # + # One platform, deliberately. Everything the matrix above exists to prove is about + # byte-identical reconstruction across architectures; a signed manifest makes no + # such claim and would not become more true for being emitted on four machines. What + # this job proves is that the manifest is still well-formed, still carries the three + # assertions, and still validates — which is architecture-independent. + c2pa-emitter: + name: C2PA manifest emission · signed, read back, validated + runs-on: linux + steps: + - uses: actions/checkout@v4 + + - name: Install Rust 1.98.0 + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + command -v rustup >/dev/null 2>&1 || \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0 + rustup toolchain install 1.98.0 --profile minimal + rustup default 1.98.0 + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + + # `rust_native_crypto` rather than the SDK's default openssl backend, so this + # job needs no system libraries — the same property the matrix above depends on. + # Asserted, not assumed: an openssl-linked build would still pass the tests here + # while quietly making the crate undistributable to a bare runner. + - name: The emitter pulls in no system crypto + working-directory: wai/wai-rs + shell: bash + run: | + for dep in openssl openssl-sys; do + if cargo tree --no-default-features --features c2pa_emit -i "$dep" >/dev/null 2>&1; then + echo "::error::$dep reached the c2pa_emit dependency graph" + exit 1 + fi + done + echo "no system crypto in the graph" + + - name: Emit, sign, read back, validate + working-directory: wai/wai-rs + run: cargo test --lib --no-default-features --features c2pa_emit c2pa_emit + + # The feature must stay optional. If `provenance` ever pulls c2pa in, every + # consumer that wanted only the binding types inherits a large dependency tree + # they did not ask for, and the "off by default" claim in the extension is false. + - name: The feature stays optional + working-directory: wai/wai-rs + shell: bash + run: | + if cargo tree --no-default-features --features provenance -i c2pa >/dev/null 2>&1; then + echo "::error::c2pa is reachable from the provenance feature; it must stay opt-in" + exit 1 + fi + cargo build --no-default-features --features provenance + echo "provenance builds without c2pa" diff --git a/wai/extensions/energy-binding.md b/wai/extensions/energy-binding.md index db301d887..37e04960f 100644 --- a/wai/extensions/energy-binding.md +++ b/wai/extensions/energy-binding.md @@ -5,7 +5,8 @@ > `c2pa.environmental-sustainability` assertion — supplying the acquisition > class, the coverage, and the outcome binding the standard assertion has no > field for. Reference impl: the `energy_binding` module of `wai-rs` -> (feature `provenance`). Companion to +> (feature `provenance`), emitted into a real signed manifest by `c2pa_emit` +> (feature `c2pa_emit`). Companion to > [Reproducible-Reconstruction Binding](reconstruction-binding.md) and the > acquisition protocol in [Energy Measurement](energy-measurement.md). > Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. @@ -100,3 +101,67 @@ already better than the common case. A validator that understands this extension additionally learns how the figure was acquired, how much of the work it covers, and which reconstruction it describes — and can check the last of those itself. + +## 6. Emitting a manifest + +The assertion shapes above are emitted into an actual signed C2PA manifest by the +`c2pa_emit` module of `wai-rs`. Four decisions in that emitter are normative for +anyone writing their own, because each is a place where the obvious +implementation produces a manifest that validators reject, or that overstates +what a decoder knows. + +### 6.1 The inception action is `c2pa.opened`, not `c2pa.created` + +A manifest **MUST NOT** describe a reconstruction with `c2pa.created`. + +C2PA 2.x requires every `c2pa.created` action to carry a `digitalSourceType`, and +that vocabulary describes how the *content* came to exist — captured, drawn by a +human, generated by a model. A decoder does not know that. It reconstructed +pixels from a codestream, and whatever was encoded could have been any of them. +Selecting `trainedAlgorithmicMedia` because a codec has a neural stage would +assert that a compressed photograph is AI-generated content, which is false, and +the vocabulary has no member meaning "decoded; origin not visible from here". + +A decode is in any case not a creation. It is a conversion of an existing asset +into another representation, which C2PA already models: the codestream is a +`parentOf` ingredient, the manifest **opens** it, and `c2pa.converted` records +what was done. No action asserts a `digitalSourceType`, so no claim about the +original's origin is made anywhere — which is precisely the position a decoder is +in. + +### 6.2 The codestream is carried as the parent ingredient + +An emitted manifest **MUST** carry the codestream as a `parentOf` ingredient. + +§3 turns on a verifier being able to re-decode. A manifest that carries the +reconstruction digest but does not name what produces it hands the verifier a +digest and no way to reach it. The digest says *which* decode the joules were +measured over; the parent ingredient says *what to decode to get there*. Both +halves, or the check §3 describes is not actually available. + +### 6.3 The bindings are `created` assertions, not `gathered` + +The bindings **MUST** be marked as attributed to the signer. + +C2PA distinguishes assertions the signer stands behind from material gathered +elsewhere and carried along — the same distinction between a claim and hearsay +that the acquisition ladder draws inside the figure itself. These are first-party +statements: the signer measured the joules and hashed the pixels. + +This is load-bearing rather than editorial. A validator looks for the inception +action among the created assertions, so an actions assertion left gathered yields +a manifest that signs without complaint and every validator then rejects as +`assertion.action.malformed` — a failure that surfaces only on the far side of a +signature. + +### 6.4 What a signature does not settle + +An emitted manifest signed by an untrusted certificate reaches +`ValidationState::Valid` — well-formed, signature intact, asset unmodified — and +not `ValidationState::Trusted`, which additionally requires a certificate +chaining to a known authority. An implementation **MUST NOT** present the first +as though it were the second. + +The gap between them is the whole of *who says so*, and it is orthogonal to +everything §3 makes checkable: a perfectly reproducible reconstruction signed by +nobody in particular is still a claim from nobody in particular. diff --git a/wai/wai-rs/Cargo.lock b/wai/wai-rs/Cargo.lock index 2595cd771..1b102d1c2 100644 --- a/wai/wai-rs/Cargo.lock +++ b/wai/wai-rs/Cargo.lock @@ -2,12 +2,40 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "abnf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a" +dependencies = [ + "abnf-core", + "nom 7.1.3", +] + +[[package]] +name = "abnf-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + [[package]] name = "aligned" version = "0.4.3" @@ -26,6 +54,30 @@ dependencies = [ "equator", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[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" @@ -64,6 +116,73 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom 7.1.3", + "num-traits", + "rusticata-macros", + "thiserror 2.0.20", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-generic" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3728566eefa873833159754f5732fb0951d3649e6e5b891cc70d56dd41673" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "atree" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239d25181cb40f1955529367ee495e35d03aab4e578028f41e7abc8b21c367a8" + [[package]] name = "audiopus_sys" version = "0.2.2" @@ -123,7 +242,7 @@ dependencies = [ "anyhow", "arrayvec", "log", - "nom", + "nom 8.0.0", "num-rational", "v_frame", ] @@ -137,6 +256,18 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64" version = "0.23.1" @@ -149,6 +280,22 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bcder" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b593e5aeaf7992d388c08a9831c921cd703718064b3e50ba8e6d666d6cf86ca7" +dependencies = [ + "bytes", + "smallvec", +] + +[[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" @@ -170,6 +317,28 @@ dependencies = [ "no_std_io2", ] +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bitvec-nom2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d988fcc40055ceaa85edc55875a08f8abd29018582647fd82ad6128dba14a5f0" +dependencies = [ + "bitvec", + "nom 7.1.3", +] + [[package]] name = "blake3" version = "1.8.7" @@ -192,6 +361,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bon" version = "3.10.0" @@ -207,7 +385,7 @@ version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4654961ad0494e4774c5c60b4cb4cd0ae9b9d92d039d901638b1dba97ebebf5" dependencies = [ - "darling", + "darling 0.24.1", "ident_case", "prettyplease", "proc-macro2", @@ -215,6 +393,60 @@ dependencies = [ "syn 3.0.4", ] +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "btree-range-map" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33" +dependencies = [ + "btree-slab", + "cc-traits", + "range-traits", + "serde", + "slab", +] + +[[package]] +name = "btree-slab" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c" +dependencies = [ + "cc-traits", + "slab", + "smallvec", +] + [[package]] name = "built" version = "0.7.7" @@ -257,12 +489,118 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" +[[package]] +name = "byteordered" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf2cd9424f5ff404aba1959c835cbc448ee8b689b870a9981c76c0fd46280e6" +dependencies = [ + "byteorder", +] + [[package]] name = "bytes" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +[[package]] +name = "c2pa" +version = "0.90.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3dc371ad12a753154b5ba21977ad62d4198539f11ceeeb1c0a47c1ab333732d" +dependencies = [ + "asn1-rs", + "async-generic", + "async-trait", + "atree", + "base64 0.22.1", + "bcder", + "brotli", + "byteorder", + "byteordered", + "bytes", + "c2pa_cbor", + "chrono", + "console_log", + "const-hex", + "const-oid 0.9.6", + "coset", + "der", + "ecdsa", + "ed25519-dalek", + "extfmt", + "getrandom 0.2.17", + "getrandom 0.3.4", + "glob", + "hex", + "http", + "id3", + "img-parts", + "iref", + "jfifdump", + "js-sys", + "lazy_static", + "log", + "memchr", + "nom 7.1.3", + "non-empty-string", + "nonempty-collections", + "num-bigint-dig", + "p256", + "p384", + "p521", + "pem", + "pkcs1", + "pkcs8", + "png_pong", + "quick-xml", + "rand 0.8.8", + "rand_chacha 0.9.0", + "range-set", + "rasn", + "rasn-cms", + "rasn-ocsp", + "rasn-pkix", + "regex", + "riff", + "rsa", + "serde", + "serde-transcode", + "serde-wasm-bindgen", + "serde_bytes", + "serde_derive", + "serde_json", + "serde_with", + "sha1", + "sha2 0.11.0", + "spki", + "static-iref", + "tempfile", + "thiserror 2.0.20", + "toml 1.1.4+spec-1.1.0", + "url", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "x509-parser", + "zeroize", + "zip", +] + +[[package]] +name = "c2pa_cbor" +version = "0.77.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b057a673824daf7a5bff38680744d7a082b61edc012c8a6dc0ebfc04c79a08c" +dependencies = [ + "half", + "serde", + "serde_bytes", +] + [[package]] name = "cbindgen" version = "0.29.4" @@ -270,7 +608,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ecb53484c9c167ba674026b656d8a27d7657a58e6066aa902bfb1a4aa00ae20" dependencies = [ "heck", - "indexmap", + "indexmap 2.14.0", "log", "proc-macro2", "quote", @@ -293,6 +631,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cc-traits" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5" +dependencies = [ + "slab", +] + [[package]] name = "cfg-expr" version = "0.15.8" @@ -319,6 +666,47 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "claxon" version = "0.4.3" @@ -334,18 +722,63 @@ dependencies = [ "cc", ] +[[package]] +name = "console_log" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86919cef3e37b9356ccf54d4421208c17ecfda01beae61393e7ffd72916c0ef1" +dependencies = [ + "log", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "const-hex" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "constant_time_eq" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "coset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1eb98d5e9155e2cf7cd942c8b3033097d4563b6fb0a00b9caecb74669555c058" +dependencies = [ + "ciborium", + "ciborium-io", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -404,6 +837,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -414,6 +859,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -423,7 +877,7 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", @@ -441,14 +895,37 @@ dependencies = [ "syn 2.0.119", ] +[[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" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.24.1", + "darling_macro 0.24.1", +] + +[[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]] @@ -464,17 +941,34 @@ dependencies = [ "syn 3.0.4", ] +[[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 = "darling_macro" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ - "darling_core", + "darling_core 0.24.1", "quote", "syn 3.0.4", ] +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + [[package]] name = "dav1d" version = "0.11.1" @@ -482,7 +976,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee89cb860616069c67520dcd66cacdb900b57c799f634a0eb6d91f6e2a82b61" dependencies = [ "av-data", - "bitflags", + "bitflags 2.13.1", "dav1d-sys", "static_assertions", ] @@ -497,24 +991,135 @@ dependencies = [ "system-deps 7.0.8", ] +[[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 2.0.20", +] + +[[package]] +name = "delegate" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "082a24a9967533dc5d743c602157637116fc1b52806d694a5a45e6f32567fcdd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", + "pem-rfc7468", "zeroize", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom 7.1.3", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.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.4", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "sha2 0.10.9", + "signature", + "spki", ] [[package]] @@ -535,8 +1140,10 @@ checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", + "rand_core 0.6.4", "serde", - "sha2", + "sha2 0.10.9", + "signature", "subtle", "zeroize", ] @@ -547,6 +1154,27 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "equator" version = "0.4.2" @@ -583,6 +1211,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "extfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a61bffc6f807b136c3efeeac295edde2c65b1e345de7ea777e75f63de7436c6" + [[package]] name = "fastrand" version = "2.5.0" @@ -598,6 +1232,16 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -632,7 +1276,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.9", +] + +[[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 = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[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-task", + "pin-project-lite", + "slab", ] [[package]] @@ -643,6 +1326,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -652,8 +1336,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -663,9 +1349,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -679,6 +1367,23 @@ dependencies = [ "r-efi 6.0.0", ] +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "half" version = "2.7.1" @@ -690,6 +1395,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.17.1" @@ -702,6 +1413,36 @@ 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_fmt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "hmac-sha256" version = "1.1.14" @@ -724,12 +1465,160 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + +[[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 = "id3" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24993fcabcbc07c8ac076a8e62db8593d1a5c4dbe81d57e531d2b7cb7f737380" +dependencies = [ + "bitflags 2.13.1", + "byteorder", + "flate2", +] + [[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 = "image" version = "0.25.10" @@ -758,12 +1647,34 @@ dependencies = [ "quick-error", ] +[[package]] +name = "img-parts" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19734e3c43b2a850f5889c077056e47c874095f2d87e853c7c41214ae67375f0" +dependencies = [ + "bytes", + "crc32fast", + "miniz_oxide 0.8.9", +] + [[package]] name = "imgref" version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7" +[[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" @@ -771,7 +1682,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", ] [[package]] @@ -785,6 +1707,28 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "iref" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374372d9ca7331cec26f307b12552554849143e6b2077be3553576aa9aa8258c" +dependencies = [ + "iref-core", +] + +[[package]] +name = "iref-core" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10559a0d518effd4f2cee107f40f83acf8583dcd3e6760b9b60293b0d2c2a70" +dependencies = [ + "pct-str", + "serde", + "smallvec", + "static-regular-grammar", + "thiserror 1.0.69", +] + [[package]] name = "itertools" version = "0.12.1" @@ -794,6 +1738,15 @@ dependencies = [ "either", ] +[[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" @@ -809,6 +1762,65 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jfifdump" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68cf72bc7b75b6615ffd06bfe6840f3c57e7e4ea615558a2a452f458ebf5551e" + +[[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 = "jobserver" version = "0.1.35" @@ -848,6 +1860,26 @@ dependencies = [ "pkg-config", ] +[[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 = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + [[package]] name = "libavif" version = "0.14.0" @@ -904,12 +1936,24 @@ dependencies = [ "cc", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[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" @@ -968,6 +2012,12 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -978,6 +2028,16 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "moxcms" version = "0.8.1" @@ -1027,6 +2087,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nom" version = "8.0.0" @@ -1036,6 +2106,25 @@ dependencies = [ "memchr", ] +[[package]] +name = "non-empty-string" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "260010741d90def3ca7c4b5ec0bbe28c26d0c775d3c7d291b38642514136677a" +dependencies = [ + "delegate", + "serde", +] + +[[package]] +name = "nonempty-collections" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "988fb92b275335f315a9bddf7a8881a02c79c6084e9a626f656f12c50776a045" +dependencies = [ + "serde", +] + [[package]] name = "noop_proc_macro" version = "0.3.0" @@ -1052,6 +2141,23 @@ dependencies = [ "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.8", + "serde", + "smallvec", + "zeroize", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -1061,6 +2167,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-derive" version = "0.4.2" @@ -1081,6 +2193,16 @@ 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-rational" version = "0.4.2" @@ -1099,6 +2221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1110,6 +2233,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -1149,6 +2281,53 @@ dependencies = [ "ureq", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2 0.10.9", +] + +[[package]] +name = "parsenic" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c695d2b8bcf1dd62a5173a9c43e6ebbe9261701c002f9b462fc9690c144bb61" +dependencies = [ + "traitful", +] + [[package]] name = "paste" version = "1.0.15" @@ -1161,6 +2340,35 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +[[package]] +name = "pct-str" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf1bdcc492c285a50bed60860dfa00b50baf1f60c73c7d6b435b01a2a11fd6ff" +dependencies = [ + "thiserror 1.0.69", + "utf8-decode", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[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" @@ -1173,6 +2381,23 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pix" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a054a84d1ff0dc456386e5fc081e099e6855ddcc8913dc9349c294d47d76bd4" + +[[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" @@ -1195,11 +2420,24 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags", + "bitflags 2.13.1", "crc32fast", "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "png_pong" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb58df2a992d49a178fd69f8c9ead5d4c70014652cd4cca9608a1cb46097aff6" +dependencies = [ + "miniz_oxide 0.9.1", + "parsenic", + "pix", + "simd-adler32", + "traitful", ] [[package]] @@ -1217,6 +2455,21 @@ dependencies = [ "portable-atomic", ] +[[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" @@ -1236,6 +2489,39 @@ dependencies = [ "syn 3.0.4", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -1264,6 +2550,21 @@ dependencies = [ "syn 2.0.119", ] +[[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", + "regex-syntax", + "unarray", +] + [[package]] name = "pxfm" version = "0.1.30" @@ -1276,6 +2577,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.47" @@ -1297,6 +2607,12 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.8" @@ -1356,6 +2672,108 @@ dependencies = [ "getrandom 0.3.4", ] +[[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 = "range-set" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "393b6d845a7f7b955dba8621ad2998590eaa470c1d9a4ee6df0f65354e0ffc31" +dependencies = [ + "num-traits", + "smallvec", +] + +[[package]] +name = "range-traits" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab" + +[[package]] +name = "rasn" +version = "0.28.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d1b71dd951343df0fe30b11bca09518f3aba8e5bd0ece4564adbc2dabd875fa" +dependencies = [ + "bitvec", + "bitvec-nom2", + "bytes", + "cfg-if", + "chrono", + "either", + "nom 7.1.3", + "num-bigint", + "num-integer", + "num-traits", + "once_cell", + "rasn-derive", + "serde_json", + "snafu", + "xml-no-std", +] + +[[package]] +name = "rasn-cms" +version = "0.28.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4abf4c2fe5537b99e2bf3099a7ad26e40bd9cf51bc003600ed58dbbff69a1c" +dependencies = [ + "rasn", + "rasn-pkix", +] + +[[package]] +name = "rasn-derive" +version = "0.28.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "979eafa601d351f7f6490f1d2f4decc168e8e51cb6517c6c6ff80111951129d5" +dependencies = [ + "proc-macro2", + "rasn-derive-impl", + "syn 2.0.119", +] + +[[package]] +name = "rasn-derive-impl" +version = "0.28.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee3136c97d6f2553c0d9f84d2c2555bb87ae9b365c5c9274e8bc5c366174431" +dependencies = [ + "either", + "itertools 0.13.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "uuid", +] + +[[package]] +name = "rasn-ocsp" +version = "0.28.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bf6709bc94437d12614fd2d34463c3478d981bf913286812cc50bb093bc657" +dependencies = [ + "rasn", + "rasn-pkix", +] + +[[package]] +name = "rasn-pkix" +version = "0.28.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c029da7ed3b94dd93b75299431984081c1eaf88ce79771c21b056f0ebf6fb964" +dependencies = [ + "rasn", +] + [[package]] name = "rav1e" version = "0.7.1" @@ -1468,12 +2886,77 @@ dependencies = [ "crossbeam-utils", ] +[[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.4", +] + +[[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 = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rgb" version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +[[package]] +name = "riff" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c601484456988d75017d86700d3743b949c21cdc7399f940c75e34680d185c5" + [[package]] name = "ring" version = "0.17.14" @@ -1488,6 +2971,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid 0.9.6", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "sha2 0.10.9", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -1497,13 +3001,22 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys", @@ -1557,6 +3070,44 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248" +[[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 = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.28" @@ -1573,6 +3124,36 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-transcode" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.229" @@ -1599,7 +3180,7 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ - "indexmap", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -1625,6 +3206,50 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_with" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +dependencies = [ + "base64 0.22.1", + "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 = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha2" version = "0.10.9" @@ -1633,7 +3258,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1648,6 +3284,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ + "digest 0.10.7", "rand_core 0.6.4", ] @@ -1666,12 +3303,39 @@ dependencies = [ "quote", ] +[[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 = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "socks" version = "0.3.4" @@ -1683,6 +3347,12 @@ dependencies = [ "winapi", ] +[[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" @@ -1699,6 +3369,37 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static-iref" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc4068497ae43896d41174586dcdc2153a1af2c82856fb308bfaaddc28e5549" +dependencies = [ + "iref", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "static-regular-grammar" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957" +dependencies = [ + "abnf", + "btree-range-map", + "ciborium", + "hex_fmt", + "indoc", + "proc-macro-error", + "proc-macro2", + "quote", + "serde", + "sha2 0.10.9", + "syn 2.0.119", + "thiserror 1.0.69", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1717,6 +3418,17 @@ 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" @@ -1739,6 +3451,17 @@ dependencies = [ "unicode-ident", ] +[[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 = "system-deps" version = "6.2.2" @@ -1765,6 +3488,12 @@ dependencies = [ "version-compare", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.16" @@ -1830,6 +3559,61 @@ dependencies = [ "syn 3.0.4", ] +[[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.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" +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 = "toml" version = "0.8.23" @@ -1848,7 +3632,7 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap", + "indexmap 2.14.0", "serde_core", "serde_spanned 1.1.1", "toml_datetime 0.7.5+spec-1.1.0", @@ -1863,7 +3647,7 @@ version = "1.1.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ - "indexmap", + "indexmap 2.14.0", "serde_core", "serde_spanned 1.1.1", "toml_datetime 1.1.1+spec-1.1.0", @@ -1905,7 +3689,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap", + "indexmap 2.14.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -1946,12 +3730,35 @@ dependencies = [ "once_cell", ] +[[package]] +name = "traitful" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d856e22ead1fb79b9fc3cec63300086f680924f2f7b0e2701f6835a28b9c4425" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[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" @@ -1970,7 +3777,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" dependencies = [ - "base64", + "base64 0.23.1", "log", "percent-encoding", "rustls", @@ -1987,18 +3794,54 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613" dependencies = [ - "base64", + "base64 0.23.1", "http", "httparse", "log", ] +[[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-decode" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca61eb27fa339aa08826a29f03e87b99b4d8f0fc2255306fd266bb1b6a9de498" + [[package]] name = "utf8-zero" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + [[package]] name = "v_frame" version = "0.3.9" @@ -2028,6 +3871,7 @@ version = "0.3.0" dependencies = [ "blake3", "byteorder", + "c2pa", "cbindgen", "claxon", "dav1d", @@ -2045,7 +3889,7 @@ dependencies = [ "rav1e 0.8.1", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "wai-quantum", "xz2", "zstd", @@ -2054,7 +3898,7 @@ dependencies = [ [[package]] name = "wai-quantum" -version = "0.3.1" +version = "0.3.7" dependencies = [ "blake3", "ed25519-dalek", @@ -2089,6 +3933,16 @@ dependencies = [ "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" @@ -2121,6 +3975,26 @@ dependencies = [ "unicode-ident", ] +[[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" @@ -2152,12 +4026,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[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" @@ -2261,6 +4188,44 @@ 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 = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom 7.1.3", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.20", + "time", +] + +[[package]] +name = "xml-no-std" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd223bc94c615fc02bf2f4bbc22a4a9bfe489c2add3ec10b1038df3aca44cac7" + [[package]] name = "xz2" version = "0.1.7" @@ -2276,6 +4241,29 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" +[[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" @@ -2296,11 +4284,91 @@ dependencies = [ "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.4", +] + +[[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "crc32fast", + "indexmap 2.14.0", + "memchr", + "typed-path", +] [[package]] name = "zmij" diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 9d6e7efa6..95dacf17b 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -59,6 +59,13 @@ ndarray = { version = "0.17", optional = true } zstd-safe = { version = "7", optional = true, features = ["std"] } # for bmshj2018 latent unpack (std → WriteBuf for Vec) sha2 = { version = "0.10", optional = true } # verify a pinned neural prior against the registered model artifact +# ---- real C2PA manifest emission (optional, behind `c2pa_emit`) ---- +# Writes an actual signed manifest carrying the energy + reconstruction +# bindings, rather than only defining their shapes. Off by default: the SDK +# is a large dependency chain and most consumers only need the types. +c2pa = { version = "0.90", optional = true, default-features = false, + features = ["rust_native_crypto"] } + # ---- interactive worlds (optional, behind the `world` feature) ---- # extensions/interactive-worlds: WWLD container, signed CausalOps, # deterministic fixed-point simulation, replay-equivalence conformance. @@ -141,6 +148,10 @@ score = ["world"] # lineage receipt for opaque captured content WAI does not reconstruct. # Reuses the JWP Merkle + Ed25519; no codec/ML deps. provenance = ["world", "dep:sha2"] +# Emit a REAL C2PA manifest (not just the assertion shapes). Off by default: +# the SDK is a large dependency chain and most WAI consumers only need the +# binding types, which `provenance` already gives them. +c2pa_emit = ["provenance", "dep:c2pa"] # wai.world.model — generative-world transport: a signed, replay-equivalent action # trace (reuses the world CausalOp log) + per-frame provenance. The neural sibling # of wai.world.replay; the renderer is a declared world model (sink-supplied, or the diff --git a/wai/wai-rs/src/c2pa_emit.rs b/wai/wai-rs/src/c2pa_emit.rs new file mode 100644 index 000000000..59734b484 --- /dev/null +++ b/wai/wai-rs/src/c2pa_emit.rs @@ -0,0 +1,688 @@ +//! Writes a **real** C2PA manifest carrying the energy and reconstruction bindings. +//! +//! # Why this module exists +//! +//! [`crate::energy_binding`] defines what a WAI reconstruction claims about its own +//! energy, and [`crate::reconstruction_binding`] defines the digest that claim is about. +//! Both produce assertion *bodies*. Neither produces a manifest, and a shape nothing +//! emits is a proposal, not an implementation — the exact gap this module closes. +//! +//! So this signs an asset with the C2PA SDK and puts three assertions in the manifest: +//! the standard `c2pa.environmental-sustainability` for interoperability, and the two +//! companions that make the standard one checkable. +//! +//! # The chain a verifier actually walks +//! +//! Two different bindings do two different jobs, and conflating them is the easy +//! mistake: +//! +//! 1. **C2PA's hard binding** covers the *delivered asset bytes*. Validating the +//! manifest proves those bytes are the ones that were signed. +//! 2. **The reconstruction digest** covers the *canonical decoded pixels*. It survives +//! re-encoding, so it says which decode the energy figure was measured over. +//! +//! A verifier decodes the delivered asset, hashes the pixels, and compares against the +//! sealed digest. It never needs to reproduce the asset bytes it was handed — only to +//! decode them. That is what lets the energy claim attach to a reconstruction rather +//! than to a particular file. +//! +//! # The one rule this module enforces that the SDK cannot +//! +//! `extensions/energy-binding.md` §2 says a manifest whose two digests disagree is +//! self-refuting: the energy figure would be measured over one decode while the +//! reconstruction assertion commits to another, and nothing in the manifest would say +//! which is the subject. The C2PA SDK has no opinion about this — third-party assertion +//! bodies are opaque to it, so it will happily sign the contradiction, and the result +//! validates. +//! +//! [`ReconstructionManifest::new`] therefore refuses to build one. The check is at +//! construction rather than at signing time so the contradiction cannot reach a +//! signature at all. +//! +//! # Signed is not trusted +//! +//! [`untrusted_local_signer`] returns an ephemeral self-signed chain. A manifest it signs +//! reaches [`c2pa::ValidationState::Valid`] — well-formed, signature intact, asset +//! unmodified — and never [`c2pa::ValidationState::Trusted`], which additionally requires +//! a certificate chaining to a known authority. The gap between those two states is the +//! whole of "who says so", so the constructor is named for its limit rather than for +//! what is convenient to assume. Production callers pass their own [`c2pa::Signer`]. + +use c2pa::assertions::{c2pa_action, Action}; +use c2pa::BuilderIntent; +use c2pa::{Builder, ClaimGeneratorInfo, EphemeralSigner, Manifest, Signer}; +use serde_json::Value; +use std::io::{Read, Seek, Write}; + +use crate::energy_binding::EnergyBinding; +use crate::reconstruction_binding::ReconstructionBinding; + +/// Errors from emitting or recovering a bound manifest. +#[derive(Debug)] +pub enum EmitError { + /// The energy figure and the reconstruction assertion name different decodes. The + /// manifest would be self-refuting, so it is refused before it can be signed. + DigestsDisagree { energy: String, reconstruction: String }, + /// A manifest carries the standard sustainability assertion with no companion. This + /// is the common case in the wild and is not an error in C2PA — it is reported as + /// one *here* because such a figure is self-declared, unattributed to any protocol, + /// and not about any reproducible output. + UnaccompaniedStandardAssertion, + /// The manifest carries none of the assertions this module emits. + NoBinding, + /// The SDK failed. + C2pa(c2pa::Error), + /// An assertion body was not the JSON this crate emits. + Malformed(&'static str), +} + +impl core::fmt::Display for EmitError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + EmitError::DigestsDisagree { energy, reconstruction } => write!( + f, + "energy figure is measured over {energy} but the reconstruction \ + assertion commits to {reconstruction}; the manifest would be \ + self-refuting" + ), + EmitError::UnaccompaniedStandardAssertion => write!( + f, + "manifest carries c2pa.environmental-sustainability with no companion \ + binding: the figure is self-declared and describes no reproducible \ + output" + ), + EmitError::NoBinding => write!(f, "manifest carries no WAI binding"), + EmitError::C2pa(e) => write!(f, "c2pa: {e}"), + EmitError::Malformed(w) => write!(f, "malformed assertion body: {w}"), + } + } +} + +impl std::error::Error for EmitError {} + +impl From for EmitError { + fn from(e: c2pa::Error) -> Self { + EmitError::C2pa(e) + } +} + +/// A signer for local use and tests: an ephemeral self-signed certificate chain. +/// +/// Named for its limit. Manifests it signs validate as +/// [`Valid`](c2pa::ValidationState::Valid) but never as +/// [`Trusted`](c2pa::ValidationState::Trusted), because nothing vouches for the +/// certificate. Use it to prove a manifest is well-formed and its assertions survive a +/// round trip; do not use it to publish a claim anyone is expected to rely on. +pub fn untrusted_local_signer() -> Result { + Ok(EphemeralSigner::new("wai-energy-binding.local")?) +} + +/// The three assertions of one reconstruction, checked for self-consistency and ready to +/// be sealed into a manifest. +pub struct ReconstructionManifest<'a> { + energy: &'a EnergyBinding, + reconstruction: &'a ReconstructionBinding, +} + +impl<'a> ReconstructionManifest<'a> { + /// Pair an energy figure with the reconstruction it was measured over. + /// + /// Refuses the pair when the two digests disagree. See the module docs: the SDK + /// cannot make this check, because the assertion bodies are opaque to it. + pub fn new( + energy: &'a EnergyBinding, + reconstruction: &'a ReconstructionBinding, + ) -> Result { + if energy.reconstruction_hash != reconstruction.hash { + return Err(EmitError::DigestsDisagree { + energy: energy.hash_hex(), + reconstruction: reconstruction.hash_hex(), + }); + } + Ok(ReconstructionManifest { energy, reconstruction }) + } + + /// Build the manifest, with the codestream as parent ingredient and all three + /// assertions attached. + /// + /// The assertion bodies are parsed from the same `to_json` functions the extension + /// documents pin, so the emitter cannot drift from the specified shape: there is one + /// definition of each assertion and this reads it rather than restating it. + /// + /// The standard assertion is added in the same call as its companion, so it is not + /// possible to emit through this module the unaccompanied figure that + /// [`EmitError::UnaccompaniedStandardAssertion`] describes. + /// + /// # Why the codestream is required, and why the action is not `c2pa.created` + /// + /// The obvious modelling — "this decoder made an image, so `c2pa.created`" — is both + /// unavailable and wrong. + /// + /// Unavailable, because C2PA 2.x requires every `c2pa.created` action to carry a + /// `digitalSourceType`, and that vocabulary describes how the *content* came to + /// exist: captured, drawn by a human, generated by a model. A decoder does not know + /// that. It reconstructed pixels from a codestream, and whatever was encoded could + /// have been any of them. Reaching for `trainedAlgorithmicMedia` because a codec has + /// a neural stage would assert that a compressed photograph is AI-generated content, + /// which is false — and there is no "I decoded something and cannot see past it" + /// member of the vocabulary to reach for instead. + /// + /// Wrong, because a decode is not a creation. It is a **conversion** of an existing + /// asset — the codestream — into another representation. C2PA already models that: + /// the codestream is a `parentOf` ingredient, the manifest opens it, and the decoded + /// pixels are what came out. No claim about the original's origin is made anywhere, + /// which is exactly the epistemic position a decoder is in. + /// + /// That modelling is not merely more correct, it is more useful. The reconstruction + /// digest says *which* decode the joules were measured over, and the parent + /// ingredient says *what to decode to get there*. A verifier holding this manifest + /// has both halves of the check rather than a digest and no way to reproduce it. + pub fn builder( + &self, + codestream: &[u8], + codestream_format: &str, + ) -> Result { + let mut b = Builder::default(); + b.set_claim_generator_info(ClaimGeneratorInfo::new("wai")); + + // Edit intent + a parentOf ingredient makes the SDK emit the inception action — + // `c2pa.opened`, tied to that ingredient. Deliberately not written by hand: the + // action must reference the ingredient's hashed URI, which does not exist until + // the claim is built, and a hand-written one that misses is reported as + // `assertion.action.ingredientMismatch` on the far side of a signature. + b.set_intent(BuilderIntent::Edit); + b.add_ingredient_from_stream( + serde_json::json!({ + "relationship": "parentOf", + "title": "WAI codestream", + "format": codestream_format, + }) + .to_string(), + codestream_format, + &mut std::io::Cursor::new(codestream.to_vec()), + )?; + + // What was done to the parent: its format was changed, by decoding it. + b.add_action( + Action::new(c2pa_action::CONVERTED) + .set_software_agent("wai") + .set_description("deterministic WAI reconstruction of the parent codestream"), + )?; + + let parse = |s: String, w: &'static str| -> Result { + serde_json::from_str(&s).map_err(|_| EmitError::Malformed(w)) + }; + + b.add_assertion( + EnergyBinding::C2PA_STANDARD_LABEL, + &parse(self.energy.to_standard_assertion_json(), "standard sustainability")?, + )?; + b.add_assertion( + EnergyBinding::C2PA_LABEL, + &parse(self.energy.to_json(), "energy binding")?, + )?; + b.add_assertion( + ReconstructionBinding::C2PA_LABEL, + &parse(self.reconstruction.to_json(), "reconstruction binding")?, + )?; + + // Mark every assertion as *created*: C2PA's word for "attributed to the signer", + // as against *gathered*, which is material collected from elsewhere and merely + // carried along. The distinction is the same one the whole family turns on — a + // claim someone stands behind versus hearsay in transit — and these are all + // first-party statements: this signer measured the joules and hashed the pixels. + // + // It is also load-bearing rather than cosmetic. A validator looks for the + // inception action in the created list; leaving the actions assertion gathered + // (which is what `add_action` does on its own) yields a manifest the SDK signs + // happily and every validator then rejects as `assertion.action.malformed`. + for a in &mut b.definition.assertions { + a.created = true; + } + Ok(b) + } + + /// Sign `source` into `dest`, embedding the manifest. Returns the manifest bytes. + /// + /// `format` is the delivered asset's MIME type or extension (`"image/png"`), and + /// `codestream_format` the parent's. This module deliberately does not encode + /// images: which container the reconstruction is delivered in is the caller's + /// decision, and the bindings are indifferent to it — the reconstruction digest is + /// over decoded pixels, not over the delivered bytes. + pub fn sign_stream( + &self, + signer: &dyn Signer, + codestream: &[u8], + codestream_format: &str, + format: &str, + source: &mut R, + dest: &mut W, + ) -> Result, EmitError> + where + R: Read + Seek + Send, + W: Write + Read + Seek + Send, + { + Ok(self.builder(codestream, codestream_format)?.sign(signer, format, source, dest)?) + } +} + +/// The assertion bodies recovered from a signed manifest, as a verifier reads them. +#[derive(Debug, Clone)] +pub struct RecoveredBindings { + /// The standard `c2pa.environmental-sustainability` body. + pub standard: Value, + /// The companion energy binding. + pub energy: Value, + /// The reconstruction binding. + pub reconstruction: Value, +} + +impl RecoveredBindings { + /// The digest both companions agree on, lowercase hex. + pub fn reconstruction_hash_hex(&self) -> Option<&str> { + self.energy.get("reconstructionHash")?.as_str() + } + + /// The authoritative integer figure. `energy_kwh` in the standard assertion is a + /// derived interop value; a verifier reads this one. + pub fn microjoules(&self) -> Option { + self.energy.get("microjoules")?.as_u64() + } + + /// Does the sealed claim describe the pixels a verifier just decoded? + /// + /// This is the whole construction in one call: re-decode the asset, pass the + /// canonical RGB8 here, and everything in the claim is confirmed except the joules. + pub fn describes(&self, rgb: &[u8]) -> bool { + use sha2::{Digest, Sha256}; + let mut hex = String::with_capacity(64); + for b in Sha256::digest(rgb) { + use std::fmt::Write; + let _ = write!(hex, "{b:02x}"); + } + self.reconstruction_hash_hex() == Some(hex.as_str()) + && self.reconstruction.get("hash").and_then(Value::as_str) == Some(hex.as_str()) + } +} + +/// Recover the bindings from a validated manifest, applying the rules the SDK does not. +/// +/// Reports [`EmitError::UnaccompaniedStandardAssertion`] for a manifest carrying only +/// the standard figure. That manifest is perfectly valid C2PA; it is refused here +/// because the figure in it is self-declared, names no acquisition class, and is not +/// about any output a reader can reproduce — which is the situation the companion +/// assertion exists to distinguish itself from. +pub fn recover(manifest: &Manifest) -> Result { + let find = |label: &str| -> Option { + manifest.assertions().iter().find(|a| a.label() == label).and_then(|a| { + a.value().ok().cloned() + }) + }; + + let standard = find(EnergyBinding::C2PA_STANDARD_LABEL); + let energy = find(EnergyBinding::C2PA_LABEL); + let reconstruction = find(ReconstructionBinding::C2PA_LABEL); + + match (standard, energy, reconstruction) { + (Some(standard), Some(energy), Some(reconstruction)) => { + let e = energy.get("reconstructionHash").and_then(Value::as_str).unwrap_or(""); + let r = reconstruction.get("hash").and_then(Value::as_str).unwrap_or(""); + if e.is_empty() || r.is_empty() { + return Err(EmitError::Malformed("missing reconstruction digest")); + } + if e != r { + // The same self-refutation the emitter refuses to create. Checked again + // on the way in, because a manifest may have been produced by anything. + return Err(EmitError::DigestsDisagree { + energy: e.to_string(), + reconstruction: r.to_string(), + }); + } + Ok(RecoveredBindings { standard, energy, reconstruction }) + } + (Some(_), None, _) => Err(EmitError::UnaccompaniedStandardAssertion), + _ => Err(EmitError::NoBinding), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::energy_binding::{Acquisition, Coverage}; + use crate::reconstruction_binding::ReconstructionDescriptor; + use c2pa::{Reader, ValidationState}; + use std::io::Cursor; + + // ---- a minimal, valid RGB8 PNG ------------------------------------------------ + // + // The emitter deliberately does not encode images, so the tests must supply an + // asset. This writes one with stored (uncompressed) deflate blocks: enough for the + // SDK to parse the chunk structure, insert its `caBX` chunk and read it back, which + // is the structural oracle these tests need. It is test scaffolding, not a codec. + + fn crc32(bytes: &[u8]) -> u32 { + let mut c = 0xffff_ffff_u32; + for &b in bytes { + c ^= u32::from(b); + for _ in 0..8 { + c = if c & 1 != 0 { 0xedb8_8320 ^ (c >> 1) } else { c >> 1 }; + } + } + !c + } + + fn chunk(out: &mut Vec, kind: &[u8; 4], data: &[u8]) { + out.extend_from_slice(&(data.len() as u32).to_be_bytes()); + let mut crc_over = kind.to_vec(); + crc_over.extend_from_slice(data); + out.extend_from_slice(kind); + out.extend_from_slice(data); + out.extend_from_slice(&crc32(&crc_over).to_be_bytes()); + } + + fn png(w: u32, h: u32, rgb: &[u8]) -> Vec { + assert_eq!(rgb.len() as u32, w * h * 3); + // Raw PNG data: one filter byte (0 = None) per scanline. + let mut raw = Vec::with_capacity((h * (1 + w * 3)) as usize); + for y in 0..h as usize { + raw.push(0); + let row = y * (w as usize) * 3; + raw.extend_from_slice(&rgb[row..row + (w as usize) * 3]); + } + // zlib: header, one final stored block, adler32. + let mut z = vec![0x78, 0x01]; + assert!(raw.len() < 65_535, "one stored block is enough for a test image"); + z.push(0x01); + z.extend_from_slice(&(raw.len() as u16).to_le_bytes()); + z.extend_from_slice(&(!(raw.len() as u16)).to_le_bytes()); + z.extend_from_slice(&raw); + let (mut a, mut b) = (1u32, 0u32); + for &byte in &raw { + a = (a + u32::from(byte)) % 65521; + b = (b + a) % 65521; + } + z.extend_from_slice(&((b << 16) | a).to_be_bytes()); + + let mut out = vec![0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a]; + let mut ihdr = Vec::new(); + ihdr.extend_from_slice(&w.to_be_bytes()); + ihdr.extend_from_slice(&h.to_be_bytes()); + ihdr.extend_from_slice(&[8, 2, 0, 0, 0]); // 8-bit, truecolour RGB, no interlace + chunk(&mut out, b"IHDR", &ihdr); + chunk(&mut out, b"IDAT", &z); + chunk(&mut out, b"IEND", &[]); + out + } + + // ---- fixtures ------------------------------------------------------------------ + + const W: u32 = 4; + const H: u32 = 4; + + fn pixels() -> Vec { + (0..(W * H * 3) as u8).collect() + } + + fn descriptor() -> ReconstructionDescriptor { + ReconstructionDescriptor { + decoder_id: 0, + profile: "int".into(), + level: "1".into(), + width: W, + height: H, + format: "RGB8".into(), + color_space: "bt709".into(), + } + } + + fn bindings(rgb: &[u8]) -> (EnergyBinding, ReconstructionBinding) { + let e = EnergyBinding::bind_pixels( + rgb, + 10_565, + Acquisition::HwShunt, + Coverage::Whole, + "extensions/energy-measurement.md", + ) + .unwrap(); + (e, ReconstructionBinding::bind(rgb, descriptor())) + } + + /// Sign the test asset and hand back the bytes a consumer would receive. + /// Stand-in for a WAI codestream: the emitter treats it as opaque parent bytes. + const CODESTREAM: &[u8] = b"WAI\x00 not a real codestream, only opaque parent bytes"; + + fn sign(m: &ReconstructionManifest<'_>, asset: &[u8]) -> Vec { + let signer = untrusted_local_signer().unwrap(); + let mut src = Cursor::new(asset.to_vec()); + let mut dst = Cursor::new(Vec::new()); + m.sign_stream( + &signer, + CODESTREAM, + "application/octet-stream", + "image/png", + &mut src, + &mut dst, + ) + .unwrap(); + dst.into_inner() + } + + // ---- tests --------------------------------------------------------------------- + + /// The gap this module closes: a manifest is actually written, actually signed, and + /// an independent reader finds all three assertions in it. + #[test] + fn a_signed_manifest_carries_all_three_assertions() { + let rgb = pixels(); + let (e, r) = bindings(&rgb); + let m = ReconstructionManifest::new(&e, &r).unwrap(); + let signed = sign(&m, &png(W, H, &rgb)); + + assert_ne!(signed, png(W, H, &rgb), "signing must change the asset bytes"); + + let reader = Reader::from_stream("image/png", Cursor::new(signed)).unwrap(); + let manifest = reader.active_manifest().expect("a manifest was embedded"); + let got = recover(manifest).unwrap(); + + // The bodies are the ones the extension documents pin, not a restatement. + assert_eq!(got.microjoules(), Some(10_565)); + assert_eq!(got.energy.get("acquisition").unwrap(), "HwShunt"); + assert_eq!(got.energy.get("coverage").unwrap(), "Whole"); + assert_eq!(got.energy.get("workUnits").unwrap(), &Value::from(rgb.len())); + assert_eq!( + got.energy.get("method").unwrap(), + "extensions/energy-measurement.md", + "method is mandatory here, unlike the standard assertion's optional field" + ); + assert!(got.standard.get("measurementMethod").is_some(), "never left empty"); + } + + /// The construction in one call: re-decode, hash, and everything is confirmed except + /// the joules. + #[test] + fn the_recovered_claim_describes_the_pixels_a_verifier_decodes() { + let rgb = pixels(); + let (e, r) = bindings(&rgb); + let signed = sign(&ReconstructionManifest::new(&e, &r).unwrap(), &png(W, H, &rgb)); + + let reader = Reader::from_stream("image/png", Cursor::new(signed)).unwrap(); + let got = recover(reader.active_manifest().unwrap()).unwrap(); + + assert!(got.describes(&rgb), "the sealed claim is about these pixels"); + + let mut other = rgb.clone(); + other[0] ^= 1; + assert!(!got.describes(&other), "and provably not about any other decode"); + } + + /// The rule the SDK cannot enforce, caught before a signature can cover it. + #[test] + fn a_manifest_whose_two_digests_disagree_is_refused_before_signing() { + let rgb = pixels(); + let mut other = rgb.clone(); + other[0] ^= 1; + + // An energy figure measured over one decode, an assertion committing to another. + let e = EnergyBinding::bind_pixels( + &other, + 10_565, + Acquisition::HwShunt, + Coverage::Whole, + "extensions/energy-measurement.md", + ) + .unwrap(); + let r = ReconstructionBinding::bind(&rgb, descriptor()); + + match ReconstructionManifest::new(&e, &r) { + Err(EmitError::DigestsDisagree { energy, reconstruction }) => { + assert_ne!(energy, reconstruction); + } + _ => panic!("a self-refuting pair must never reach a signer"), + } + } + + /// The common case in the wild: a C2PA manifest whose energy figure is + /// self-declared and about nothing reproducible. C2PA has no objection to it — the + /// assertion is well-formed and validates — and this module refuses it anyway. + #[test] + fn an_unaccompanied_standard_figure_is_reported_as_such() { + let rgb = pixels(); + let (e, r) = bindings(&rgb); + + // The same manifest, minus the two companions. + let mut b = ReconstructionManifest::new(&e, &r) + .unwrap() + .builder(CODESTREAM, "application/octet-stream") + .unwrap(); + b.definition + .assertions + .retain(|a| !a.label.starts_with("science.transaction.wai")); + + let signer = untrusted_local_signer().unwrap(); + let mut src = Cursor::new(png(W, H, &rgb)); + let mut dst = Cursor::new(Vec::new()); + b.sign(&signer, "image/png", &mut src, &mut dst).unwrap(); + + let reader = Reader::from_stream("image/png", Cursor::new(dst.into_inner())).unwrap(); + let manifest = reader.active_manifest().unwrap(); + + // The SDK is content: nothing it checks has anything to say about the figure. + let faults: Vec<&str> = reader + .validation_results() + .and_then(|r| r.active_manifest().map(|m| m.failure().iter().map(|s| s.code()).collect())) + .unwrap_or_default(); + assert!( + !faults.iter().any(|c| c.starts_with("assertion.")), + "an unaccompanied sustainability figure is valid C2PA, not a malformed one: {faults:?}" + ); + + // This module is not content. + assert!(matches!( + recover(manifest), + Err(EmitError::UnaccompaniedStandardAssertion) + )); + } + + /// Signed is not trusted. The test pins the exact verdict and the exact single + /// reason, so a real defect can never hide behind the expected one — which is not + /// hypothetical: while this module was being written, a missing inception action + /// made the manifest malformed, and reading only the overall state would have + /// attributed that to the certificate. + #[test] + fn the_local_signer_signs_but_does_not_claim_trust() { + let rgb = pixels(); + let (e, r) = bindings(&rgb); + let signed = sign(&ReconstructionManifest::new(&e, &r).unwrap(), &png(W, H, &rgb)); + + let reader = Reader::from_stream("image/png", Cursor::new(signed)).unwrap(); + let state = reader.validation_state(); + assert_eq!( + state, + ValidationState::Valid, + "the manifest is well-formed, the signature intact and the asset unmodified" + ); + assert_ne!( + state, + ValidationState::Trusted, + "but nothing vouches for an ephemeral certificate, and the name says so" + ); + + let results = reader.validation_results().expect("validation ran"); + let codes: Vec<&str> = results + .active_manifest() + .map(|m| m.failure().iter().map(|s| s.code()).collect()) + .unwrap_or_default(); + assert_eq!( + codes, + vec!["signingCredential.untrusted"], + "the credential is the ONLY thing wrong: no hash, signature or action fault \ + may hide behind it" + ); + + let ok: Vec<&str> = results + .active_manifest() + .map(|m| m.success().iter().map(|s| s.code()).collect()) + .unwrap_or_default(); + assert!(ok.contains(&"claimSignature.validated"), "signature intact"); + assert!(ok.contains(&"assertion.dataHash.match"), "asset unmodified since signing"); + } + + /// Modelled quantities are not smuggled in beside a measured one, and the integer + /// stays authoritative. + #[test] + fn no_modelled_carbon_or_water_travels_beside_the_measured_figure() { + let rgb = pixels(); + let (e, r) = bindings(&rgb); + let signed = sign(&ReconstructionManifest::new(&e, &r).unwrap(), &png(W, H, &rgb)); + + let reader = Reader::from_stream("image/png", Cursor::new(signed)).unwrap(); + let got = recover(reader.active_manifest().unwrap()).unwrap(); + + assert!(got.standard.get("carbon_kgco2e").is_none()); + assert!(got.standard.get("water_litres").is_none()); + + // The derived kWh is a lossy interop value; the integer is the measurement. + let kwh = got.standard.get("energy_kwh").unwrap().as_f64().unwrap(); + assert_eq!(got.microjoules(), Some(10_565)); + assert!( + (kwh - 10_565.0 / 3.6e12).abs() < 1e-20, + "energy_kwh is derived from the integer, not the other way round" + ); + } + + + /// A verifier needs both halves: which decode the joules describe, and what to + /// decode to get there. The digest is the first; the parent ingredient is the second. + #[test] + fn the_manifest_names_the_codestream_it_was_decoded_from() { + let rgb = pixels(); + let (e, r) = bindings(&rgb); + let signed = sign(&ReconstructionManifest::new(&e, &r).unwrap(), &png(W, H, &rgb)); + + let reader = Reader::from_stream("image/png", Cursor::new(signed)).unwrap(); + let manifest = reader.active_manifest().unwrap(); + + let parent = manifest + .ingredients() + .iter() + .find(|i| *i.relationship() == c2pa::Relationship::ParentOf) + .expect("the codestream is carried as the parent ingredient"); + assert_eq!(parent.title(), Some("WAI codestream")); + + // The inception action is `opened` on that parent, not `created` — the decoder + // makes no claim about how the encoded content originally came to exist. + let actions = manifest + .assertions() + .iter() + .find(|a| a.label().starts_with("c2pa.actions")) + .and_then(|a| a.value().ok().cloned()) + .expect("an actions assertion"); + let list = actions.get("actions").and_then(Value::as_array).unwrap(); + assert_eq!(list[0].get("action").unwrap(), "c2pa.opened"); + assert_eq!(list[1].get("action").unwrap(), "c2pa.converted"); + assert!( + list.iter().all(|a| a.get("digitalSourceType").is_none()), + "no action claims a digitalSourceType: a decoder cannot see past the codestream" + ); + } +} diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index df1d10759..9aa4075d2 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -204,6 +204,14 @@ pub mod reconstruction_binding; #[cfg(feature = "provenance")] pub mod energy_binding; +/// Writes a real, signed C2PA manifest carrying those bindings. +/// +/// [`energy_binding`] and [`reconstruction_binding`] define assertion *shapes*; this +/// emits them into an actual manifest, and enforces the one rule the C2PA SDK cannot — +/// that the two digests in a manifest must name the same decode. +#[cfg(feature = "c2pa_emit")] +pub mod c2pa_emit; + // wai.world.model — generative-world transport: a signed, replay-equivalent action // trace (the world CausalOp log) + per-frame provenance. The wire carries the // action trace + a declared model_id, not the pixels; the sink renders. The neural