Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
56 changes: 1 addition & 55 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,60 +106,6 @@ jobs:
- name: Generated files are current
run: python3 scripts/regen-identity-handles.py --check

# ---------------------------------------------------------------------------
# Verifiers: the committed bb-generated Solidity verifiers reproduce from
# the pinned libid-circuits release. This replaces the reproducibility gate
# that died with the monorepo split — there, the deploy workflow
# recompiled the circuit next to the contracts; here, the circuits live in
# libid-circuits and this job re-derives both verifiers from that repo's
# released verification keys (verifier ⟵ vk alone, no nargo needed) and
# byte-compares against the committed sources. A mismatch means the
# deployed verifier no longer corresponds to the circuit the release
# ships — the web prover and the on-chain verifier must roll together.
#
# solidity/circuits-version pins the release tag. Until that release is
# actually cut on libid-org/libid-circuits, the download step fails with an
# explicit message — that is intended, not flaky.
#
# The download/verify/regenerate/compare chain is scripts/regen-verifiers.sh
# (--check here); the "Regenerate verifiers" dispatch workflow runs the same
# script with --write to refresh the committed files after a pin bump.
#
# publish-crates hangs its `needs:` off this job so nothing publishes over a
# drifted verifier; publish-npm inherits the gate through publish-crates.
# ---------------------------------------------------------------------------
verifiers:
name: Verifiers match circuits release
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# Keyed on the pin file, not the manifest's bb version — the manifest is
# only known after the download, which now happens inside the script. A
# pin bump starts a fresh cache; the script reuses a cached bb when its
# version matches the new manifest and installs via bbup otherwise.
- name: Cache barretenberg
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ~/.bb
key: bb-${{ runner.os }}-circuits-${{ hashFiles('solidity/circuits-version') }}

# Same forge as everywhere else in this workflow, binary only — fmt
# reads foundry.toml but compiles nothing.
- uses: foundry-rs/foundry-toolchain@908c540300062bd5a7e473851cdb4282204cee09 # v1.9.1
with:
version: ${{ env.FOUNDRY_VERSION }}

# The whole chain — download the pinned release, verify sha256s, install
# bb at the manifest's version, regenerate with post-processing,
# byte-compare — lives in the script, shared with the "Regenerate
# verifiers" dispatch workflow so the check and the writer cannot drift.
- name: Regenerate verifiers and compare
env:
GH_TOKEN: ${{ github.token }}
run: ./scripts/regen-verifiers.sh --check

# ---------------------------------------------------------------------------
# Rust: vendored artifacts, formatting, clippy, tests. Needs forge twice
# over — the vendored artifacts are generated from a fresh `forge build`,
Expand Down Expand Up @@ -454,7 +400,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
if: github.event_name == 'release' && github.event.action == 'published'
needs: [verify-tag, versions, solidity, rust, publish-check, verifiers]
needs: [verify-tag, versions, solidity, rust, publish-check]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand Down
86 changes: 0 additions & 86 deletions .github/workflows/regen-verifiers.yml

This file was deleted.

2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ solidity/broadcast/
ts/node_modules/
ts/packages/*/node_modules/
ts/packages/*/dist/
solidity/.circuits-check/
circuits-release/

# Generated from `forge build`, never committed. Both are packaged into their
# published artefact (the crate embeds artifacts/ via include_dir!, the npm
Expand Down
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
# libID contracts

Smart contracts for libID, laid out per chain. `solidity/` is a self-contained
Foundry project holding the EVM contracts (login wallets, transfer bank
diamond, identity naming); room is reserved for `solana/` and other networks,
and `rust/` and `ts/` ABI wrapper packages are coming.
Foundry project holding the EVM contracts: the ceremony verification path
(Notary Service, Proof Verifier, Platform Verifiers, and the Google JWT root
list the Google verifier reads), the identity naming system, and the
deterministic deployment factory. `rust/` and `ts/` hold the ABI wrapper packages; room is reserved
for `solana/` and other networks.

## Layout

```
solidity/ # Foundry project root
contracts/
login/ # registry, web wallets, OIDC + ZK session verifiers
transfer/ # bank diamond and facets
identity/ # platform handle naming and identity verifiers
ceremony/ # NotaryService, CeremonyProofVerifier, Platform Verifiers,
# GoogleJwtRoots
identity/ # IdentityNames, handle normalization
factory/ # LibidFactory: deterministic CREATE3 deployment
WTIA9.sol # wrapped TIA
script/Deploy.s.sol
lib/ # git submodules (openzeppelin, forge-std)
rust/contracts/ # libid-contracts crate: alloy bindings + embedded artifacts
ts/packages/contracts/ # @libid/contracts: viem ABIs, call builders, identity helpers
scripts/
vendor-artifacts.sh
regen-identity-handles.py
```

Expand Down Expand Up @@ -45,10 +51,6 @@ CI runs both in every job that compiles the crate or the package, and again in
the publish jobs — the published crate and npm package carry the generated
output even though git does not.

Some login OIDC flow tests read locally generated proof artifacts from
`circuits/jwt_email/target/`; when those files are absent the tests skip
themselves — that is expected.

## Handle vectors

`solidity/contracts/identity/handles.json` is the source of truth for platform
Expand All @@ -59,8 +61,10 @@ python3 scripts/regen-identity-handles.py # rewrite generated outputs
python3 scripts/regen-identity-handles.py --check # verify nothing drifted
```

Today this generates `solidity/contracts/identity/HandleVectors.sol`; the Rust
and TypeScript outputs activate once those packages exist in this repo.
This generates `solidity/contracts/identity/HandleVectors.sol`,
`rust/identity/src/handle_vectors.rs` and
`ts/packages/contracts/src/identity/handleVectors.ts`; CI's handle-tables job
fails when any of them drifts from `handles.json`.

## Releasing

Expand Down Expand Up @@ -91,10 +95,6 @@ Publishing the release triggers CI's release jobs:
provenance. A prerelease publishes under its first prerelease identifier
as the dist-tag (`1.2.0-rc.1` → `rc`); a plain version under `latest`.

To pick up a new circuits release: bump `solidity/circuits-version` on a
branch, run the "Regenerate verifiers" action on that branch, then review the
bot commit and merge — CI's verifiers job re-proves the regeneration.

## License

Dual-licensed under MIT and Apache-2.0; see `LICENSE-MIT`, `LICENSE-APACHE`
Expand Down
2 changes: 1 addition & 1 deletion rust/contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2021"
rust-version = "1.94.1"
license = "MIT OR Apache-2.0"
repository = "https://github.com/libid-org/libid-contracts"
description = "Typed alloy bindings, embedded forge artifacts, and deploy/upgrade helpers for the libid contract stack (login, transfer bank diamond, identity names)."
description = "Typed alloy bindings, embedded forge artifacts, and deploy/upgrade helpers for the libid identity stack (Notary Service, ceremony proof verifier, Google JWT roots, identity names, deterministic factory)."
keywords = ["ethereum", "alloy", "contracts", "deployment"]
categories = ["cryptography::cryptocurrencies"]
# The vendored artifacts are part of the crate: consumers deploy from the
Expand Down
89 changes: 43 additions & 46 deletions rust/contracts/README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
# libid-contracts

Typed [alloy](https://github.com/alloy-rs/alloy) bindings, embedded forge
artifacts, and deploy/upgrade helpers for the libid contract stack: the login
contracts (`Registry`, `WebWallet`, `WalletFactory`, verifiers), the Bank
EIP-2535 diamond, and the identity-names contracts.
artifacts, and deploy/upgrade helpers for the libid identity stack: the
ceremony verification path (`NotaryService`, `CeremonyProofVerifier`, and
`GoogleJwtRoots`, the signing keys the `google/v1` verifier trusts), the
naming system (`IdentityNames`), and the deterministic deployment factory
(`LibidFactory`).

The compiled artifacts are vendored into the crate, so a consumer can deploy
or upgrade the whole stack against a live network with **zero filesystem
dependencies at runtime**. They are generated, not committed:
`scripts/vendor-artifacts.sh` produces `artifacts/` from `solidity/` and CI
runs it before every build, test and publish. Working in this repo, run it
once after cloning — the crate embeds the directory with `include_dir!`, so
until it exists `cargo build` fails at macro expansion. Signing stays on the consumer's
side: every helper is generic over an alloy `Provider` you have already wired
with a wallet.
until it exists `cargo build` fails at macro expansion. Signing stays on the
consumer's side: every helper is generic over an alloy `Provider` you have
already wired with a wallet.

## Example: deploy the login stack
## Example: deploy the Notary Service and the Google JWT root list

```rust,no_run
use alloy::{primitives::Address, providers::ProviderBuilder};
use alloy::{primitives::{Address, U256}, providers::ProviderBuilder};
use libid_contracts::{
bindings::login::{IRegistryAdmin, Registry, WalletFactory},
deploy::{deploy_behind_proxy, deploy_contract},
bindings::ceremony::{GoogleJwtRoots, NotaryService},
deploy::deploy_behind_proxy,
Artifacts,
};

Expand All @@ -32,69 +34,64 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.wallet(/* your signer */ todo!())
.connect_http("https://rpc.example.org".parse()?);
let artifacts = Artifacts::embedded();
let (owner, notary, backend): (Address, Address, Address) = todo!();
let (owner, notary_key): (Address, Address) = todo!();

// WebWallet implementation, then WalletFactory + Registry behind
// ERC1967 proxies.
let wallet_impl = deploy_contract(
&provider,
artifacts.bytecode("WebWallet")?,
"WebWallet (impl)",
)
.await?;
let factory = deploy_behind_proxy(
// The Notary Service first: every notarized session is verified through
// it, and it charges the Notary Fee for doing so.
let notary = deploy_behind_proxy(
&provider,
&artifacts,
"WalletFactory",
&WalletFactory::initializeCall {
"NotaryService",
&NotaryService::initializeCall {
owner_: owner,
walletImpl_: wallet_impl,
registry_: Address::ZERO,
notary_: notary_key,
fee_: U256::from(1_000),
},
None,
)
.await?;
let registry = deploy_behind_proxy(

// The Google JWT root list pays that fee on every rotation. It starts empty:
// a keeper submits a notarized reading of Google's JWKS to seed it.
let roots = deploy_behind_proxy(
&provider,
&artifacts,
"Registry",
&IRegistryAdmin::initializeCall {
_notary: notary,
_backend: backend,
_walletFactory: factory,
_owner: owner,
"GoogleJwtRoots",
&GoogleJwtRoots::initializeCall {
owner_: owner,
notary_: notary,
},
None,
)
.await?;
WalletFactory::new(factory, &provider)
.setRegistry(registry)
.send()
.await?
.get_receipt()
.await?;

println!("registry {registry:#x}, resolves via {:#x}", registry);
let fee = GoogleJwtRoots::new(roots, &provider)
.quoteRotation()
.call()
.await?;
println!("roots {roots:#x} verify through {notary:#x}; a rotation costs {fee} wei");
Ok(())
}
```

Other entry points:

- `diamond::deploy_bank_diamond` / `diamond::replace_bank_facets` — the Bank
EIP-2535 deploy and facet upgrade.
- `deploy::load_linked_bytecode` (or `Artifacts::linked_bytecode`) — deploys
and links external libraries (`ZKTranscriptLib` for the generated UltraHonk
verifiers) before returning the creation bytecode.
- `deploy::upgrade_uups` — deploy a fresh implementation and
`upgradeToAndCall` a UUPS proxy onto it.
- `Artifacts::facet_selectors` / `Artifacts::method_identifiers` — selector
extraction from the vendored `methodIdentifiers`.
- `factory::ensure_factory` / `factory::factory_deploy` — install the
canonical cross-network factory where missing and deploy protocol proxies
through it at name-derived CREATE3 addresses.
- `deploy::load_linked_bytecode` (or `Artifacts::linked_bytecode`) — deploys
and links external libraries before returning the creation bytecode. Nothing
covered today links one; the UltraHonk verifiers the ceremony circuits bring
will.
- `Artifacts::method_identifiers` — selector extraction from the vendored
`methodIdentifiers`.

## Testing

Unit tests run everywhere; the integration tests in `tests/anvil.rs` spawn
`anvil` (foundry) and deploy every stack for real. From `rust/`:
`anvil` (foundry) and deploy the stack for real. From `rust/`:

```sh
cargo +nightly fmt --all # nightly only — stable rustfmt mangles imports
Expand Down
Loading
Loading