Skip to content

plucena-coti/sdk-research

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 

Repository files navigation

coti-sdk-typescript 1.0.5 project - itUint256 and ctUint256 types

This directory contains unit and integration tests for the COTI SDK Typescript library. Below is a detailed documentation of the tests related to itUint256 and ctUint256 types.

Table of Contents

Contract Implementation Details

The operations and type definitions for itUint256 and ctUint256 are implemented in the coti-contracts repository.

Structure Definitions

Both structures are defined in contracts/utils/mpc/MpcCore.sol:

  • gtUint256 (line 18): Garbled Text 256-bit integer, composed of two gtUint128 (High and Low parts).
  • ctUint256 (line 42): Composed of two ctUint128 (High and Low parts).
  • itUint256 (line 77): Contains the ctUint256 ciphertext and a bytes[2][2] signature.

Operations

The logic for handling these types is implemented in the MpcCore library. Below is a summary of the supported 256-bit operations for gtUint256:

1. Arithmetic & Bitwise

  • Arithmetic: add (Addition), sub (Subtraction), mul (Multiplication).

    [!NOTE] div (Division) and rem (Remainder) are NOT implemented for gtUint256.

  • Bitwise: and (AND), or (OR), xor (XOR).
  • Shifts: shl (Shift Left), shr (Shift Right).

2. Comparisons

  • Equality: eq (==), ne (!=).
  • Relational: gt (>), ge (>=), lt (<), le (<=).
  • Selection: min (Minimum), max (Maximum), mux (Multiplexer/Select).

3. Input/Output & Conversions

  • validateCiphertext: itUint256gtUint256 (Validates and converts input).
  • onBoard: ctUint256gtUint256 (Converts ciphertext to garbled text).
  • offBoard: gtUint256ctUint256 (Converts garbled text back to ciphertext).
  • offBoardToUser: gtUint256ctUint256 (Re-encrypts the result for a specific user).
  • setPublic256: uint256gtUint256 (Converts a public value to garbled text).
  • decrypt: gtUint256uint256 (Decrypts garbled text to public value; usually for tests/debug).

4. Randomness

  • rand256: Generates a random gtUint256.
  • randBoundedBits256: Generates a random gtUint256 with a specific bit size.

Client-Side Execution Example (using @coti-ethers)

Arithmetic and bitwise operations are not executed directly in the client-side JavaScript/TypeScript code. Instead, you use @coti-ethers to deploy and call a Smart Contract that implements these operations using MpcCore.sol.

The Process:

  1. Write a Solidity Contract: Create a contract that imports MpcCore.sol and exposes functions to call MpcCore.add(...), MpcCore.sub(...), etc.
  2. Generate Input (Client-Side): Use @coti-io/coti-sdk-typescript to create an encrypted input (itUint256).
  3. Call Contract (Client-Side): Use @coti-ethers to send a transaction to your contract with that input.
  4. Execute (On-Chain): The network executes the operation securely on the MPC nodes.
  5. Decrypt Result (Client-Side): Use @coti-io/coti-sdk-typescript to decrypt the result returned by the contract (after it's offboarded).

Conceptual Example:

import { prepareIT256, decryptUint256 } from '@coti-io/coti-sdk-typescript';
import { Contract, Wallet } from 'ethers'; // or coti-ethers

// 1. Prepare Input (Client)
// Encrypt the value 100
const { ciphertext, signature } = prepareIT256(100n, sender, contractAddress, functionSelector);

// 2. Call Contract (Using coti-ethers)
// This sends the tx to the blockchain where 'MpcCore.add' is actually run
const tx = await myContract.addEncryptedValues(
    { ciphertext, signature }, // itUint256
    someOtherEncryptedValue    // gtUint256 (internal) or another input
);

// 3. Decrypt Result (Client-Side)
const result = await tx.wait();
// ... logic to parse event or view function result ...
const decrypted = decryptUint256(resultCiphertext, userKey);

Usage Example

A clear example of how to use these in a contract can be found in contracts/mocks/utils/mpc/Miscellaneous256BitTestsContract.sol.

This contract demonstrates several key operations for gtUint256:

  • validateCiphertextTest: Accepts an array of itUint256 inputs, validates them into gtUint256 using MpcCore.validateCiphertext, and decrypts them back to uint256.
  • setPublicTest: Converts an array of public uint256 values into gtUint256 using MpcCore.setPublic256 and decrypts them.
  • offBoardToUserTest: Demonstrates taking a public value, converting it to gtUint256, and then re-encrypting it for the caller (offboarding) using MpcCore.offBoardToUser.
  • transferTest: Simulates a transfer operation. It takes sender balance (a), receiver balance (b), and an transfer amount, all as public integers. It converts them to gtUint256, performs an encrypted transfer using MpcCore.transfer, and decrypts the results (new balances and success status).
  • transferWithAllowanceTest: Similar to transferTest but includes an allowance check, employing MpcCore.transferWithAllowance.

/coti-sdk-typescript type definitions

itUint256 (Input Text Uint256)

Represents the input structure required for sending an encrypted 256-bit unsigned integer to a contract. It includes the split ciphertext and a signature for integrity.

type itUint256 = {
  ciphertext: { 
    ciphertextHigh: bigint; 
    ciphertextLow: bigint 
  };
  signature: Uint8Array;
};

ctUint256 (Cipher Text Uint256)

Represents the ciphertext of a 256-bit unsigned integer, split into two 128-bit parts (High and Low) to accommodate the underlying encryption scheme.

type ctUint256 = {
  ciphertextHigh: bigint;
  ciphertextLow: bigint;
};

Ciphertext and Signature Generation

Ciphertext Generation

Source: createCiphertext256 (Lines 482-488) in src/crypto_utils.ts

The 256-bit integer is encrypted using a split-key AES approach, handling the value as two 128-bit blocks (High and Low).

  1. Splitting: The 256-bit integer is split into two 128-bit parts:
    • High Part: The most significant 128 bits. (If input <= 128 bits, this is 0).
    • Low Part: The least significant 128 bits.
  2. Encryption (per 128-bit block): Each part is encrypted separately using AES-128 with a unique random value $r$.
    • Generates a random 128-bit value $r$.
    • Computes $EncryptedR = AES_{key}(r)$.
    • Computes $Ciphertext = EncryptedR \oplus Plaintext$.
  3. Combination: The final ciphertext is a 64-byte array structured as:
    • [CiphertextHigh (16b), rHigh (16b), CiphertextLow (16b), rLow (16b)]

Signature Generation

Source: signIT (Lines 426-431) in src/crypto_utils.ts

To ensure integrity and prevent replay attacks, a signature is generated over the transaction parameters.

  1. Packing: The following data is packed and hashed using keccak256:
    • senderAddress (bytes)
    • contractAddress (bytes)
    • functionSelector (bytes4)
    • ciphertext (64 bytes, as generated above)
  2. Signing: The resulting hash is signed using the sender's ECDSA private key.
  3. Result: The signature is returned as a 65-byte Uint8Array.
    • Format: [...r, ...s, v]
    • r: 32 bytes (ECDSA signature component)
    • s: 32 bytes (ECDSA signature component)
    • v: 1 byte (Recovery identifier, 0 or 1)
    • Why Uint8Array?: This is the standard binary data format in JavaScript/TypeScript environments, ensuring compatibility with the bytes type in Solidity contracts and efficient raw data handling.

Process Visualized

flowchart LR
    subgraph Inputs
        SA[Sender Address]
        CA[Contract Address]
        FS[Function Selector]
        CT[Ciphertext]
        PK[Private Key]
    end

    subgraph Transformation
        P[Pack Parameters]
        H[Keccak256 Hash]
        S[ECDSA Sign]
    end

    subgraph Output
        Sig[Signature Uint8Array]
    end
    
    subgraph Components
        R[r: 32 bytes]
        s_val[s: 32 bytes]
        V[v: 1 byte]
    end

    SA & CA & FS & CT --> P
    P --> H
    H --> S
    PK --> S
    S --> Sig
    Sig --> R
    Sig --> s_val
    Sig --> V

    style S fill:#f9f,stroke:#333
    style Sig fill:#bbf,stroke:#333
Loading

SDK Comparison

This section details the new functions added in this local version of the SDK compared to the latest published version (@coti-io/coti-sdk-typescript@1.0.4).

The published version supports up to 128-bit integers using buildInputText. The local version extends this to support 256-bit integers with a new split-key encryption scheme and introduces new named functions.

Feature Published Version (1.0.4) Local Version (0.5.5+)
Input Generation buildInputText buildInputText, prepareIT, prepareIT256
Max Integer Size 128-bit 256-bit
Ciphertext Format Single bigint (128-bit) bigint (128-bit) OR Struct { high, low } (256-bit)
Decryption decryptUint decryptUint, decryptUint256
New Types itUint, ctUint itUint, ctUint, itUint256, ctUint256

Important

API Changes:

  • prepareIT: Included in the local version as an alias/alternative to buildInputText.
  • prepareIT256: A completely new function required for encrypting uint256 values (e.g., ERC20 amounts). It produces a 2-part ciphertext that must be handled differently by the contract.
  • decryptUint256: Added to decrypt the new split-format 256-bit ciphertexts.

itUint256 and ctUint256 Tests

These tests verify the functionality of 256-bit integer encryption (prepareIT256) and decryption (decryptUint256), as well as the structure of the resulting types.

These tests focus on the cryptographic correctness of the prepareIT256 and decryptUint256 functions.

1. prepareIT256 with variable bit sizes

Source: Lines 771-774 Function Tested: prepareIT256

  • Purpose: To verify that prepareIT256 correctly handles plaintexts of different bit lengths.
  • Method Executed:
    prepareIT256(
        PLAINTEXT, 
        sender, 
        contractAddress, 
        functionSelector
    )
  • Parameters Used:
    • PLAINTEXT: (2n ** BigInt(bitSize)) - 1n for bitSize = 100, 129, 200, 255, and 256.
    • sender: { wallet: new Wallet(TEST_PRIVATE_KEY), userKey: TEST_USER_KEY }
    • contractAddress: '0x0000000000000000000000000000000000000001'
    • functionSelector: '0x11223344'
  • Expected Results:
    • Returns object with ciphertext (ciphertextHigh, ciphertextLow) and signature.
    • ciphertextHigh > 0n, ciphertextLow > 0n.
    • signature is a non-empty Uint8Array.

2. prepareIT256 Error Handling

Source: Lines 776-794 Function Tested: prepareIT256

  • Purpose: To ensure the function throws a RangeError for plaintexts larger than 256 bits.
  • Method Executed: prepareIT256(PLAINTEXT, ...)
  • Parameters Used:
    • PLAINTEXT: 2n ** 256n (Which is $2^{256}$, a 257-bit number).
  • Expected Results: Throws RangeError with message "Plaintext size must be 256 bits or smaller".

3. Round-Trip Encryption/Decryption (prepareIT256 -> decryptUint256)

Source: Lines 796-799 Functions Tested:

  • prepareIT256 (Encryption)

  • decryptUint256 (Decryption)

  • Purpose: To verify that encryption followed by decryption yields the original value.

  • Methods Executed:

    1. const { ciphertext } = prepareIT256(PLAINTEXT, sender, ...)
    2. const decrypted = decryptUint256(ciphertext, USER_KEY)
  • Parameters Used:

    • PLAINTEXT values:
      • (2n ** 100n) - 12345n (100-bit)
      • 2n ** 128n + 12345n (129-bit)
      • (2n ** 200n) - 12345n (200-bit)
      • (2n ** 256n) - 1n (256-bit)
    • USER_KEY: Matching the key used in sender for prepareIT256.
  • Expected Results: decrypted === PLAINTEXT for all cases.

These tests focus on the structure and format compatibility of the produced objects, ensuring they align with contract expectations (struct consistency).

1. Output Structure Verification

  • Purpose: To validate that prepareIT256 returns an object matching the itUint256 TypeScript type definition.
  • Inputs: plaintext = $2^{200}$.
  • Expected Results:
    • Result object has ciphertext and signature properties.
    • ciphertext has ciphertextHigh and ciphertextLow.
    • Properties have correct types (bigint, Uint8Array).

2. Values Validity

  • Purpose: To ensure the generated BigInts are valid positive integers.
  • Inputs: plaintext = $2^{200}$.
  • Expected Results: ciphertextHigh and ciphertextLow are greater than 0.

3. Contract Struct Compatibility

  • Purpose: To check if the output can be serialized into a format suitable for passing to a smart contract struct (e.g., string representation of numbers).
  • Inputs: plaintext = $2^{200}$.
  • Expected Results: ciphertextHigh and ciphertextLow can be successfully converted to strings.

4. Hex String Conversion

  • Purpose: To verify that the BigInt components can be converted to Hex strings (common for Ethereum JSON-RPC interactions).
  • Inputs: plaintext = $2^{200}$.
  • Expected Results: ciphertextHigh and ciphertextLow convert to valid hex strings starting with 0x.

Test Execution Report

The following table documents the results of running the "Round-Trip Encryption/Decryption" unit tests for prepareIT256 and decryptUint256.

Test Case Input Value (Dec) Encrypted Sample (High, Low) Decrypted Result
100-bit value 0xffffffff...ffffcfc7 High: 0xcbfabb8e...
Low: 0x31032483...
0xffffffff...ffffcfc7 PASS
129-bit value 0x10000000...00003039 High: 0x2daa19a9...
Low: 0xda3bb4a2...
0x10000000...00003039 PASS
200-bit value 0xffffffff...ffffcfc7 High: 0x7dcbe32e...
Low: 0xd106cdb3...
0xffffffff...ffffcfc7 PASS
256-bit value 0xffffffff...ffffffff High: 0x57076ee1...
Low: 0x48625a01...
0xffffffff...ffffffff PASS

Note: Encrypted values change on each run due to random salt/iv.

Reproducing these Results

To generate these values, you can run the following script using npx ts-node.

  1. Create a file generate_report.ts in the project root:
import { prepareIT256, decryptUint256 } from './src/crypto_utils';
import { Wallet } from 'ethers';
import dotenv from 'dotenv';
dotenv.config();

const TEST_CONSTANTS = {
    PRIVATE_KEY: process.env.TEST_PRIVATE_KEY || '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
    USER_KEY: process.env.TEST_USER_KEY || '00112233445566778899aabbccddeeff',
    CONTRACT_ADDRESS: '0x0000000000000000000000000000000000000001',
    FUNCTION_SELECTOR: '0x11223344'
};
const sender = { wallet: new Wallet(TEST_CONSTANTS.PRIVATE_KEY), userKey: TEST_CONSTANTS.USER_KEY };

const testValues = [
    { name: '100-bit value', val: (2n ** 100n) - 12345n },
    { name: '129-bit value', val: 2n ** 128n + 12345n },
    { name: '200-bit value', val: (2n ** 200n) - 12345n },
    { name: '256-bit value', val: (2n ** 256n) - 1n }
];

console.log('| Test Case | Input Value (Dec) | Encrypted (High, Low) | Decrypted | Result |');
console.log('|---|---|---|---|---|');

testValues.forEach(test => {
    const it = prepareIT256(test.val, sender, TEST_CONSTANTS.CONTRACT_ADDRESS, TEST_CONSTANTS.FUNCTION_SELECTOR);
    const decrypted = decryptUint256(it.ciphertext, TEST_CONSTANTS.USER_KEY);
    const result = decrypted === test.val ? 'PASS' : 'FAIL';
    
    // Format helper
    const fmt = (v: any) => '0x' + v.toString(16);
    const trunc = (s: string) => s.length > 20 ? s.slice(0, 10) + '...' + s.slice(-8) : s;

    const inputHex = fmt(test.val);
    const ctHighHex = fmt(it.ciphertext.ciphertextHigh);
    const ctLowHex = fmt(it.ciphertext.ciphertextLow);
    const decryptedHex = fmt(decrypted);

    console.log(`| ${test.name} | \`${trunc(inputHex)}\` | High: \`${trunc(ctHighHex)}\`<br>Low: \`${trunc(ctLowHex)}\` | \`${trunc(decryptedHex)}\` | ${result} |`);
});
  1. Run the script:
npx ts-node generate_report.ts

Next Steps for Publishing

To release these changes as a new version of the @coti-io/coti-sdk-typescript package:

  1. Run Tests: Ensure all unit and integration tests pass.
    npm test
  2. Increment Version: Update the version field in package.json according to Semantic Versioning (e.g., 0.6.0 or 1.1.0 since this adds new features).
    npm version minor
  3. Build: Compile the TypeScript source code to JavaScript (outputs to /dist).
    npm run build
  4. Publish: Push the new version to the npm registry.
    npm publish --access public

Changes to @coti-ethers

To support the new 256-bit types and operations, the @coti-ethers library has been updated with new methods in both Wallet and JsonRpcSigner classes.

New Exports

The library now re-exports the following types from @coti-io/coti-sdk-typescript:

  • itUint256
  • ctUint256

New Methods

Both Wallet (for direct private key usage) and JsonRpcSigner (for browser wallets like MetaMask) now support:

1. encryptValue256

Encrypts a plaintext value (integer or bigint) into an itUint256 structure.

async encryptValue256(
    plaintextValue: bigint | number,
    contractAddress: string,
    functionSelector: string
): Promise<itUint256>
  • Input: Auto-detects bit size. Throws error if value > 256 bits.
  • Implementation Difference:
    • Wallet: Uses prepareIT256 directly from the SDK.
    • JsonRpcSigner: Re-implements the logic internally to support signMessage() (personal_sign) which is required for browser wallets, as they don't expose raw private keys.

2. decryptValue256

Decrypts a ctUint256 ciphertext back to a bigint.

async decryptValue256(ciphertext: ctUint256): Promise<bigint>
  • Input: Must be a valid ctUint256 object { ciphertextHigh, ciphertextLow }.
  • Prerequisite: The wallet/signer must be onboarded (have a user AES key).

Test Coverage


Critique & Improvement Suggestions

This section provides an objective analysis of the current itUint256/ctUint256 implementation, highlighting areas for improvement.

Areas of Concern

1. Missing Operations

The contract implementation (MpcCore.sol) explicitly does not support div and rem for gtUint256. This is a significant limitation for financial applications that require division (e.g., fee calculations, percentage splits).

Warning

Applications requiring encrypted division must find workarounds (e.g., pre-computing values off-chain or using multiplication by inverses).

2. Incomplete Test Coverage

  • Wallet class in coti-ethers has no unit tests for encryptValue256 or decryptValue256. This creates risk when using Wallet in server-side or automated scripts.
  • Integration tests do not cover end-to-end contract interactions—only format validation.
  • Error path coverage is minimal (only overflow is tested, not invalid key formats, signature mismatches, etc.).

3. API Duplication & Inconsistency

The SDK offers both buildInputText and prepareIT for similar purposes, but they have different limitations:

  • buildInputText: Strictly limited to 64-bit values (throws error for larger).
  • prepareIT: Supports up to 128-bit values.
  • prepareIT256: Supports up to 256-bit values. This inconsistency can lead to runtime errors if developers assume buildInputText works for all "standard" inputs.

4. Documentation Gaps

  • No documented gas cost comparisons for 256-bit vs. 128-bit operations.
  • Missing migration guide for upgrading from 128-bit to 256-bit types.
  • The signIT function's role in replay attack prevention is mentioned but not thoroughly explained.

5. Security Considerations Not Addressed

  • Key rotation: No guidance on how users should handle AES key rotation.
  • Signature replay: While signatures include contract address and function selector, there's no explicit nonce or timestamp to prevent cross-contract replay attacks if the same selector is used.

Recommended Improvements

Priority Area Suggestion
High Testing Add 256-bit tests for Wallet class in coti-ethers.
High Testing Create E2E tests that deploy Miscellaneous256BitTestsContract and call operations.
Medium SDK Deprecate buildInputText in favor of prepareIT for consistency.
Medium Contracts Document (or implement) workarounds for missing div/rem operations.
Medium Docs Add gas benchmarks for 256-bit operations vs. 128-bit.
Low Docs Add a migration guide for existing 128-bit applications.
Low API Consider adding prepareIT128 as an explicit alias to match prepareIT256 naming.

Future Considerations

  1. Batch Operations: Consider adding batch encryption/decryption for multiple values to reduce overhead.
  2. Streaming Decryption: For large datasets, a streaming API could be more memory-efficient than loading all ciphertexts at once.
  3. WebAssembly Build: Providing a WASM version of the crypto utilities could improve performance in browser environments.
  4. Hardware Wallet Support: JsonRpcSigner supports browser wallets, but hardware wallet flows (Ledger/Trezor) may require additional testing.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors