@coti-io/coti-sdk-typescript
Version:
A library for encryption, decryption and cryptographic utilities for the COTI blockchain.
426 lines (425 loc) • 20.1 kB
TypeScript
import { BaseWallet } from 'ethers';
import { BuildItUint256WithSignerParams, CtUint256Like, ctInt256, ctString, ctUint, ctUint256, itInt256, itString, itUint, itUint256, itUint256Signed, SerializableCtUint, SerializableCtUint256 } from './types';
/**
* Low-level AES block encryption used by the COTI garbled-circuit scheme.
*
* Encrypts up to 16 bytes of plaintext by XORing with AES-ECB(random `r`).
* Returns the 16-byte ciphertext block and the random `r` used (also 16 bytes).
*
* @param key - 128-bit AES key (16 bytes).
* @param plaintext - Up to 16 bytes; shorter inputs are zero-padded on the left.
* @returns The ciphertext block and random `r` (each 16 bytes).
* @throws RangeError if `plaintext` exceeds 16 bytes or `key` is not 16 bytes.
*/
export declare function encrypt(key: Uint8Array, plaintext: Uint8Array): {
ciphertext: Uint8Array;
r: Uint8Array;
};
/**
* Low-level AES block decryption. Inverts {@link encrypt}.
*
* Decrypts one 16-byte block. When `r2` and `ciphertext2` are provided, decrypts
* a second block and returns the 32-byte concatenation of both plaintext blocks.
*
* @param key - 128-bit AES key (16 bytes).
* @param r - 16-byte random value from encryption.
* @param ciphertext - 16-byte ciphertext block.
* @param r2 - Optional second-block random value (must pair with `ciphertext2`).
* @param ciphertext2 - Optional second-block ciphertext (must pair with `r2`).
* @returns 16-byte plaintext, or 32 bytes when a second block is supplied.
* @throws RangeError if any input has an invalid length or block pairs are mismatched.
*/
export declare function decrypt(key: Uint8Array, r: Uint8Array, ciphertext: Uint8Array, r2?: Uint8Array | null, ciphertext2?: Uint8Array | null): Uint8Array;
/**
* Generates a 2048-bit RSA key pair in DER encoding.
*
* @returns Public and private keys as `Uint8Array` DER blobs.
*/
export declare function generateRSAKeyPair(): {
publicKey: Uint8Array;
privateKey: Uint8Array;
};
/**
* Decrypts an RSA-OAEP (SHA-256) ciphertext with a DER-encoded private key.
*
* @param privateKey - RSA private key as a DER `Uint8Array`.
* @param ciphertext - Hex-encoded RSA ciphertext (no `0x` prefix required by forge).
* @returns Decrypted payload as a lowercase hex string.
* @throws Error if the key or ciphertext format is invalid.
*/
export declare function decryptRSA(privateKey: Uint8Array, ciphertext: string): string;
/**
* Recovers a user's AES key by XORing two RSA-decrypted key shares.
*
* Each share is RSA-OAEP encrypted; after decryption both shares are parsed as
* hex AES keys and XORed to produce the final 128-bit user key.
*
* @param privateKey - RSA private key used to decrypt both shares.
* @param encryptedKeyShare0 - Hex RSA ciphertext of the first share.
* @param encryptedKeyShare1 - Hex RSA ciphertext of the second share.
* @returns Recovered AES key as a 32-character lowercase hex string.
* @throws Error if RSA decryption or key parsing fails.
*/
export declare function recoverUserKey(privateKey: Uint8Array, encryptedKeyShare0: string, encryptedKeyShare1: string): string;
/**
* Signs a message hash with an Ethereum secp256k1 private key.
*
* @param message - 32-byte message digest as a hex string (e.g. from `solidityPackedKeccak256`).
* @param privateKey - Signer's private key (hex string).
* @returns 65-byte signature (`r` || `s` || `v`) as a `Uint8Array`.
*/
export declare function sign(message: string, privateKey: string): Uint8Array;
/**
* Signs a ctUint ciphertext for COTI input-text (IT) submission.
*
* Hashes `(signer, contract, selector, ciphertext)` with `solidityPackedKeccak256`
* and signs the digest with the sender's wallet private key.
*
* @param sender - Wallet and user AES key (key is not used for signing).
* @param contractAddress - Target contract address.
* @param functionSelector - 4-byte function selector (e.g. `"0x11223344"`).
* @param ct - Encrypted value ({@link ctUint} bigint) being signed.
* @returns 65-byte signature as a `Uint8Array`.
*/
export declare function signInputText(sender: {
wallet: BaseWallet;
userKey: string;
}, contractAddress: string, functionSelector: string, ct: bigint): Uint8Array;
/**
* @deprecated Use {@link prepareIT} for unsigned integer input-text values. This
* legacy helper is limited to 64-bit plaintexts and will be removed in a
* future major version.
*
* Encrypts a plaintext, signs the ciphertext, and returns an {@link itUint}
* ready for smart contract submission.
*
* @param plaintext - Unsigned value up to 64 bits.
* @param sender - Wallet and user AES key.
* @param contractAddress - Target contract address.
* @param functionSelector - 4-byte function selector.
* @returns Signed input text with {@link ctUint} ciphertext.
* @throws RangeError if `plaintext` exceeds 64 bits or is negative.
*/
export declare function buildInputText(plaintext: bigint, sender: {
wallet: BaseWallet;
userKey: string;
}, contractAddress: string, functionSelector: string): itUint;
/**
* Builds signed input text for a UTF-8 string.
*
* Encodes the string in 8-byte chunks (each stored as a {@link ctUint}),
* encrypts and signs each chunk independently, and returns an {@link itString}.
*
* @param plaintext - UTF-8 string to encrypt.
* @param sender - Wallet and user AES key.
* @param contractAddress - Target contract address.
* @param functionSelector - 4-byte function selector.
* @returns Signed string input text with one ciphertext/signature pair per chunk.
* @throws RangeError if any chunk exceeds 64 bits after encoding.
*/
export declare function buildStringInputText(plaintext: string, sender: {
wallet: BaseWallet;
userKey: string;
}, contractAddress: string, functionSelector: string): itString;
/**
* Decrypts a ctUint ciphertext using the user's AES key.
*
* `ctUint` is stored on-chain as a bigint packing 32 bytes (16-byte AES
* ciphertext + 16-byte random `r`). This function decrypts to a plaintext
* of up to 64 bits. For 128-bit input-text values use `prepareIT` instead.
*
* - A zero ciphertext is short-circuited to `0n` without validating the key,
* since it represents uninitialized/empty on-chain storage. This allows DApps
* to read empty balances before the user has configured their AES key.
* Note: this means `decryptUint(0n, invalidKey)` returns `0n` without throwing.
* - For non-zero ciphertexts, key validation is performed by `encodeKey`
* (strips "0x", lowercases, enforces 128-bit hex). Invalid keys throw
* rather than producing garbage.
*
* @param ciphertext - The ctUint value (32-byte wire format as a bigint).
* @param userKey - The AES key (32 hex chars, optionally "0x"-prefixed).
* @returns The decrypted plaintext as a bigint (up to 64 bits).
* @throws Error if the key is invalid (null, wrong length, non-hex) and ciphertext is non-zero.
*/
export declare function decryptUint(ciphertext: ctUint, userKey: string): bigint;
/**
* Decrypts a canonical {@link ctUint256} ciphertext using the user's AES key.
*
* @param ciphertext - Object with `ciphertextHigh` and `ciphertextLow` parts.
* @param userKey - AES key (32 hex chars, optionally `0x`-prefixed).
* @returns Decrypted plaintext as a bigint (up to 256 bits).
* @throws Error if the key is invalid.
*/
export declare function decryptUint256(ciphertext: ctUint256, userKey: string): bigint;
/**
* Decrypts a {@link ctString} ciphertext back to a UTF-8 string.
*
* Each chunk is decrypted via {@link decryptUint}; trailing zero padding added
* during {@link buildStringInputText} is trimmed before decoding.
*
* @param ciphertext - String ciphertext with an array of {@link ctUint} chunks.
* @param userKey - AES key (32 hex chars, optionally `0x`-prefixed).
* @returns Decrypted UTF-8 string.
* @throws Error if the key is invalid for any non-zero chunk.
*/
export declare function decryptString(ciphertext: ctString, userKey: string): string;
/**
* Generates 16 bytes of cryptographically random AES key material as a
* node-forge binary string (each character's code point is one byte 0–255).
*
* @returns 16-character forge binary string (not hex).
* @example
* ```ts
* const hexKey = Buffer.from(generateRandomAesKeyBinaryString(), "binary").toString("hex")
* ```
*/
export declare function generateRandomAesKeyBinaryString(): string;
/**
* @deprecated Use {@link generateRandomAesKeyBinaryString} instead. Despite the
* name, the return value is a 16-byte binary string, not a numeric or hex value.
*/
export declare function generateRandomAesKeySizeNumber(): string;
/**
* Converts a node-forge binary string into a `Uint8Array`.
*
* In forge binary strings each character's Unicode code point represents one
* byte (0–255). This is **not** UTF-8 encoding of human-readable text.
*
* @param binaryString - Forge binary string (one byte per character code point).
* @returns Byte array with one entry per character.
*/
export declare function binaryStringToBytes(binaryString: string): Uint8Array;
/**
* @deprecated Use {@link binaryStringToBytes} instead. Despite the name, this does
* not UTF-8-encode a string; it converts a forge binary string to bytes.
*/
export declare function encodeString(str: string): Uint8Array;
/**
* Validates and normalizes an AES key: ensures it is present, strips the "0x"
* prefix, and lowercases it. COTI uses a 128-bit AES key, so only 32-character
* hex strings are accepted.
*
* @param aesKey - The AES key, optionally prefixed with "0x".
* @returns The normalized lowercase hex string.
* @throws Error if the key is empty/null/undefined, contains non-hex characters, or is not 32 hex characters.
*/
export declare function normalizeAesKey(aesKey: string | null | undefined): string;
/**
* Parses and validates a user AES key hex string into a 16-byte `Uint8Array`.
*
* Delegates to {@link normalizeAesKey} for validation (strips `0x`, lowercases,
* enforces 128-bit length) before hex decoding.
*
* @param userKey - AES key as a 32-character hex string (optional `0x` prefix).
* @returns 128-bit key as 16 bytes.
* @throws Error if the key is missing, wrong length, or contains non-hex characters.
*/
export declare function encodeKey(userKey: string): Uint8Array;
/**
* Encodes an unsigned integer as a 16-byte big-endian byte array.
*
* @param plaintext - Integer value (typically up to 128 bits).
* @returns 16-byte big-endian representation.
*/
export declare function encodeUint(plaintext: bigint): Uint8Array;
/**
* Decodes a big-endian byte array into an unsigned bigint.
*
* @param plaintextBytes - Byte array (commonly 16 bytes from {@link encodeUint}).
* @returns Decoded unsigned integer.
* @throws SyntaxError if `plaintextBytes` is empty.
*/
export declare function decodeUint(plaintextBytes: Uint8Array): bigint;
/**
* AES-ECB encrypts a 16-byte random value (the `r` component of COTI encryption).
*
* @param r - Random value as a forge binary string or 16-byte `Uint8Array`.
* @param key - 128-bit AES key (16 bytes).
* @returns Encrypted 16-byte block.
* @throws RangeError if `key` is not 16 bytes.
*/
export declare function encryptNumber(r: string | Uint8Array, key: Uint8Array): Uint8Array;
/**
* Prepares a signed input text ({@link itUint}) for an unsigned integer.
*
* Encrypts up to **128-bit** plaintext, signs the resulting {@link ctUint},
* and returns the ciphertext/signature pair for smart contract submission.
* For 256-bit values use {@link prepareIT256} instead.
*
* @param plaintext - Unsigned value up to 128 bits.
* @param sender - Wallet and user AES key.
* @param contractAddress - Target contract address.
* @param functionSelector - 4-byte function selector.
* @returns Signed input text.
* @throws RangeError if `plaintext` exceeds 128 bits or is negative.
*/
export declare function prepareIT(plaintext: bigint, sender: {
wallet: BaseWallet;
userKey: string;
}, contractAddress: string, functionSelector: string): itUint;
/**
* Encrypts an unsigned value into a {@link ctUint} without building an IT signature.
*
* Plaintext must fit in **64 bits**. The resulting {@link ctUint} bigint packs
* 32 bytes on the wire (16-byte ciphertext + 16-byte random `r`). For 128-bit
* input-text values use {@link prepareIT} instead.
*
* @param plaintext - Unsigned value up to 64 bits.
* @param userKey - AES key (32 hex chars, optionally `0x`-prefixed).
* @returns Encrypted ctUint ciphertext as a bigint.
* @throws RangeError if `plaintext` exceeds 64 bits or is negative.
* @throws Error if the key is invalid.
*/
export declare function encryptUint(plaintext: bigint, userKey: string): ctUint;
/**
* Encrypts an unsigned value into a {@link ctUint256} without building an IT signature.
*
* Values up to 128 bits use compact encoding; larger values up to 256 bits use
* full two-block encoding. For signed submission use {@link prepareIT256},
* {@link prepareSignedIT256}, or {@link buildItUint256WithSigner} instead.
*
* @param plaintext - Unsigned value up to 256 bits.
* @param userKey - AES key (32 hex chars, optionally `0x`-prefixed).
* @returns Encrypted ctUint256 ciphertext.
* @throws RangeError if `plaintext` exceeds 256 bits or is negative.
* @throws Error if the key is invalid.
*/
export declare function encryptUint256(plaintext: bigint, userKey: string): ctUint256;
/**
* Strict coercion for app/JSON payloads into canonical ctUint types.
*
* - `'ctUint64'`: coerces string/number/bigint to {@link ctUint}.
* - `'ctUint256'`: accepts flat `{ ciphertextHigh, ciphertextLow }` or
* `[high, low]` tuples only.
*
* For nested on-chain shapes use {@link decryptCtUint256} instead.
*
* @param value - Serializable payload from JSON/RPC.
* @param type - Target ciphertext type discriminator.
* @returns Canonical `bigint` or {@link ctUint256}.
* @throws Error if the payload cannot be coerced.
*/
export declare function normalizeCtPayload(value: SerializableCtUint, type: 'ctUint64'): ctUint;
export declare function normalizeCtPayload(value: SerializableCtUint256, type: 'ctUint256'): ctUint256;
export declare function normalizeCtPayload(value: SerializableCtUint | SerializableCtUint256, type: 'ctUint64' | 'ctUint256'): ctUint | ctUint256;
/**
* Returns whether `value` matches a supported {@link ctUint256} wire shape.
*
* Accepts flat objects, two-element tuples, and nested four-limb objects as
* defined by {@link CtUint256Like}. Does not validate ciphertext correctness.
*
* @param value - Value to inspect (e.g. from RPC or contract return data).
* @returns `true` if the shape is recognized.
*/
export declare function isCtUint256Shape(value: unknown): value is CtUint256Like;
/**
* Returns whether a ctUint256 value represents zero / uninitialized storage.
*
* Handles scalar zero, flat `{ ciphertextHigh, ciphertextLow }`, tuple, and
* nested four-limb shapes. Returns `false` for unrecognized shapes.
*
* @param ciphertext - Ciphertext value in any supported wire format.
* @returns `true` if all ciphertext limbs are zero.
*/
export declare function isZeroCtUint256(ciphertext: unknown): boolean;
/**
* Decrypts a ctUint256 from any supported wire shape.
*
* Unlike {@link decryptUint256}, accepts flat, tuple, and nested on-chain/RPC
* formats (see {@link isCtUint256Shape}). For strict JSON coercion use
* {@link normalizeCtPayload} first.
*
* @param ciphertext - Flat, tuple, or nested ctUint256 payload.
* @param userKey - AES key (32 hex chars, optionally `0x`-prefixed).
* @returns Decrypted plaintext as a bigint (up to 256 bits).
* @throws Error if the shape is invalid or the key is invalid.
*/
export declare function decryptCtUint256(ciphertext: unknown, userKey: string): bigint;
/**
* Builds a signed {@link itUint256Signed} using an external wallet signer.
*
* Encrypts the value, then signs an ABI-packed payload of
* `(signer, contract, selector, ciphertextHigh, ciphertextLow)`. Intended for
* browser wallets — **not** interchangeable with {@link prepareIT256}, which
* signs raw ciphertext bytes for private-key callers.
*
* @param params - Value, AES key, addresses, selector, and async sign callback.
* @returns Encrypted ctUint256 and hex signature string from the wallet.
* @throws RangeError if `value` exceeds 256 bits or is negative.
* @throws Error if the AES key is invalid.
*/
export declare function buildItUint256WithSigner({ value, aesKey, signerAddress, contractAddress, functionSelector, signMessage }: BuildItUint256WithSignerParams): Promise<itUint256Signed>;
/**
* Prepares a signed 256-bit input text ({@link itUint256}) for smart contract submission.
*
* Encrypts the plaintext (up to 256 bits), signs the raw 64-byte ciphertext bytes
* with the sender's private key, and returns the ciphertext/signature pair.
* For browser-wallet signing use {@link buildItUint256WithSigner} instead.
*
* @param plaintext - Unsigned value up to 256 bits.
* @param sender - Wallet and user AES key.
* @param contractAddress - Target contract address.
* @param functionSelector - 4-byte function selector.
* @returns Signed 256-bit input text.
* @throws RangeError if `plaintext` exceeds 256 bits or is negative.
*/
export declare function prepareIT256(plaintext: bigint, sender: {
wallet: BaseWallet;
userKey: string;
}, contractAddress: string, functionSelector: string): itUint256;
/**
* Encrypts a signed int256 into a {@link ctInt256} without building an IT signature.
*
* Negative values are encoded as 256-bit two's complement, then encrypted with
* the same wire format as {@link encryptUint256}.
*
* @param plaintext - Signed value in `[-(2^255), 2^255 - 1]`.
* @param userKey - AES key (32 hex chars, optionally `0x`-prefixed).
* @returns Encrypted ctInt256 ciphertext.
* @throws RangeError if `plaintext` is outside int256 range.
* @throws Error if the key is invalid.
*/
export declare function encryptInt256(plaintext: bigint, userKey: string): ctInt256;
/**
* Decrypts a {@link ctInt256} ciphertext and returns the signed plaintext.
*
* @param ciphertext - Encrypted int256 (same wire shape as ctUint256).
* @param userKey - AES key (32 hex chars, optionally `0x`-prefixed).
* @returns Signed plaintext in int256 range.
* @throws Error if the key is invalid.
*/
export declare function decryptInt256(ciphertext: ctInt256, userKey: string): bigint;
/**
* Prepares a signed int256 input text ({@link itInt256}) for smart contract submission.
*
* Encodes `plaintext` as 256-bit two's complement, then delegates to
* {@link prepareIT256} for encrypt + private-key IT signature.
*
* @param plaintext - Signed value in `[-(2^255), 2^255 - 1]`.
* @param sender - Wallet and user AES key.
* @param contractAddress - Target contract address.
* @param functionSelector - 4-byte function selector.
* @returns Signed 256-bit input text.
* @throws RangeError if `plaintext` is outside int256 range.
*/
export declare function prepareSignedIT256(plaintext: bigint, sender: {
wallet: BaseWallet;
userKey: string;
}, contractAddress: string, functionSelector: string): itInt256;
/**
* Builds a COTI input-text (IT) signature over (signer, contract, selector, ciphertext).
*
* @deprecated Use `signInputText` with an ethers `Wallet` for private-key
* signing. For browser signers, use `buildItUint256WithSigner` for 256-bit
* values. This private-key convenience wrapper will be removed in a future
* major version.
*
* @param signerAddress - Address of the signer; must match the address derived from privateKey.
* @param contractAddress - Target contract address.
* @param functionSelector - 4-byte function selector (e.g. "0x11223344").
* @param ciphertext - The encrypted value being signed.
* @param privateKey - Signer's private key.
* @returns The 65-byte signature as a hex string.
* @throws Error if signerAddress does not match the address derived from privateKey.
*/
export declare function buildItSignature(signerAddress: string, contractAddress: string, functionSelector: string, ciphertext: bigint, privateKey: string): string;