> ## Documentation Index
> Fetch the complete documentation index at: https://developers.squads.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Primary Provider Integration

> Language-agnostic implementation details for direct API integration with email-based Grid Accounts with Privy as the primary key management service.

<Warning>
  Using the Grid API directly requires **advanced configurations**. Grid SDK is
  the recommended way to create accounts. It handles account creation, key
  management, authentication, automatic failover, and transaction signing. Learn
  more about the Grid SDK in the [Grid SDK](/grid/v1/sdk-reference) guide.
</Warning>

Grid uses Privy as the default key management service for email-based accounts. This guide covers the complete implementation process for making direct API calls with default configuration email-based accounts.
Privy uses **TEE-based HPKE encryption** with specific cryptographic algorithms and requires **JSON canonicalization** for payload signing.

## Overview

<Note>
  Privy is Grid's **default provider** in a multi-provider authentication system
  that includes Turnkey, Passkey, and optional external signers. To learn more
  about the multi-provider authentication system, see how [Grid
  Accounts](/grid/v1/accounts/fault-tolerance) work.
</Note>

This guide provides language-agnostic implementation details for recreating the complete Grid SDK account creation and signing flow.

### Implementation Process

<Steps>
  <Step title="Initiate Account Creation">
    Call POST /accounts to start the account creation process
  </Step>

  <Step title="Generate HPKE Keypair">
    Create client-side HPKE keys using P-256 curve while waiting for OTP
  </Step>

  <Step title="Verify OTP">
    Complete account creation via POST /accounts/verify with HPKE public key
  </Step>

  <Step title="Receive Encrypted Authorization Key">
    Grid returns the authorization key encrypted with HPKE
  </Step>

  <Step title="Decrypt Authorization Key">
    Use your private key to decrypt the authorization key for transaction signing
  </Step>

  <Step title="Sign Transaction Payloads">
    Sign payloads using JSON canonicalization and ECDSA
  </Step>
</Steps>

### Implementation Responsibilities

**Client-side requirements:**

* Generate HPKE keypairs using P-256 curve and proper key formatting
* Decrypt authorization keys received from Privy using HPKE
* Sign transaction payloads with JSON canonicalization and ECDSA

**Server-side automation:**

* Creates Grid Accounts on Solana blockchain
* Generates authorization keys in Privy
* Returns encrypted authorization keys using your HPKE public key
* Submits signed transactions for payment intents, KYC operations, and other transactions

## Complete Implementation Flow

The Grid SDK account creation and transaction signing flow consists of **cryptographic key generation**, **2 API calls**, and **transaction signing**:

<Steps>
  <Step title="POST /accounts - Initiate Account Creation">
    Use the [account creation endpoint](/grid/v1/api-reference/endpoint/account-management/post) to initiate the account creation process.

    **What Happens**:

    1. Server creates account record in pending state
    2. Server generates and sends 6-digit OTP to email address
    3. Server returns account metadata with 15-minute expiration
  </Step>

  <Step title="Generate HPKE Keypair">
    **While waiting for OTP**: Create P-256 HPKE keys with SPKI/PKCS#8 DER
    formatting and store private key securely. See the [HPKE Keypair
    Generation](#hpke-keypair-generation) section below for detailed
    implementation.
  </Step>

  <Step title="POST /accounts/verify - Verify Account OTP">
    Submit the OTP code to the [verification endpoint](/grid/v1/api-reference/endpoint/account-management/verify) to complete the account creation process.

    **Request**:

    ```http theme={null}
    POST /accounts/verify
    Authorization: Bearer {apiKey}
    Content-Type: application/json

    {
      "email": "user@example.com",
      "otp_code": "123456",
      "provider": "privy", // default provider
      "kms_provider_config": {
        "encryption_public_key": "MFkwEwYHKoZI..." // SPKI base64
      },
      "expiration": 1705408800 // Optional Unix timestamp
    }
    ```

    **Response**:

    ```json theme={null}
    {
      "address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
      "grid_user_id": "550e8400-e29b-41d4-a716-446655440000",
      "policies": {
        "spending_limits": [],
        "transaction_filters": [],
        "allowlists": []
      },
      "authentication": [
        {
          "provider": "Privy",
          "session": {
            "user_id": "did:privy:cm4abc123...",
            "session": {
              "encrypted_authorization_key": {
                "encapsulated_key": "MFkwEwYHKoZI...", // Base64 ephemeral public key
                "ciphertext": "A8B9C7D6E5F4..." // Base64 HPKE-encrypted auth key
              }
            }
          }
        }
      ]
    }
    ```

    **What Happens**:

    1. Server verifies OTP code against email (15-minute window)
    2. Server creates Grid Account on Solana blockchain
    3. Privy generates authorization key in TEE using the HPKE keypair generated
    4. Privy encrypts authorization key using client's HPKE public key
    5. Grid returns Grid Account address and encrypted authorization key
  </Step>

  <Step title="HPKE Decryption">
    Decrypt authorization key using ECDH + HKDF + ChaCha20-Poly1305 with your
    private key. See the [Decrypting Authorization
    Keys](#decrypting-authorization-keys) section below for detailed
    implementation.
  </Step>

  <Step title="Extract Auth Key">
    Remove "wallet-auth:" prefix from decrypted plaintext if present.
  </Step>

  <Step title="Get KMS Payload">
    When making API calls that require signing, endpoints will return a KMS
    payload that you need to sign with the authorization key.
  </Step>

  <Step title="Canonicalize JSON">
    Recursively sort all object keys in KMS payload alphabetically. See the [JSON
    Canonicalization](#json-canonicalization) section for implementation details.
  </Step>

  <Step title="Extract Signing Key">
    Find \[0x04, 0x20] pattern in auth key, extract 32 bytes following it. See the
    [Extract Signing Key](#extract-signing-key) section for implementation.
  </Step>

  <Step title="Sign Payload">
    ECDSA P-256 SHA-256 sign the canonicalized JSON string. See the [Sign the
    Payload](#sign-the-payload) section for implementation.
  </Step>

  <Step title="Submit Transaction">
    Send signed transaction to Grid API to the [Transaction Submission endpoint](/grid/v1/api-reference/endpoint/transaction-operations/submit) with the signature.
  </Step>
</Steps>

### Existing Account Authentication

For existing accounts, use [POST /auth/verify](/grid/v1/api-reference/endpoint/authentication/verify) with the same encrypted authorization key format.

## HPKE Keypair Generation

**Before calling the verification endpoint**, you must generate an HPKE keypair client-side using **P-256 curve** with proper **DER formatting**.

### Required Cryptographic Libraries

* **Elliptic Curve Cryptography**: P-256 (secp256r1) curve support
* **HKDF**: HMAC-based Key Derivation Function (RFC 5869)
* **ChaCha20-Poly1305**: AEAD encryption (RFC 8439)
* **DER Encoding/Decoding**: PKCS#8 and SPKI format support
* **Base64 Encoding/Decoding**: Standard base64 operations

### Key Format Specifications

**SPKI Public Key Structure (DER-encoded)**:

```
SEQUENCE {
  SEQUENCE {
    OBJECT IDENTIFIER 1.2.840.10045.2.1 (ecPublicKey)
    OBJECT IDENTIFIER 1.2.840.10045.3.1.7 (prime256v1)
  }
  BIT STRING (65 bytes: 0x04 + 32-byte X + 32-byte Y coordinates)
}
```

**PKCS#8 Private Key Structure (DER-encoded)**:

```
SEQUENCE {
  INTEGER 0
  SEQUENCE {
    OBJECT IDENTIFIER 1.2.840.10045.2.1 (ecPublicKey)
    OBJECT IDENTIFIER 1.2.840.10045.3.1.7 (prime256v1)
  }
  OCTET STRING {
    SEQUENCE {
      INTEGER 1
      OCTET STRING (32-byte private key)
    }
  }
}
```

### Generate HPKE Keys

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { p256 } from '@noble/curves/p256';

  // Generate HPKE keypair for Privy
  function generateHPKEKeyPair() {
  // Generate random 32-byte private key
  const privateKeyBytes = p256.utils.randomPrivateKey();
  const publicKeyBytes = p256.getPublicKey(privateKeyBytes);

  // Format as SPKI (public) and PKCS#8 (private)
  const spkiPublicKey = createSPKIPublicKey(publicKeyBytes);
  const pkcs8PrivateKey = createPKCS8PrivateKey(privateKeyBytes);

  return {
  publicKey: Buffer.from(spkiPublicKey).toString('base64'),
  privateKey: Buffer.from(pkcs8PrivateKey).toString('base64')
  };
  }

  // Algorithm:
  // 1. Generate random 32-byte private key using P-256 curve
  // 2. Compute public key: publicKey = privateKey \* G (curve point)
  // 3. Wrap public key in SPKI DER format
  // 4. Wrap private key in PKCS#8 DER format
  // 5. Base64-encode both keys

  // Store the private key securely - you'll need it to decrypt the response!
  const keypair = generateHPKEKeyPair();

  ```

  ```python Python theme={null}
  from cryptography.hazmat.primitives import serialization
  from cryptography.hazmat.primitives.asymmetric import ec
  import base64

  # Generate HPKE keypair for Privy
  def generate_hpke_keypair():
      # Generate P-256 private key
      private_key = ec.generate_private_key(ec.SECP256R1())
      public_key = private_key.public_key()

      # Serialize to DER format
      public_key_spki = public_key.public_key_bytes(
          encoding=serialization.Encoding.DER,
          format=serialization.PublicFormat.SubjectPublicKeyInfo
      )
      private_key_pkcs8 = private_key.private_bytes(
          encoding=serialization.Encoding.DER,
          format=serialization.PrivateFormat.PKCS8,
          encryption_algorithm=serialization.NoEncryption()
      )

      return {
          'public_key': base64.b64encode(public_key_spki).decode(),
          'private_key': base64.b64encode(private_key_pkcs8).decode()
      }

  # Algorithm:
  # 1. Generate random 32-byte private key using P-256 curve
  # 2. Compute public key from private key
  # 3. Serialize public key to SPKI DER format
  # 4. Serialize private key to PKCS#8 DER format
  # 5. Base64-encode both keys

  # Store the private key securely - you'll need it to decrypt the response!
  keypair = generate_hpke_keypair()
  ```
</CodeGroup>

### Using the Public Key in API Calls

Include your **public key** in the `kms_provider_config` when calling:

**Account Verification:**

```http theme={null}
POST /accounts/verify
{
  "email": "user@example.com",
  "otp_code": "123456",
  "provider": "privy",
  "kms_provider_config": {
    "encryption_public_key": "MFkwEwYHKoZI..." // Your generated public key
  }
}
```

**Account Authentication:**

```http theme={null}
POST /auth/verify
{
  "email": "user@example.com",
  "otp_code": "123456",
  "provider": "privy",
  "kms_provider_config": {
    "encryption_public_key": "MFkwEwYHKoZI..." // Your generated public key
  }
}
```

<Warning>
  **Critical**: Store your private key securely! You need it to decrypt the
  `encrypted_authorization_key` that Grid returns. If you lose the private key,
  you cannot decrypt the authorization key and will need to re-authenticate.
</Warning>

## Decrypting Authorization Keys

After receiving the encrypted response from either endpoint, use your private key to decrypt the authorization key.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { CipherSuite, KemId, KdfId, AeadId } from "@hpke/core";

  async function decryptAuthorizationKey(
  encryptedData: any,
  privateKeyB64: string
  ): Promise<string> {
  // Configure HPKE suite for Privy
  const suite = new CipherSuite({
  kem: KemId.DhP256HkdfSha256,
  kdf: KdfId.HkdfSha256,
  aead: AeadId.Chacha20Poly1305
  });

  // Decode the encrypted data
  const encapsulatedKey = Buffer.from(encryptedData.encapsulated_key, 'base64');
  const ciphertext = Buffer.from(encryptedData.ciphertext, 'base64');
  const privateKeyDer = Buffer.from(privateKeyB64, 'base64');

  // Create recipient context
  const recipient = await suite.createRecipientContext({
  recipientKey: privateKeyDer,
  enc: encapsulatedKey,
  info: new Uint8Array(), // Empty info
  });

  // Decrypt the ciphertext
  const plaintext = await recipient.open(ciphertext, new Uint8Array()); // Empty AAD
  const authKey = new TextDecoder().decode(plaintext);

  // Remove "wallet-auth:" prefix if present
  return authKey.replace("wallet-auth:", "");
  }

  // Usage after API call
  const privyAuth = response.authentication.find(auth => auth.provider === 'Privy');
  const encryptedAuthKey = privyAuth.session.session.encrypted_authorization_key;
  const decryptedAuthKey = await decryptAuthorizationKey(encryptedAuthKey, keypair.privateKey);

  ```

  ```python Python theme={null}
  from cryptography.hazmat.primitives import hashes, serialization
  from cryptography.hazmat.primitives.kdf.hkdf import HKDF
  from cryptography.hazmat.primitives.asymmetric import ec
  from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
  import base64

  def decrypt_authorization_key(encrypted_data, private_key_b64):
      # Import private key from PKCS#8 DER
      private_key_der = base64.b64decode(private_key_b64)
      private_key = serialization.load_der_private_key(private_key_der, password=None)

      # Import ephemeral public key from SPKI DER
      ephemeral_key_der = base64.b64decode(encrypted_data['encapsulated_key'])
      ephemeral_key = serialization.load_der_public_key(ephemeral_key_der)

      # Perform ECDH to get shared secret
      shared_secret = private_key.exchange(ec.ECDH(), ephemeral_key)

      # HPKE Suite IDs for key derivation context
      KEM_SUITE_ID = b'KEM' + (0x0010).to_bytes(2, 'big')  # 7 bytes
      HPKE_SUITE_ID = (0x0010).to_bytes(2, 'big') + (0x0001).to_bytes(2, 'big') + (0x0003).to_bytes(2, 'big')

      # HKDF key derivation following RFC 9180
      # Step 1: Extract KEM shared secret
      kem_hkdf = HKDF(
          algorithm=hashes.SHA256(),
          length=32,
          salt=b'',
          info=b'eae_prk' + KEM_SUITE_ID
      )
      kem_shared_secret = kem_hkdf.derive(shared_secret)

      # Step 2: Expand to get decryption key
      decrypt_hkdf = HKDF(
          algorithm=hashes.SHA256(),
          length=32,
          salt=kem_shared_secret,
          info=b'key' + HPKE_SUITE_ID
      )
      decrypt_key = decrypt_hkdf.derive(b'')

      # Step 3: Expand to get base nonce
      nonce_hkdf = HKDF(
          algorithm=hashes.SHA256(),
          length=12,
          salt=kem_shared_secret,
          info=b'base_nonce' + HPKE_SUITE_ID
      )
      base_nonce = nonce_hkdf.derive(b'')

      # Decrypt with ChaCha20-Poly1305
      cipher = ChaCha20Poly1305(decrypt_key)
      ciphertext = base64.b64decode(encrypted_data['ciphertext'])

      plaintext = cipher.decrypt(base_nonce, ciphertext, None)  # No AAD
      auth_key = plaintext.decode()

      # Remove "wallet-auth:" prefix if present
      return auth_key.replace("wallet-auth:", "")

  # Usage after POST /accounts/verify response
  privy_auth = next(auth for auth in response['authentication'] if auth['provider'] == 'Privy')
  encrypted_auth_key = privy_auth['session']['session']['encrypted_authorization_key']
  decrypted_auth_key = decrypt_authorization_key(encrypted_auth_key, keypair['private_key'])
  ```
</CodeGroup>

## Signing Transaction Payloads

Now use the decrypted authorization key to sign Grid API transaction payloads.

### Extract Signing Key

First, extract the 32-byte ECDSA private key from the authorization key:

<CodeGroup>
  ```typescript TypeScript theme={null}
  function extractSigningKey(authKeyB64: string): Uint8Array {
    const pkcs8Bytes = Buffer.from(authKeyB64, 'base64');

  // Look for pattern [0x04, 0x20] followed by 32-byte key
  const pattern = Buffer.from([0x04, 0x20]);
  const patternIndex = pkcs8Bytes.indexOf(pattern);

  if (patternIndex === -1) {
  throw new Error('Private key marker not found');
  }

  const keyStart = patternIndex + 2;
  return new Uint8Array(pkcs8Bytes.slice(keyStart, keyStart + 32));
  }

  ```

  ```python Python theme={null}
  def extract_signing_key(auth_key_b64):
      pkcs8_bytes = base64.b64decode(auth_key_b64)

      # Look for pattern [0x04, 0x20] followed by 32-byte key
      pattern = bytes([0x04, 0x20])
      pattern_index = pkcs8_bytes.find(pattern)

      if pattern_index == -1:
          raise ValueError('Private key marker not found')

      key_start = pattern_index + 2
      return pkcs8_bytes[key_start:key_start + 32]
  ```
</CodeGroup>

### JSON Canonicalization

Privy requires recursive key sorting of all JSON objects:

<CodeGroup>
  ```typescript TypeScript theme={null}
  function canonicalizeJson(obj: any): any {
    if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
      const sorted: any = {};
      // Sort keys alphabetically
      Object.keys(obj).sort().forEach(key => {
        sorted[key] = canonicalizeJson(obj[key]);
      });
      return sorted;
    } else if (Array.isArray(obj)) {
      return obj.map(item => canonicalizeJson(item));
    }
    return obj;
  }

  function serializeCanonical(payload: any): string {
  const canonical = canonicalizeJson(payload);
  return JSON.stringify(canonical);
  }

  ```

  ```python Python theme={null}
  import json

  def canonicalize_json(obj):
      if isinstance(obj, dict):
          # Sort keys alphabetically and recursively canonicalize values
          return {k: canonicalize_json(v) for k, v in sorted(obj.items())}
      elif isinstance(obj, list):
          return [canonicalize_json(item) for item in obj]
      else:
          return obj

  def serialize_canonical(payload):
      canonical = canonicalize_json(payload)
      return json.dumps(canonical, separators=(',', ':'))
  ```
</CodeGroup>

### Sign the Payload

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { p256 } from '@noble/curves/p256';

  function signPayload(kmsPayloadB64: string, authKeyB64: string): string {
  // Decode and parse the KMS payload
  const payloadJson = Buffer.from(kmsPayloadB64, 'base64').toString('utf-8');
  const payload = JSON.parse(payloadJson);

  // Canonicalize the JSON
  const canonicalPayload = serializeCanonical(payload);

  // Extract the signing key
  const privateKeyBytes = extractSigningKey(authKeyB64);

  // Sign with ECDSA P-256
  const signature = p256.sign(Buffer.from(canonicalPayload), privateKeyBytes);

  // Return base64 DER signature
  return Buffer.from(signature.toDERRawBytes()).toString('base64');
  }

  // Usage
  const signature = signPayload(kmsPayload.payload, decryptedAuthKey);

  ```

  ```python Python theme={null}
  from cryptography.hazmat.primitives.asymmetric import ec
  from cryptography.hazmat.primitives import hashes

  def sign_payload(kms_payload_b64, auth_key_b64):
      # Decode and parse the KMS payload
      payload_json = base64.b64decode(kms_payload_b64).decode('utf-8')
      payload = json.loads(payload_json)

      # Canonicalize the JSON
      canonical_payload = serialize_canonical(payload)

      # Extract the signing key
      private_key_bytes = extract_signing_key(auth_key_b64)

      # Create signing key
      private_key = ec.derive_private_key(
          int.from_bytes(private_key_bytes, 'big'),
          ec.SECP256R1()
      )

      # Sign with ECDSA P-256
      signature = private_key.sign(
          canonical_payload.encode(),
          ec.ECDSA(hashes.SHA256())
      )

      # Return base64 DER signature
      return base64.b64encode(signature).decode()

  # Usage with KMS payload from Grid API
  signature = sign_payload(kms_payload['payload'], decrypted_auth_key)
  ```
</CodeGroup>
