Encryption

Shade402 provides RSA encryption utilities for resource field privacy.

Overview

Resource encryption prevents payment request replay attacks by ensuring the resource field is encrypted and can only be decrypted by the server.

Generating Keys

Generate an RSA key pair:

import { generateKeyPair } from '@shade402/core';

const { publicKey, privateKey } = generateKeyPair();

// Keys are in PEM format
console.log(publicKey);
// -----BEGIN PUBLIC KEY-----
// ...
// -----END PUBLIC KEY-----

console.log(privateKey);
// -----BEGIN PRIVATE KEY-----
// ...
// -----END PRIVATE KEY-----

Store keys securely:

  • Public key: Can be shared (included in 402 responses)

  • Private key: Must be kept secret (server-side only)

Encrypting Resources

Encrypt a resource string using the server's public key:

import { encryptResource } from '@shade402/core';

const plaintext = '/api/premium-data';
const encrypted = encryptResource(plaintext, serverPublicKey);

console.log('Encrypted:', encrypted); // Base64-encoded

The encryption uses:

  • RSA-OAEP padding

  • SHA-256 hash algorithm

  • 2048-bit keys

Decrypting Resources

Decrypt an encrypted resource using the server's private key:

import { decryptResource } from '@shade402/core';

const decrypted = decryptResource(encrypted, serverPrivateKey);

console.log('Decrypted:', decrypted); // '/api/premium-data'

Server-side Setup

Generate and Store Keys

import { generateKeyPair } from '@shade402/core';
import fs from 'fs';

// Generate keys (do this once)
const { publicKey, privateKey } = generateKeyPair();

// Store keys securely
fs.writeFileSync('keys/public.pem', publicKey);
fs.writeFileSync('keys/private.pem', privateKey);

Load Keys in Application

import fs from 'fs';

const publicKey = fs.readFileSync('keys/public.pem', 'utf8');
const privateKey = fs.readFileSync('keys/private.pem', 'utf8');

initX402({
  // ... other config ...
  encryptionPublicKey: publicKey,
  encryptionPrivateKey: privateKey,
});

Using Environment Variables

// Load from environment variables
initX402({
  // ... other config ...
  encryptionPublicKey: process.env.ENCRYPTION_PUBLIC_KEY!,
  encryptionPrivateKey: process.env.ENCRYPTION_PRIVATE_KEY!,
});

Client-side Usage

The client automatically handles encryption:

import { X402AutoClient } from '@shade402/client';

const client = new X402AutoClient(wallet);

// Client automatically:
// 1. Receives public key from 402 response
// 2. Encrypts resource field
// 3. Includes encrypted resource in retry request

const response = await client.get(url);

Manual Encryption (Explicit Client)

import { X402Client } from '@shade402/client';

const client = new X402Client(wallet);

// Get 402 response
let response = await client.get(url);
const paymentRequest = client.parsePaymentRequest(response);

// Encrypt resource
const encryptedResource = client.encryptResource(paymentRequest.resource);

// Retry with encrypted resource
response = await client.get(url, {
  payment: authorization,
  encryptedResource: encryptedResource,
});

Complete Example

Server

import { initX402, paymentRequired } from '@shade402/express';
import { generateKeyPair } from '@shade402/core';

// Generate keys (once)
const { publicKey, privateKey } = generateKeyPair();

initX402({
  paymentAddress: process.env.PAYMENT_WALLET_ADDRESS!,
  tokenMint: process.env.TOKEN_MINT!,
  network: 'solana-devnet',
  encryptionPublicKey: publicKey,
  encryptionPrivateKey: privateKey,
});

app.get('/api/premium-data',
  paymentRequired({ amount: '0.01' }),
  (req, res) => {
    // req.decryptedResource contains decrypted resource
    console.log('Decrypted resource:', req.decryptedResource);
    res.json({ data: 'Premium data' });
  }
);

Client

import { X402AutoClient } from '@shade402/client';

const client = new X402AutoClient(wallet);

// Encryption handled automatically
const response = await client.get('https://api.example.com/premium-data');

Security Considerations

  1. Keep private key secure: Never expose the private key

  2. Use strong keys: 2048-bit keys are recommended

  3. Rotate keys periodically: Generate new keys and update configuration

  4. Use secure storage: Store keys in environment variables or key management service

  5. Separate keys per environment: Use different keys for dev/staging/production

Key Management

Using Key Management Service

import { SecretsManager } from 'aws-sdk';

async function loadKeys() {
  const sm = new SecretsManager();
  
  const publicKey = await sm.getSecretValue({
    SecretId: 'x402-encryption-public-key',
  }).promise();
  
  const privateKey = await sm.getSecretValue({
    SecretId: 'x402-encryption-private-key',
  }).promise();
  
  return {
    publicKey: publicKey.SecretString!,
    privateKey: privateKey.SecretString!,
  };
}

Using Environment Variables

# .env
ENCRYPTION_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
ENCRYPTION_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"

Error Handling

import { encryptResource, decryptResource } from '@shade402/core';

try {
  const encrypted = encryptResource(plaintext, publicKey);
} catch (error) {
  console.error('Encryption failed:', error);
}

try {
  const decrypted = decryptResource(encrypted, privateKey);
} catch (error) {
  console.error('Decryption failed:', error);
}

Best Practices

  1. Generate keys once and reuse them

  2. Store keys securely (environment variables, key management service)

  3. Rotate keys periodically

  4. Use separate keys for different environments

  5. Never commit keys to version control

  6. Monitor for encryption/decryption failures

  7. Test encryption/decryption in your application

Next Steps

Last updated