@opendatalabs/vana-sdk
Version:
A TypeScript library for interacting with Vana Network smart contracts.
375 lines • 12 kB
JavaScript
import { getPGPKeyGenParams } from "./shared/pgp-utils.js";
import { wrapCryptoError } from "./shared/error-utils.js";
import { lazyImport } from "../utils/lazy-import.js";
import { WalletKeyEncryptionService } from "../crypto/services/WalletKeyEncryptionService.js";
import { parseEncryptedDataBuffer } from "../utils/crypto-utils.js";
import { toHex, fromHex, stringToBytes, bytesToString, concat } from "viem";
import * as secp256k1 from "@noble/secp256k1";
import { BrowserECIESUint8Provider } from "../crypto/ecies/browser.js";
const getOpenPGP = lazyImport(() => import("openpgp"));
class BrowserCryptoAdapter {
eciesProvider = new BrowserECIESUint8Provider();
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.
* Can include or omit the '0x' prefix.
* @returns Encrypted data as a hex string without '0x' prefix
*
* @throws {Error} If encryption fails or public key is invalid
*/
async encryptWithPublicKey(data, publicKeyHex) {
try {
const prefixedHex = publicKeyHex.startsWith("0x") ? publicKeyHex : `0x${publicKeyHex}`;
const publicKeyBytes = fromHex(prefixedHex, "bytes");
const encrypted = await this.eciesProvider.encrypt(
publicKeyBytes,
stringToBytes(data)
);
const result = concat([
encrypted.iv,
encrypted.ephemPublicKey,
encrypted.ciphertext,
encrypted.mac
]);
return toHex(result).slice(2);
} catch (error) {
throw wrapCryptoError("encryptWithPublicKey", error);
}
}
/**
* Decrypts ECIES-encrypted data using a private key.
*
* @param encryptedData - Hex string containing encrypted data.
* Can include or omit the '0x' prefix.
* @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
*/
async decryptWithPrivateKey(encryptedData, privateKeyHex) {
try {
const encryptedHex = encryptedData.startsWith("0x") ? encryptedData : `0x${encryptedData}`;
const privateHex = privateKeyHex.startsWith("0x") ? privateKeyHex : `0x${privateKeyHex}`;
const encryptedBytes = fromHex(encryptedHex, "bytes");
const privateKeyBytes = fromHex(privateHex, "bytes");
const encrypted = parseEncryptedDataBuffer(encryptedBytes);
const decrypted = await this.eciesProvider.decrypt(
privateKeyBytes,
encrypted
);
return bytesToString(decrypted);
} catch (error) {
throw wrapCryptoError("decryptWithPrivateKey", 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.
* Automatically handles compressed/uncompressed formats.
* @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("encryptWithWalletPublicKey", 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("decryptWithWalletPrivateKey", error);
}
}
/**
* Generates a new secp256k1 key pair for ECIES operations.
*
* @returns Object containing hex-encoded public and private keys
* @returns returns.privateKey - Private key in hex format without '0x'
* @returns returns.publicKey - Compressed public key in hex format without '0x'
*
* @throws {Error} If key generation fails
*/
async generateKeyPair() {
try {
const privateKeyBytes = secp256k1.utils.randomPrivateKey();
const publicKeyBytes = secp256k1.getPublicKey(privateKeyBytes, true);
return {
privateKey: toHex(privateKeyBytes).slice(2),
publicKey: toHex(publicKeyBytes).slice(2)
};
} catch (error) {
throw wrapCryptoError("generateKeyPair", 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 with automatic
* salt generation for security.
*
* @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"
});
return new Uint8Array(encrypted);
} catch (error) {
throw wrapCryptoError("encryptWithPassword", 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 } = await openpgp.decrypt({
message,
passwords: [password],
format: "binary"
});
return new Uint8Array(data);
} catch (error) {
throw wrapCryptoError("decryptWithPassword", error);
}
}
}
class BrowserPGPAdapter {
/**
* 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 new Error(`PGP encryption failed: ${String(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 new Error(`PGP decryption failed: ${String(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 BrowserHttpAdapter {
/**
* Performs an HTTP request using the Fetch API.
*
* @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
*/
async fetch(url, options) {
return fetch(url, options);
}
}
class BrowserCacheAdapter {
prefix = "vana_cache_";
/**
* Retrieves a cached value by key.
*
* @param key - The cache key to look up.
* Automatically prefixed to avoid conflicts.
* @returns The cached value or null if not found
*/
get(key) {
try {
if (typeof sessionStorage === "undefined") {
return null;
}
return sessionStorage.getItem(this.prefix + key);
} catch {
return null;
}
}
/**
* Stores a value in sessionStorage.
*
* @param key - The cache key.
* Automatically prefixed with 'vana_cache_'.
* @param value - The value to cache.
* Will be cleared when tab closes.
*/
set(key, value) {
try {
if (typeof sessionStorage === "undefined") {
return;
}
sessionStorage.setItem(this.prefix + key, value);
} catch {
}
}
/**
* Removes a specific key from the cache.
*
* @param key - The cache key to remove.
* Only removes the prefixed key.
*/
delete(key) {
try {
if (typeof sessionStorage === "undefined") {
return;
}
sessionStorage.removeItem(this.prefix + key);
} catch {
}
}
/**
* Clears all Vana-prefixed cache entries.
*
* @remarks
* Only removes entries with the 'vana_cache_' prefix,
* preserving other sessionStorage data.
*/
clear() {
try {
if (typeof sessionStorage === "undefined") {
return;
}
const keysToRemove = [];
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
if (key?.startsWith(this.prefix)) {
keysToRemove.push(key);
}
}
keysToRemove.forEach((key) => {
sessionStorage.removeItem(key);
});
} catch {
}
}
}
class BrowserPlatformAdapter {
crypto = new BrowserCryptoAdapter();
pgp = new BrowserPGPAdapter();
http = new BrowserHttpAdapter();
cache = new BrowserCacheAdapter();
platform = "browser";
}
export {
BrowserPlatformAdapter
};
//# sourceMappingURL=browser.js.map