Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
107 changes: 82 additions & 25 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# CI + release for zk-encryption (the shape of eid-circuits' ci.yml).
#
# noir ──────┐
# rust ──────┼─> ci ──> tag (cog, push:main) ──> github-release ──> publish (release assets, R2)
# rust ──────┼─> ci ──> tag (cog, push:main) ──┬─> publish (release assets, R2) ──┬─> github-release
# └─> crates-io (zk-encryption) ─────┘
#
# Everything is published before the GitHub release exists: a release only
# appears once its assets answer on the CDN and its crate is on crates.io.
# typos ─────┤
# supply-chain
#
Expand Down Expand Up @@ -260,35 +264,55 @@ jobs:
echo "tag=${tag}"
echo "tag=${tag}" >> "$GITHUB_OUTPUT"

github-release:
name: Create GitHub release
# The wallet crate on crates.io, at the released tag. A version already
# published (a re-release of an existing tag) is skipped: crates.io refuses
# a second upload of a version anyway.
crates-io:
name: Publish zk-encryption to crates.io
runs-on: ubuntu-latest
needs: [resolve, tag]
if: |
always() &&
((needs.tag.result == 'success' && needs.tag.outputs.tag != '') ||
needs.resolve.outputs.release_tag != '')
env:
TAG: ${{ needs.tag.outputs.tag || needs.resolve.outputs.release_tag }}
steps:
- uses: softprops/action-gh-release@v2
- uses: actions/checkout@v5
with:
tag_name: ${{ needs.tag.outputs.tag || needs.resolve.outputs.release_tag }}
name: ${{ needs.tag.outputs.tag || needs.resolve.outputs.release_tag }}
generate_release_notes: true
ref: ${{ needs.tag.outputs.tag || needs.resolve.outputs.release_tag }}
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rust-src-dir: rust
- name: cargo publish
working-directory: rust
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -euo pipefail
version="${TAG#v}"
if curl -fsS -A "zk-encryption-ci" "https://crates.io/api/v1/crates/zk-encryption/$version" >/dev/null 2>&1; then
echo "zk-encryption $version is already on crates.io"
exit 0
fi
cargo publish -p zk-encryption --locked

# The release assets: the frozen manifest, the frozen resources (ABIs and
# keys) as a tarball with its SHA-256, and catalog.json, uploaded to the
# GitHub release and to the circuits bucket under
# https://circuits.zk-experiments.dev/<R2_PREFIX>/<version>/ (and
# <R2_PREFIX>/latest/catalog.json). The prefix is a workflow variable so
# other repositories share the bucket under their own prefixes.
# circuits bucket under https://circuits.zk-experiments.dev/<R2_PREFIX>/<version>/
# (and <R2_PREFIX>/latest/catalog.json) and handed to github-release as a
# workflow artifact. The prefix is a workflow variable so other repositories
# share the bucket under their own prefixes.
publish:
name: Publish release assets
runs-on: ubuntu-latest
needs: [resolve, tag, github-release]
if: always() && needs.github-release.result == 'success'
needs: [resolve, tag]
if: |
always() &&
((needs.tag.result == 'success' && needs.tag.outputs.tag != '') ||
needs.resolve.outputs.release_tag != '')
timeout-minutes: 60
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.tag.outputs.tag || needs.resolve.outputs.release_tag }}
R2_PREFIX: ${{ vars.R2_PREFIX_ZK_ENCRYPTION || 'zk-encryption' }}
R2_BASE: https://circuits.zk-experiments.dev
Expand All @@ -315,12 +339,6 @@ jobs:
mise run freeze:check
- name: Write the release assets
run: mise run release:assets
- name: Upload to the release
run: |
set -euo pipefail
for f in target/release/manifest.toml target/release/resources.tar.gz target/release/resources.tar.gz.sha256 target/release/catalog.json; do
gh release upload "$TAG" "$f" --clobber
done
- name: Upload to the circuits R2 bucket
if: vars.R2_CIRCUITS_ZK_EXPERIMENTS_BUCKET != ''
env:
Expand All @@ -342,16 +360,55 @@ jobs:
curl -fsS "$base/resources.tar.gz" -o published.tar.gz
echo "$(cat target/release/resources.tar.gz.sha256) published.tar.gz" | shasum -a 256 -c
curl -fsS "$R2_BASE/$R2_PREFIX/latest/catalog.json" | cmp - target/release/catalog.json
- name: Add the downloads to the release notes
- name: Hand the assets to github-release
uses: actions/upload-artifact@v4
with:
name: release-assets
path: |
target/release/manifest.toml
target/release/resources.tar.gz
target/release/resources.tar.gz.sha256
target/release/catalog.json
if-no-files-found: error

# Last: the release appears only once its assets are on the CDN and its crate
# is on crates.io, with the assets attached and the downloads in its notes.
github-release:
name: Create GitHub release
runs-on: ubuntu-latest
needs: [resolve, tag, crates-io, publish]
if: always() && needs.crates-io.result == 'success' && needs.publish.result == 'success'
env:
TAG: ${{ needs.tag.outputs.tag || needs.resolve.outputs.release_tag }}
R2_PREFIX: ${{ vars.R2_PREFIX_ZK_ENCRYPTION || 'zk-encryption' }}
R2_BASE: https://circuits.zk-experiments.dev
steps:
- uses: actions/download-artifact@v4
with:
name: release-assets
path: assets
- name: The downloads section of the release notes
run: |
set -euo pipefail
version="${TAG#v}"
base="$R2_BASE/$R2_PREFIX/$version"
{
gh release view "$TAG" --json body -q .body
echo
echo "## Frozen circuits"
echo
echo "[\`catalog.json\`]($base/catalog.json) (the families and their roots), [\`manifest.toml\`]($base/manifest.toml), [\`resources.tar.gz\`]($base/resources.tar.gz) (ABIs and verification keys, SHA-256 \`$(cat target/release/resources.tar.gz.sha256)\`); the same files are attached to this release. The \`zk-encryption-circuits\` crate carries the bytecode and every pin, so the host is a mirror, not a trust anchor."
echo "[\`catalog.json\`]($base/catalog.json) (the families and their roots), [\`manifest.toml\`]($base/manifest.toml), [\`resources.tar.gz\`]($base/resources.tar.gz) (ABIs and verification keys, SHA-256 \`$(cat assets/resources.tar.gz.sha256)\`); the same files are attached to this release. The \`zk-encryption-circuits\` crate carries the bytecode and every pin, so the host is a mirror, not a trust anchor."
echo
echo "## Crate"
echo
echo "[\`zk-encryption $version\`](https://crates.io/crates/zk-encryption/$version) on crates.io."
} > notes.md
gh release edit "$TAG" --notes-file notes.md
- uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG }}
name: ${{ env.TAG }}
generate_release_notes: true
body_path: notes.md
files: |
assets/manifest.toml
assets/resources.tar.gz
assets/resources.tar.gz.sha256
assets/catalog.json
2 changes: 1 addition & 1 deletion rust/Cargo.lock

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

9 changes: 7 additions & 2 deletions rust/zk-encryption/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
[package]
name = "zk-encryption"
version = "0.1.0"
version.workspace = true
description = "Post-quantum encrypted channel for ZK pipelines, the wallet side: the receiver's keys, bundle and scan, the sender's handshake and ratchet, payload sealing and opening, the lattice encryption and the Poseidon2 duplex the circuits match"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish.workspace = true
# The one crate of this workspace on crates.io (the circuits ship as release assets on the CDN).
publish = true
repository.workspace = true
readme = "README.md"
documentation = "https://docs.rs/zk-encryption"
keywords = ["zero-knowledge", "post-quantum", "ml-kem", "poseidon2", "noir"]
categories = ["cryptography"]

[dependencies]
ark-bn254 = "0.6"
Expand Down
10 changes: 5 additions & 5 deletions rust/zk-encryption/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# pq-channel
# zk-encryption

The post-quantum envelope channel of the folded eid + Emit V2 transfer, as a Rust library: everything a wallet or a receiver service does off-chain, matching the circuits in [`noir/`](../../noir). The protocol is [`PROTOCOL.md`](../../PROTOCOL.md); this crate is its reference implementation, and [`rust/reference`](../reference) drives it through real folded proofs against a mock chain.

Expand All @@ -19,12 +19,12 @@ No proving here: the crate has no dependency on the Noir toolchain or barretenbe
| `emit` | Emit V2's hashes (`Emit`), `NoteOpening`, `Kem` (the session app's outputs with the carried ciphertext), the `Envelope` event with its 2,080-byte layout, `Delivery`, `Dg1` (its encoding as a 6-field payload, `to_payload`/`from_payload`, and its sealing); `NoteOpening` is a payload too. |
| `witness` | `NoirToml`: the session app's inputs (`context` and the `channel` tables) and the envelope apps' inputs (the payload, its commitment salt, `context`, `s`), in the `Prover.toml` layout the circuits take. |

`Error` is the crate's one error type (bundle parsing has its own, `bundle::BundleError`). Field elements are `ark_bn254::Fr`, re-exported as `pq_channel::Fr`.
`Error` is the crate's one error type (bundle parsing has its own, `bundle::BundleError`). Field elements are `ark_bn254::Fr`, re-exported as `zk_encryption::Fr`.

## Receiver flow

```rust
use pq_channel::{bundle::{Bundle, Ek}, emit::{Delivery, Emit, Envelope}, receiver::{Receiver, Scan}};
use zk_encryption::{bundle::{Bundle, Ek}, emit::{Delivery, Emit, Envelope}, receiver::{Receiver, Scan}};

// Once: long-term keys and the bundle to publish. `pk` is the shielded
// address (Emit::pk(sk)) transfers to this receiver commit their note to.
Expand Down Expand Up @@ -53,7 +53,7 @@ match receiver.scan(&delivery) {
## Sender flow

```rust
use pq_channel::{sender::Sender, witness::NoirToml};
use zk_encryption::{sender::Sender, witness::NoirToml};

// Once per receiver: accept the bundle (format, chain, validity, V, ek and
// its commitment). `fetch` resolves an ek URL, or `&|_| None` for inline only.
Expand Down Expand Up @@ -85,4 +85,4 @@ Nothing here is constant-time beyond what the underlying crates provide; see PRO

## Tests

`cargo test -p pq-channel --release` (add `--features serde` for the persistence round trip): known answers against nargo, decryption of real ML-KEM-768 ciphertexts, the extreme-noise bound, the ratchet's windows, the bundle, and a receiver's scans over natively built transfers. The folded proofs are exercised by `rust/reference`'s e2e test.
`cargo test -p zk-encryption --release` (add `--features serde` for the persistence round trip): known answers against nargo, decryption of real ML-KEM-768 ciphertexts, the extreme-noise bound, the ratchet's windows, the bundle, and a receiver's scans over natively built transfers. The folded proofs are exercised by `rust/reference`'s e2e test.
Loading