@reclaimprotocol/js-sdk 5.3.0-dev.1 → 5.3.0

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 CHANGED
@@ -701,6 +701,52 @@ const { isVerified } = await verifyProof(proof, {
701
701
  });
702
702
  ```
703
703
 
704
+ ### Replay Protection
705
+
706
+ Proofs are submitted by the user, so the server must not trust any field on the proof in isolation. `verifyProof` (and `verifyTeeAttestation`) cryptographically bind a proof to a specific session, but **the SDK is stateless** — it cannot tell whether the same valid proof has already been verified. Preventing replay is the caller's responsibility.
707
+
708
+ **What the SDK guarantees**
709
+
710
+ The `attestationNonce` baked into the proof context is computed as `keccak256("RECLAIM_TEE_NONCE_V1" : applicationId : sessionId : timestamp : appSecret)`. When you call `verifyProof` with `teeAttestation: { appSecret }`, the SDK:
711
+
712
+ - Recomputes the nonce from `(applicationId, sessionId, timestamp)` in the proof context using **your** `appSecret` and asserts it matches — only the holder of the app secret can mint a valid nonce, so the user cannot rebind a proof to a different `sessionId` or `applicationId`.
713
+ - Confirms the recomputed nonce appears in the TEE attestation token's `eat_nonce` (signed by Google's Confidential Computing OIDC issuer), so the TEE-signed binding cannot be forged either.
714
+ - Asserts the `applicationId` in the nonce data matches the address derived from `appSecret`, and that `sessionId` in the nonce data agrees with `reclaimSessionId` in `claimData.context` and `proxySessionId`/`sessionId` in `claimData.parameters`.
715
+ - Asserts `claimData.timestampS` is within 10 minutes of the nonce timestamp.
716
+
717
+ In short: the user cannot mutate `sessionId`, `applicationId`, or `timestamp` on a proof without invalidating it. They *can*, however, resubmit the same valid proof.
718
+
719
+ **What you must do on your server**
720
+
721
+ 1. **Use a session you initiated.** Call `ReclaimProofRequest.init(...)` and `getSessionId()` server-side (or record the `sessionId` returned to your callback). When verifying, reject any proof whose `sessionId` you did not issue.
722
+ 2. **Dedupe by `sessionId`.** Persist accepted `sessionId`s (e.g., a unique index in your DB or a TTL cache) and reject a `sessionId` that has already been verified. This is the primary replay defense.
723
+ 3. **Require `teeAttestation`** in `verifyProof` for any flow where replay or tampering matters — without it, the cryptographic binding to `appSecret` is not checked.
724
+ 4. **Enforce a freshness window.** Reject proofs whose `claimData.timestampS` is older than your business policy allows (the SDK only enforces a 10-minute skew between the nonce timestamp and claim timestamp, not absolute freshness).
725
+
726
+ ```javascript
727
+ // Pseudocode for a server-side verification handler
728
+ const expectedSessionId = await loadSessionForUser(req.user); // session you created
729
+ const sessionId = JSON.parse(proof.claimData.context).reclaimSessionId;
730
+
731
+ if (sessionId !== expectedSessionId) {
732
+ throw new Error("session mismatch");
733
+ }
734
+ if (await db.consumedSessions.has(sessionId)) {
735
+ throw new Error("proof already used"); // replay
736
+ }
737
+
738
+ const { isVerified, error } = await verifyProof(proof, {
739
+ ...providerVersion,
740
+ teeAttestation: { appSecret: process.env.APP_SECRET },
741
+ });
742
+ if (!isVerified) throw error;
743
+
744
+ await db.consumedSessions.add(sessionId); // mark consumed atomically
745
+ ```
746
+
747
+ > [!WARNING]
748
+ > Without server-side session tracking and dedup, a malicious user can submit the same valid proof repeatedly. The cryptographic checks in `verifyProof` are necessary but not sufficient for replay protection.
749
+
704
750
  ## TEE Attestation Verification
705
751
 
706
752
  The SDK supports verifying TEE (Trusted Execution Environment) attestations included in proofs. The attestation flow verifies the Google Confidential Computing OIDC token returned by Popcorn's GCP attestor and checks that it is bound to the proof nonce, application identity, and image digests.
package/dist/index.d.ts CHANGED
@@ -772,6 +772,13 @@ type ProviderHashRequirementsResponse = {
772
772
  * * `getProviderHashRequirementsFromSpec()` - To get the expected proof hash requirements from a provider spec.
773
773
  * * All 3 functions above are alternatives of each other and result from these functions can be directly used as `config` parameter in this function for proof validation.
774
774
  *
775
+ * Replay protection: this function is stateless. It verifies that a proof is cryptographically
776
+ * bound to a specific `sessionId`/`applicationId` (via the `appSecret`-keyed attestation nonce
777
+ * when `teeAttestation` is provided) but does not track which proofs have already been
778
+ * verified. Callers must (a) verify the proof's `sessionId` matches a session they initiated
779
+ * and (b) persist accepted `sessionId`s and reject duplicates. See the README's
780
+ * "Replay Protection" section for details.
781
+ *
775
782
  * @param proofOrProofs - A single proof object or an array of proof objects to be verified.
776
783
  * @param config - Verification configuration that specifies required hashes, allowed extra hashes, or disables content validation. Optionally includes `teeAttestation` to require TEE attestation verification.
777
784
  * @returns Verification result with `isVerified`, `isTeeAttestationVerified` (always boolean), extracted `data` from each proof, and optional `error` on failure. The application ID is derived from `appSecret` automatically.
@@ -1509,6 +1516,10 @@ type TeeVerificationResult = {
1509
1516
  * was generated for your application.
1510
1517
  * Returns a result object with `isVerified` and an optional `error` message.
1511
1518
  *
1519
+ * This check is stateless and does not protect against replay of a previously
1520
+ * valid proof. Callers must enforce session matching and dedup `sessionId` on
1521
+ * their server. See the README's "Replay Protection" section.
1522
+ *
1512
1523
  * @param proof - The proof containing TEE attestation data
1513
1524
  * @param appSecret - Your application secret (Ethereum private key). Used to
1514
1525
  * derive the application ID and recompute the attestation nonce.
package/dist/index.js CHANGED
@@ -84,7 +84,7 @@ var require_package = __commonJS({
84
84
  "package.json"(exports2, module2) {
85
85
  module2.exports = {
86
86
  name: "@reclaimprotocol/js-sdk",
87
- version: "5.3.0-dev.1",
87
+ version: "5.3.0",
88
88
  description: "Designed to request proofs from the Reclaim protocol and manage the flow of claims and witness interactions.",
89
89
  main: "dist/index.js",
90
90
  types: "dist/index.d.ts",