@lemmaoracle/seal 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # @lemmaoracle/seal
2
+
3
+ ZK auth circuit for **Proof-based sign-in** to the Lemma developer dashboard.
4
+
5
+ `seal` lets a developer prove they hold a valid Lemma API key **without
6
+ revealing the key**. It is a [zero-knowledge proof](https://lemma.frame00.com)
7
+ of the pre-image of the key's SHA-256 hash.
8
+
9
+ ## How it works
10
+
11
+ The dashboard's sign-in flow:
12
+
13
+ 1. The dashboard BFF issues a challenge `nonce`.
14
+ 2. The developer generates a `seal` proof: it proves knowledge of the
15
+ API key whose SHA-256 hash equals the `key_hash` stored in the
16
+ workers `api_keys` table, bound to that `nonce`.
17
+ 3. The BFF verifies the proof, reads the attested `key_hash`, looks it
18
+ up in `api_keys`, and resolves the caller's `scope_id`.
19
+ 4. The BFF issues a session token tied to the scope.
20
+
21
+ The proof reveals only the `key_hash` (already public, since it is what
22
+ the API stores) and the `nonce` — never the API key itself.
23
+
24
+ > Proof-based sign-in requires an existing API key. First-time users
25
+ > onboard via GitHub OAuth, which issues their first key.
26
+
27
+ ## Package layout
28
+
29
+ ```
30
+ seal/
31
+ ├── circuits/
32
+ │ ├── src/seal-identity.circom Circuit: SHA-256 pre-image proof
33
+ │ ├── src/seal-identity.test.ts Circuit witness tests (needs a build)
34
+ │ └── scripts/build.sh Compile circom → wasm + groth16 setup
35
+ ├── scripts/
36
+ │ ├── register-circuit.ts Pin artifacts to IPFS, register via SDK
37
+ │ └── setup-toolchain.sh Install Rust + circom
38
+ ├── src/ TypeScript proof helpers (published)
39
+ │ ├── bits.ts API key ↔ circuit signal conversions
40
+ │ ├── proof.ts generateSealProof / verifySealProof
41
+ │ └── index.ts Public API
42
+ └── .env.example Credentials for register-circuit.ts
43
+ ```
44
+
45
+ There is no `normalize/` step: the circuit's witness input is the raw
46
+ API key string (already canonical), so seal uses the existing
47
+ [`passthrough-v1`](../passthrough) schema rather than a bespoke
48
+ normalizer.
49
+
50
+ `seal` is a **reference definition**. It is published to npm so
51
+ developers can generate proofs; it is *not* imported by `workers` or the
52
+ dashboard at runtime — the circuit reaches them through the normal
53
+ Lemma circuit-registration path.
54
+
55
+ ## The circuit
56
+
57
+ `seal-identity.circom` proves `SHA-256(apiKey) == keyHash`:
58
+
59
+ - **Private input** — `keyBits[512]`: the 64-byte ASCII API key as bits.
60
+ Lemma keys are 32 random bytes rendered as 64 hex characters (see the
61
+ workers `generate_api_key.js`).
62
+ - **Public input** — `nonce`: the dashboard challenge, bound into the
63
+ constraint system for replay protection.
64
+ - **Public output** — `keyHash[256]`: the SHA-256 digest bits.
65
+
66
+ The hashing matches the workers `middleware/auth.ts`
67
+ (`SHA-256(utf8_bytes(apiKey))`), so a proof's `keyHash` is directly
68
+ comparable to `api_keys.key_hash`.
69
+
70
+ ## Build the circuit
71
+
72
+ The circom toolchain is **not** required to install or test this package
73
+ — only to compile the circuit and run `register-circuit.ts`.
74
+
75
+ ```bash
76
+ # One-time: install Rust + circom (≈ a few minutes)
77
+ ./scripts/setup-toolchain.sh
78
+
79
+ # Compile → build/seal-identity_js/seal-identity.wasm + _final.zkey
80
+ cd circuits && npm install && npm run build
81
+ ```
82
+
83
+ `build.sh` downloads the 2^17 Hermez powers-of-tau file (~290 MB) on
84
+ first run — SHA-256 over a 512-bit pre-image is ~60k constraints.
85
+
86
+ ## Register the circuit
87
+
88
+ ```bash
89
+ cp .env.example .env # then fill in LEMMA_API_KEY + PINATA_* keys
90
+ npm run register:circuit
91
+ ```
92
+
93
+ This pins the `.wasm` / `.zkey` to IPFS via Pinata and registers a
94
+ `CircuitMeta` (`circuitId: seal-identity-v1`, `schema: passthrough-v1`,
95
+ off-chain `groth16-bn254-snarkjs` verifier) with the workers API. The
96
+ dashboard BFF then fetches verification params at runtime via
97
+ `GET /v1/circuits/seal-identity-v1`.
98
+
99
+ ## Generate a proof (developer usage)
100
+
101
+ ```ts
102
+ import { generateSealProof } from "@lemmaoracle/seal";
103
+
104
+ const { proof, publicSignals, keyHash } = await generateSealProof(
105
+ { apiKey: process.env.LEMMA_API_KEY!, nonce: challengeNonce },
106
+ { wasm: "seal-identity.wasm", zkey: "seal-identity_final.zkey" },
107
+ );
108
+ // POST { proof, publicSignals } to the dashboard sign-in endpoint.
109
+ ```
110
+
111
+ ## Scripts
112
+
113
+ | Command | Description |
114
+ | :------------------------ | :----------------------------------------- |
115
+ | `npm run build` | Compile the TypeScript proof helpers |
116
+ | `npm test` | Run the pure unit tests (no toolchain) |
117
+ | `npm run build:circuit` | Compile the circom circuit |
118
+ | `npm run test:circuit` | Run circuit witness tests (needs a build) |
119
+ | `npm run register:circuit`| Pin artifacts to IPFS and register via SDK |
120
+
121
+ ## License
122
+
123
+ MIT
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Compile the seal-identity circuit and run the groth16 trusted setup.
4
+ # Prerequisite: circom + the circuits/ npm dependencies — install them
5
+ # with ../scripts/setup-toolchain.sh.
6
+ #
7
+ set -euo pipefail
8
+
9
+ CIRCUIT_NAME="seal-identity"
10
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
11
+ SRC_DIR="$SCRIPT_DIR/src"
12
+ BUILD_DIR="$SCRIPT_DIR/build"
13
+ PTAU_DIR="$BUILD_DIR/ptau"
14
+ CIRCOMLIB_DIR="$SCRIPT_DIR/node_modules"
15
+ SNARKJS="npx snarkjs"
16
+
17
+ # SHA-256 over a 512-bit pre-image compiles to ~60k R1CS constraints
18
+ # (two 512-bit blocks), so phase-1 powers of tau must cover 2^17.
19
+ PTAU_POWER=17
20
+ PTAU="$PTAU_DIR/pot${PTAU_POWER}_final.ptau"
21
+ PTAU_URL="https://storage.googleapis.com/zkevm/ptau/powersOfTau28_hez_final_${PTAU_POWER}.ptau"
22
+
23
+ mkdir -p "$BUILD_DIR" "$PTAU_DIR"
24
+
25
+ command -v circom >/dev/null 2>&1 || {
26
+ echo "✗ circom not found — run ../scripts/setup-toolchain.sh first" >&2
27
+ exit 1
28
+ }
29
+
30
+ echo "→ compiling $CIRCUIT_NAME"
31
+ circom "$SRC_DIR/$CIRCUIT_NAME.circom" \
32
+ --r1cs \
33
+ --wasm \
34
+ --sym \
35
+ -l "$CIRCOMLIB_DIR" \
36
+ -o "$BUILD_DIR"
37
+
38
+ echo "→ constraint info"
39
+ $SNARKJS r1cs info "$BUILD_DIR/$CIRCUIT_NAME.r1cs"
40
+
41
+ # Phase 1 — powers of tau. The 2^17 file is ~290 MB; downloading the
42
+ # pre-generated Hermez ceremony output is far faster and safer than
43
+ # generating one locally.
44
+ if [ ! -f "$PTAU" ]; then
45
+ echo "→ downloading powers of tau (2^${PTAU_POWER})"
46
+ curl -fL "$PTAU_URL" -o "$PTAU"
47
+ fi
48
+
49
+ echo "→ groth16 setup"
50
+ $SNARKJS groth16 setup \
51
+ "$BUILD_DIR/$CIRCUIT_NAME.r1cs" \
52
+ "$PTAU" \
53
+ "$BUILD_DIR/${CIRCUIT_NAME}_0000.zkey"
54
+
55
+ $SNARKJS zkey contribute \
56
+ "$BUILD_DIR/${CIRCUIT_NAME}_0000.zkey" \
57
+ "$BUILD_DIR/${CIRCUIT_NAME}_final.zkey" \
58
+ --name="lemma seal-identity" -v -e="lemma seal-identity $(date +%s)"
59
+
60
+ echo "→ exporting verification key"
61
+ $SNARKJS zkey export verificationkey \
62
+ "$BUILD_DIR/${CIRCUIT_NAME}_final.zkey" \
63
+ "$BUILD_DIR/${CIRCUIT_NAME}_vkey.json"
64
+
65
+ echo "✓ $CIRCUIT_NAME built → $BUILD_DIR"
@@ -0,0 +1,65 @@
1
+ pragma circom 2.1.0;
2
+
3
+ include "circomlib/circuits/sha256/sha256.circom";
4
+
5
+ /**
6
+ * SealIdentity — Proof-based sign-in for the Lemma developer dashboard.
7
+ *
8
+ * Proves knowledge of the pre-image of an API key hash without revealing
9
+ * the key. The pre-image is the raw API key string; the hash is the
10
+ * SHA-256 `key_hash` stored in the workers `api_keys` D1 table.
11
+ *
12
+ * This matches the hashing in workers `middleware/auth.ts`:
13
+ * key_hash = SHA-256( utf8_bytes(apiKey) )
14
+ *
15
+ * Lemma API keys are 32 random bytes rendered as a 64-character
16
+ * lowercase hex string (see workers `generate_api_key.js`), so the
17
+ * SHA-256 pre-image is exactly 64 ASCII bytes = 512 bits.
18
+ *
19
+ * Private input:
20
+ * keyBits[512] Bit decomposition of the 64-byte ASCII API key,
21
+ * most-significant-bit first within each byte.
22
+ *
23
+ * Public input:
24
+ * nonce Dashboard-issued challenge nonce. Bound into the
25
+ * constraint system so a proof cannot be replayed
26
+ * against a different challenge.
27
+ *
28
+ * Public output:
29
+ * keyHash[256] SHA-256 digest bits. The dashboard BFF reassembles
30
+ * these into the hex `key_hash` and looks it up in
31
+ * `api_keys` to resolve the caller's `scope_id`.
32
+ */
33
+ template SealIdentity(keyBytes) {
34
+ var keyBitLen = keyBytes * 8;
35
+
36
+ signal input keyBits[keyBitLen];
37
+ signal input nonce;
38
+ signal output keyHash[256];
39
+
40
+ // Constrain every pre-image bit to be boolean. Without this a
41
+ // malicious prover could feed non-binary field values into the
42
+ // SHA-256 gadget and forge a witness.
43
+ for (var i = 0; i < keyBitLen; i++) {
44
+ keyBits[i] * (keyBits[i] - 1) === 0;
45
+ }
46
+
47
+ // SHA-256 over the API key bytes. circomlib's Sha256 handles message
48
+ // padding internally; a 512-bit pre-image spans two 512-bit blocks.
49
+ component sha = Sha256(keyBitLen);
50
+ for (var i = 0; i < keyBitLen; i++) {
51
+ sha.in[i] <== keyBits[i];
52
+ }
53
+ for (var i = 0; i < 256; i++) {
54
+ keyHash[i] <== sha.out[i];
55
+ }
56
+
57
+ // Bind the challenge nonce into the constraint system. The squaring
58
+ // is otherwise meaningless — its only purpose is to make `nonce` a
59
+ // constrained public signal, so the verifier knows the proof was
60
+ // generated for this specific challenge.
61
+ signal nonceBinding;
62
+ nonceBinding <== nonce * nonce;
63
+ }
64
+
65
+ component main {public [nonce]} = SealIdentity(64);
package/dist/bits.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Bit-level conversions between API key strings, circuit witness
3
+ * signals, and SHA-256 hashes. Pure and dependency-free.
4
+ */
5
+ import type { Bit } from "./types.js";
6
+ /** The seal circuit's fixed pre-image length: a 64-byte ASCII API key. */
7
+ export declare const SEAL_KEY_BYTES = 64;
8
+ /** The seal circuit's pre-image bit length (512). */
9
+ export declare const SEAL_KEY_BITS: number;
10
+ /**
11
+ * Convert a raw API key string into the `keyBits` witness signal — the
12
+ * UTF-8 bytes of the key, MSB-first per byte.
13
+ *
14
+ * Throws if the key is not exactly {@link SEAL_KEY_BYTES} ASCII bytes,
15
+ * since the circuit's pre-image length is fixed.
16
+ */
17
+ export declare const apiKeyToBits: (apiKey: string) => ReadonlyArray<Bit>;
18
+ /**
19
+ * Reassemble the circuit's 256-bit `keyHash` public output into the
20
+ * lowercase hex `key_hash` string stored in the `api_keys` table.
21
+ */
22
+ export declare const hashBitsToHex: (bits: ReadonlyArray<string | number>) => string;
23
+ //# sourceMappingURL=bits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bits.d.ts","sourceRoot":"","sources":["../src/bits.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,0EAA0E;AAC1E,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,qDAAqD;AACrD,eAAO,MAAM,aAAa,QAAqB,CAAC;AAShD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,KAAG,aAAa,CAAC,GAAG,CAS9D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,MAAM,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,KACnC,MAKO,CAAC"}
package/dist/bits.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Bit-level conversions between API key strings, circuit witness
3
+ * signals, and SHA-256 hashes. Pure and dependency-free.
4
+ */
5
+ /** The seal circuit's fixed pre-image length: a 64-byte ASCII API key. */
6
+ export const SEAL_KEY_BYTES = 64;
7
+ /** The seal circuit's pre-image bit length (512). */
8
+ export const SEAL_KEY_BITS = SEAL_KEY_BYTES * 8;
9
+ /**
10
+ * Decompose a byte into 8 bits, most-significant-bit first — the bit
11
+ * order circomlib's SHA-256 gadget expects.
12
+ */
13
+ const byteToBits = (byte) => Array.from({ length: 8 }, (_, i) => ((byte >> (7 - i)) & 1));
14
+ /**
15
+ * Convert a raw API key string into the `keyBits` witness signal — the
16
+ * UTF-8 bytes of the key, MSB-first per byte.
17
+ *
18
+ * Throws if the key is not exactly {@link SEAL_KEY_BYTES} ASCII bytes,
19
+ * since the circuit's pre-image length is fixed.
20
+ */
21
+ export const apiKeyToBits = (apiKey) => {
22
+ const bytes = new TextEncoder().encode(apiKey);
23
+ return bytes.length === SEAL_KEY_BYTES
24
+ ? [...bytes].flatMap(byteToBits)
25
+ : (() => {
26
+ throw new Error(`seal: API key must be ${SEAL_KEY_BYTES} bytes, got ${bytes.length}`);
27
+ })();
28
+ };
29
+ /**
30
+ * Reassemble the circuit's 256-bit `keyHash` public output into the
31
+ * lowercase hex `key_hash` string stored in the `api_keys` table.
32
+ */
33
+ export const hashBitsToHex = (bits) => bits.length === 256
34
+ ? BigInt(`0b${bits.map(String).join("")}`).toString(16).padStart(64, "0")
35
+ : (() => {
36
+ throw new Error(`seal: keyHash must be 256 bits, got ${bits.length}`);
37
+ })();
38
+ //# sourceMappingURL=bits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bits.js","sourceRoot":"","sources":["../src/bits.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AAEjC,qDAAqD;AACrD,MAAM,CAAC,MAAM,aAAa,GAAG,cAAc,GAAG,CAAC,CAAC;AAEhD;;;GAGG;AACH,MAAM,UAAU,GAAG,CAAC,IAAY,EAAsB,EAAE,CACtD,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAO,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAQ,CAAC,CAAC;AAE3E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAc,EAAsB,EAAE;IACjE,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,MAAM,KAAK,cAAc;QACpC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;QAChC,CAAC,CAAC,CAAC,GAAG,EAAE;YACJ,MAAM,IAAI,KAAK,CACb,yBAAyB,cAAc,eAAe,KAAK,CAAC,MAAM,EAAE,CACrE,CAAC;QACJ,CAAC,CAAC,EAAE,CAAC;AACX,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,IAAoC,EAC5B,EAAE,CACV,IAAI,CAAC,MAAM,KAAK,GAAG;IACjB,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC;IACzE,CAAC,CAAC,CAAC,GAAG,EAAE;QACJ,MAAM,IAAI,KAAK,CAAC,uCAAuC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACxE,CAAC,CAAC,EAAE,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @lemmaoracle/seal — ZK auth circuit for Lemma dashboard sign-in.
3
+ *
4
+ * `seal` lets a developer prove knowledge of their Lemma API key
5
+ * without revealing it. The dashboard BFF issues a challenge nonce, the
6
+ * developer generates a proof, and the BFF resolves the attested
7
+ * SHA-256 `key_hash` to a `scope_id`.
8
+ *
9
+ * The circuit (`circuits/src/seal-identity.circom`) is registered with
10
+ * the Lemma workers API as a normal circuit resource. This package is a
11
+ * reference definition: it is published for developers to generate
12
+ * proofs and is not imported by workers or the dashboard at runtime.
13
+ */
14
+ export type { Bit, SealArtifacts, SealProof, SealProofInput, } from "./types.js";
15
+ export { SEAL_KEY_BYTES, SEAL_KEY_BITS, apiKeyToBits, hashBitsToHex, } from "./bits.js";
16
+ export { generateSealProof, verifySealProof } from "./proof.js";
17
+ /** The circuit id under which seal is registered with the Lemma API. */
18
+ export declare const SEAL_CIRCUIT_ID = "seal-identity-v1";
19
+ /** The schema the seal circuit is registered against. */
20
+ export declare const SEAL_SCHEMA = "passthrough-v1";
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,YAAY,EACV,GAAG,EACH,aAAa,EACb,SAAS,EACT,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEhE,wEAAwE;AACxE,eAAO,MAAM,eAAe,qBAAqB,CAAC;AAElD,yDAAyD;AACzD,eAAO,MAAM,WAAW,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @lemmaoracle/seal — ZK auth circuit for Lemma dashboard sign-in.
3
+ *
4
+ * `seal` lets a developer prove knowledge of their Lemma API key
5
+ * without revealing it. The dashboard BFF issues a challenge nonce, the
6
+ * developer generates a proof, and the BFF resolves the attested
7
+ * SHA-256 `key_hash` to a `scope_id`.
8
+ *
9
+ * The circuit (`circuits/src/seal-identity.circom`) is registered with
10
+ * the Lemma workers API as a normal circuit resource. This package is a
11
+ * reference definition: it is published for developers to generate
12
+ * proofs and is not imported by workers or the dashboard at runtime.
13
+ */
14
+ export { SEAL_KEY_BYTES, SEAL_KEY_BITS, apiKeyToBits, hashBitsToHex, } from "./bits.js";
15
+ export { generateSealProof, verifySealProof } from "./proof.js";
16
+ /** The circuit id under which seal is registered with the Lemma API. */
17
+ export const SEAL_CIRCUIT_ID = "seal-identity-v1";
18
+ /** The schema the seal circuit is registered against. */
19
+ export const SEAL_SCHEMA = "passthrough-v1";
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,OAAO,EACL,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEhE,wEAAwE;AACxE,MAAM,CAAC,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAElD,yDAAyD;AACzD,MAAM,CAAC,MAAM,WAAW,GAAG,gBAAgB,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Seal proof generation and verification.
3
+ *
4
+ * Dashboard sign-in flow:
5
+ * 1. The BFF issues a challenge `nonce`.
6
+ * 2. The developer runs {@link generateSealProof} with their API key.
7
+ * 3. The BFF runs {@link verifySealProof} and reads the attested
8
+ * `keyHash` to resolve the caller's scope.
9
+ *
10
+ * snarkjs is imported lazily so that consumers who only need the pure
11
+ * bit helpers ({@link apiKeyToBits} etc.) do not pay for it.
12
+ */
13
+ import type { SealArtifacts, SealProof, SealProofInput } from "./types.js";
14
+ /**
15
+ * Generate a groth16 proof that the caller knows the API key whose
16
+ * SHA-256 hash is `keyHash`, bound to the given challenge `nonce`.
17
+ *
18
+ * Requires the compiled circuit artifacts — build them with
19
+ * `npm run build` in `packages/seal/circuits` (see the package README).
20
+ */
21
+ export declare const generateSealProof: (input: SealProofInput, artifacts: SealArtifacts) => Promise<SealProof>;
22
+ /**
23
+ * Verify a seal proof against the circuit verification key. Resolves to
24
+ * the attested `keyHash` and `nonce` on success, or `null` if invalid.
25
+ */
26
+ export declare const verifySealProof: (proof: SealProof, verificationKey: Readonly<Record<string, unknown>>) => Promise<Readonly<{
27
+ keyHash: string;
28
+ nonce: string;
29
+ }> | null>;
30
+ //# sourceMappingURL=proof.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../src/proof.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAK3E;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO,cAAc,EACrB,WAAW,aAAa,KACvB,OAAO,CAAC,SAAS,CAgBnB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO,SAAS,EAChB,iBAAiB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KACjD,OAAO,CAAC,QAAQ,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAa7D,CAAC"}
package/dist/proof.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Seal proof generation and verification.
3
+ *
4
+ * Dashboard sign-in flow:
5
+ * 1. The BFF issues a challenge `nonce`.
6
+ * 2. The developer runs {@link generateSealProof} with their API key.
7
+ * 3. The BFF runs {@link verifySealProof} and reads the attested
8
+ * `keyHash` to resolve the caller's scope.
9
+ *
10
+ * snarkjs is imported lazily so that consumers who only need the pure
11
+ * bit helpers ({@link apiKeyToBits} etc.) do not pay for it.
12
+ */
13
+ import { apiKeyToBits, hashBitsToHex } from "./bits.js";
14
+ /** Number of `keyHash` bits at the head of the public signal vector. */
15
+ const KEY_HASH_BITS = 256;
16
+ /**
17
+ * Generate a groth16 proof that the caller knows the API key whose
18
+ * SHA-256 hash is `keyHash`, bound to the given challenge `nonce`.
19
+ *
20
+ * Requires the compiled circuit artifacts — build them with
21
+ * `npm run build` in `packages/seal/circuits` (see the package README).
22
+ */
23
+ export const generateSealProof = async (input, artifacts) => {
24
+ const { groth16 } = await import("snarkjs");
25
+ const witness = {
26
+ keyBits: apiKeyToBits(input.apiKey).map(Number),
27
+ nonce: input.nonce,
28
+ };
29
+ const { proof, publicSignals } = await groth16.fullProve(witness, artifacts.wasm, artifacts.zkey);
30
+ return {
31
+ proof,
32
+ publicSignals,
33
+ keyHash: hashBitsToHex(publicSignals.slice(0, KEY_HASH_BITS)),
34
+ };
35
+ };
36
+ /**
37
+ * Verify a seal proof against the circuit verification key. Resolves to
38
+ * the attested `keyHash` and `nonce` on success, or `null` if invalid.
39
+ */
40
+ export const verifySealProof = async (proof, verificationKey) => {
41
+ const { groth16 } = await import("snarkjs");
42
+ const ok = await groth16.verify(verificationKey, [...proof.publicSignals], proof.proof);
43
+ return ok
44
+ ? {
45
+ keyHash: hashBitsToHex(proof.publicSignals.slice(0, KEY_HASH_BITS)),
46
+ nonce: proof.publicSignals[KEY_HASH_BITS] ?? "",
47
+ }
48
+ : null;
49
+ };
50
+ //# sourceMappingURL=proof.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proof.js","sourceRoot":"","sources":["../src/proof.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAGxD,wEAAwE;AACxE,MAAM,aAAa,GAAG,GAAG,CAAC;AAE1B;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EACpC,KAAqB,EACrB,SAAwB,EACJ,EAAE;IACtB,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAmB;QAC9B,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;QAC/C,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC;IACF,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,MAAM,OAAO,CAAC,SAAS,CACtD,OAAO,EACP,SAAS,CAAC,IAAI,EACd,SAAS,CAAC,IAAI,CACf,CAAC;IACF,OAAO;QACL,KAAK;QACL,aAAa;QACb,OAAO,EAAE,aAAa,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;KAC9D,CAAC;AACJ,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAClC,KAAgB,EAChB,eAAkD,EACY,EAAE;IAChE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5C,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,MAAM,CAC7B,eAAe,EACf,CAAC,GAAG,KAAK,CAAC,aAAa,CAAC,EACxB,KAAK,CAAC,KAAK,CACZ,CAAC;IACF,OAAO,EAAE;QACP,CAAC,CAAC;YACE,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;YACnE,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,aAAa,CAAC,IAAI,EAAE;SAChD;QACH,CAAC,CAAC,IAAI,CAAC;AACX,CAAC,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @lemmaoracle/seal — public types for generating dashboard sign-in proofs.
3
+ */
4
+ import type { Groth16Proof } from "snarkjs";
5
+ /** A single bit (0 or 1). */
6
+ export type Bit = 0 | 1;
7
+ /** Inputs required to generate a seal sign-in proof. */
8
+ export type SealProofInput = Readonly<{
9
+ /** The raw API key string — a 64-character lowercase hex string. */
10
+ apiKey: string;
11
+ /** The dashboard-issued challenge nonce, as a decimal string. */
12
+ nonce: string;
13
+ }>;
14
+ /** Filesystem paths (or URLs) of the compiled circuit artifacts. */
15
+ export type SealArtifacts = Readonly<{
16
+ /** Path/URL to `seal-identity.wasm`. */
17
+ wasm: string;
18
+ /** Path/URL to `seal-identity_final.zkey`. */
19
+ zkey: string;
20
+ }>;
21
+ /** A groth16 proof together with its public signals. */
22
+ export type SealProof = Readonly<{
23
+ /** The groth16 proof object (snarkjs shape). */
24
+ proof: Groth16Proof;
25
+ /** Public signals: the 256 `keyHash` bits followed by `nonce`. */
26
+ publicSignals: ReadonlyArray<string>;
27
+ /** The SHA-256 `key_hash` (64-char hex) this proof attests to. */
28
+ keyHash: string;
29
+ }>;
30
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,6BAA6B;AAC7B,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;AAExB,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAEH,oEAAoE;AACpE,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,CAAC;AAEH,wDAAwD;AACxD,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC;IAC/B,gDAAgD;IAChD,KAAK,EAAE,YAAY,CAAC;IACpB,kEAAkE;IAClE,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @lemmaoracle/seal — public types for generating dashboard sign-in proofs.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
package/dist/vkey.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The groth16 verification key for seal-identity-v1.
3
+ *
4
+ * This is public information — it can only verify proofs, not generate them.
5
+ * Imported as a static JSON module — Vite / Astro will inline it at build
6
+ * time, so no filesystem access is needed at runtime (Cloudflare Workers).
7
+ */
8
+ declare const _default: Readonly<Record<string, unknown>>;
9
+ export default _default;
10
+ //# sourceMappingURL=vkey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vkey.d.ts","sourceRoot":"","sources":["../src/vkey.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;wBAIoB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAAxD,wBAAyD"}
package/dist/vkey.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The groth16 verification key for seal-identity-v1.
3
+ *
4
+ * This is public information — it can only verify proofs, not generate them.
5
+ * Imported as a static JSON module — Vite / Astro will inline it at build
6
+ * time, so no filesystem access is needed at runtime (Cloudflare Workers).
7
+ */
8
+ import vkey from "./vkeys/seal-identity-v1.json" with { type: "json" };
9
+ export default vkey;
10
+ //# sourceMappingURL=vkey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vkey.js","sourceRoot":"","sources":["../src/vkey.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,IAAI,MAAM,+BAA+B,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAEvE,eAAe,IAAyC,CAAC"}