@opendatalabs/vana-sdk
Version:
A TypeScript library for interacting with Vana Network smart contracts.
403 lines • 12.8 kB
JavaScript
import { getPGPKeyGenParams } from "./shared/pgp-utils.js";
import { wrapCryptoError } from "./shared/error-utils.js";
import { streamToUint8Array } from "./shared/stream-utils.js";
import { lazyImport } from "../utils/lazy-import.js";
import { WalletKeyEncryptionService } from "../crypto/services/WalletKeyEncryptionService.js";
import {
processWalletPrivateKey,
parseEncryptedDataBuffer,
processWalletPublicKey
} from "../utils/crypto-utils.js";
const getOpenPGP = lazyImport(() => import("openpgp"));
import { NodeECIESUint8Provider } from "../crypto/ecies/node.js";
import { ECIESError } from "../crypto/ecies/interface.js";
import { randomBytes } from "crypto";
import secp256k1Import from "secp256k1";
class NodeCryptoAdapter {
eciesProvider = new NodeECIESUint8Provider();
walletService = new WalletKeyEncryptionService({
eciesProvider: this.eciesProvider
});
/**
* Encrypts data using ECIES with a public key.
*
* @param data - The plaintext string to encrypt.
* Typically user data or sensitive information.
* @param publicKeyHex - The recipient's public key in hex format.
* Obtain from key generation or user profile.
* @returns Encrypted data as a hex string containing IV, ephemeral key, ciphertext, and MAC
*
* @throws {Error} If encryption fails or public key is invalid
*/
async encryptWithPublicKey(data, publicKeyHex) {
try {
const publicKeyBytes = processWalletPublicKey(publicKeyHex);
const publicKey = Buffer.from(publicKeyBytes);
const message = Buffer.from(data, "utf8");
const encrypted = await this.eciesProvider.encrypt(publicKey, message);
const result = Buffer.concat([
encrypted.iv,
encrypted.ephemPublicKey,
encrypted.ciphertext,
encrypted.mac
]);
return result.toString("hex");
} catch (error) {
if (error instanceof ECIESError) {
throw error;
}
throw new Error(
`Encryption failed: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Decrypts ECIES-encrypted data using a private key.
*
* @param encryptedData - Hex string containing encrypted data.
* Must include IV, ephemeral public key, ciphertext, and MAC.
* @param privateKeyHex - The private key in hex format.
* Must correspond to the public key used for encryption.
* @returns The decrypted plaintext string
*
* @throws {Error} If decryption fails or MAC verification fails
* @throws {ECIESError} If using custom ECIES and specific error occurs
*/
async decryptWithPrivateKey(encryptedData, privateKeyHex) {
try {
const privateKeyBuffer = processWalletPrivateKey(privateKeyHex);
const encryptedHex = encryptedData.startsWith("0x") ? encryptedData.slice(2) : encryptedData;
const encryptedBuffer = Buffer.from(encryptedHex, "hex");
const { iv, ephemPublicKey, ciphertext, mac } = parseEncryptedDataBuffer(encryptedBuffer);
const encryptedObj = {
iv,
ephemPublicKey,
ciphertext,
mac
};
const decrypted = await this.eciesProvider.decrypt(
privateKeyBuffer,
encryptedObj
);
return new TextDecoder().decode(decrypted);
} catch (error) {
if (error instanceof ECIESError) {
throw error;
}
throw new Error(
`Decryption failed: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Generates a new secp256k1 key pair for ECIES operations.
*
* @returns Object containing hex-encoded public and private keys
* @returns returns.publicKey - Compressed public key in hex format
* @returns returns.privateKey - Private key in hex format
*
* @throws {Error} If key generation fails
*/
async generateKeyPair() {
try {
const secp256k1 = secp256k1Import;
let privateKey;
do {
privateKey = randomBytes(32);
} while (!secp256k1.privateKeyVerify(privateKey));
const publicKey = Buffer.from(
secp256k1.publicKeyCreate(privateKey, true)
);
return {
privateKey: privateKey.toString("hex"),
publicKey: publicKey.toString("hex")
};
} catch (error) {
throw wrapCryptoError("key generation", error);
}
}
/**
* Encrypts data using a wallet's public key.
*
* @param data - The plaintext string to encrypt.
* Typically permission data or DLP metadata.
* @param publicKey - The wallet's public key (with or without 0x prefix).
* Obtain from wallet connection or user profile.
* @returns Encrypted data as a hex string
*
* @throws {Error} If encryption fails or key processing fails
*/
async encryptWithWalletPublicKey(data, publicKey) {
try {
return await this.walletService.encryptWithWalletPublicKey(
data,
publicKey
);
} catch (error) {
throw wrapCryptoError("encrypt with wallet public key", error);
}
}
/**
* Decrypts data using a wallet's private key.
*
* @param encryptedData - Hex string containing encrypted data.
* Must be encrypted with corresponding wallet public key.
* @param privateKey - The wallet's private key.
* Obtain from wallet connection (handle with care).
* @returns The decrypted plaintext string
*
* @throws {Error} If decryption fails or key is invalid
*/
async decryptWithWalletPrivateKey(encryptedData, privateKey) {
try {
return await this.walletService.decryptWithWalletPrivateKey(
encryptedData,
privateKey
);
} catch (error) {
throw wrapCryptoError("decrypt with wallet private key", error);
}
}
/**
* Encrypts binary data using password-based encryption.
*
* @param data - Binary data to encrypt.
* Typically file contents or serialized objects.
* @param password - Password for encryption.
* Often derived from wallet signatures.
* @returns Encrypted data as Uint8Array
*
* @remarks
* Uses OpenPGP for password-based encryption. Note that this is not
* deterministic due to OpenPGP's random salt generation.
*
* @throws {Error} If encryption fails
*/
async encryptWithPassword(data, password) {
try {
const openpgp = await getOpenPGP();
const message = await openpgp.createMessage({
binary: data
});
const encrypted = await openpgp.encrypt({
message,
passwords: [password],
format: "binary"
});
if (encrypted instanceof Uint8Array) {
return encrypted;
}
if (encrypted && typeof encrypted === "object" && "getReader" in encrypted) {
return await streamToUint8Array(
encrypted
);
}
throw new Error("Unexpected encrypted data format");
} catch (error) {
throw wrapCryptoError("encrypt with password", error);
}
}
/**
* Decrypts password-encrypted binary data.
*
* @param encryptedData - Password-encrypted data as Uint8Array.
* Must be encrypted with the same password.
* @param password - Password for decryption.
* Must match the encryption password.
* @returns Decrypted data as Uint8Array
*
* @throws {Error} If decryption fails or password is incorrect
*/
async decryptWithPassword(encryptedData, password) {
try {
const openpgp = await getOpenPGP();
const message = await openpgp.readMessage({
binaryMessage: encryptedData
});
const { data: decrypted } = await openpgp.decrypt({
message,
passwords: [password],
format: "binary"
});
return new Uint8Array(decrypted);
} catch (error) {
throw wrapCryptoError("decrypt with password", error);
}
}
}
class NodePGPAdapter {
/**
* Encrypts data using PGP public key encryption.
*
* @param data - The plaintext string to encrypt.
* Typically messages or structured data.
* @param publicKeyArmored - ASCII-armored PGP public key.
* Obtain from PGP key generation or key servers.
* @returns ASCII-armored encrypted message
*
* @throws {Error} If encryption fails or public key is invalid
*/
async encrypt(data, publicKeyArmored) {
try {
const openpgp = await getOpenPGP();
const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: data }),
encryptionKeys: publicKey,
config: {
preferredCompressionAlgorithm: openpgp.enums.compression.zlib
}
});
return encrypted;
} catch (error) {
throw wrapCryptoError("PGP encryption", error);
}
}
/**
* Decrypts PGP-encrypted data using a private key.
*
* @param encryptedData - ASCII-armored encrypted message.
* Must be encrypted with corresponding public key.
* @param privateKeyArmored - ASCII-armored PGP private key.
* Must correspond to the public key used for encryption.
* @returns The decrypted plaintext string
*
* @throws {Error} If decryption fails or private key is invalid
*/
async decrypt(encryptedData, privateKeyArmored) {
try {
const openpgp = await getOpenPGP();
const privateKey = await openpgp.readPrivateKey({
armoredKey: privateKeyArmored
});
const message = await openpgp.readMessage({
armoredMessage: encryptedData
});
const { data: decrypted } = await openpgp.decrypt({
message,
decryptionKeys: privateKey
});
return decrypted;
} catch (error) {
throw wrapCryptoError("PGP decryption", error);
}
}
/**
* Generates a new PGP key pair.
*
* @param options - Key generation options
* @param options.name - Name for the key identity.
* Defaults to 'Vana User'.
* @param options.email - Email for the key identity.
* Defaults to 'user@vana.com'.
* @param options.passphrase - Passphrase to protect the private key.
* If not provided, key is unprotected.
* @returns ASCII-armored public and private keys
*
* @throws {Error} If key generation fails
*/
async generateKeyPair(options) {
try {
const openpgp = await getOpenPGP();
const keyGenParams = getPGPKeyGenParams(options);
const { privateKey, publicKey } = await openpgp.generateKey(keyGenParams);
return { publicKey, privateKey };
} catch (error) {
throw wrapCryptoError("PGP key generation", error);
}
}
}
class NodeHttpAdapter {
/**
* Performs an HTTP request using fetch.
*
* @param url - The URL to fetch.
* Must be a valid HTTP/HTTPS URL.
* @param options - Standard fetch options.
* See MDN fetch documentation for details.
* @returns Standard fetch Response object
*
* @throws {Error} If fetch is not available in the environment
*/
async fetch(url, options) {
if (typeof globalThis.fetch !== "undefined") {
return globalThis.fetch(url, options);
}
throw new Error("No fetch implementation available in Node.js environment");
}
}
class NodeCacheAdapter {
cache = /* @__PURE__ */ new Map();
defaultTtl = 2 * 60 * 60 * 1e3;
// 2 hours in milliseconds
/**
* Retrieves a cached value by key.
*
* @param key - The cache key to look up.
* Typically derived from operation parameters.
* @returns The cached value or null if not found/expired
*/
get(key) {
const entry = this.cache.get(key);
if (!entry) {
return null;
}
if (Date.now() > entry.expires) {
this.cache.delete(key);
return null;
}
return entry.value;
}
/**
* Stores a value in the cache with TTL.
*
* @param key - The cache key.
* Should be unique per operation.
* @param value - The value to cache.
* Typically serialized data or signatures.
*/
set(key, value) {
this.cache.set(key, {
value,
expires: Date.now() + this.defaultTtl
});
}
/**
* Removes a specific key from the cache.
*
* @param key - The cache key to remove.
* Use when cached data becomes invalid.
*/
delete(key) {
this.cache.delete(key);
}
/**
* Clears all cached values.
*
* @remarks
* Use with caution as this removes all cached signatures
* and other performance optimizations.
*/
clear() {
this.cache.clear();
}
}
class NodePlatformAdapter {
crypto;
pgp;
http;
cache;
platform = "node";
constructor() {
this.crypto = new NodeCryptoAdapter();
this.pgp = new NodePGPAdapter();
this.http = new NodeHttpAdapter();
this.cache = new NodeCacheAdapter();
}
}
const nodePlatformAdapter = new NodePlatformAdapter();
export {
NodePlatformAdapter,
nodePlatformAdapter
};
//# sourceMappingURL=node.js.map