@zkp2p/zkp2p-attestation 1.5.1 → 1.6.2

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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.6.2
4
+
5
+ - Default the production environment to `https://attestation-service.zkp2p.xyz` and bundle a production PCR8 pin for that hostname. The historical `attestation-service-preprod.zkp2p.xyz` hostname remains pinned to the same production PCR8.
6
+ - Retry transient network-layer `GET /attestation` failures before surfacing `ATTESTATION_FETCH_FAILED`; HTTP errors, invalid responses, and timeouts still fail immediately.
7
+
8
+ ## 1.6.1
9
+
10
+ - Add Cash App identity request typing for `register_cashapp`. Cash App identity session material requires `sessionCookie` and `requestPayload`, accepts optional `requestHeaders`, and uses `{}` public params.
11
+
12
+ ## 1.6.0
13
+
14
+ - Add generic buyer TEE Zelle typing for `platform: "zelle"` and bank-specific action types `transfer_zelle_bofa`, `transfer_zelle_chase`, and `transfer_zelle_citi`; direct bank-specific Zelle routes and method hashes remain legacy drain-only.
15
+
3
16
  ## 1.5.1
4
17
 
5
18
  - Change Venmo identity registration to derive the stories URL from `params.SENDER_ID`. Venmo identity session material now requires only `Cookie`, while the service verifies the authenticated account id before replaying the derived stories request.
package/README.md CHANGED
@@ -29,9 +29,9 @@ Environment defaults:
29
29
  | Environment | Service URL | PCR8 source |
30
30
  |---|---|---|
31
31
  | `staging` | `https://attestation-service-staging.zkp2p.xyz` | bundled staging pin |
32
- | `production` | `https://attestation-service-preprod.zkp2p.xyz` | bundled production/preprod pin |
32
+ | `production` | `https://attestation-service.zkp2p.xyz` | bundled production pin |
33
33
 
34
- Callers can still pass `attestationServiceUrl` and `trust.expectedPcr8Hex` directly. Explicit trust pins always win.
34
+ Callers can still pass `attestationServiceUrl` and `trust.expectedPcr8Hex` directly. Explicit trust pins always win. The historical `https://attestation-service-preprod.zkp2p.xyz` hostname remains pinned to the same production PCR8 while it continues to serve the Nitro prod deployment.
35
35
 
36
36
  ## Fast Path
37
37
 
@@ -75,7 +75,7 @@ const verifiedCredentialBundle = verifySellerCredentialBundle(credentialBundle,
75
75
  });
76
76
  ```
77
77
 
78
- Venmo, Cash App, and PayPal still require a top-level `payeeId`; existing callers can keep using the same call shape:
78
+ Venmo, Cash App, PayPal, and Zelle still require a top-level `payeeId`; existing callers can keep using the same call shape:
79
79
 
80
80
  ```ts
81
81
  const encryptedUpload = await nitro.createEncryptedSellerCredentialUpload({
@@ -88,6 +88,8 @@ Venmo seller uploads can use either the existing session material or Google OAut
88
88
 
89
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
90
 
91
+ Zelle seller uploads use the same Gmail OAuth shape with `platform: "zelle"` and a top-level `payeeId` for the seller's Zelle recipient identifier. The service validates the OAuth mailbox by finding at least one DKIM-verified incoming Zelle receipt from a supported bank/Zelle sender, then later resolves `/seller/verify/zelle` by matching `txId` against the received receipt's transaction, confirmation, reference, or payment id. Seller Zelle attestations use the generic on-chain payment method `keccak256("zelle")`; receipt providers such as Chase, Citi, and Bank of America are metadata only.
92
+
91
93
  Buyer TEE requests use the same attested upload key and compact JWE envelope. The typed helper encrypts the captured
92
94
  session material, posts `{ encryptedSessionMaterial, params, chainId, intent }` to
93
95
  `POST /buyer/verify/:platform/:actionType`, unwraps the service envelope, and returns a typed `AttestationOutput`.
@@ -152,8 +154,15 @@ Buyer TEE platform matrix:
152
154
  | `chime` | `transfer_chime` | `Cookie`, `body` | `{}` |
153
155
  | `chase` | `transfer_zelle` | `Cookie`, `x-jpmc-channel`, `x-jpmc-csrf-token`, `Referer`, `Origin` | `index` |
154
156
  | `bankofamerica` | `transfer_zelle` | `Cookie` | `index` |
157
+ | `zelle` | `transfer_zelle_bofa` | `Cookie` | `index` |
158
+ | `zelle` | `transfer_zelle_chase` | `Cookie`, `x-jpmc-channel`, `x-jpmc-csrf-token`, `Referer`, `Origin` | `index` |
159
+ | `zelle` | `transfer_zelle_citi` | `Cookie` | `index` |
155
160
  | `paypal` | `transfer_paypal` | `Cookie` | `PAYMENT_ID` |
156
161
 
162
+ The generic Zelle buyer routes use bank-specific action types and attest `intent.paymentMethod = hashUtf8("zelle")`.
163
+ New Zelle deposits and intents should use generic `zelle`. Direct bank-specific routes remain available only for
164
+ legacy drain support with `hashUtf8("zelle-citi")`, `hashUtf8("zelle-chase")`, or `hashUtf8("zelle-bofa")`.
165
+
157
166
  Per-platform session-material types require these headers using the canonical names shown above while still allowing
158
167
  additional captured headers. Captured request bodies are session material because they can contain sensitive data and
159
168
  are encrypted before being sent to the service.
@@ -194,14 +203,19 @@ Identity platform matrix:
194
203
  | `venmo` | `register_venmo` | `Cookie` | `SENDER_ID` |
195
204
  | `paypal` | `register_paypal` | `Cookie` | `{}` |
196
205
  | `wise` | `register_wise` | `Cookie`, `X-Access-Token` | `PROFILE_ID` |
206
+ | `cashapp` | `register_cashapp` | `sessionCookie`, `requestPayload`, optional `requestHeaders` | `{}` |
197
207
 
198
208
  For Venmo identity, pass `SENDER_ID` in public params and include a replayable `Cookie` header in encrypted session
199
209
  material. The service verifies the authenticated account id, derives the stories URL from `SENDER_ID`, and requires
200
210
  Venmo to return a valid stories response with an explicit `stories` array.
201
211
 
212
+ For Cash App identity, pass the replayable Cash App session cookie as `sessionMaterial.sessionCookie` and the canonical
213
+ `MY_ACTIVITY_WEB_V2` activity request body as `sessionMaterial.requestPayload`. Include captured replayable headers in
214
+ `sessionMaterial.requestHeaders` when needed; the service ignores public params for this flow.
215
+
202
216
  Buyer TEE and identity expose discriminated unions keyed by `platform` and `actionType`, including per-platform
203
217
  `params`, so invalid pairings fail during TypeScript compilation. Seller credential upload keeps its existing
204
- platform narrowing (`wise` derives payee id from the credential; Venmo, Cash App, and PayPal require `payeeId`) but
218
+ platform narrowing (`wise` derives payee id from the credential; Venmo, Cash App, PayPal, and Zelle require `payeeId`) but
205
219
  does not add exhaustive `sessionMaterial` typing: each seller platform can accept multiple credential sources, such as
206
220
  cookie headers or Google OAuth with optional partner OAuth client material, and encoding all combinations would make
207
221
  the SDK brittle without improving the wire contract.
@@ -4267,6 +4267,8 @@ function parseAdvertisedPcr8(value, label) {
4267
4267
 
4268
4268
  // src/attestation/fetch.ts
4269
4269
  var MAX_ATTESTATION_RESPONSE_BYTES = 256 * 1024;
4270
+ var NETWORK_LAYER_FETCH_ATTEMPTS = 3;
4271
+ var NETWORK_LAYER_RETRY_DELAY_MS = 150;
4270
4272
  var ETH_ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
4271
4273
  function buildAttestationUrl(attestationServiceUrl, nonceHex) {
4272
4274
  const url = new URL(attestationServiceUrl);
@@ -4278,7 +4280,7 @@ function buildAttestationUrl(attestationServiceUrl, nonceHex) {
4278
4280
  }
4279
4281
  async function fetchAttestationEnvelope(params) {
4280
4282
  const url = buildAttestationUrl(params.attestationServiceUrl, params.nonceHex);
4281
- const response = await fetchWithTimeout(url.toString(), params.fetch, params.timeoutMs);
4283
+ const response = await fetchWithNetworkRetry(url.toString(), params.fetch, params.timeoutMs);
4282
4284
  const responseText = await readResponseText(response);
4283
4285
  if (!response.ok) {
4284
4286
  fail("ATTESTATION_FETCH_FAILED", `GET /attestation failed with HTTP ${response.status}`, {
@@ -4372,6 +4374,20 @@ function parseOptionalAdvertisedPcr8(value, label) {
4372
4374
  fail("ATTESTATION_RESPONSE_INVALID", `${label} must be a 96-character lowercase hex string`, { cause });
4373
4375
  }
4374
4376
  }
4377
+ async function fetchWithNetworkRetry(url, fetcher, timeoutMs) {
4378
+ let attempt = 1;
4379
+ while (true) {
4380
+ try {
4381
+ return await fetchWithTimeout(url, fetcher, timeoutMs);
4382
+ } catch (cause) {
4383
+ if (!isNetworkLayerFetchFailure(cause) || attempt >= NETWORK_LAYER_FETCH_ATTEMPTS) {
4384
+ throw cause;
4385
+ }
4386
+ await sleep(NETWORK_LAYER_RETRY_DELAY_MS * attempt);
4387
+ attempt += 1;
4388
+ }
4389
+ }
4390
+ }
4375
4391
  async function fetchWithTimeout(url, fetcher, timeoutMs) {
4376
4392
  const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
4377
4393
  let timeoutHandle;
@@ -4400,9 +4416,18 @@ async function fetchWithTimeout(url, fetcher, timeoutMs) {
4400
4416
  if (cause instanceof Error && (cause.name === "AbortError" || cause.message === "timeout")) {
4401
4417
  fail("TIMEOUT", `GET /attestation timed out after ${timeoutMs}ms`, { cause });
4402
4418
  }
4403
- fail("ATTESTATION_FETCH_FAILED", "GET /attestation failed at the network layer", { cause });
4419
+ fail("ATTESTATION_FETCH_FAILED", "GET /attestation failed at the network layer", {
4420
+ cause,
4421
+ details: { layer: "network" }
4422
+ });
4404
4423
  }
4405
4424
  }
4425
+ function isNetworkLayerFetchFailure(cause) {
4426
+ return cause instanceof NitroAttestationError && cause.code === "ATTESTATION_FETCH_FAILED" && cause.details?.layer === "network";
4427
+ }
4428
+ function sleep(ms) {
4429
+ return new Promise((resolve) => setTimeout(resolve, ms));
4430
+ }
4406
4431
  function isRecord(value) {
4407
4432
  return typeof value === "object" && value !== null && !Array.isArray(value);
4408
4433
  }
@@ -4482,7 +4507,8 @@ var STAGING_PCR8 = "5636e3bd96f847cf12cfd9de7faa8cad0e6fa00962ce16ba185f8e5ea571
4482
4507
  var PRODUCTION_PCR8 = "41a4ae0b9b96752cab5addb7d22689b3070e564e29f90a54316fa33fa38ea51387a6e887ea4f5a4b0cc34f69cea3f40e";
4483
4508
  var DEFAULT_EXPECTED_PCR8_BY_HOST = {
4484
4509
  "attestation-service-staging.zkp2p.xyz": normalizePcr8Pin(STAGING_PCR8, "bundled staging PCR8"),
4485
- "attestation-service-preprod.zkp2p.xyz": normalizePcr8Pin(PRODUCTION_PCR8, "bundled production PCR8")
4510
+ "attestation-service-preprod.zkp2p.xyz": normalizePcr8Pin(PRODUCTION_PCR8, "bundled production PCR8"),
4511
+ "attestation-service.zkp2p.xyz": normalizePcr8Pin(PRODUCTION_PCR8, "bundled production PCR8")
4486
4512
  };
4487
4513
 
4488
4514
  // src/trust/resolveExpectedPcr8.ts