Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
6e60560
feat: add the ceremony wire constructions
SupremaLex Aug 20, 2026
02b6c09
test: pin the attestation encoding against the Solidity decoder
SupremaLex Aug 20, 2026
71937a6
feat: require exact transcript coverage where a profile demands it
SupremaLex Aug 20, 2026
0b2f454
feat: add the ceremony profile constants and token-exchange contract
SupremaLex Aug 20, 2026
d9e15fd
feat: make the identity-session request checks one call
SupremaLex Aug 20, 2026
0a57124
feat: build attested data from a notarized session
SupremaLex Aug 20, 2026
d9194a9
feat: select the ceremony reveal layouts
SupremaLex Aug 21, 2026
8670503
docs: say where the attestation format's requirement numbers come from
SupremaLex Aug 21, 2026
d170d3d
refactor: publish only what Rust reads, gate the rest behind `vectors`
SupremaLex Aug 24, 2026
e710f06
refactor!: the notary records, it does not judge
SupremaLex Aug 24, 2026
6e90cae
refactor!: drop the three fields the notary was told rather than saw
SupremaLex Aug 24, 2026
42a3dc1
refactor!: one error, because there is one way to fail
SupremaLex Aug 24, 2026
07bab2e
refactor!: derive the encoder
SupremaLex Aug 24, 2026
6440492
refactor!: name the reveal mode for what it does, not when it was wri…
SupremaLex Aug 24, 2026
3b182c5
refactor!: the prover chooses what it reveals, and says so once
SupremaLex Aug 24, 2026
1b8d4ff
docs: correct what the churn left behind
SupremaLex Aug 24, 2026
3aca6de
refactor!: let the caller state its own request, and drop the progres…
SupremaLex Aug 24, 2026
9dd3050
fix!: a malformed chunked body is an error, not a short one
SupremaLex Aug 24, 2026
91e1a1e
fix: restore the two phase signals the callback removal took with it
SupremaLex Aug 24, 2026
c7c59d6
feat: report prover phases again, typed
SupremaLex Aug 24, 2026
0a1c2ed
feat!: reveal the status line in every response layout
SupremaLex Aug 25, 2026
f137dac
revert: do not reveal the status line
SupremaLex Aug 25, 2026
f53428f
refactor!: drop what the notary stopped reading
SupremaLex Aug 25, 2026
239a4bb
fix!: the identity response reveals everything, and a test says why
SupremaLex Aug 25, 2026
e8b0dd2
fix!: the token service returns an attestation, and the exact bytes a…
SupremaLex Aug 26, 2026
c490e4e
revert!: the identity response reveals its two members, and commits t…
SupremaLex Aug 26, 2026
dec025b
feat(tlsn): hand back what opens the commitments a session made
SupremaLex Sep 2, 2026
cafd9a0
refactor(transcript): give the notary's record one definition, not two
SupremaLex Sep 2, 2026
193bf24
feat(ceremony): give the attestation the one string the wire has room…
SupremaLex Sep 2, 2026
391078f
fix(tlsn): send the request-target in origin-form
xgreenx Sep 3, 2026
4e89f55
Revert "feat(ceremony): give the attestation the one string the wire …
SupremaLex Sep 3, 2026
dec0371
Merge pull request #5 from libid-org/fix/origin-form-request-target
xgreenx Sep 7, 2026
25d7fda
chore!: remove libid-attestations, the builders for removed contracts
xgreenx Sep 7, 2026
a53c341
docs: cite requirements that exist
xgreenx Sep 7, 2026
0c4eed8
chore: the release surface follows the code
xgreenx Sep 7, 2026
087b08f
fix(transcript): match the delimiter the verifier matches
xgreenx Sep 7, 2026
e20eb5e
Merge pull request #7 from libid-org/docs/cite-requirements-that-exist
xgreenx Sep 8, 2026
ab8d0b2
Merge pull request #8 from libid-org/fix/reveal-layouts-match-the-ver…
xgreenx Sep 8, 2026
02b3240
Merge pull request #6 from libid-org/chore/remove-libid-attestations
xgreenx Sep 8, 2026
b4f88e1
fix(transcript): refuse a member that chunk framing runs through
xgreenx Sep 8, 2026
b9a939e
refactor(transcript): name the field once, take both boundaries from …
xgreenx Sep 8, 2026
5bb4fc7
test(transcript): pin the member boundaries the layout depends on
xgreenx Sep 8, 2026
cc40b0c
Merge pull request #9 from libid-org/fix/reveal-layouts-follow-the-re…
xgreenx Sep 8, 2026
aff961d
refactor(transcript): the layouts are constructors on the type they p…
xgreenx Sep 8, 2026
073e52f
refactor(transcript): the member finders are constructors on JsonMember
xgreenx Sep 8, 2026
49b0ed6
refactor(tlsn): attesting is what a session does, not what a function…
xgreenx Sep 8, 2026
1b2a130
refactor(ceremony): the authority id is named for the field it fills
xgreenx Sep 8, 2026
a79d322
fix(ceremony): canonicalize the authority where the field is built
xgreenx Sep 8, 2026
8d23492
test(transcript): pin the sort every identity response depends on
xgreenx Sep 8, 2026
40bea69
refactor(tlsn): put the constructor on the record, through a local trait
xgreenx Sep 8, 2026
b2e9a22
refactor(tlsn): one trait, both records, so the two constructors match
xgreenx Sep 8, 2026
f96a0f0
docs(tlsn): say why the crate boundary holds, not that it already shi…
xgreenx Sep 8, 2026
ea22524
Merge pull request #11 from libid-org/refactor/ceremony-constructors
xgreenx Sep 8, 2026
02ce722
feat(transcript): take the ceremony profiles from the chain's own table
xgreenx Sep 8, 2026
2964a70
test(transcript): drive the identity layout with GitHub's profile too
xgreenx Sep 8, 2026
c91b291
fix(transcript): read a bare id the way the verifier reads it
xgreenx Sep 8, 2026
62a268f
test(crypto): the rejections, which had no tests at all
xgreenx Sep 8, 2026
df8989f
Merge pull request #13 from libid-org/test/crypto-rejects-malformed-i…
xgreenx Sep 8, 2026
9bc598c
fix(tlsn): commit under SHA-256, the algorithm the circuit opens
xgreenx Sep 8, 2026
23f8352
test(tlsn): assert the record carries the authority and the clock it …
xgreenx Sep 8, 2026
1772952
docs: drop the claims that no longer hold
xgreenx Sep 8, 2026
297857c
test(tlsn): commit two ranges per direction, and satisfy the lint tha…
xgreenx Sep 8, 2026
05e60a7
refactor!: remove what the dyaka product left behind
xgreenx Sep 8, 2026
8f994cd
Merge pull request #14 from libid-org/fix/prover-commits-sha256
xgreenx Sep 8, 2026
a32eeb9
Merge pull request #12 from libid-org/feat/profiles-from-contracts
xgreenx Sep 8, 2026
e1f08b1
Merge remote-tracking branch 'origin/feat/ceremony-constructions' int…
xgreenx Sep 8, 2026
5ea935c
Merge pull request #15 from libid-org/refactor/remove-the-dyaka-remnants
xgreenx Sep 8, 2026
7e5d9e1
chore: drop the workspace dependencies nothing claims
xgreenx Sep 8, 2026
3386786
Merge pull request #16 from libid-org/chore/drop-orphaned-workspace-deps
xgreenx Sep 8, 2026
59d010e
docs(tlsn): name the contract that exists, again
xgreenx Sep 9, 2026
ee03eeb
test(tlsn): commit the credential the fixture claims to hide
xgreenx Sep 9, 2026
af15583
chore: drop the comment for a dependency this branch removed
xgreenx Sep 9, 2026
8409101
fix(tlsn): refuse a commitment the session could not have carried
xgreenx Sep 9, 2026
25d182d
Merge pull request #17 from libid-org/fix/restore-the-rename
xgreenx Sep 9, 2026
48e3859
test(ceremony): the fixture verifier is the current section 7 vector
xgreenx Sep 9, 2026
ada633d
chore: take libid-profiles and libid-identity 0.9
xgreenx Sep 9, 2026
de6a6e1
docs: describe the crates that exist after the dead-code sweep
xgreenx Sep 9, 2026
8954d84
fix(tlsn): a driver that finishes after the session ran is the peer c…
xgreenx Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ jobs:
cargo publish --dry-run
-p libid-crypto
-p libid-transcript
-p libid-attestations
-p libid-ceremony
-p libid-signer

# ---------------------------------------------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/scripts/publish-crates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ version="${1:?usage: publish-crates.sh <version>}"
: "${CARGO_REGISTRY_TOKEN:?CARGO_REGISTRY_TOKEN must be set}"

# Dependency order: crypto has no intra-workspace deps; transcript is
# standalone; attestations depends on crypto; signer dev-depends on crypto.
CRATES=(libid-crypto libid-transcript libid-attestations libid-signer)
# standalone; ceremony depends on crypto; signer dev-depends on crypto.
CRATES=(libid-crypto libid-transcript libid-ceremony libid-signer)

# Sparse-index path for a crate name (all our names are >= 4 chars).
index_path() {
Expand Down
112 changes: 57 additions & 55 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 14 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,29 @@ repository = "https://github.com/libid-org/libid-rs"
libid-crypto = { path = "crates/libid-crypto", version = "0.3.0" }
libid-signer = { path = "crates/libid-signer", version = "0.3.0" }
libid-transcript = { path = "crates/libid-transcript", version = "0.3.0" }
libid-attestations = { path = "crates/libid-attestations", version = "0.3.0" }
libid-ceremony = { path = "crates/libid-ceremony", version = "0.3.0" }

alloy = { version = "1", default-features = false }
alloy-primitives = { version = "1", features = ["serde"] }
alloy-sol-types = "1"
# KMS-backed signing. alloy's `signer-aws` feature re-exports
# `alloy::signers::aws::AwsSigner`, but constructing one needs an
# `aws_sdk_kms::Client`, so the SDK is a direct dependency too. Both are
# pinned to the 1.x line so cargo unifies them with whatever alloy resolves.
aws-config = "1"
aws-sdk-kms = "1"
# Pinned exactly: the encoded bytes are a signed preimage, so a layout change
# in a patch release would change what every notary signs.
bincode = { version = "=2.0.1", features = ["derive"] }
hex = "0.4"
# The ceremony profiles, generated in libid-contracts from the same
# `profiles.json` its verifiers read. Zero dependencies of its own.
libid-profiles = "0.9"
# The generated handle table, for the test that keeps the platform names of
# the ceremony profiles and the identity system from drifting apart. A
# dev-dependency only: nothing published carries it.
libid-identity = "0.9"
# Chunk-size parsing. Hand-rolling it is how a malformed chunk becomes a short
# body instead of an error.
httparse = "1"
http-body-util = "0.1"
hyper = { version = "1.1", features = ["client", "http1"] }
hyper-util = { version = "0.1", features = ["full"] }
Expand All @@ -51,6 +62,4 @@ tlsn = { git = "https://github.com/tlsnotary/tlsn", tag = "v0.1.0-alpha.15" }
tokio = { version = "1", features = ["rt", "macros", "net", "io-util", "sync"] }
tokio-util = { version = "0.7", features = ["compat"] }
tracing = "0.1"
# TS bindings codegen — opt-in via the `ts` feature of libid-transcript.
ts-rs = { version = "10", features = ["serde-compat"] }
webpki-root-certs = "1.0"
73 changes: 41 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

Shared Rust crates for MPC-TLS / zkTLS infrastructure: run TLSNotary-style
notarization sessions, carve selective-disclosure ranges out of TLS
transcripts, build the Merkle/EIP-191 proof material, and produce the exact
digests the libID on-chain verifiers check.
transcripts, sign the EIP-191 material, and produce the exact attested-data
record the libID on-chain verifiers check.

## Crates

| Crate | crates.io | What it is |
| --- | --- | --- |
| `libid-crypto` | yes | Contract-agnostic primitives: keccak256, EIP-191 sign/recover (27/28 `v`, low-s), OpenZeppelin-compatible sorted-pair keccak Merkle tree (root, inclusion proofs, verify, double-hashed prefixed leaves), Ethereum address and hex-key helpers. Minimal deps: `k256`, `tiny-keccak`, `hex`. |
| `libid-transcript` | yes | The tlsn-free half of the MPC-TLS toolkit. HTTP/JSON transcript range math for selective disclosure (header/body/chunked decoding, JSON field and `"key":"value"` snippet ranges, bare-number id snippets, anchored lookups, notary reveal ranges); the length-prefixed JSON wire protocol notary and prover speak after MPC-TLS closes; the `EvmProof` / `NotaryResponse` / `TlsHandshakeData` types. |
| `libid-attestations` | yes | Contract-ABI-shaped digest builders, byte-pinned against the Solidity verifiers: chain-bound notary digest, JWKS-rotation notary digest (legacy 6-slot), backend digest, identity hash, and the XZkVerifier token/me attestation digests with their op-tags. |
| `libid-crypto` | yes | Contract-agnostic primitives: keccak256, EIP-191 sign/recover (27/28 `v`, low-s) — the pair a notary signature is made and checked with — plus address derivation and hex-key parsing. Minimal deps: `k256`, `tiny-keccak`, `hex`. |
| `libid-transcript` | yes | The tlsn-free half of the MPC-TLS toolkit. HTTP/JSON transcript range math for selective disclosure (header/body/chunked decoding, `"key":"value"` and bare-number member ranges); the per-session ceremony reveal layouts, built from the profile table generated in libid-contracts; the length-prefixed JSON wire protocol notary and prover speak after MPC-TLS closes; the `AttestationWire` type. |
| `libid-ceremony` | yes | The attested-data record a notary signs: the types a Platform Profile pins, their big-endian fixed-width encoder, and the keccak256 over it that is the only preimage a notary signs. Also the GitHub Token Service request and response records with the bounds a served call must satisfy. |
| `libid-signer` | yes | `ManagedSigner` — one signing identity over a local hex key or an AWS KMS key: EIP-191 claim signing (byte-compatible with `libid_crypto::sign_eth_claim`), bare prehash signing (the tlsn `Secp256k1Eth` format), alloy transaction wallets, public-key accessors, and `SignerSource::from_spec` shape-classified key-spec parsing (64-hex → local key, anything else → KMS). |
| `libid-tlsn` | **no — git only** | The MPC-TLS session driver over the upstream `tlsn` crate: `prover` / `prover_generic` / `verifier` over any async socket, TLS 1.2 handshake-data extraction, WebPKI root store. |
| `libid-tlsn` | **no — git only** | The MPC-TLS session driver over the upstream `tlsn` crate: `prover_generic` and `verifier` over any async socket, the attested-data record built from what a session was observed to be, WebPKI root store. |

## The tlsn git-dep caveat

Expand All @@ -27,16 +27,9 @@ libid-tlsn = { git = "https://github.com/libid-org/libid-rs", tag = "v0.3.0" }
```

The crate split exists precisely so this caveat stays contained: everything
that does not need `tlsn` types — range math, wire protocol, proof types,
digests, signing — is published normally and never drags the git pin into
your lockfile.

## Feature flags

* `libid-transcript/ts` — derives `ts_rs::TS` on `EvmProof` and
`NotaryResponse` for TypeScript bindings generation. Off by default so
production builds don't carry `ts-rs`.
* `libid-tlsn` and everything else: no features.
that does not need `tlsn` types — range math, reveal layouts, the attested-data
record, wire protocol, signing — is published normally and never drags the git
pin into your lockfile.

## Usage sketch

Expand All @@ -45,34 +38,50 @@ answers over the same socket:

```rust,ignore
let result = libid_tlsn::verifier(socket).await?;
// inspect result.partial_transcript / result.tls_transcript, build an
// EvmProof with libid_crypto merkle + libid_attestations digests, sign it
// with libid_signer::ManagedSigner, then:
// describe the session as a libid_tlsn::attest::ObservedSession and build
// the record with AttestedData::from_observed,
// sign its digest with libid_signer::ManagedSigner, then:
libid_transcript::write_msg(&mut result.recovered_io, &response).await?;
```

A prover connects to a notary and fetches an authenticated endpoint,
revealing only the chosen JSON snippets:
A prover connects to a notary, sends one request inside MPC-TLS, and decides
what of the exchange is revealed and what is committed. For a launch profile
that decision is `libid_transcript::ceremony`'s, built from the profile table
`libid-contracts` generates, so the prover and the on-chain verifier read one
definition:

```rust,ignore
let out = libid_tlsn::prover(
use libid_tlsn::{Bytes, HttpBody, HttpRequest};
use libid_transcript::ceremony::{profiles, Layout};

let x = profiles::X.identity.expect("x notarizes an identity session");
let request = HttpRequest::builder()
.method(x.session.method)
.uri(format!("https://{}{}", x.session.authority, x.session.path))
.header("authorization", format!("Bearer {access_token}"))
.header("accept", "application/json")
.header("host", x.session.authority)
.header("connection", "close")
.body(HttpBody::new(Bytes::new()))?;

let out = libid_tlsn::prover_generic(
socket,
access_token,
&libid_tlsn::UserInfoParams {
api_host: "api.x.com",
user_info_path: "/2/users/me",
username_field: "username",
id_field: Some(("id", true)),
user_agent: "my-prover/1.0",
request,
|sent, recv| {
let layouts = Layout::identity_request(sent)
.and_then(|s| Layout::identity_response(recv, &x).map(|r| (s, r)));
layouts.map_err(|e| libid_tlsn::Error::MpcTlsFailed { detail: e.to_string() })
},
|step| tracing::info!(?step),
)
.await?;
// out.response_body, out.secrets, out.commitment_openings, out.recovered_io
```

Unauthenticated full-reveal flows (e.g. notarizing a JWKS endpoint) use
`prover_generic` with `bearer_token: None` and a closure returning
`vec![0..recv.len()]`.
The URI is absolute because the host names the server; the wire carries the
origin-form request line the verifiers pin. A session that reads a public
document and reveals all of it -- notarizing a JWKS endpoint --
states its own layouts, revealing the whole of each direction.

## Versioning and releases

Expand Down
Loading