Repository navigation
refactor!: sign the ceremony attested data, and nothing else - #3
Conversation
Replaces the two bespoke attestation digests with the byte layout of ceremony-common section 9.1. The endpoint now returns the attested data and one signature over it, and the notary signs keccak256 of those exact bytes and no other preimage. The old shape could not express the new one, so this is not a re-sign. It flattened a session into one bearer hash, one range and two revealed blobs: the received direction carried no offsets at all, exactly one commitment was admissible, and the transcript lengths were nowhere. Those lengths are what REQ-COMMON-36 makes the only source for the coverage check, so without them the Platform Verifier cannot tell whether a request left bytes undisclosed. The session's own tlsn output is now kept whole and mapped at signing time. The notary stops deciding profile-specific things. The range-count rules encoded one endpoint's shape into the notary -- /token reveals exactly one sent range, /me exactly two -- which is the judgement REQ-COMMON-33 forbids it from making; which ranges a profile expects belongs to the Platform Verifier. The handle, user_id and session_addr query parameters are gone with them: the notary must place no value it obtained by applying a profile rule (REQ-COMMON-61), and each is derivable from the revealed ranges, where a second signed copy could disagree with the bytes it came from. The check that the claimed id appeared in the revealed transcript goes too, because the notary no longer claims an id. `--x-zk-verifier-address` is removed rather than deprecated. The attested data binds no Consumer Chain and no Platform Verifier by design, so a verifier address is not merely unused here but contrary to the format: binding one would stop a newly registered version checking attestations made before it existed. `?session_type=` survives, now selecting the operation tag alone, and takes `identity` where it took `me`. BREAKING CHANGE: the response shape, the query parameters and the signed preimage all change together, and the CLI drops a flag. Callers move in step with the Platform Verifiers of W4.3. Depends on libid-rs v0.2.0 for `libid-ceremony` and `libid_tlsn::attest`; the tag does not exist yet, so this does not build until it is cut. Cargo.lock is deliberately left alone for the same reason -- it regenerates against the tag. Assisted-by: Claude Opus 5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
`?session_type=token|identity` is gone, and so is the platform name the
attested data used to carry. Both were things the notary was told and wrote
down as though it had observed them; the record now holds only what it saw --
the authenticated server name, the transcript lengths, the ranges the client
revealed and the commitments over the rest -- plus its own clock reading, which
REQ-COMMON-57 requires it to supply.
Which session of a ceremony an attestation covers is the request line the
notary recorded, and the Platform Verifier already compares that against the
path its profile pins. A label from the party being attested was a second and
weaker answer to a question the bytes answer.
`libid-ceremony` drops out of the dependency list with the last tag.
Two things on this branch had never compiled, because its libid-rs pin is a tag
that does not exist yet and nothing had built it:
- the JWKS prover did not pass the `RevealMode` that `prover_generic` has
taken since the ceremony work began. It passes `Legacy`: the JWKS session
reads a public document, carries no credential and reaches no Platform
Verifier, so the ceremony layouts have nothing to say about it.
- a unit test still set `zk_verifying_contract` and an integration test still
passed `--x-zk-verifier-address`, both removed with the bespoke digests.
Verified against a local checkout of the libid-rs branch: 25 tests, clippy and
fmt clean. The tag pin is restored, so CI stays red until libid-rs merges and
v0.3.0 is cut.
Signed-off-by: SupremaLex <georglutsenko@gmail.com>
`RevealMode::Legacy` became `CallerSelected`. The JWKS session was never the legacy flow that name referred to: it reads a public document, carries no credential, and reaches no Platform Verifier, so no ceremony layout applies to it and no ceremony path will replace it. It picks its own ranges, which is what the new name says. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
`prover_generic` no longer takes a reveal mode and a closure that could contradict it; it takes one callback returning both directions' layouts. The JWKS session says what it always meant: the request line and `Host` revealed as the notary needs them, and the whole response revealed with nothing committed -- there is nothing in a public key set to hide. It reads a public document, carries no credential, and reaches no Platform Verifier, so no ceremony layout applies to it. Verified against a local checkout of the libid-rs branch: 25 tests, clippy and fmt clean. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The section 9.1 record is built from four things: what the session revealed,
the server the notary authenticated, the commitments over the rest, and the
notary's own clock. None of them depends on how the bytes reached the notary,
so tying the record to one transport was an accident of where it was written
rather than a property of what it says.
Only the ProxyMode path filled `raw_attest`. An MPC-TLS session ends with the
same `VerifierResult` -- `partial_transcript`, `server_name`,
`transcript_commitments` -- so it now fills it too, alongside the pre-ceremony
EVM proof it already stored. Which of the two a caller wants is the caller's
business.
`GET /zk/proxy/attestation/{id}` becomes `GET /attestation/{id}`. The old name
said ProxyMode in the path, which is now false, and the endpoint's contract had
already changed when it stopped taking `?session_type=`. A browser client
calling the old path must move.
Verified against a local checkout of the libid-rs branch: 25 tests, clippy and
fmt clean.
Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Its section header and one log line still said ProxyMode owned it. Both transports fill the record now, so the header names the endpoint by what it serves rather than by which path first had it. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
`GET /notary` was labelled "legacy path kept for backward compat" -- the old
Rust TCP-over-WS route. Nothing in the organisation calls it.
`GET /evm-proof/{id}` returned the pre-ceremony `NotaryResponse` over REST
after an MPC-TLS session. Nothing calls that either, and the reason is
structural rather than accidental: the only prover on that path is
libid-server-rs, which opens a raw `TcpStream` to the wire port and reads the
response off the socket. `handle_tcp_prover` still writes it there. The REST
endpoint was a second way to deliver something already delivered, kept for a
browser that never asked.
`evm_proof_result` goes with it -- the session map no longer holds a copy of a
response that leaves over the wire.
Six routes remain, and each has a caller: `/session` and `/notarize-proxy` and
`/attestation` are the browser ceremony path; `/notarize` and `/proxy` are the
tlsn-js MPC transport; `/info` is the tlsn-js handshake.
25 tests, clippy and fmt clean.
Signed-off-by: SupremaLex <georglutsenko@gmail.com>
`GET /notarize` and `GET /proxy` were the tlsn-js browser MPC path: a WebSocket carrying the MPC-TLS session, and a raw WebSocket-to-TCP bridge to reach the platform. Both go, with `handle_ws_prover`, `proxy_ws_to_tcp` and their pumps -- 140 lines. MPC has a future here, but not from a browser. GitHub's token exchange is notarized by the Token-Exchange Service, which opens a raw `TcpStream` to the wire port and never touches an HTTP route. That path is untouched: `handle_tcp_prover` still serves it, and the ceremony record it produces is the same one a ProxyMode session produces. What went was specifically the browser half, and the specification had already ruled on it. Browser MPC is a deferred alternative that "requires a new ceremony profile"; REQ-PLAT-26 forbids application-controlled platform egress in the launch profile, which is exactly what `/proxy` was -- a bridge to `?token=<host>:443`. Nothing in the organisation called either route. Four remain: `/session` and `/notarize-proxy` and `/attestation` are the browser ceremony path, `/info` is the tlsn-js handshake. Server-side proving uses the TCP listener and no route at all. 25 tests, clippy and fmt clean. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
…sion map `handle_notary_session` took a `session_id` that was always `None`. Its only caller is the TCP listener, which has none to give -- the argument existed for the WebSocket entry, and that went with the browser MPC transport. Which made the previous commit's promise empty: the ceremony record was stored under `if let Some(ref sid)`, so on this path it was never stored at all. The mechanism was the browser's, and the browser does not do MPC. The prover here opened the socket and reads its results off it, so the record goes the same way, right after `NotaryResponse`. Server-side proving now gets the section 9.1 attestation from an MPC-TLS session, which is what it could not do before. The dead argument goes with it. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
`prover_generic` takes a finished `hyper::Request` now instead of a bag it translates, so this caller writes the headers it means to send. They are the same four as before -- the library used to add three of them, which a notarized request cannot afford, even though this session is not one. The empty progress callback goes with the parameter that carried it. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Three comments still described the arrangement I wrote and then replaced. The module doc said the endpoint signs a completed session "MPC-TLS or ProxyMode alike", and `raw_attest` said "both transports fill it". Neither is true since the MPC record started going back down the socket the prover opened: only the ProxyMode path fills that field, and only a browser fetches from that route. The distinction is worth stating rather than deleting, because it is not about the record. The bytes say nothing about how they arrived. Retrieval does: a browser has a session id and comes back for the result later, while an MPC-TLS prover is still holding the socket it opened. The test helper's doc also still pointed at `compute_*_attest_digest` and a `RawAttestationData` that no longer exist. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Five comments described the notary as it was before it stopped deciding things. The trust model said it "signs the transcript root" and that "the ZK circuit or smart contract verifies the signature". It signs the section 9.1 attested data for a ceremony -- a Merkle root only on the pre-ceremony path that has not been retired -- and the circuit verifies no notary signature at all: an attestation is authenticated on chain, and the circuit proves only what cannot be read from authenticated evidence. `chain_id` and `mpc_verifying_contract` were documented as an EIP-712-style domain separator baked into "the signed digest". The ceremony record carries neither, on purpose: it describes an observed session and says nothing about where the evidence will be spent, which is what lets one attestation serve every chain that trusts the key. They now say they belong to `compute_notary_digest` and will go with it. The `XZkVerifier` in that doc has not existed under that name for some time. One comment still named the reveal shapes -- "/token the `client_id=` slice, /me the request prefix" -- which is exactly the endpoint knowledge that was taken out of the notary, so the comment outlived the thing it described. And the server-identity check was justified by "the digest attests `platform_name`". The record attests the cert-verified name as `authorityId`; the check is still right and the reason is sharper than it was written. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
`prover_generic` takes a phase callback again. This caller passes a no-op and says why: nobody waits on a JWKS rotation the way a user waits on a claim. The keeper runs it on a timer and reads the result. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The tag this named does not exist, so nothing resolved: `Cargo.lock` still held v0.2.0 and the branch did not build. The ceremony wire constructions this server encodes live in libid-rs PR #2, so the pin is the head of that PR. A revision costs one thing a tag does not: it is reachable only while the branch that carries it is. Squash-merging that PR would strand it, and CI would break after the merge rather than before. This returns to `tag = "v0.3.0"` the moment the tag is cut, and it must, before this is released. libid-ceremony arrives transitively through libid-tlsn, from the same pin. The direct `tlsn` entry still names the revision libid-tlsn pins at that commit, and the wasm bundle script still reads exactly one revision out of the lock. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Both transports ended at the same three steps -- build the section 9.1 attested data, encode it, sign `keccak256` of the encoding -- written out twice. The transport says nothing about the TLS session the record describes, so two copies were two chances to disagree about the format this notary's key vouches for. `sign_ceremony_attestation` is that one place now, and the two call sites keep only what actually differs: where the session came from, and how a failure is reported. `run_proxy_verifier_for_test` goes. It was `pub` and `#[doc(hidden)]` for in-process end-to-end tests, and nothing anywhere called it -- not this crate, not `tests/`. It also took `chain_id` and `mpc_verifying_contract`, which a ProxyMode session has no use for: a ceremony attestation binds neither. And a doc correction. The `verifyingContract` flag cited `GitHubIdentityVerifier` as its identity-deployment example; that contract no longer exists. It says what the flag reaches instead -- the pre-ceremony path only, because a ceremony attestation names no chain and no contract. The Authorization Digest does that, on the chain itself (REQ-COMMON-61). Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The MPC path ran two ceremonies back to back. First tlsn's own: read the prover's attestation request, build an `Attestation`, capture the header its builder wants signed and sign it for real through `ManagedSigner`. Then ours: extract the handshake, find the request line, hash a `recv:` leaf per revealed range, build a Merkle tree, bind chain id and `verifyingContract` into `compute_notary_digest`, sign that too. Both went down the socket as `NotaryResponse`, followed by the section 9.1 record. The Merkle half existed for `XZkVerifier` and `Registry` -- the login stack -- and the tlsn half existed to fill the field beside it. Neither is what this notary is for any more, so both go, and the whole apparatus behind them goes with them: `HeaderCaptureSigner` and the capture-then-sign dance it needed, `EvmProof`, `NotaryResponse`, `double_hash_leaf`, `compute_notary_digest`, `find_request_line_range`. The two configuration values the old digest bound go too, and they are the clearest statement of the difference. A ceremony attestation names no chain and no contract, deliberately: it describes an observed TLS session and says nothing about where the evidence will be spent, so one record serves every chain that trusts the key. Binding the chain is the Authorization Digest's job, on the chain itself (REQ-COMMON-61). `--chain-id`, `--verifying-contract`, its `REGISTRY_CONTRACT_ADDRESS` alias and `resolve_verifying_contract` are gone. BREAKING, in two directions. The wire is now one message where it was two, so a prover reading `NotaryResponse` first reads a ceremony attestation instead -- libid-rs `ProverResult` still carries `request` and `recv_segments`, and both are now unread by this server. And `XZkVerifier`/`Registry` lose their signature source; they were already the stack this work does not carry forward. The README's endpoint table listed five routes that do not exist -- `/evm-proof`, `/zk/proxy/attestation`, `WS /notarize`, `WS /proxy`, `WS /notary`. It lists the four that do. -361/+22. 17 unit tests, both integration suites, clippy and fmt. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The prover's side of this change landed in libid-rs: `ProverResult` no longer carries the attestation request or the revealed recv segments, and `EvmProof` and `NotaryResponse` are gone with them. Both sides of the wire have to move together, so the pin moves with it. Still a revision, still for the same reason: `v0.3.0` is not cut. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
|
The inherited The new attested data signs the TLS-authenticated Please remove |
`check_server_identity` refused a ProxyMode session whose cert-verified server name was not the configured `NOTARY_PLATFORM_NAME`. It was written when the platform was hashed into a bespoke digest and the notary's signature was the only thing asserting it. The record says it now. `authorityId` is the TLS-authenticated server name the notary observed, signed, and every Platform Verifier compares it against the authority its own profile pins -- `WrongAuthority` otherwise. An attestation naming an attacker's host is refused on chain, by the contract that knows which host the session was supposed to reach. The gate here adds nothing to that. What it costs is real. One platform identity cannot serve one platform: GitHub needs `github.com` for the exchange and `api.github.com` for the identity session. It certainly cannot serve a notary shared by X and GitHub, which is what a common notary address is for. Limiting who may use a public notary is access control, or an explicit multi-authority policy. Neither is a single hostname compiled into the identity the service claims. `--platform-name`, `NOTARY_PLATFORM_NAME`, the state field and the four unit tests that pinned the equality go with it. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The layout that pairs with `requireFullyRevealed` landed there, together with the end-to-end test that would have caught the mismatch: the layouts, the section 9.1 record they produce, and the rules a Platform Verifier applies to it, driven in one place. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
libid-rs rebased onto a main that carries the alpha.15 upgrade, so this pin follows on both lines at once. The direct `tlsn` entry has to move with libid-tlsn's: cargo unifies one source into one copy, and a divergent pin produces two incompatible `tlsn` crates and type errors at the boundary between them. The wasm bundle script broke on the same move, silently until run. It read the pin from `Cargo.lock` by matching the REQUEST -- `?rev=<sha>` -- and a tag pin writes `?tag=<name>` instead, so the match found nothing and the script exited with "could not read the tlsn rev". It reads the RESOLVED commit after the `#` now, which every pin form carries. Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Done |
The record this server writes to a completed session was defined here, privately, and mirrored privately in every prover that reads it. Two definitions of one message, agreeing only until somebody renames a field -- at which point the reader fails with a JSON error that says nothing about which side moved. It now comes from `libid_transcript`, beside the `write_msg` that frames it. Nothing about what this server signs or serves changes: the same fields, the same byte shape, the same two endpoints. What changes is that the prover and the notary can no longer disagree about it silently. The pin moves with it, four commits along the same branch. Nothing in between touched what this server uses -- the two breaking changes are prover-side layout selection -- and the suite says so. Assisted-by: Claude Opus 5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Adopt the released ceremony attestation API and keep the removed digest and Merkle helpers local to the JWKS duty that still consumes them. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Signed-off-by: Wondertan <hlibwondertan@gmail.com> Assisted-by: GPT-5
Signed-off-by: Wondertan <hlibwondertan@gmail.com> Assisted-by: GPT-5
Signed-off-by: Wondertan <hlibwondertan@gmail.com> Assisted-by: GPT-5
Signed-off-by: Wondertan <hlibwondertan@gmail.com> Assisted-by: GPT-5
Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Pin the notary and release WASM build to the libid TLSN integration revision containing upstream PRs #1178 and #1179. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Promote the PoC-qualified TLSN pin with per-consumer RCOT and KOS domain separation. Repeat its MPZ workspace patches so the notary and SDK test clients resolve the same dependency graph as the browser WASM build. Depends on tlsnotary/tlsn#1173 and tlsnotary/mpz2#446, alongside the existing #1178 and #1179 fork changes. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Preserve the generated wrapper, WASM, and hashed web-spawn module paths in the release archive so CCDP can deploy them without rewriting generated source. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Repin TLSN to its ownership-only integration commit and resolve the KOS domain-separation patch from libid-org/mpz at the same qualified commit. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Pin the minimal alpha.6-compatible executor lifetime backport so early HTTP failures do not cancel active TLSN protocol tasks. Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
libid-org/mpz#1 merged into `libid/kos-domain-separation-alpha6` (4db9454): a typed KOS `InstanceId` in place of a bare `Block`, instances derived by PRG stream id so a solo instance is byte-identical to stock KOS, `Default` removed from `kos::Receiver`, and the bench, tests and CHANGELOG entry mpz#446 shipped without. libid-org/tlsn#3 (dc31702) ports the TLSNotary fork to that API on top of `integration/notary-alpha15`. Move both pins together: the TLSN patch and the three SDK test-client dev-dependencies to dc31702, and all 24 MPZ patch entries from 1dd2349 (mpz#2 only) to 4db9454 (mpz#2 + mpz#1). Cargo.lock resolves exactly one TLSN source, which is what scripts/build-tlsn-wasm.sh requires, so the browser prover is rebuilt from the same revision. Wire note: c0379ef's zero-id salt perturbed the Proxy derivation; the stream-id rework does not, so a browser WASM published from 94aaaf3 will not interoperate with a notary on this pin. Server and browser artifacts ship together. Verified: `cargo +nightly fmt --all -- --check`; `cargo clippy --all-targets --all-features -- -D warnings`; `cargo test --all` (15 passed, including the Proxy-WebSocket and MPC recovered-socket end-to-end checks); `bash -n scripts/build-tlsn-wasm.sh`. Assisted-by: Claude Fable 5.1 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
…merge commit libid-org/tlsn#3 merged as a merge commit (550220e, parents 94aaaf3 and dc31702), so dc31702 stays reachable and nothing was stranded. Pin the branch tip anyway: the notary should name the state of integration/notary-alpha15, not a side-branch commit. Tree contents are identical; only the pin moves. Cargo.lock still resolves exactly one TLSN source, as build-tlsn-wasm.sh requires. `cargo +nightly fmt --all -- --check`, `cargo clippy --all-targets --all-features -- -D warnings` and `cargo test --all` (15 passed) unchanged. Assisted-by: Claude Fable 5.1 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The notary used to dispatch on the TLS-verified server name and answer `www.googleapis.com` with a Merkle proof of its own shape, signed over a digest of its own design, for the legacy `JwksOracle`. That was the notary deciding what a session was for -- the profile-specific decision REQ-COMMON-33 forbids it from making -- and a second wire format kept in step with a second contract. `GoogleJwtRoots` now verifies through `NotaryService` like every Platform Verifier, so the second format has no reader. Delete the JWKS duty and everything only it used: `src/jwks/` (the Merkle path, the prover helpers and the mock), the `jwks_enabled` flag, the dispatch in `handle_verified_session`, the `Jwks`/`Http`/`Base64` error variants, and the `alloy-*`, `serde_with`, `base64`, `k256` and non-test `reqwest` dependencies. The notary signs the section 9.1 record for every session; which host it observed is in the record, and the contract that reads it pins the host. The prover-side helpers were only ever keeper code hosted here for reuse, and hosting them here made the keeper depend on this whole crate and match its libid-rs revision exactly. They move to the keeper, on libid-rs's primitives (`libid_tlsn::prover_generic`, `libid_transcript`). The notary crate still builds as a library so the smoke tests can embed `run`. Supersedes #7, whose branch carried pre-force-push copies of #6, and #8. Assisted-by: Claude Fable 5.1 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Review of the removal found four leftovers, none functional:
* the Dockerfile's port comment still said the wire port served JWKS
sessions -- align it with config.rs;
* `Error::Crypto` existed only so the deleted mock's `sign_eth_claim(..)?`
compiled; nothing constructs it now. `libid-crypto` stays for `keccak256`;
* two doubled blank lines in Cargo.toml where the dependency blocks were;
* the README described `run` as taking caller-supplied listeners, which no
public API does: it takes a `NotaryServerConfig` and reports the bound
port through `NotaryServerHandle::local_addr`.
Operationally, `--jwks-enabled` is now a usage error and `NOTARY_JWKS_ENABLED`
is inert. No deployment passes the flag; grndd-systems/deployment sets the env
var in two overlays (libid-testnet/identity-notary.env, testnet/notary.env),
which keep starting and should drop the line when this rolls out.
Assisted-by: Claude Fable 5.1
Signed-off-by: xgreenx <xgreenx9999@gmail.com>
v0.4.0 carries libid-profiles and libid-identity 0.10, and two transcript fixes the ceremony layouts depend on: a JSON member is found with the whitespace JSON allows, and one scan finds the key for both member shapes. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
These three were the only direct dependencies whose requirement no longer reached the newest release; every other caret range already resolves to it. reqwest 0.13 renamed the `rustls-tls` feature. The test client only GETs /info over plain HTTP on loopback, so it takes no TLS backend at all rather than the renamed one -- that drops rustls-platform-verifier, quinn and an Android JNI tree, leaving the lockfile 17 packages smaller than before. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The service writes nothing and binds only ports above 1024, so root bought nothing; it now runs as uid 10001. `ldd` on the built binary lists libc, libm and libgcc_s only -- the TLS stack is rustls, so libssl-dev and libssl3 were installed and never linked. The HEALTHCHECK probed a literal 7047 while --port/NOTARY_PORT moves the listener, which would have reported a healthy server as unhealthy forever. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The package is public, so a consumer's CI needs no login step, no packages permission and no PAT -- worth stating, because the opposite is assumed. Also records that the image is amd64-only, and that a custom- tag is a build artefact to be replaced by a version tag once the PR behind it merges. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The previous comment read as though the image had no OpenSSL. It still does: ca-certificates depends on openssl, which pulls libssl3 in regardless, so the image is the same 125MB either way. Only the false claim of a dependency is gone. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Consumers span architectures: amd64 runners and x86 nodes, arm64 runners and Graviton, and Apple Silicon, which until now ran the image under QEMU with a platform-mismatch warning. Each platform now builds on a runner of its own architecture — emulating the tlsn/mpz graph would cost hours — and pushes an untagged image by digest; a final job joins the digests into the manifest list the release and custom tags point at. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
buildx defaults to an OCI index, which `docker manifest inspect` cannot read before Docker 23 — the first multi-arch tag read as "no such manifest" on a 20.10 client that pulls and runs it fine. Nothing here needs OCI-only features: provenance and SBOM attestations are already off. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
A digest-only push leaves an untagged version in the ghcr package, and the manifest list is the only reference to it. A "delete untagged versions" retention policy would therefore break every tag, not tidy the package. Assisted-by: Claude Opus 5 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Cargo.toml and Cargo.lock keep this branch's v0.4.0 pins at the canonical libID-rs URL; the README keeps this branch's description under the credit, reworded for what the notary is now: it runs the verifier and keeps no attestations. Assisted-by: Claude Fable 5.1 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The notary signs one record now: the attested data of ceremony-common §9.1. It
used to sign three.
16 commits, 8 files, +330 / −1107. Almost all of the change is deletion.
What it signs
libid_ceremony::AttestedData::from_observed(...)builds the record andlibid-ceremonyowns the byte layout, so this service implements none of it. The format cannot
drift between the notary and the contracts that decode it without libid-rs
drifting first.
It signs
keccak256(attestedData)and no other preimage (REQ-COMMON-47).createdAtis its own clock, never the prover's and never a response header(REQ-COMMON-57).
What it stopped signing, and why the list is long
REQ-COMMON-61 says the notary must place no value it derived by applying a
profile rule. Each of these was either derivable from a revealed range or
already bound by the Authorization Digest:
The notary is a serializer, not an interpreter. The only value it originates is
created_at.Both transports, one record
The MPC path used to run two ceremonies back to back. First tlsn's own: read the
prover's attestation request, build an
Attestation, capture the header itsbuilder wants signed and sign it for real through
ManagedSigner. Then ours:extract the handshake, find the request line, hash a
recv:leaf per revealedrange, build a Merkle tree, bind chain id and
verifyingContractintocompute_notary_digest, sign that too. Both went down the socket asNotaryResponse, followed by the §9.1 record.Gone, along with
HeaderCaptureSignerand the capture-then-sign dance itneeded,
EvmProof,NotaryResponse,double_hash_leaf,compute_notary_digestandfind_request_line_range.What remains is one helper both transports end at. The transport says nothing
about the TLS session the record describes, so building it twice was two chances
to disagree about the format this key vouches for:
sessionId; fetchesGET /attestation/{sessionId}sessionIdis not part of the proof. It is the address of a mailbox, for thetransport that does not hold its connection open.
Configuration shrank, and that is the point
A ceremony attestation names no chain and no contract, deliberately: it
describes an observed TLS session and says nothing about where the evidence will
be spent, so one record serves every chain that trusts the key. Binding the
chain is the Authorization Digest's job, on the chain itself.
So
--chain-id,--verifying-contract, itsREGISTRY_CONTRACT_ADDRESSalias,resolve_verifying_contractand--x-zk-verifier-addressare all gone.Routes
Four, and the README now lists these rather than the five that no longer exist:
GET /info{version, publicKey}POST /session{sessionId}WS /notarize-proxy?sessionId=GET /attestation/{sessionId}A server-side MPC-TLS prover needs no route.
Breaking, in two directions
The wire is one message where it was two. A prover reading
NotaryResponsefirst will read a ceremony attestation instead. libid-rs
ProverResultstillcarries
requestandrecv_segments— both are unread by this server now, andthat is the matching change on the prover side.
XZkVerifierandRegistrylose their signature source. They are the loginstack, which this work does not carry forward; the identity verifiers that used
to consume the same digest were deleted from libid-contracts in
libid-contracts#13.
What is still here
The JWKS duty still signs a
JwksRotationProoffor an MPC-TLS session whoseTLS-verified SNI is
www.googleapis.com, on the same listener and signingidentity. It remains a different duty with a different consumer —
IdentityJwksRoots. Because libid-rs v0.3.0 removed its legacy digest andMerkle helpers, those JWKS-only operations now live with this module.
Dependency pin
Cargo.tomlpins the libid-rs crates to the releasedv0.3.0tag fromlibid-rs#2.
Verification
13 unit tests, both integration suites,
cargo clippy -D warnings,cargo +nightly fmt --check.