@opendatalabs/vana-sdk
Version:
A TypeScript library for interacting with Vana Network smart contracts.
268 lines • 10.2 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var base_exports = {};
__export(base_exports, {
BaseECIESUint8: () => BaseECIESUint8
});
module.exports = __toCommonJS(base_exports);
var import_interface = require("./interface");
var import_constants = require("./constants");
var import_utils = require("./utils");
var import_viem = require("viem");
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 === import_constants.CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH) {
if (publicKey[0] !== import_constants.CURVE.PREFIX.UNCOMPRESSED) {
throw new import_interface.ECIESError(
"Invalid uncompressed public key prefix",
"INVALID_KEY"
);
}
if (!this.validatePublicKey(publicKey)) {
throw new import_interface.ECIESError("Invalid public key", "INVALID_KEY");
}
BaseECIESUint8.validatedKeys.set(publicKey, true);
return publicKey;
}
if (publicKey.length === import_constants.CURVE.COMPRESSED_PUBLIC_KEY_LENGTH) {
if (publicKey[0] === import_constants.CURVE.PREFIX.COMPRESSED_EVEN || publicKey[0] === import_constants.CURVE.PREFIX.COMPRESSED_ODD) {
const decompressed = this.decompressPublicKey(publicKey);
if (!decompressed) {
throw new import_interface.ECIESError(
"Failed to decompress public key",
"INVALID_KEY"
);
}
BaseECIESUint8.validatedKeys.set(decompressed, true);
return decompressed;
}
throw new import_interface.ECIESError(
`Invalid compressed public key prefix: expected 0x02 or 0x03, got 0x${publicKey[0].toString(16).padStart(2, "0")}`,
"INVALID_KEY"
);
}
throw new import_interface.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 import_interface.ECIESError("Public key must be a Uint8Array", "INVALID_KEY");
}
if (!(message instanceof Uint8Array)) {
throw new import_interface.ECIESError(
"Message must be a Uint8Array",
"ENCRYPTION_FAILED"
);
}
if (publicKey.length === 0) {
throw new import_interface.ECIESError("Public key cannot be empty", "INVALID_KEY");
}
const pubKey = this.normalizePublicKey(publicKey);
do {
ephemeralPrivateKey = this.generateRandomBytes(
import_constants.CURVE.PRIVATE_KEY_LENGTH
);
} while (!this.verifyPrivateKey(ephemeralPrivateKey));
const ephemeralPublicKey = this.createPublicKey(
ephemeralPrivateKey,
false
);
if (!ephemeralPublicKey) {
throw new import_interface.ECIESError(
"Failed to generate ephemeral public key",
"ENCRYPTION_FAILED"
);
}
sharedSecret = this.performECDH(pubKey, ephemeralPrivateKey);
kdf = this.sha512(sharedSecret);
encryptionKey = kdf.slice(
import_constants.KDF.ENCRYPTION_KEY_OFFSET,
import_constants.KDF.ENCRYPTION_KEY_OFFSET + import_constants.KDF.ENCRYPTION_KEY_LENGTH
);
macKey = kdf.slice(
import_constants.KDF.MAC_KEY_OFFSET,
import_constants.KDF.MAC_KEY_OFFSET + import_constants.KDF.MAC_KEY_LENGTH
);
const iv = this.generateRandomBytes(import_constants.CIPHER.IV_LENGTH);
const ciphertext = await this.aesEncrypt(encryptionKey, iv, message);
const macData = (0, import_viem.concat)([iv, ephemeralPublicKey, ciphertext]);
const mac = this.hmacSha256(macKey, macData);
return {
iv,
ephemPublicKey: ephemeralPublicKey,
ciphertext,
mac
};
} catch (error) {
if (error instanceof import_interface.ECIESError) throw error;
throw new import_interface.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 import_interface.ECIESError("Private key must be a Uint8Array", "INVALID_KEY");
}
if (!(0, import_interface.isECIESEncrypted)(encrypted)) {
throw new import_interface.ECIESError(
"Invalid encrypted data structure",
"DECRYPTION_FAILED"
);
}
if (privateKey.length !== import_constants.CURVE.PRIVATE_KEY_LENGTH) {
throw new import_interface.ECIESError(
`Invalid private key length: ${privateKey.length}`,
"INVALID_KEY"
);
}
if (!this.verifyPrivateKey(privateKey)) {
throw new import_interface.ECIESError("Invalid private key", "INVALID_KEY");
}
if (encrypted.ephemPublicKey.length !== import_constants.CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH) {
throw new import_interface.ECIESError(
`Invalid ephemeral public key: expected ${import_constants.CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH} bytes (uncompressed), got ${encrypted.ephemPublicKey.length} bytes`,
"INVALID_KEY"
);
}
if (encrypted.ephemPublicKey[0] !== import_constants.CURVE.PREFIX.UNCOMPRESSED) {
throw new import_interface.ECIESError(
"Invalid ephemeral public key: must be uncompressed format with 0x04 prefix (eccrypto standard)",
"INVALID_KEY"
);
}
if (!this.validatePublicKey(encrypted.ephemPublicKey)) {
throw new import_interface.ECIESError("Invalid ephemeral public key", "INVALID_KEY");
}
const ephemeralPublicKey = encrypted.ephemPublicKey;
sharedSecret = this.performECDH(ephemeralPublicKey, privateKey);
kdf = this.sha512(sharedSecret);
encryptionKey = kdf.slice(
import_constants.KDF.ENCRYPTION_KEY_OFFSET,
import_constants.KDF.ENCRYPTION_KEY_OFFSET + import_constants.KDF.ENCRYPTION_KEY_LENGTH
);
macKey = kdf.slice(
import_constants.KDF.MAC_KEY_OFFSET,
import_constants.KDF.MAC_KEY_OFFSET + import_constants.KDF.MAC_KEY_LENGTH
);
const macData = (0, import_viem.concat)([
encrypted.iv,
encrypted.ephemPublicKey,
encrypted.ciphertext
]);
const expectedMac = this.hmacSha256(macKey, macData);
if (!(0, import_utils.constantTimeEqual)(encrypted.mac, expectedMac)) {
throw new import_interface.ECIESError("MAC verification failed", "MAC_MISMATCH");
}
const decrypted = await this.aesDecrypt(
encryptionKey,
encrypted.iv,
encrypted.ciphertext
);
return decrypted;
} catch (error) {
if (error instanceof import_interface.ECIESError) throw error;
throw new import_interface.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);
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BaseECIESUint8
});
//# sourceMappingURL=base.cjs.map