Skip to content

refactor!: sign the ceremony attested data, and nothing else - #3

Merged
xgreenx merged 54 commits into
mainfrom
feat/ceremony-attestation
Sep 19, 2026
Merged

xgreenx merged 54 commits into
mainfrom
feat/ceremony-attestation

Conversation

@SupremaLex

@SupremaLex SupremaLex commented Aug 24, 2026 •

Copy link
Copy Markdown
Member

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 and libid-ceremony
owns 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.

authorityId           32   keccak of the DNS name the notary authenticated
createdAt              8   the notary's own clock at session completion
sentTranscriptLength   4
recvTranscriptLength   4
sentBlock  recvBlock

directionBlock  = U16BE(n) ‖ n×revealedRange ‖ U16BE(m) ‖ m×rangeCommitment
revealedRange   = U32BE(start) ‖ U32BE(end) ‖ (end−start) bytes
rangeCommitment = U32BE(start) ‖ U32BE(end) ‖ 32-byte commitment

It signs keccak256(attestedData) and no other preimage (REQ-COMMON-47).
createdAt is 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:

Removed Why it was never the notary's to say
handle a profile rule read it out of a response
account identifier the same
session address the Authorization Digest binds the transaction
chain identifier the digest binds the chain
verifying-contract address the same

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 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 §9.1 record.

Gone, along with HeaderCaptureSigner and the capture-then-sign dance it
needed, EvmProof, NotaryResponse, double_hash_leaf,
compute_notary_digest and find_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:

Transport How the prover gets it
MPC-TLS opened the socket; the record is written back down it
ProxyMode has a sessionId; fetches GET /attestation/{sessionId}

sessionId is not part of the proof. It is the address of a mailbox, for the
transport 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, its REGISTRY_CONTRACT_ADDRESS alias,
resolve_verifying_contract and --x-zk-verifier-address are all gone.

Routes

Four, and the README now lists these rather than the five that no longer exist:

Route What it does
GET /info {version, publicKey}
POST /session create a session → {sessionId}
WS /notarize-proxy?sessionId= ProxyMode verifier session
GET /attestation/{sessionId} long-poll the finished session's §9.1 record

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 NotaryResponse
first will read a ceremony attestation instead. libid-rs ProverResult still
carries request and recv_segments — both are unread by this server now, and
that is the matching change on the prover side.

XZkVerifier and Registry lose their signature source. They are the login
stack, 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 JwksRotationProof for an MPC-TLS session whose
TLS-verified SNI is www.googleapis.com, on the same listener and signing
identity. It remains a different duty with a different consumer —
IdentityJwksRoots. Because libid-rs v0.3.0 removed its legacy digest and
Merkle helpers, those JWKS-only operations now live with this module.

Dependency pin

Cargo.toml pins the libid-rs crates to the released v0.3.0 tag from
libid-rs#2.

Verification

13 unit tests, both integration suites, cargo clippy -D warnings,
cargo +nightly fmt --check.

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>
@SupremaLex
SupremaLex marked this pull request as ready for review August 25, 2026 18:54
@SupremaLex
SupremaLex requested a review from xgreenx August 25, 2026 18:56
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>
@SupremaLex SupremaLex changed the title feat: sign the ceremony attested data, and stop signing what a profile derives refactor!: sign the ceremony attested data, and nothing else Aug 25, 2026
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>
@Wondertan

Copy link
Copy Markdown
Member

The inherited NOTARY_PLATFORM_NAME / check_server_identity gate should be removed as part of this refactor.

The new attested data signs the TLS-authenticated authorityId itself, and each Platform Verifier checks that value against its pinned authority. The additional server-side equality check no longer protects proof soundness; it only restricts one notary deployment to one hostname (api.x.com by default), which prevents the common notaryAddress from serving both X and GitHub browser sessions.

Please remove platform_name from configuration/state and the ProxyMode equality check. If limiting use of the public notary later becomes operationally necessary, that belongs in service access control or an optional multi-authority policy rather than a single platform identity.

`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>
@SupremaLex

Copy link
Copy Markdown
Member Author

The inherited NOTARY_PLATFORM_NAME / check_server_identity gate should be removed as part of this refactor.

The new attested data signs the TLS-authenticated authorityId itself, and each Platform Verifier checks that value against its pinned authority. The additional server-side equality check no longer protects proof soundness; it only restricts one notary deployment to one hostname (api.x.com by default), which prevents the common notaryAddress from serving both X and GitHub browser sessions.

Please remove platform_name from configuration/state and the ProxyMode equality check. If limiting use of the public notary later becomes operationally necessary, that belongs in service access control or an optional multi-authority policy rather than a single platform identity.

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>
Wondertan and others added 27 commits September 15, 2026 01:30
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>
@xgreenx
xgreenx merged commit 8a738b7 into main Sep 19, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants