Skip to content

Manage Multiple Keypairs

Managing multiple keypairs securely, especially when dealing with multiple merchants and campaigns, requires careful organization. Here are a few approaches to consider for securely loading keypairs from the filesystem or cloud storage, with example code for each.

Approaches Overview

  1. File Structure: Simple and effective for development and small-scale applications.
  2. Environment Variables: Adds security by abstracting paths from the codebase.
  3. Cloud Storage: Provides secure access for distributed environments with secure API controls.
  4. Database Storage: Useful for high-volume, dynamic applications requiring on-the-fly keypair access and encrypted storage.

1. Organize Keypairs by Merchant and Campaign Structure

Store keypair files in a directory structure organized by merchantId and campaignId. This keeps files separated and accessible based on merchant and campaign.

File Structure Example:

keypairs/
└── merchant1/
├── campaign1.json
└── campaign2.json
└── merchant2/
└── campaign1.json

Code Sample:

import fs from 'fs';
import { Keypair } from '@solana/web3.js';
import path from 'path';
// Function to load a specific keypair given a merchantId and campaignId
const loadKeypair = (merchantId, campaignId) => {
const filePath = path.join(__dirname, 'keypairs', merchantId, `${campaignId}.json`);
try {
const keypairData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
return Keypair.fromSecretKey(new Uint8Array(keypairData));
} catch (error) {
console.error(`Failed to load keypair for merchant ${merchantId}, campaign ${campaignId}:`, error);
throw error;
}
};
// Usage Example
const keypair = loadKeypair('merchant1', 'campaign1');
console.log("Public Key:", keypair.publicKey.toString());

2. Environment Variable-Based Paths for Secure Access

In a production environment, it’s safer to pass file paths through environment variables. Each merchantId and campaignId could have a corresponding environment variable that points to the keypair file location.

Code Sample:

import fs from 'fs';
import { Keypair } from '@solana/web3.js';
// Function to load keypair using environment variables
const loadKeypairFromEnv = (merchantId, campaignId) => {
const envVar = `KEYPAIR_PATH_${merchantId}_${campaignId}`;
const filePath = process.env[envVar];
if (!filePath) {
throw new Error(`Environment variable ${envVar} is not defined.`);
}
try {
const keypairData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
return Keypair.fromSecretKey(new Uint8Array(keypairData));
} catch (error) {
console.error(`Failed to load keypair for ${merchantId}, ${campaignId}:`, error);
throw error;
}
};
// Usage: Ensure environment variables are set before calling this function
// e.g., process.env.KEYPAIR_PATH_merchant1_campaign1 = "/path/to/keypair.json";
const keypair = loadKeypairFromEnv('merchant1', 'campaign1');
console.log("Public Key:", keypair.publicKey.toString());

3. Cloud Storage Access with Secure Authentication

If keypairs are stored in a cloud storage provider (e.g., AWS S3, Google Cloud Storage), access can be managed securely with API keys, IAM roles, or service accounts. Here’s an example using AWS S3:

Code Sample (Using AWS SDK):

import AWS from 'aws-sdk';
import { Keypair } from '@solana/web3.js';
const s3 = new AWS.S3();
// Function to fetch keypair data from AWS S3
const loadKeypairFromS3 = async (bucketName, merchantId, campaignId) => {
const key = `keypairs/${merchantId}/${campaignId}.json`;
try {
const data = await s3.getObject({ Bucket: bucketName, Key: key }).promise();
const keypairData = JSON.parse(data.Body.toString('utf-8'));
return Keypair.fromSecretKey(new Uint8Array(keypairData));
} catch (error) {
console.error(`Failed to load keypair from S3 for ${merchantId}, ${campaignId}:`, error);
throw error;
}
};
// Usage Example
(async () => {
const keypair = await loadKeypairFromS3('my-s3-bucket', 'merchant1', 'campaign1');
console.log("Public Key:", keypair.publicKey.toString());
})();

4. Database-Backed Keypair Management

In cases where multiple merchants and campaigns require dynamic access, storing keypairs in an encrypted database could be effective. Here’s an example using MongoDB with encryption:

Code Sample (Using Mongoose):

import mongoose from 'mongoose';
import { Keypair } from '@solana/web3.js';
const keypairSchema = new mongoose.Schema({
merchantId: String,
campaignId: String,
keypairData: { type: [Number], required: true } // Storing as an array of integers
});
const KeypairModel = mongoose.model('Keypair', keypairSchema);
// Function to load keypair from the database
const loadKeypairFromDB = async (merchantId, campaignId) => {
try {
const record = await KeypairModel.findOne({ merchantId, campaignId });
if (!record) throw new Error("Keypair not found in database");
return Keypair.fromSecretKey(new Uint8Array(record.keypairData));
} catch (error) {
console.error(`Failed to load keypair from DB for ${merchantId}, ${campaignId}:`, error);
throw error;
}
};
// Usage Example
(async () => {
await mongoose.connect('mongodb://localhost:27017/keypairsDB');
const keypair = await loadKeypairFromDB('merchant1', 'campaign1');
console.log("Public Key:", keypair.publicKey.toString());
})();