UNPKG

@opendatalabs/vana-sdk

Version:

A TypeScript library for interacting with Vana Network smart contracts.

1 lines 11.4 kB
{"version":3,"sources":["../../../src/crypto/ecies/interface.ts"],"sourcesContent":["/**\n * ECIES (Elliptic Curve Integrated Encryption Scheme) Interface\n *\n * @remarks\n * Defines the contract for platform-specific ECIES implementations.\n * All implementations maintain compatibility with the eccrypto format to ensure\n * backward compatibility with existing encrypted data.\n *\n * **Format specification:**\n * `[iv (16 bytes)][ephemPublicKey (65 bytes)][ciphertext (variable)][mac (32 bytes)]`\n *\n * @category Cryptography\n */\n\nimport { CIPHER, CURVE, MAC, FORMAT } from \"./constants\";\nimport { fromHex, toHex } from \"viem\";\n\n/**\n * Represents ECIES encrypted data in eccrypto-compatible format.\n *\n * @remarks\n * This structure maintains backward compatibility with data encrypted using\n * the legacy eccrypto library.\n */\nexport interface ECIESEncrypted {\n /** Initialization vector (16 bytes) */\n iv: Uint8Array;\n /** Ephemeral public key (65 bytes uncompressed) */\n ephemPublicKey: Uint8Array;\n /** Encrypted data */\n ciphertext: Uint8Array;\n /** Message authentication code (32 bytes) */\n mac: Uint8Array;\n}\n\n/**\n * Provides ECIES encryption and decryption operations.\n *\n * @remarks\n * Platform-specific implementations handle the underlying cryptographic primitives\n * while maintaining consistent data format across environments.\n *\n * @category Cryptography\n */\nexport interface ECIESProvider {\n /**\n * Encrypts data using ECIES with secp256k1.\n *\n * @param publicKey - Recipient's public key (65 bytes uncompressed or 33 bytes compressed).\n * Obtain via `vana.server.getIdentity(userAddress).public_key`.\n * @param message - Data to encrypt.\n * @returns Encrypted data structure compatible with eccrypto format.\n * @throws {ECIESError} When public key is invalid.\n * Verify key format matches secp256k1 requirements.\n *\n * @example\n * ```typescript\n * const encrypted = await provider.encrypt(\n * fromHex(publicKey, 'bytes'),\n * new TextEncoder().encode('sensitive data')\n * );\n * ```\n */\n encrypt(publicKey: Uint8Array, message: Uint8Array): Promise<ECIESEncrypted>;\n\n /**\n * Decrypts ECIES encrypted data.\n *\n * @param privateKey - Recipient's private key (32 bytes).\n * @param encrypted - Encrypted data structure from `encrypt()` or legacy eccrypto.\n * @returns Decrypted message as Uint8Array.\n * @throws {ECIESError} When MAC verification fails.\n * Ensure the private key matches the public key used for encryption.\n *\n * @example\n * ```typescript\n * const decrypted = await provider.decrypt(\n * fromHex(privateKey, 'bytes'),\n * encrypted\n * );\n * const message = new TextDecoder().decode(decrypted);\n * ```\n */\n decrypt(\n privateKey: Uint8Array,\n encrypted: ECIESEncrypted,\n ): Promise<Uint8Array>;\n\n /**\n * Normalizes a public key to uncompressed format (65 bytes with 0x04 prefix).\n *\n * @remarks\n * Strict policy: Only accepts properly formatted compressed (33 bytes) or\n * uncompressed (65 bytes) public keys. Does not accept 64-byte raw coordinates\n * to ensure data integrity and prevent masking of malformed inputs.\n *\n * @param publicKey - Public key in compressed or uncompressed format\n * @returns Normalized uncompressed public key (65 bytes with 0x04 prefix)\n * @throws {Error} When public key format is invalid, including raw coordinates (64 bytes)\n * @throws {Error} When decompression of compressed key fails\n *\n * @example\n * ```typescript\n * // Compressed key (33 bytes)\n * const compressed = new Uint8Array(33);\n * compressed[0] = 0x02;\n * const uncompressed = provider.normalizeToUncompressed(compressed);\n * console.log(uncompressed.length); // 65\n * console.log(uncompressed[0]); // 0x04\n *\n * // Already uncompressed (65 bytes)\n * const already = provider.normalizeToUncompressed(uncompressedKey);\n * console.log(already === uncompressedKey); // true (returns same reference)\n *\n * // Raw coordinates rejected (64 bytes)\n * const raw = new Uint8Array(64);\n * provider.normalizeToUncompressed(raw); // Throws error\n * ```\n */\n normalizeToUncompressed(publicKey: Uint8Array): Uint8Array;\n}\n\n/**\n * Configures ECIES operation behavior.\n */\nexport interface ECIESOptions {\n /** Use compressed public keys (33 bytes) instead of uncompressed (65 bytes) */\n useCompressed?: boolean;\n}\n\n/**\n * Represents failures in ECIES cryptographic operations.\n *\n * @remarks\n * Provides specific error codes to help identify and recover from\n * different failure scenarios.\n *\n * @category Errors\n */\nexport class ECIESError extends Error {\n constructor(\n message: string,\n public readonly code:\n | \"INVALID_KEY\"\n | \"ENCRYPTION_FAILED\"\n | \"DECRYPTION_FAILED\"\n | \"MAC_MISMATCH\"\n | \"ECDH_FAILED\",\n public override readonly cause?: Error,\n ) {\n super(message);\n this.name = \"ECIESError\";\n }\n}\n\n/**\n * Validates if an object conforms to the ECIESEncrypted structure.\n *\n * @param obj - Object to validate.\n * @returns `true` if object is a valid ECIESEncrypted structure.\n *\n * @example\n * ```typescript\n * if (isECIESEncrypted(data)) {\n * const decrypted = await provider.decrypt(privateKey, data);\n * }\n * ```\n */\nexport function isECIESEncrypted(obj: unknown): obj is ECIESEncrypted {\n if (!obj || typeof obj !== \"object\") return false;\n const enc = obj as Record<string, unknown>;\n\n const isUint8Array = (value: unknown): value is Uint8Array => {\n return (\n value instanceof Uint8Array ||\n (typeof Buffer !== \"undefined\" && Buffer.isBuffer(value))\n );\n };\n\n return (\n isUint8Array(enc.iv) &&\n enc.iv.length === CIPHER.IV_LENGTH &&\n isUint8Array(enc.ephemPublicKey) &&\n (enc.ephemPublicKey.length === CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH ||\n enc.ephemPublicKey.length === CURVE.COMPRESSED_PUBLIC_KEY_LENGTH) &&\n isUint8Array(enc.ciphertext) &&\n enc.ciphertext.length > 0 &&\n isUint8Array(enc.mac) &&\n enc.mac.length === MAC.LENGTH\n );\n}\n\n/**\n * Serializes ECIESEncrypted to hex string for storage or transmission.\n *\n * @param encrypted - Encrypted data structure from `encrypt()`.\n * @returns Hex string representation.\n *\n * @example\n * ```typescript\n * const hexString = serializeECIES(encrypted);\n * // Store hexString in database or send over network\n * ```\n */\nexport function serializeECIES(encrypted: ECIESEncrypted): string {\n const combined = new Uint8Array(\n encrypted.iv.length +\n encrypted.ephemPublicKey.length +\n encrypted.ciphertext.length +\n encrypted.mac.length,\n );\n\n let offset = 0;\n combined.set(encrypted.iv, offset);\n offset += encrypted.iv.length;\n combined.set(encrypted.ephemPublicKey, offset);\n offset += encrypted.ephemPublicKey.length;\n combined.set(encrypted.ciphertext, offset);\n offset += encrypted.ciphertext.length;\n combined.set(encrypted.mac, offset);\n\n return toHex(combined).slice(2);\n}\n\n/**\n * Deserializes hex string to ECIESEncrypted structure.\n *\n * @param hex - Hex string from `serializeECIES()` or storage.\n * @returns ECIESEncrypted structure ready for decryption.\n * @throws {ECIESError} When hex string format is invalid.\n * Verify the hex string is complete and uncorrupted.\n *\n * @example\n * ```typescript\n * const encrypted = deserializeECIES(hexString);\n * const decrypted = await provider.decrypt(privateKey, encrypted);\n * ```\n */\nexport function deserializeECIES(hex: string): ECIESEncrypted {\n const hexWithPrefix = hex.startsWith(\"0x\") ? hex : `0x${hex}`;\n const bytes = fromHex(hexWithPrefix as `0x${string}`, \"bytes\");\n\n // Check minimum length before accessing prefix byte\n // Need at least: IV (16 bytes) + 1 byte for prefix check + MAC (32 bytes) + 1 byte ciphertext\n const absoluteMinLength = FORMAT.IV_LENGTH + 1 + MAC.LENGTH + 1;\n if (bytes.length < absoluteMinLength) {\n throw new ECIESError(\n `Invalid ECIES data: too short (${bytes.length} bytes, minimum ${absoluteMinLength} bytes required)`,\n \"DECRYPTION_FAILED\",\n );\n }\n\n // Validate ephemeral public key prefix (must be uncompressed for eccrypto compatibility)\n const prefix = bytes[FORMAT.EPHEMERAL_KEY_OFFSET];\n\n if (prefix !== CURVE.PREFIX.UNCOMPRESSED) {\n throw new ECIESError(\n `Invalid ephemeral public key: must be uncompressed format (0x04 prefix), got 0x${prefix.toString(16).padStart(2, \"0\")}`,\n \"DECRYPTION_FAILED\",\n );\n }\n\n const ephemKeySize = CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH;\n\n const minLength = FORMAT.IV_LENGTH + ephemKeySize + MAC.LENGTH + 1; // +1 for at least 1 byte of ciphertext\n if (bytes.length < minLength) {\n throw new ECIESError(\n `Invalid ECIES data: too short (${bytes.length} bytes, minimum ${minLength} bytes required)`,\n \"DECRYPTION_FAILED\",\n );\n }\n\n return {\n iv: bytes.subarray(FORMAT.IV_OFFSET, FORMAT.IV_OFFSET + FORMAT.IV_LENGTH),\n ephemPublicKey: bytes.subarray(\n FORMAT.EPHEMERAL_KEY_OFFSET,\n FORMAT.EPHEMERAL_KEY_OFFSET + ephemKeySize,\n ),\n ciphertext: bytes.subarray(\n FORMAT.EPHEMERAL_KEY_OFFSET + ephemKeySize,\n bytes.length - MAC.LENGTH,\n ),\n mac: bytes.subarray(bytes.length - MAC.LENGTH),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,uBAA2C;AAC3C,kBAA+B;AA4HxB,MAAM,mBAAmB,MAAM;AAAA,EACpC,YACE,SACgB,MAMS,OACzB;AACA,UAAM,OAAO;AARG;AAMS;AAGzB,SAAK,OAAO;AAAA,EACd;AAAA,EAVkB;AAAA,EAMS;AAK7B;AAeO,SAAS,iBAAiB,KAAqC;AACpE,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,MAAM;AAEZ,QAAM,eAAe,CAAC,UAAwC;AAC5D,WACE,iBAAiB,cAChB,OAAO,WAAW,eAAe,OAAO,SAAS,KAAK;AAAA,EAE3D;AAEA,SACE,aAAa,IAAI,EAAE,KACnB,IAAI,GAAG,WAAW,wBAAO,aACzB,aAAa,IAAI,cAAc,MAC9B,IAAI,eAAe,WAAW,uBAAM,kCACnC,IAAI,eAAe,WAAW,uBAAM,iCACtC,aAAa,IAAI,UAAU,KAC3B,IAAI,WAAW,SAAS,KACxB,aAAa,IAAI,GAAG,KACpB,IAAI,IAAI,WAAW,qBAAI;AAE3B;AAcO,SAAS,eAAe,WAAmC;AAChE,QAAM,WAAW,IAAI;AAAA,IACnB,UAAU,GAAG,SACX,UAAU,eAAe,SACzB,UAAU,WAAW,SACrB,UAAU,IAAI;AAAA,EAClB;AAEA,MAAI,SAAS;AACb,WAAS,IAAI,UAAU,IAAI,MAAM;AACjC,YAAU,UAAU,GAAG;AACvB,WAAS,IAAI,UAAU,gBAAgB,MAAM;AAC7C,YAAU,UAAU,eAAe;AACnC,WAAS,IAAI,UAAU,YAAY,MAAM;AACzC,YAAU,UAAU,WAAW;AAC/B,WAAS,IAAI,UAAU,KAAK,MAAM;AAElC,aAAO,mBAAM,QAAQ,EAAE,MAAM,CAAC;AAChC;AAgBO,SAAS,iBAAiB,KAA6B;AAC5D,QAAM,gBAAgB,IAAI,WAAW,IAAI,IAAI,MAAM,KAAK,GAAG;AAC3D,QAAM,YAAQ,qBAAQ,eAAgC,OAAO;AAI7D,QAAM,oBAAoB,wBAAO,YAAY,IAAI,qBAAI,SAAS;AAC9D,MAAI,MAAM,SAAS,mBAAmB;AACpC,UAAM,IAAI;AAAA,MACR,kCAAkC,MAAM,MAAM,mBAAmB,iBAAiB;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,SAAS,MAAM,wBAAO,oBAAoB;AAEhD,MAAI,WAAW,uBAAM,OAAO,cAAc;AACxC,UAAM,IAAI;AAAA,MACR,kFAAkF,OAAO,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,MACtH;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,uBAAM;AAE3B,QAAM,YAAY,wBAAO,YAAY,eAAe,qBAAI,SAAS;AACjE,MAAI,MAAM,SAAS,WAAW;AAC5B,UAAM,IAAI;AAAA,MACR,kCAAkC,MAAM,MAAM,mBAAmB,SAAS;AAAA,MAC1E;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,MAAM,SAAS,wBAAO,WAAW,wBAAO,YAAY,wBAAO,SAAS;AAAA,IACxE,gBAAgB,MAAM;AAAA,MACpB,wBAAO;AAAA,MACP,wBAAO,uBAAuB;AAAA,IAChC;AAAA,IACA,YAAY,MAAM;AAAA,MAChB,wBAAO,uBAAuB;AAAA,MAC9B,MAAM,SAAS,qBAAI;AAAA,IACrB;AAAA,IACA,KAAK,MAAM,SAAS,MAAM,SAAS,qBAAI,MAAM;AAAA,EAC/C;AACF;","names":[]}