npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zkp2p/zkp2p-attestation

v2.0.0

Published

Browser, React Native, and Node verifier/encrypter for ZKP2P Nitro attested uploads

Downloads

14,963

Readme

@zkp2p/zkp2p-attestation

Client-side verifier and encryptor for ZKP2P Nitro identity attestations, seller credential uploads, and buyer TEE session-material requests.

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.

Install

yarn add @zkp2p/zkp2p-attestation

During monorepo development, point clients at attestation-service/package/zkp2p-attestation.

Environment Init

Use the client factory when the host owns an environment variable:

import { createNitroAttestationClient } from "@zkp2p/zkp2p-attestation";

const nitro = createNitroAttestationClient({
  environment: process.env.ZKP2P_ATTESTATION_ENV === "production" ? "production" : "staging",
});

Environment defaults:

| Environment | Service URL | PCR8 source | |---|---|---| | staging | https://attestation-service-staging.zkp2p.xyz | bundled staging pin | | production | https://attestation-service.zkp2p.xyz | bundled production pin |

Callers can still pass attestationServiceUrl and trust.expectedPcr8Hex directly. Explicit trust pins always win. The preprod hostname is pinned to the isolated preprod enclave; it is not a production alias.

Fast Path

Wise uploads use the server-derived payee id path:

const encryptedUpload = await nitro.createEncryptedSellerCredentialUpload({
  platform: "wise",
  sessionMaterial: {
    apiToken: "<wise-api-token>",
    // Optional. Omit for single-profile PATs; include after a
    // WISE_PROFILE_SELECTION_REQUIRED response for multi-profile PATs.
    profileId: "41246868",
  },
});

await fetch(`${attestationServiceUrl}/seller/credentials/wise`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ encryptedUpload }),
});

This fetches GET /attestation?nonce=..., verifies the Nitro document to the pinned AWS root and PCR8, extracts the attested seller-upload RSA SPKI, and returns a compact JWE for POST /seller/credentials/:platform.

For the same flow without hand-rolling the POST:

const credentialBundle = await nitro.uploadSellerCredential({
  platform: "wise",
  sessionMaterial: {
    apiToken: "<wise-api-token>",
    profileId: "41246868",
  },
});

const verifiedCredentialBundle = verifySellerCredentialBundle(credentialBundle, {
  trustedSigners,
  expectedPlatform: "wise",
  expectedPayeeIdHash: hashUtf8("<wise-recipient-id>"),
});

Venmo, Cash App, and PayPal still require a top-level payeeId; existing callers can keep using the same call shape:

const encryptedUpload = await nitro.createEncryptedSellerCredentialUpload({
  payeeId: "1130030979",
  sessionMaterial: venmoSessionMaterial,
});

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.

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.

Buyer TEE requests use the same attested upload key and compact JWE envelope. The typed helper encrypts the captured session material, posts { encryptedSessionMaterial, params, chainId, intent } to POST /buyer/verify/:platform/:actionType, unwraps the service envelope, and returns a typed AttestationOutput. The service does not enforce capture-age or one-use replay limits for buyer TEE session material; verification depends on the upstream session still being active. A leaked encrypted JWE is therefore valid for the upstream session lifetime — treat any accidental disclosure as equivalent to leaking the underlying upstream credential (cookies, PAT, etc.) and rotate the upstream session.

const attestation = await nitro.verifyBuyerTeePayment({
  platform: "venmo",
  actionType: "transfer_venmo",
  sessionMaterial: {
    Cookie: "<captured-cookie-header>",
    "User-Agent": "<captured-user-agent>",
  },
  params: { SENDER_ID: "<venmo-account-id>", index: 0 },
  chainId,
  intent,
});

const verifiedAttestation = verifyBuyerTeePaymentAttestation(attestation, {
  trustedSigners,
  expectedPlatform: "venmo",
  expectedActionType: "transfer_venmo",
  expectedDomain: { chainId, verifyingContract },
  expectedIntentHash: intent.intentHash,
});

The helper is a thin wrapper over the existing wire format; callers can still hand-roll the final POST:

const encryptedSessionMaterial = await nitro.createEncryptedBuyerTeeSessionMaterial({
  platform: "wise",
  actionType: "transfer_wise",
  sessionMaterial: { "X-Access-Token": "<captured-token>" },
});

await fetch(`${attestationServiceUrl}/buyer/verify/wise/transfer_wise`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    encryptedSessionMaterial,
    params: { PROFILE_ID: "41246868", TRANSACTION_ID: "123456789" },
    chainId,
    intent,
  }),
});

Buyer TEE platform matrix:

| Platform | actionType | Required encrypted session material | Public params fields | |---|---|---|---| | venmo | transfer_venmo | Cookie | SENDER_ID, index | | 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 | | monzo | transfer_monzo | Authorization | TX_ID | | wise | transfer_wise | Cookie or X-Access-Token | PROFILE_ID, TRANSACTION_ID | | revolut | transfer_revolut | Cookie, x-device-id | index | | chime | transfer_chime | Cookie, body | {} | | zelle | transfer_zelle_bofa | Cookie | index | | zelle | transfer_zelle_chase | Cookie, x-jpmc-channel, x-jpmc-csrf-token, Referer, Origin | index | | zelle | transfer_zelle_citi | Cookie | index | | paypal | transfer_paypal | Cookie | PAYMENT_ID |

Zelle buyer verification uses only platform: "zelle" with bank-specific action types. Every Zelle action attests intent.paymentMethod = hashUtf8("zelle"); direct bank-specific platforms are unsupported.

Per-platform session-material types require these headers using the canonical names shown above while still allowing additional captured headers. Captured request bodies are session material because they can contain sensitive data and are encrypted before being sent to the service.

Identity requests use the same attested upload key and compact JWE plaintext shape as buyer TEE, but with a narrower platform/action matrix. The typed helper encrypts captured session material, posts { platform, actionType, callerAddress, encryptedSessionMaterial, params } to POST /identity, unwraps the service response, and returns an IdentityAttestationOutput. verifyIdentityAttestation checks the EIP-712 signature, trusted signer, platform/action binding, expected caller address, expected payee hash, canonical identity dataHash, and validUntil, then returns normalized identity details.

const identityPayload = await nitro.requestIdentityAttestation({
  platform: "wise",
  actionType: "register_wise",
  callerAddress: "0x0000000000000000000000000000000000000002",
  sessionMaterial: {
    Cookie: "<captured-cookie-header>",
    "X-Access-Token": "<captured-token>",
  },
  params: { PROFILE_ID: "41246868" },
});

const identity = verifyIdentityAttestation(identityPayload, {
  trustedSigners,
  expectedPlatform: "wise",
  expectedActionType: "register_wise",
  expectedCallerAddress: "0x0000000000000000000000000000000000000002",
  expectedPayeeIdHash: hashUtf8("<wise-recipient-id>"),
});
// identity: { payeeIdHash, payeeId, username, metadata, ... }

Identity platform matrix:

| Platform | actionType | Required encrypted session material | Public params fields | |---|---|---|---| | venmo | register_venmo | Cookie | SENDER_ID | | paypal | register_paypal | Cookie | {} | | wise | register_wise | Cookie, X-Access-Token | PROFILE_ID | | cashapp | register_cashapp | sessionCookie, requestPayload, optional requestHeaders | {} |

For Venmo identity, pass SENDER_ID in public params and include a replayable Cookie header in encrypted session material. The service verifies the authenticated account id, derives the stories URL from SENDER_ID, and requires Venmo to return a valid stories response with an explicit stories array.

For Cash App identity, pass the replayable Cash App session cookie as sessionMaterial.sessionCookie and the canonical MY_ACTIVITY_WEB_V2 activity request body as sessionMaterial.requestPayload. Include captured replayable headers in sessionMaterial.requestHeaders when needed; the service ignores public params for this flow.

Buyer TEE and identity expose discriminated unions keyed by platform and actionType, including per-platform params, so invalid pairings fail during TypeScript compilation. Seller credential upload keeps its existing platform narrowing (wise derives payee id from the credential; Venmo, Cash App, and PayPal require payeeId) but does not add exhaustive sessionMaterial typing: each seller platform can accept multiple credential sources, such as cookie headers or Google OAuth with optional partner OAuth client material, and encoding all combinations would make the SDK brittle without improving the wire contract.

Cache-Friendly Path

const verified = await nitro.fetchAndVerifyAttestation();

const encryptedUpload = await nitro.encryptSellerCredentialUpload({
  key: verified.attestedSellerUploadKey,
  platform: "wise",
  sessionMaterial: {
    apiToken: "<wise-api-token>",
  },
});

For buyer TEE flows with a cached attestation:

const encryptedSessionMaterial = await nitro.encryptBuyerTeeSessionMaterial({
  key: verified.attestedSellerUploadKey,
  platform: "venmo",
  actionType: "transfer_venmo",
  sessionMaterial: {
    Cookie: "<captured-cookie-header>",
  },
});

For identity flows with a cached attestation:

const encryptedSessionMaterial = await nitro.encryptIdentitySessionMaterial({
  key: verified.attestedSellerUploadKey,
  platform: "paypal",
  actionType: "register_paypal",
  sessionMaterial: {
    Cookie: "<captured-cookie-header>",
  },
});

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.

React Native Wiring

React Native must provide Web Crypto-compatible primitives when they are not on globalThis.crypto:

const nitro = createNitroAttestationClient({
  environment: "staging",
  subtle: webCrypto.subtle,
  getRandomValues: (out) => webCrypto.getRandomValues(out),
  fetch,
});

Any conforming SubtleCrypto works. The package does not require Buffer.

Direct Entrypoints

import {
  createEncryptedBuyerTeeSessionMaterial,
  createEncryptedIdentitySessionMaterial,
  createEncryptedSellerCredentialUpload,
  encryptBuyerTeeSessionMaterial,
  encryptIdentitySessionMaterial,
  encryptSellerCredentialUpload,
  fetchAndVerifyAttestation,
  getUnifiedPaymentVerifierDomainSeparator,
  hashUtf8,
  requestIdentityAttestation,
  uploadSellerCredential,
  verifyBuyerTeePaymentAttestation,
  verifyBuyerTeePayment,
  verifyIdentityAttestation,
  verifySellerCredentialBundle,
} from "@zkp2p/zkp2p-attestation";

Direct functions accept attestationServiceUrl, trust, fetch, subtle, getRandomValues, now, onWarning, and timeoutMs overrides. If no caller or bundled PCR8 pin exists and trust.strictPin !== true, the verifier falls back to the service-advertised PCR8 and emits TRUST_PIN_NOT_PROVIDED; production callers should pin.

PCR8 Rotation Runbook

  1. Update src/trust/pins.ts from the deployment STATE.md published by the attestation-service team.
  2. Refresh tests/golden/staging-2026-05-05.raw.json and expected metadata if staging rotates.
  3. Run yarn test:coverage and STAGING_E2E=true yarn test:integration.
  4. Release a patch version for PCR8-only rotations. Use a major version for wire-format or algorithm changes.

CLI

The existing verifier is preserved on top of the library:

yarn verify --service https://attestation-service-staging.zkp2p.xyz

Optional EIP-712 signer cross-check remains available through --verify-signature.