@opendatalabs/vana-sdk
Version:
A TypeScript library for interacting with Vana Network smart contracts.
1,641 lines (1,627 loc) • 691 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/platform/shared/pgp-utils.ts
function processPGPKeyOptions(options) {
return {
name: options?.name ?? "Vana User",
email: options?.email ?? "user@vana.org",
passphrase: options?.passphrase
};
}
function getPGPKeyGenParams(options) {
const { name, email, passphrase } = processPGPKeyOptions(options);
return {
type: "rsa",
rsaBits: 2048,
userIDs: [{ name, email }],
passphrase,
config: STANDARD_PGP_CONFIG
};
}
var STANDARD_PGP_CONFIG;
var init_pgp_utils = __esm({
"src/platform/shared/pgp-utils.ts"() {
"use strict";
STANDARD_PGP_CONFIG = {
preferredCompressionAlgorithm: 2,
// zlib (openpgp.enums.compression.zlib)
preferredSymmetricAlgorithm: 7
// aes256 (openpgp.enums.symmetric.aes256)
};
}
});
// src/platform/shared/error-utils.ts
function wrapCryptoError(operation, error) {
const message = error instanceof Error ? error.message : "Unknown error";
return new Error(`${operation} failed: ${message}`);
}
var init_error_utils = __esm({
"src/platform/shared/error-utils.ts"() {
"use strict";
}
});
// src/utils/lazy-import.ts
function lazyImport(importFn) {
let cached = null;
return () => {
cached ??= importFn().catch((err) => {
cached = null;
throw new Error("Failed to load module", { cause: err });
});
return cached;
};
}
var init_lazy_import = __esm({
"src/utils/lazy-import.ts"() {
"use strict";
}
});
// src/utils/crypto-utils.ts
import { fromHex } from "viem";
function processWalletPublicKey(publicKey) {
return typeof publicKey === "string" ? fromHex(
publicKey.startsWith("0x") ? publicKey : `0x${publicKey}`,
"bytes"
) : publicKey;
}
function processWalletPrivateKey(privateKey) {
return typeof privateKey === "string" ? fromHex(
privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`,
"bytes"
) : privateKey;
}
function parseEncryptedDataBuffer(encryptedBuffer) {
return {
iv: encryptedBuffer.slice(0, 16),
ephemPublicKey: encryptedBuffer.slice(16, 81),
// 65 bytes for uncompressed public key
ciphertext: encryptedBuffer.slice(81, -32),
mac: encryptedBuffer.slice(-32)
};
}
var init_crypto_utils = __esm({
"src/utils/crypto-utils.ts"() {
"use strict";
}
});
// src/crypto/services/WalletKeyEncryptionService.ts
import { stringToBytes, bytesToString, toHex as toHex2, fromHex as fromHex2, concat } from "viem";
var WalletKeyEncryptionService;
var init_WalletKeyEncryptionService = __esm({
"src/crypto/services/WalletKeyEncryptionService.ts"() {
"use strict";
init_crypto_utils();
WalletKeyEncryptionService = class {
eciesProvider;
constructor(config) {
this.eciesProvider = config.eciesProvider;
}
/**
* Encrypts data using a wallet's public key.
*
* @param data - The plaintext message to encrypt for the wallet owner.
* @param publicKey - The recipient wallet's public key for encryption.
* @returns A promise that resolves to the encrypted data as a hex string.
* @throws {Error} When encryption fails due to invalid key format.
*
* @example
* ```typescript
* const encrypted = await processor.encryptWithWalletPublicKey(
* "Secret message",
* "0x04..." // 65-byte uncompressed public key
* );
* console.log(`Encrypted: ${encrypted}`);
* ```
*/
async encryptWithWalletPublicKey(data, publicKey) {
const publicKeyBytes = processWalletPublicKey(publicKey);
const normalizedKey = this.eciesProvider.normalizeToUncompressed(publicKeyBytes);
const dataBytes = stringToBytes(data);
const encrypted = await this.eciesProvider.encrypt(
normalizedKey,
dataBytes
);
const result = concat([
encrypted.iv,
encrypted.ephemPublicKey,
encrypted.ciphertext,
encrypted.mac
]);
return toHex2(result).slice(2);
}
/**
* Decrypts data using a wallet's private key.
*
* @param encryptedData - The hex-encoded encrypted data to decrypt.
* @param privateKey - The wallet's private key for decryption.
* @returns A promise that resolves to the decrypted plaintext message.
* @throws {Error} When decryption fails due to invalid data or key format.
*
* @example
* ```typescript
* const decrypted = await processor.decryptWithWalletPrivateKey(
* encryptedHexString,
* "0x..." // 32-byte private key
* );
* console.log(`Decrypted: ${decrypted}`);
* ```
*/
async decryptWithWalletPrivateKey(encryptedData, privateKey) {
const privateKeyBytes = processWalletPrivateKey(privateKey);
const prefixedHex = encryptedData.startsWith("0x") ? encryptedData : `0x${encryptedData}`;
const encryptedBytes = fromHex2(prefixedHex, "bytes");
const encrypted = parseEncryptedDataBuffer(encryptedBytes);
const decrypted = await this.eciesProvider.decrypt(
privateKeyBytes,
encrypted
);
return bytesToString(decrypted);
}
/**
* Encrypts a Uint8Array with a wallet public key
*
* @param data - Binary data to encrypt
* @param publicKey - Public key as hex string or Uint8Array
* @returns Encrypted data structure
*/
async encryptBinary(data, publicKey) {
const publicKeyBytes = processWalletPublicKey(publicKey);
const normalizedKey = this.eciesProvider.normalizeToUncompressed(publicKeyBytes);
return this.eciesProvider.encrypt(normalizedKey, data);
}
/**
* Decrypts to a Uint8Array with a wallet private key
*
* @param encrypted - Encrypted data structure
* @param privateKey - Private key as hex string or Uint8Array
* @returns Decrypted binary data
*/
async decryptBinary(encrypted, privateKey) {
const privateKeyBytes = processWalletPrivateKey(privateKey);
return this.eciesProvider.decrypt(privateKeyBytes, encrypted);
}
/**
* Gets the underlying ECIES provider
*
* @returns The ECIES provider instance
*/
getECIESProvider() {
return this.eciesProvider;
}
};
}
});
// src/crypto/ecies/constants.ts
var CURVE, CIPHER, KDF, MAC, FORMAT;
var init_constants = __esm({
"src/crypto/ecies/constants.ts"() {
"use strict";
CURVE = {
/** The elliptic curve used (secp256k1 - same as Bitcoin/Ethereum) */
name: "secp256k1",
/** Private key length in bytes */
PRIVATE_KEY_LENGTH: 32,
/** Compressed public key length in bytes (0x02 or 0x03 prefix + 32 bytes) */
COMPRESSED_PUBLIC_KEY_LENGTH: 33,
/** Uncompressed public key length in bytes (0x04 prefix + 64 bytes) */
UNCOMPRESSED_PUBLIC_KEY_LENGTH: 65,
/** ECDH shared secret X coordinate length */
SHARED_SECRET_LENGTH: 32,
/** Public key prefixes */
PREFIX: {
/** Uncompressed public key prefix */
UNCOMPRESSED: 4,
/** Compressed public key prefix for even Y */
COMPRESSED_EVEN: 2,
/** Compressed public key prefix for odd Y */
COMPRESSED_ODD: 3
},
/** X coordinate starts at byte 1 (after prefix) */
X_COORDINATE_OFFSET: 1,
/** X coordinate ends at byte 33 (1 + 32) */
X_COORDINATE_END: 33
};
CIPHER = {
/** Cipher algorithm - must match eccrypto */
algorithm: "aes-256-cbc",
/** AES key length in bytes */
KEY_LENGTH: 32,
/** Initialization vector length in bytes */
IV_LENGTH: 16,
/** Block size for AES */
BLOCK_SIZE: 16
};
KDF = {
/** Hash algorithm for key derivation - must match eccrypto */
algorithm: "sha512",
/** Output length of SHA-512 in bytes */
OUTPUT_LENGTH: 64,
/** Encryption key slice (first 32 bytes of KDF output) */
ENCRYPTION_KEY_OFFSET: 0,
ENCRYPTION_KEY_LENGTH: 32,
/** MAC key slice (last 32 bytes of KDF output) */
MAC_KEY_OFFSET: 32,
MAC_KEY_LENGTH: 32
};
MAC = {
/** MAC algorithm - must match eccrypto */
algorithm: "sha256",
/** HMAC-SHA256 output length in bytes */
LENGTH: 32
};
FORMAT = {
/** Offsets for each component in serialized format */
IV_OFFSET: 0,
IV_LENGTH: CIPHER.IV_LENGTH,
/** Ephemeral public key (always uncompressed in eccrypto format) */
EPHEMERAL_KEY_OFFSET: CIPHER.IV_LENGTH,
EPHEMERAL_KEY_LENGTH: CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH,
/** Ciphertext starts after IV and ephemeral key */
CIPHERTEXT_OFFSET: CIPHER.IV_LENGTH + CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH,
/** MAC is always the last 32 bytes */
MAC_LENGTH: MAC.LENGTH,
/** Minimum size of encrypted data (IV + ephemKey + MAC, no ciphertext) */
MIN_ENCRYPTED_LENGTH: CIPHER.IV_LENGTH + CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH + MAC.LENGTH,
/**
* Helper to calculate total length of encrypted data
*
* @param ciphertextLength - Length of the ciphertext portion
* @returns Total length including all components
*/
getTotalLength: (ciphertextLength) => CIPHER.IV_LENGTH + CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH + ciphertextLength + MAC.LENGTH
};
}
});
// src/crypto/ecies/interface.ts
import { fromHex as fromHex3, toHex as toHex3 } from "viem";
function isECIESEncrypted(obj) {
if (!obj || typeof obj !== "object") return false;
const enc = obj;
const isUint8Array = (value) => {
return value instanceof Uint8Array || typeof Buffer !== "undefined" && Buffer.isBuffer(value);
};
return isUint8Array(enc.iv) && enc.iv.length === CIPHER.IV_LENGTH && isUint8Array(enc.ephemPublicKey) && (enc.ephemPublicKey.length === CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH || enc.ephemPublicKey.length === CURVE.COMPRESSED_PUBLIC_KEY_LENGTH) && isUint8Array(enc.ciphertext) && enc.ciphertext.length > 0 && isUint8Array(enc.mac) && enc.mac.length === MAC.LENGTH;
}
function serializeECIES(encrypted) {
const combined = new Uint8Array(
encrypted.iv.length + encrypted.ephemPublicKey.length + encrypted.ciphertext.length + encrypted.mac.length
);
let offset = 0;
combined.set(encrypted.iv, offset);
offset += encrypted.iv.length;
combined.set(encrypted.ephemPublicKey, offset);
offset += encrypted.ephemPublicKey.length;
combined.set(encrypted.ciphertext, offset);
offset += encrypted.ciphertext.length;
combined.set(encrypted.mac, offset);
return toHex3(combined).slice(2);
}
function deserializeECIES(hex) {
const hexWithPrefix = hex.startsWith("0x") ? hex : `0x${hex}`;
const bytes = fromHex3(hexWithPrefix, "bytes");
const absoluteMinLength = FORMAT.IV_LENGTH + 1 + MAC.LENGTH + 1;
if (bytes.length < absoluteMinLength) {
throw new ECIESError(
`Invalid ECIES data: too short (${bytes.length} bytes, minimum ${absoluteMinLength} bytes required)`,
"DECRYPTION_FAILED"
);
}
const prefix = bytes[FORMAT.EPHEMERAL_KEY_OFFSET];
if (prefix !== CURVE.PREFIX.UNCOMPRESSED) {
throw new ECIESError(
`Invalid ephemeral public key: must be uncompressed format (0x04 prefix), got 0x${prefix.toString(16).padStart(2, "0")}`,
"DECRYPTION_FAILED"
);
}
const ephemKeySize = CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH;
const minLength = FORMAT.IV_LENGTH + ephemKeySize + MAC.LENGTH + 1;
if (bytes.length < minLength) {
throw new ECIESError(
`Invalid ECIES data: too short (${bytes.length} bytes, minimum ${minLength} bytes required)`,
"DECRYPTION_FAILED"
);
}
return {
iv: bytes.subarray(FORMAT.IV_OFFSET, FORMAT.IV_OFFSET + FORMAT.IV_LENGTH),
ephemPublicKey: bytes.subarray(
FORMAT.EPHEMERAL_KEY_OFFSET,
FORMAT.EPHEMERAL_KEY_OFFSET + ephemKeySize
),
ciphertext: bytes.subarray(
FORMAT.EPHEMERAL_KEY_OFFSET + ephemKeySize,
bytes.length - MAC.LENGTH
),
mac: bytes.subarray(bytes.length - MAC.LENGTH)
};
}
var ECIESError;
var init_interface = __esm({
"src/crypto/ecies/interface.ts"() {
"use strict";
init_constants();
ECIESError = class extends Error {
constructor(message, code, cause) {
super(message);
this.code = code;
this.cause = cause;
this.name = "ECIESError";
}
code;
cause;
};
}
});
// src/crypto/ecies/utils.ts
function constantTimeEqual(a, b) {
if (a.length !== b.length) return false;
let result = 0;
for (let i = 0; i < a.length; i++) {
result |= a[i] ^ b[i];
}
return result === 0;
}
var init_utils = __esm({
"src/crypto/ecies/utils.ts"() {
"use strict";
}
});
// src/crypto/ecies/base.ts
import { concat as concat2 } from "viem";
var BaseECIESUint8;
var init_base = __esm({
"src/crypto/ecies/base.ts"() {
"use strict";
init_interface();
init_constants();
init_utils();
BaseECIESUint8 = class _BaseECIESUint8 {
// Cache for validated public keys to avoid repeated validation
static validatedKeys = /* @__PURE__ */ new WeakMap();
/**
* Normalizes a public key to uncompressed format.
*
* @param publicKey - Public key in any format.
* @returns Uncompressed public key (65 bytes).
* @throws {ECIESError} If key format is invalid.
*/
normalizePublicKey(publicKey) {
if (_BaseECIESUint8.validatedKeys.get(publicKey)) {
return publicKey;
}
if (publicKey.length === CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH) {
if (publicKey[0] !== CURVE.PREFIX.UNCOMPRESSED) {
throw new ECIESError(
"Invalid uncompressed public key prefix",
"INVALID_KEY"
);
}
if (!this.validatePublicKey(publicKey)) {
throw new ECIESError("Invalid public key", "INVALID_KEY");
}
_BaseECIESUint8.validatedKeys.set(publicKey, true);
return publicKey;
}
if (publicKey.length === CURVE.COMPRESSED_PUBLIC_KEY_LENGTH) {
if (publicKey[0] === CURVE.PREFIX.COMPRESSED_EVEN || publicKey[0] === CURVE.PREFIX.COMPRESSED_ODD) {
const decompressed = this.decompressPublicKey(publicKey);
if (!decompressed) {
throw new ECIESError(
"Failed to decompress public key",
"INVALID_KEY"
);
}
_BaseECIESUint8.validatedKeys.set(decompressed, true);
return decompressed;
}
throw new ECIESError(
`Invalid compressed public key prefix: expected 0x02 or 0x03, got 0x${publicKey[0].toString(16).padStart(2, "0")}`,
"INVALID_KEY"
);
}
throw new ECIESError(
`Invalid public key length: ${publicKey.length}`,
"INVALID_KEY"
);
}
/**
* Encrypts data using ECIES.
*
* @param publicKey - The recipient's public key (compressed or uncompressed)
* @param message - The data to encrypt
* @returns Promise resolving to encrypted data structure
*/
async encrypt(publicKey, message) {
let ephemeralPrivateKey;
let sharedSecret;
let kdf;
let encryptionKey;
let macKey;
try {
if (!(publicKey instanceof Uint8Array)) {
throw new ECIESError("Public key must be a Uint8Array", "INVALID_KEY");
}
if (!(message instanceof Uint8Array)) {
throw new ECIESError(
"Message must be a Uint8Array",
"ENCRYPTION_FAILED"
);
}
if (publicKey.length === 0) {
throw new ECIESError("Public key cannot be empty", "INVALID_KEY");
}
const pubKey = this.normalizePublicKey(publicKey);
do {
ephemeralPrivateKey = this.generateRandomBytes(
CURVE.PRIVATE_KEY_LENGTH
);
} while (!this.verifyPrivateKey(ephemeralPrivateKey));
const ephemeralPublicKey = this.createPublicKey(
ephemeralPrivateKey,
false
);
if (!ephemeralPublicKey) {
throw new ECIESError(
"Failed to generate ephemeral public key",
"ENCRYPTION_FAILED"
);
}
sharedSecret = this.performECDH(pubKey, ephemeralPrivateKey);
kdf = this.sha512(sharedSecret);
encryptionKey = kdf.slice(
KDF.ENCRYPTION_KEY_OFFSET,
KDF.ENCRYPTION_KEY_OFFSET + KDF.ENCRYPTION_KEY_LENGTH
);
macKey = kdf.slice(
KDF.MAC_KEY_OFFSET,
KDF.MAC_KEY_OFFSET + KDF.MAC_KEY_LENGTH
);
const iv = this.generateRandomBytes(CIPHER.IV_LENGTH);
const ciphertext = await this.aesEncrypt(encryptionKey, iv, message);
const macData = concat2([iv, ephemeralPublicKey, ciphertext]);
const mac = this.hmacSha256(macKey, macData);
return {
iv,
ephemPublicKey: ephemeralPublicKey,
ciphertext,
mac
};
} catch (error) {
if (error instanceof ECIESError) throw error;
throw new ECIESError(
`Encryption failed: ${error instanceof Error ? error.message : "Unknown error"}`,
"ENCRYPTION_FAILED",
error instanceof Error ? error : void 0
);
} finally {
if (ephemeralPrivateKey) this.clearBuffer(ephemeralPrivateKey);
if (sharedSecret) this.clearBuffer(sharedSecret);
if (kdf) this.clearBuffer(kdf);
if (encryptionKey) this.clearBuffer(encryptionKey);
if (macKey) this.clearBuffer(macKey);
}
}
/**
* Decrypts ECIES encrypted data.
*
* @param privateKey - The recipient's private key (32 bytes)
* @param encrypted - The encrypted data structure from encrypt()
* @returns Promise resolving to the original plaintext
*/
async decrypt(privateKey, encrypted) {
let sharedSecret;
let kdf;
let encryptionKey;
let macKey;
try {
if (!(privateKey instanceof Uint8Array)) {
throw new ECIESError("Private key must be a Uint8Array", "INVALID_KEY");
}
if (!isECIESEncrypted(encrypted)) {
throw new ECIESError(
"Invalid encrypted data structure",
"DECRYPTION_FAILED"
);
}
if (privateKey.length !== CURVE.PRIVATE_KEY_LENGTH) {
throw new ECIESError(
`Invalid private key length: ${privateKey.length}`,
"INVALID_KEY"
);
}
if (!this.verifyPrivateKey(privateKey)) {
throw new ECIESError("Invalid private key", "INVALID_KEY");
}
if (encrypted.ephemPublicKey.length !== CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH) {
throw new ECIESError(
`Invalid ephemeral public key: expected ${CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH} bytes (uncompressed), got ${encrypted.ephemPublicKey.length} bytes`,
"INVALID_KEY"
);
}
if (encrypted.ephemPublicKey[0] !== CURVE.PREFIX.UNCOMPRESSED) {
throw new ECIESError(
"Invalid ephemeral public key: must be uncompressed format with 0x04 prefix (eccrypto standard)",
"INVALID_KEY"
);
}
if (!this.validatePublicKey(encrypted.ephemPublicKey)) {
throw new ECIESError("Invalid ephemeral public key", "INVALID_KEY");
}
const ephemeralPublicKey = encrypted.ephemPublicKey;
sharedSecret = this.performECDH(ephemeralPublicKey, privateKey);
kdf = this.sha512(sharedSecret);
encryptionKey = kdf.slice(
KDF.ENCRYPTION_KEY_OFFSET,
KDF.ENCRYPTION_KEY_OFFSET + KDF.ENCRYPTION_KEY_LENGTH
);
macKey = kdf.slice(
KDF.MAC_KEY_OFFSET,
KDF.MAC_KEY_OFFSET + KDF.MAC_KEY_LENGTH
);
const macData = concat2([
encrypted.iv,
encrypted.ephemPublicKey,
encrypted.ciphertext
]);
const expectedMac = this.hmacSha256(macKey, macData);
if (!constantTimeEqual(encrypted.mac, expectedMac)) {
throw new ECIESError("MAC verification failed", "MAC_MISMATCH");
}
const decrypted = await this.aesDecrypt(
encryptionKey,
encrypted.iv,
encrypted.ciphertext
);
return decrypted;
} catch (error) {
if (error instanceof ECIESError) throw error;
throw new ECIESError(
`Decryption failed: ${error instanceof Error ? error.message : "Unknown error"}`,
"DECRYPTION_FAILED",
error instanceof Error ? error : void 0
);
} finally {
if (sharedSecret) this.clearBuffer(sharedSecret);
if (kdf) this.clearBuffer(kdf);
if (encryptionKey) this.clearBuffer(encryptionKey);
if (macKey) this.clearBuffer(macKey);
}
}
/**
* Clears sensitive data from memory using multi-pass overwrite.
*
* @remarks
* Uses multiple passes with different patterns to make it harder
* for JIT compilers to optimize away the operation. While not
* guaranteed in JavaScript, this is a best-effort approach to
* clear sensitive data from memory.
*
* @param buffer - The buffer to clear
*/
clearBuffer(buffer) {
if (buffer && buffer.length > 0) {
buffer.fill(0);
buffer.fill(255);
buffer.fill(170);
buffer.fill(0);
for (let i = 0; i < buffer.length; i++) {
buffer[i] = i & 255 ^ 90;
}
buffer.fill(0);
}
}
};
}
});
// src/crypto/ecies/browser.ts
import * as secp256k12 from "@noble/secp256k1";
import { toHex as toHex5 } from "viem";
import { hmac as hmac2 } from "@noble/hashes/hmac";
import { sha256 as sha2563, sha512 as nobleSha512 } from "@noble/hashes/sha2";
var BrowserECIESUint8Provider;
var init_browser = __esm({
"src/crypto/ecies/browser.ts"() {
"use strict";
init_base();
BrowserECIESUint8Provider = class extends BaseECIESUint8 {
generateRandomBytes(length) {
const bytes = new Uint8Array(length);
crypto.getRandomValues(bytes);
return bytes;
}
verifyPrivateKey(privateKey) {
try {
return secp256k12.utils.isValidPrivateKey(privateKey);
} catch {
return false;
}
}
createPublicKey(privateKey, compressed) {
try {
return secp256k12.getPublicKey(privateKey, compressed);
} catch {
return null;
}
}
validatePublicKey(publicKey) {
try {
secp256k12.Point.fromHex(publicKey);
return true;
} catch {
return false;
}
}
decompressPublicKey(publicKey) {
try {
const point = secp256k12.Point.fromHex(publicKey);
return point.toRawBytes(false);
} catch {
return null;
}
}
performECDH(publicKey, privateKey) {
try {
const sharedPoint = secp256k12.getSharedSecret(
privateKey,
publicKey,
true
);
return sharedPoint.slice(1);
} catch (error) {
throw new Error(
`ECDH failed: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
sha512(data) {
return nobleSha512(data);
}
hmacSha256(key, data) {
return hmac2(sha2563, key, data);
}
async aesEncrypt(key, iv, plaintext) {
const cryptoKey = await crypto.subtle.importKey(
"raw",
key,
{ name: "AES-CBC" },
false,
["encrypt"]
);
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-CBC", iv },
cryptoKey,
plaintext
);
return new Uint8Array(encrypted);
}
async aesDecrypt(key, iv, ciphertext) {
const cryptoKey = await crypto.subtle.importKey(
"raw",
key,
{ name: "AES-CBC" },
false,
["decrypt"]
);
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-CBC", iv },
cryptoKey,
ciphertext
);
return new Uint8Array(decrypted);
}
/**
* Normalizes a public key to uncompressed format (65 bytes with 0x04 prefix).
* Handles compressed (33 bytes) and uncompressed (65 bytes) formats only.
*
* @remarks
* Strict policy: Does not accept 64-byte raw coordinates to avoid masking
* malformed data. Callers must provide properly formatted keys.
*
* @param publicKey - The public key to normalize (33 or 65 bytes)
* @returns The normalized uncompressed public key (65 bytes)
* @throws {Error} When public key format is invalid or decompression fails
*/
normalizeToUncompressed(publicKey) {
const len = publicKey.length;
if (len === 65 && publicKey[0] === 4) {
return publicKey;
}
if (len === 33 && (publicKey[0] === 2 || publicKey[0] === 3)) {
const decompressed = this.decompressPublicKey(publicKey);
if (!decompressed) {
throw new Error(
`Failed to decompress public key with prefix ${toHex5(publicKey[0])}`
);
}
return decompressed;
}
if (len === 64) {
throw new Error(
"Raw public key coordinates (64 bytes) are not accepted. Please provide a properly formatted compressed (33 bytes) or uncompressed (65 bytes) public key."
);
}
throw new Error(
`Invalid public key format: expected compressed (33 bytes) or uncompressed (65 bytes), got ${len} bytes`
);
}
};
}
});
// src/platform/browser.ts
var browser_exports = {};
__export(browser_exports, {
BrowserPlatformAdapter: () => BrowserPlatformAdapter
});
import { toHex as toHex6, fromHex as fromHex4, stringToBytes as stringToBytes2, bytesToString as bytesToString2, concat as concat3 } from "viem";
import * as secp256k13 from "@noble/secp256k1";
var getOpenPGP2, BrowserCryptoAdapter, BrowserPGPAdapter, BrowserHttpAdapter, BrowserCacheAdapter, BrowserPlatformAdapter;
var init_browser2 = __esm({
"src/platform/browser.ts"() {
"use strict";
init_pgp_utils();
init_error_utils();
init_lazy_import();
init_WalletKeyEncryptionService();
init_crypto_utils();
init_browser();
getOpenPGP2 = lazyImport(() => import("openpgp"));
BrowserCryptoAdapter = class {
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 = fromHex4(prefixedHex, "bytes");
const encrypted = await this.eciesProvider.encrypt(
publicKeyBytes,
stringToBytes2(data)
);
const result = concat3([
encrypted.iv,
encrypted.ephemPublicKey,
encrypted.ciphertext,
encrypted.mac
]);
return toHex6(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 = fromHex4(encryptedHex, "bytes");
const privateKeyBytes = fromHex4(privateHex, "bytes");
const encrypted = parseEncryptedDataBuffer(encryptedBytes);
const decrypted = await this.eciesProvider.decrypt(
privateKeyBytes,
encrypted
);
return bytesToString2(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 = secp256k13.utils.randomPrivateKey();
const publicKeyBytes = secp256k13.getPublicKey(privateKeyBytes, true);
return {
privateKey: toHex6(privateKeyBytes).slice(2),
publicKey: toHex6(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 openpgp2 = await getOpenPGP2();
const message = await openpgp2.createMessage({ binary: data });
const encrypted = await openpgp2.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 openpgp2 = await getOpenPGP2();
const message = await openpgp2.readMessage({
binaryMessage: encryptedData
});
const { data } = await openpgp2.decrypt({
message,
passwords: [password],
format: "binary"
});
return new Uint8Array(data);
} catch (error) {
throw wrapCryptoError("decryptWithPassword", error);
}
}
};
BrowserPGPAdapter = class {
/**
* 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 openpgp2 = await getOpenPGP2();
const publicKey = await openpgp2.readKey({ armoredKey: publicKeyArmored });
const encrypted = await openpgp2.encrypt({
message: await openpgp2.createMessage({ text: data }),
encryptionKeys: publicKey,
config: {
preferredCompressionAlgorithm: openpgp2.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 openpgp2 = await getOpenPGP2();
const privateKey = await openpgp2.readPrivateKey({
armoredKey: privateKeyArmored
});
const message = await openpgp2.readMessage({
armoredMessage: encryptedData
});
const { data: decrypted } = await openpgp2.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 openpgp2 = await getOpenPGP2();
const keyGenParams = getPGPKeyGenParams(options);
const { privateKey, publicKey } = await openpgp2.generateKey(keyGenParams);
return { publicKey, privateKey };
} catch (error) {
throw wrapCryptoError("PGP key generation", error);
}
}
};
BrowserHttpAdapter = class {
/**
* 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);
}
};
BrowserCacheAdapter = class {
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 {
}
}
};
BrowserPlatformAdapter = class {
crypto = new BrowserCryptoAdapter();
pgp = new BrowserPGPAdapter();
http = new BrowserHttpAdapter();
cache = new BrowserCacheAdapter();
platform = "browser";
};
}
});
// src/errors.ts
var VanaError = class extends Error {
constructor(message, code) {
super(message);
this.code = code;
this.name = this.constructor.name;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
code;
};
var RelayerError = class extends VanaError {
constructor(message, statusCode, response) {
super(message, "RELAYER_ERROR");
this.statusCode = statusCode;
this.response = response;
}
statusCode;
response;
};
var UserRejectedRequestError = class extends VanaError {
constructor(message = "User rejected the signature request") {
super(message, "USER_REJECTED_REQUEST");
}
};
var InvalidConfigurationError = class extends VanaError {
constructor(message) {
super(message, "INVALID_CONFIGURATION");
}
};
var ContractNotFoundError = class extends VanaError {
constructor(contractName, chainId) {
super(
`Contract ${contractName} not found on chain ${chainId}`,
"CONTRACT_NOT_FOUND"
);
}
};
var BlockchainError = class extends VanaError {
constructor(message, originalError) {
super(message, "BLOCKCHAIN_ERROR");
this.originalError = originalError;
}
originalError;
};
var SerializationError = class extends VanaError {
constructor(message) {
super(message, "SERIALIZATION_ERROR");
}
};
var SignatureError = class extends VanaError {
constructor(message, originalError) {
super(message, "SIGNATURE_ERROR");
this.originalError = originalError;
}
originalError;
};
var NetworkError = class extends VanaError {
constructor(message, originalError) {
super(message, "NETWORK_ERROR");
this.originalError = originalError;
}
originalError;
};
var NonceError = class extends VanaError {
constructor(message) {
super(message, "NONCE_ERROR");
}
};
var PersonalServerError = class extends VanaError {
constructor(message, originalError) {
super(message, "PERSONAL_SERVER_ERROR");
this.originalError = originalError;
}
originalError;
};
var ServerUrlMismatchError = class extends VanaError {
constructor(existingUrl, providedUrl, serverId) {
super(
`Server ${serverId} is already registered with URL "${existingUrl}". Cannot change to "${providedUrl}".`,
"SERVER_URL_MISMATCH"
);
this.existingUrl = existingUrl;
this.providedUrl = providedUrl;
this.serverId = serverId;
}
existingUrl;
providedUrl;
serverId;
};
var PermissionError = class extends VanaError {
constructor(message, originalError) {
super(message, "PERMISSION_ERROR");
this.originalError = originalError;
}
originalError;
};
var ReadOnlyError = class extends VanaError {
constructor(operation, suggestion = "Initialize the SDK with a walletClient to perform this operation") {
super(
`Operation '${operation}' requires a wallet client. ${suggestion}`,
"READ_ONLY_ERROR"
);
this.operation = operation;
this.suggestion = suggestion;
}
/** The operation that was attempted */
operation;
/** Suggested solution for fixing the error */
suggestion;
};
var TransactionPendingError = class extends VanaError {
constructor(operationId, message, lastKnownStatus) {
super(
`Transaction operation pending: ${message} (operationId: ${operationId})`,
"TRANSACTION_PENDING"
);
this.operationId = operationId;
this.lastKnownStatus = lastKnownStatus;
}
operationId;
lastKnownStatus;
/**
* Converts the error to a JSON-serializable format.
*
* @remarks
* Useful for logging, storage, or transmission of error details.
*
* @returns JSON representation of the error
*/
toJSON() {
return {
name: this.name,
code: this.code,
message: this.message,
operationId: this.operationId,
lastKnownStatus: this.lastKnownStatus
};
}
};
// src/contracts/contractController.ts
import {
getContract
} from "viem";
// src/generated/abi/ComputeEngineImplementation.ts
var ComputeEngineABI = [
{
inputs: [],
stateMutability: "nonpayable",
type: "constructor"
},
{
inputs: [],
name: "AccessControlBadConfirmation",
type: "error"
},
{
inputs: [
{
internalType: "address",
name: "account",
type: "address"
},
{
internalType: "bytes32",
name: "neededRole",
type: "bytes32"
}
],
name: "AccessControlUnauthorizedAccount",
type: "error"
},
{
inputs: [
{
internalType: "address",
name: "target",
type: "address"
}
],
name: "AddressEmptyCode",
type: "error"
},
{
inputs: [
{
internalType: "address",
name: "account",
type: "address"
}
],
name: "AddressInsufficientBalance",
type: "error"
},
{
inputs: [
{
internalType: "address",
name: "implementation",
type: "address"
}
],
name: "ERC1967InvalidImplementation",
type: "error"
},
{
inputs: [],
name: "ERC1967NonPayable",
type: "error"
},
{
inputs: [],
name: "EnforcedPause",
type: "error"
},
{
inputs: [],
name: "ExpectedPause",
type: "error"
},
{
inputs: [],
name: "FailedInnerCall",
type: "error"
},
{
inputs: [],
name: "FailedToAssignTee",
type: "error"
},
{
inputs: [
{
internalType: "uint256",
name: "computeInstructionId",
type: "uint256"
}
],
name: "InstructionNotFound",
type: "error"
},
{
inputs: [],
name: "InsufficientBalance",
type: "error"
},
{
inputs: [],
name: "InvalidAmount",
type: "error"
},
{
inputs: [],
name: "InvalidInitialization",
type: "error"
},
{
inputs: [
{
internalType: "enum IComputeEngine.JobStatus",
name: "currentStatus",
type: "uint8"
},
{
internalType: "enum IComputeEngine.JobStatus",
name: "newStatus",
type: "uint8"
}
],
name: "InvalidStatusTransition",
type: "error"
},
{
inputs: [],
name: "InvalidVanaAmount",
type: "error"
},
{
inputs: [],
name: "JobAlreadyDone",
type: "error"
},
{
inputs: [],
name: "JobLifeCycleEnded",
type: "error"
},
{
inputs: [
{
internalType: "uint256",
name: "jobId",
type: "uint256"
}
],
name: "JobNotFound",
type: "error"
},
{
inputs: [
{
internalType: "uint256",
name: "jobId",
type: "uint256"
}
],
name: "JobNotSubmitted",
type: "error"
},
{
inputs: [],
name: "NotInitializing",
type: "error"
},
{
inputs: [],
name: "NotJobOwner",
type: "error"
},
{
inputs: [],
name: "NotLongRunningJob",
type: "error"
},
{
inputs: [],
name: "NotQueryEngine",
type: "error"
},
{
inputs: [],
name: "NotTee",
type: "error"
},
{
inputs: [],
name: "OnlyRegisteredJobStatus",
type: "error"
},
{
inputs: [],
name: "ReentrancyGuardReentrantCall",
type: "error"
},
{
inputs: [
{
internalType: "address",
name: "token",
type: "address"
}
],
name: "SafeERC20FailedOperation",
type: "error"
},
{
inputs: [
{
internalType: "uint256",
name: "jobId",
type: "uint256"
}
],
name: "TeeAlreadyAssigned",
type: "error"
},
{
inputs: [],
name: "TeePoolNotFound",
type: "error"
},
{
inputs: [],
name: "UUPSUnauthorizedCallContext",
type: "error"
},
{
inputs: [
{
internalType: "bytes32",
name: "slot",
type: "bytes32"
}
],
name: "UUPSUnsupportedProxiableUUID",
type: "error"
},
{
inputs: [],
name: "UnauthorizedPaymentRequestor",
type: "error"
},
{
inputs: [],
name: "UnexpectedVanaDeposit",
type: "error"
},
{
inputs: [],
name: "ZeroAddress",
type: "error"
},
{
inputs: [],
name: "ZeroTeeAddress",
type: "error"
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "account",
type: "address"
},
{
indexed: true,
internalType: "address",
name: "token",
type: "address"
},
{
indexed: false,
internalType: "uint256",
name: "amount",
type: "uint256"
}
],
name: "Deposit",
type: "event"
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: "uint64",
name: "version",
type: "uint64"
}
],
name: "Initialized",
type: "event"
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "uint256",
name: "jobId",
type: "uint256"
}
],
name: "JobCanceled",
type: "event"
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "uint256",
name: "jobId",
type: "uint256"
},
{
indexed: true,
internalType: "address",
name: "ownerAddress",
type: "address"
}
],
name: "JobRegistered",
type: "event"
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "uint256",
name: "jobId",
type: "uint256"
},
{
indexed: false,
internalType: "enum IComputeEngi