@zkp2p/zkp2p-attestation 1.3.0 → 1.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.0
4
+
5
+ - Add typed identity flow helpers: `encryptIdentitySessionMaterial`, `createEncryptedIdentitySessionMaterial`, and `requestIdentityAttestation`.
6
+ - Add response signature verifiers for all TEE flows: `verifyBuyerTeePaymentAttestation`, `verifyIdentityAttestation`, and `verifySellerCredentialBundle`.
7
+ - Require caller-provided intent/payee bindings in response verifiers so valid same-domain attestations cannot be replayed for the wrong buyer intent, identity, or seller credential.
8
+ - Export `getUnifiedPaymentVerifierDomainSeparator` and `hashUtf8` so consumers can pin the EIP-712 domain and compute expected payee hashes.
9
+ - Add identity platform/action/params typing for Venmo, PayPal, and Wise. Wrong platform/action pairings now fail at TypeScript compile time.
10
+ - Keep buyer TEE's per-platform param typing and add identity param typing. Seller credential upload keeps its existing Wise-vs-payee-id platform narrowing while leaving `sessionMaterial` flexible because each seller platform can accept multiple credential sources.
11
+ - Move `ethers` to runtime dependencies because public response verifiers recover EIP-712 signers.
12
+
13
+ ## 1.3.1
14
+
15
+ - Republish of 1.3.0 from a clean build. The 1.3.0 tarball on npm shipped a stale `dist/` (built from a pre-rebase checkout): the runtime `dist/index.cjs` still posted the legacy `metadata` field and the typings still exported `*BuyerTeeMetadata` / `BuyerTeeMetadataByPlatform`. The new server only accepts `params`, so consumers on 1.3.0 hit a 4xx on every buyer TEE verify call.
16
+ - Action: upgrade to 1.3.1. 1.3.0 will be `npm deprecate`'d.
17
+
3
18
  ## 1.3.0
4
19
 
5
20
  - BREAKING: Buyer TEE verify request body field renamed from `metadata` to `params`. The server Zod schema rejects the legacy `metadata` field. All `*BuyerTeeMetadata` types in `@zkp2p/zkp2p-attestation` are renamed to `*BuyerTeeParams`, and `BuyerTeeMetadataByPlatform` is now `BuyerTeeParamsByPlatform`.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @zkp2p/zkp2p-attestation
2
2
 
3
- Client-side verifier and encryptor for ZKP2P Nitro seller credential uploads and buyer TEE session-material requests.
3
+ Client-side verifier and encryptor for ZKP2P Nitro identity attestations, seller credential uploads, and buyer TEE session-material requests.
4
4
 
5
5
  The package runs in Chrome MV3 service workers, browser DOM contexts, React Native with a Web Crypto polyfill, and Node >= 20. Library entrypoints use `fetch`, `SubtleCrypto`, `crypto.getRandomValues`, and `Uint8Array`; they do not import `node:*` modules or use `Buffer`.
6
6
 
@@ -64,8 +64,15 @@ const credentialBundle = await nitro.uploadSellerCredential({
64
64
  platform: "wise",
65
65
  sessionMaterial: {
66
66
  apiToken: "<wise-api-token>",
67
+ profileId: "41246868",
67
68
  },
68
69
  });
70
+
71
+ const verifiedCredentialBundle = verifySellerCredentialBundle(credentialBundle, {
72
+ trustedSigners,
73
+ expectedPlatform: "wise",
74
+ expectedPayeeIdHash: hashUtf8("<wise-recipient-id>"),
75
+ });
69
76
  ```
70
77
 
71
78
  Venmo, Cash App, and PayPal still require a top-level `payeeId`; existing callers can keep using the same call shape:
@@ -77,6 +84,10 @@ const encryptedUpload = await nitro.createEncryptedSellerCredentialUpload({
77
84
  });
78
85
  ```
79
86
 
87
+ Venmo seller uploads can use either the existing session material or Google OAuth. For Google OAuth, pass the numeric Venmo account id as `payeeId` and use `sessionMaterial: { credentialSource: "google_oauth", authorizationCode, redirectUri }`; the service validates Gmail access against venmo.com DKIM-signed incoming payment receipts. To onboard through a partner-owned Google OAuth app instead of Peer's default app, include `oauthClient: { clientId, clientSecret }` inside that encrypted `sessionMaterial`. The client secret is seller credential material: the package only places it in the compact JWE upload plaintext, it reaches the service enclave through that JWE, and the service persists it only inside the KMS-envelope-sealed seller credential bundle that the curator holds as ciphertext. It rides the same encrypted credential envelope as the Gmail refresh token and is reused only for later Gmail token refresh.
88
+
89
+ PayPal Gmail OAuth seller uploads use the same app-selection model. Pass the seller PayPal email as `payeeId` and use `sessionMaterial: { credentialSource: "google_oauth", authorizationCode, redirectUri }`; the service validates Gmail access against paypal.com DKIM-signed payment receipts. To onboard through a partner-owned Google OAuth app, include `oauthClient: { clientId, clientSecret }` inside the encrypted `sessionMaterial`. The PayPal partner client secret is handled as seller credential material: it rides only in the compact JWE upload plaintext, is sealed with the Gmail refresh token in the KMS-envelope seller credential bundle, and is reused only to refresh that seller's Gmail access token.
90
+
80
91
  Buyer TEE requests use the same attested upload key and compact JWE envelope. The typed helper encrypts the captured
81
92
  session material, posts `{ encryptedSessionMaterial, params, chainId, intent }` to
82
93
  `POST /buyer/verify/:platform/:actionType`, unwraps the service envelope, and returns a typed `AttestationOutput`.
@@ -93,10 +104,18 @@ const attestation = await nitro.verifyBuyerTeePayment({
93
104
  Cookie: "<captured-cookie-header>",
94
105
  "User-Agent": "<captured-user-agent>",
95
106
  },
96
- params: { senderId: "<venmo-account-id>", index: 0 },
107
+ params: { SENDER_ID: "<venmo-account-id>", index: 0 },
97
108
  chainId,
98
109
  intent,
99
110
  });
111
+
112
+ const verifiedAttestation = verifyBuyerTeePaymentAttestation(attestation, {
113
+ trustedSigners,
114
+ expectedPlatform: "venmo",
115
+ expectedActionType: "transfer_venmo",
116
+ expectedDomain: { chainId, verifyingContract },
117
+ expectedIntentHash: intent.intentHash,
118
+ });
100
119
  ```
101
120
 
102
121
  The helper is a thin wrapper over the existing wire format; callers can still hand-roll the final POST:
@@ -113,7 +132,7 @@ await fetch(`${attestationServiceUrl}/buyer/verify/wise/transfer_wise`, {
113
132
  headers: { "content-type": "application/json" },
114
133
  body: JSON.stringify({
115
134
  encryptedSessionMaterial,
116
- params: { profileId: "41246868", transactionId: "123456789" },
135
+ params: { PROFILE_ID: "41246868", TRANSACTION_ID: "123456789" },
117
136
  chainId,
118
137
  intent,
119
138
  }),
@@ -124,26 +143,63 @@ Buyer TEE platform matrix:
124
143
 
125
144
  | Platform | `actionType` | Required encrypted session material | Public params fields |
126
145
  |---|---|---|---|
127
- | `venmo` | `transfer_venmo` | `Cookie` | `senderId`, `index` |
128
- | `cashapp` | `transfer_cashapp` | `Cookie`, `x-csrf-token`, `x-device-name`, `x-request-signature`, `x-request-uuid`, `cash-web-request`, `x-web-device-info`, `x-web-context`, `x-bt-id` | `senderId`, `index` |
129
- | `luxon` | `transfer_luxon` | `X-Auth-Token` | `transferId` |
130
- | `monzo` | `transfer_monzo` | `Authorization` | `txId` |
131
- | `n26` | `transfer_n26` | `Cookie`, `csrf-token`, `body` | `{}` |
132
- | `wise` | `transfer_wise` | `Cookie` or `X-Access-Token` | `profileId`, `transactionId` |
146
+ | `venmo` | `transfer_venmo` | `Cookie` | `SENDER_ID`, `index` |
147
+ | `cashapp` | `transfer_cashapp` | `Cookie`, `x-csrf-token`, `x-device-name`, `x-request-signature`, `x-request-uuid`, `cash-web-request`, `x-web-device-info`, `x-web-context`, `x-bt-id` | `SENDER_ID`, `index` |
148
+ | `monzo` | `transfer_monzo` | `Authorization` | `TX_ID` |
149
+ | `wise` | `transfer_wise` | `Cookie` or `X-Access-Token` | `PROFILE_ID`, `TRANSACTION_ID` |
133
150
  | `revolut` | `transfer_revolut` | `Cookie`, `x-device-id` | `index` |
134
- | `idfc` | `transfer_idfc` | `Cookie` | `senderId`, `index` |
135
151
  | `citi` | `transfer_zelle` | `Cookie` | `index` |
136
152
  | `chime` | `transfer_chime` | `Cookie`, `body` | `{}` |
137
153
  | `chase` | `transfer_zelle` | `Cookie`, `x-jpmc-channel`, `x-jpmc-csrf-token`, `Referer`, `Origin` | `index` |
138
154
  | `bankofamerica` | `transfer_zelle` | `Cookie` | `index` |
139
- | `mercadopago` | `transfer_mercadopago` | `Cookie` | `paymentId`, `urlParamsFrom` |
140
- | `paypal` | `transfer_paypal` | `Cookie` | `index` |
141
- | `alipay` | `transfer_alipay` | `Cookie` | `tradeNo` |
155
+ | `paypal` | `transfer_paypal` | `Cookie` | `PAYMENT_ID` |
142
156
 
143
157
  Per-platform session-material types require these headers using the canonical names shown above while still allowing
144
158
  additional captured headers. Captured request bodies are session material because they can contain sensitive data and
145
159
  are encrypted before being sent to the service.
146
160
 
161
+ Identity requests use the same attested upload key and compact JWE plaintext shape as buyer TEE, but with a narrower
162
+ platform/action matrix. The typed helper encrypts captured session material, posts
163
+ `{ platform, actionType, encryptedSessionMaterial, params }` to `POST /identity`, unwraps the service response, and
164
+ returns an `IdentityAttestationOutput`. `verifyIdentityAttestation` checks the EIP-712 signature, trusted signer,
165
+ platform/action binding, expected payee hash, canonical identity `dataHash`, and `validUntil`, then returns normalized
166
+ identity details.
167
+
168
+ ```ts
169
+ const identityPayload = await nitro.requestIdentityAttestation({
170
+ platform: "wise",
171
+ actionType: "register_wise",
172
+ sessionMaterial: {
173
+ Cookie: "<captured-cookie-header>",
174
+ "X-Access-Token": "<captured-token>",
175
+ },
176
+ params: { PROFILE_ID: "41246868" },
177
+ });
178
+
179
+ const identity = verifyIdentityAttestation(identityPayload, {
180
+ trustedSigners,
181
+ expectedPlatform: "wise",
182
+ expectedActionType: "register_wise",
183
+ expectedPayeeIdHash: hashUtf8("<wise-recipient-id>"),
184
+ });
185
+ // identity: { payeeIdHash, payeeId, username, metadata, ... }
186
+ ```
187
+
188
+ Identity platform matrix:
189
+
190
+ | Platform | `actionType` | Required encrypted session material | Public params fields |
191
+ |---|---|---|---|
192
+ | `venmo` | `register_venmo` | `Cookie` | `SENDER_ID`, `index` |
193
+ | `paypal` | `register_paypal` | `Cookie` | `{}` |
194
+ | `wise` | `register_wise` | `Cookie`, `X-Access-Token` | `PROFILE_ID` |
195
+
196
+ Buyer TEE and identity expose discriminated unions keyed by `platform` and `actionType`, including per-platform
197
+ `params`, so invalid pairings fail during TypeScript compilation. Seller credential upload keeps its existing
198
+ platform narrowing (`wise` derives payee id from the credential; Venmo, Cash App, and PayPal require `payeeId`) but
199
+ does not add exhaustive `sessionMaterial` typing: each seller platform can accept multiple credential sources, such as
200
+ cookie headers or Google OAuth with optional partner OAuth client material, and encoding all combinations would make
201
+ the SDK brittle without improving the wire contract.
202
+
147
203
  ## Cache-Friendly Path
148
204
 
149
205
  ```ts
@@ -171,6 +227,19 @@ const encryptedSessionMaterial = await nitro.encryptBuyerTeeSessionMaterial({
171
227
  });
172
228
  ```
173
229
 
230
+ For identity flows with a cached attestation:
231
+
232
+ ```ts
233
+ const encryptedSessionMaterial = await nitro.encryptIdentitySessionMaterial({
234
+ key: verified.attestedSellerUploadKey,
235
+ platform: "paypal",
236
+ actionType: "register_paypal",
237
+ sessionMaterial: {
238
+ Cookie: "<captured-cookie-header>",
239
+ },
240
+ });
241
+ ```
242
+
174
243
  The attested SPKI is stable within one enclave process. If the enclave restarts, cached keys become stale and upload decrypt will fail; fetch a fresh attestation for a new session.
175
244
 
176
245
  ## React Native Wiring
@@ -193,12 +262,20 @@ Any conforming `SubtleCrypto` works. The package does not require `Buffer`.
193
262
  ```ts
194
263
  import {
195
264
  createEncryptedBuyerTeeSessionMaterial,
265
+ createEncryptedIdentitySessionMaterial,
196
266
  createEncryptedSellerCredentialUpload,
197
267
  encryptBuyerTeeSessionMaterial,
268
+ encryptIdentitySessionMaterial,
198
269
  encryptSellerCredentialUpload,
199
270
  fetchAndVerifyAttestation,
271
+ getUnifiedPaymentVerifierDomainSeparator,
272
+ hashUtf8,
273
+ requestIdentityAttestation,
200
274
  uploadSellerCredential,
275
+ verifyBuyerTeePaymentAttestation,
201
276
  verifyBuyerTeePayment,
277
+ verifyIdentityAttestation,
278
+ verifySellerCredentialBundle,
202
279
  } from "@zkp2p/zkp2p-attestation";
203
280
  ```
204
281
 
@@ -4389,10 +4389,14 @@ async function fetchWithTimeout(url, fetcher, timeoutMs) {
4389
4389
  }),
4390
4390
  timeoutPromise
4391
4391
  ]);
4392
- clearTimeout(timeoutHandle);
4392
+ if (timeoutHandle !== void 0) {
4393
+ clearTimeout(timeoutHandle);
4394
+ }
4393
4395
  return response;
4394
4396
  } catch (cause) {
4395
- clearTimeout(timeoutHandle);
4397
+ if (timeoutHandle !== void 0) {
4398
+ clearTimeout(timeoutHandle);
4399
+ }
4396
4400
  if (cause instanceof Error && (cause.name === "AbortError" || cause.message === "timeout")) {
4397
4401
  fail("TIMEOUT", `GET /attestation timed out after ${timeoutMs}ms`, { cause });
4398
4402
  }