Skip to main content

Overview

This page documents all the key data structures, classes, and type definitions used in the Tornado Nova system.

Contract Types

These types mirror the Solidity structs used in the smart contracts.

ExtData

interface ExtData {
  recipient: string        // Address (hex string, 20 bytes)
  extAmount: BigNumber     // External amount (positive for deposit, negative for withdrawal)
  relayer: string          // Relayer address (hex string, 20 bytes)
  fee: BigNumber           // Relayer fee amount
  encryptedOutput1: string // Encrypted first output UTXO (hex string)
  encryptedOutput2: string // Encrypted second output UTXO (hex string)
  isL1Withdrawal: boolean  // Whether this is an L1 withdrawal
  l1Fee: BigNumber         // L1 execution fee (only for L1 withdrawals)
}
External data structure passed to transaction functions. Contains public transaction information. Source: contracts/TornadoPool.sol:42
recipient
string
Withdrawal recipient address. Use toFixedHex(address, 20) to format. Set to 0 for pure transfers.
extAmount
BigNumber
External amount in token wei:
  • Positive: Deposit (adding funds to pool)
  • Negative: Withdrawal (removing funds from pool)
  • Zero: Pure transfer (no external funds movement)
Formula: extAmount = sum(outputs) - sum(inputs) + fee
relayer
string
Address to receive the relayer fee. Use toFixedHex(address, 20) to format.
fee
BigNumber
Fee paid to relayer for submitting transaction. Enables privacy by separating payer from recipient.
encryptedOutput1
string
First output UTXO encrypted for recipient. Contains amount and blinding factor. Format: 0x + hex (24 bytes nonce + 32 bytes ephemeral key + ciphertext).
encryptedOutput2
string
Second output UTXO (often used for change back to sender).
isL1Withdrawal
boolean
If true, funds are bridged to L1 via OmniBridge and unwrapped to ETH.
l1Fee
BigNumber
Fee for L1 transaction execution. Deducted from withdrawal amount and sent to L1 fee receiver.

Proof

interface Proof {
  proof: string              // ZK-SNARK proof bytes (hex string)
  root: string               // Merkle tree root (bytes32)
  inputNullifiers: string[]  // Array of nullifier hashes (bytes32[])
  outputCommitments: string[]// Array of 2 commitment hashes (bytes32[2])
  publicAmount: BigNumber    // Public amount field for ZK circuit
  extDataHash: string        // Hash of ExtData (bytes32)
}
Proof structure containing ZK-SNARK proof and transaction data. Source: contracts/TornadoPool.sol:53
proof
string
ZK-SNARK proof bytes generated by the prover. Hex string starting with 0x.
root
string
Merkle tree root at the time of proof generation. Must be a known root (stored in contract history).
inputNullifiers
string[]
Array of nullifier hashes for input UTXOs being spent. Length must be 2 or 16. Each nullifier can only be used once.
outputCommitments
string[]
Array of exactly 2 commitment hashes for output UTXOs being created.
publicAmount
BigNumber
Public amount in field element format. Calculated as (extAmount - fee) mod FIELD_SIZE.
extDataHash
string
Keccak256 hash of ExtData structure, modulo FIELD_SIZE. Used to bind external data to the proof.

Account

interface Account {
  owner: string      // Ethereum address
  publicKey: string  // Public key bytes (hex string, 64 bytes)
}
Account structure for registering users in the system. Sources:
  • contracts/TornadoPool.sol:62
  • contracts/bridge/L1Unwrapper.sol:32
owner
string
Ethereum address of the account owner. Must match msg.sender when registering.
publicKey
string
Combined public key (64 bytes = 128 hex chars):
  • Bytes 0-31: Poseidon public key for signature verification
  • Bytes 32-63: x25519 encryption key for receiving encrypted notes
Get from: keypair.toString()

JavaScript Classes

Keypair

class Keypair {
  privkey: string       // Private key (hex string with 0x prefix)
  pubkey: BigNumber     // Poseidon public key  
  encryptionKey: string // x25519 encryption key (base64)
  
  constructor(privkey?: string)
  
  toString(): string
  address(): string
  static fromString(str: string): Keypair
  
  sign(commitment: BigNumberish, merklePath: BigNumberish): BigNumber
  encrypt(bytes: Buffer): string
  decrypt(data: string): Buffer
}
Keypair class for managing private/public keys, signing, and encryption. Source: src/keypair.js:36

constructor

constructor(privkey?: string)
Creates a new keypair. If no private key is provided, generates a random one.
privkey
string
Private key as hex string with ‘0x’ prefix. If omitted, generates random key.
const keypair1 = new Keypair() // Random
const keypair2 = new Keypair('0x1234...') // From existing key

toString / address

toString(): string
address(): string
Returns the keypair as a string (address format). Both methods are identical.Returns: 128-character hex string (with ‘0x’ prefix):
  • First 64 chars (32 bytes): Poseidon pubkey
  • Last 64 chars (32 bytes): Encryption key
const address = keypair.toString()
// "0x1abc...def" (128 hex chars)

fromString

static fromString(str: string): Keypair
Creates a keypair from an address string (public keys only, no private key).
str
string
required
128-character hex string (with or without ‘0x’ prefix)
Returns: Keypair with privkey: null
const publicKeypair = Keypair.fromString(address)
// Can encrypt but cannot decrypt or sign

sign

sign(commitment: BigNumberish, merklePath: BigNumberish): BigNumber
Signs a commitment with the private key. Used for nullifier generation.
commitment
BigNumberish
required
UTXO commitment hash
merklePath
BigNumberish
required
Index in merkle tree
Returns: Signature as poseidonHash(privkey, commitment, merklePath)
const signature = keypair.sign(utxo.getCommitment(), utxo.index)

encrypt

encrypt(bytes: Buffer): string
Encrypts data using the keypair’s encryption key.
bytes
Buffer
required
Data to encrypt
Returns: Hex string with encrypted data
const encrypted = keypair.encrypt(Buffer.from('secret'))

decrypt

decrypt(data: string): Buffer
Decrypts data using the private key. Requires private key.
data
string
required
Hex string with encrypted data
Returns: Decrypted data as Buffer
const decrypted = keypair.decrypt(encrypted)
console.log(decrypted.toString('utf8'))

Utxo

class Utxo {
  amount: BigNumber    // UTXO amount in wei
  blinding: BigNumber  // Random blinding factor
  keypair: Keypair     // Owner's keypair
  index: number | null // Index in merkle tree
  
  constructor({ amount?, keypair?, blinding?, index? })
  
  getCommitment(): BigNumber
  getNullifier(): BigNumber
  encrypt(): string
  static decrypt(keypair: Keypair, data: string, index: number): Utxo
}
UTXO (Unspent Transaction Output) class representing a private note. Source: src/utxo.js:6

constructor

constructor({
  amount?: BigNumberish,
  keypair?: Keypair,
  blinding?: BigNumberish,
  index?: number | null
})
Creates a new UTXO.
amount
BigNumberish
default:"0"
UTXO amount in token wei
keypair
Keypair
default:"new Keypair()"
Owner’s keypair. Random keypair generated if omitted.
blinding
BigNumberish
default:"randomBN()"
Random blinding factor for privacy. Random value generated if omitted.
index
number | null
default:"null"
Position in merkle tree. Set when UTXO is committed on-chain.
// Create deposit UTXO
const utxo = new Utxo({
  amount: ethers.utils.parseEther('10'),
  keypair: myKeypair
})

// Create dummy UTXO (for padding)
const dummy = new Utxo()

getCommitment

getCommitment(): BigNumber
Calculates and caches the commitment hash.Returns: poseidonHash(amount, pubkey, blinding)
const commitment = utxo.getCommitment()

getNullifier

getNullifier(): BigNumber
Calculates the nullifier hash. Requires index and keypair.privkey.Returns: poseidonHash(commitment, index, signature)Throws: Error if index or private key is missing for non-zero amount UTXOs
utxo.index = 42 // Set from event
const nullifier = utxo.getNullifier()

encrypt

encrypt(): string
Encrypts the UTXO data (amount and blinding) using the keypair.Returns: Hex string with encrypted data (24 bytes nonce + 32 bytes ephemeral key + 62 bytes ciphertext)
const encrypted = utxo.encrypt()
// Store in ExtData.encryptedOutput1

decrypt (static)

static decrypt(keypair: Keypair, data: string, index: number): Utxo
Decrypts a UTXO from encrypted data.
keypair
Keypair
required
Keypair with private key for decryption
data
string
required
Encrypted data hex string
index
number
required
UTXO index from NewCommitment event
Returns: Decrypted Utxo instanceThrows: Error if decryption fails (wrong keypair)
// Listen for commitments
tornadoPool.on('NewCommitment', (commitment, index, encrypted) => {
  try {
    const utxo = Utxo.decrypt(myKeypair, encrypted, index)
    console.log(`Received ${utxo.amount}!`)
  } catch (e) {
    // Not for us
  }
})

Type Aliases

type BigNumberish = BigNumber | string | number
type Address = string // Ethereum address (0x...)
type Bytes32 = string // 32-byte hex string (0x...)
type HexString = string // Hex string with 0x prefix

Usage Examples

const { Keypair } = require('./src/keypair')
const Utxo = require('./src/utxo')
const { prepareTransaction } = require('./src/index')
const { ethers } = require('hardhat')

// Create keypair
const keypair = new Keypair()

// Create UTXOs
const input = new Utxo({
  amount: ethers.utils.parseEther('10'),
  keypair,
  index: 42
})

const output = new Utxo({
  amount: ethers.utils.parseEther('8'),
  keypair: recipientKeypair
})

const change = new Utxo({
  amount: ethers.utils.parseEther('1.9'),
  keypair
})

// Prepare transaction
const { args, extData } = await prepareTransaction({
  tornadoPool,
  inputs: [input],
  outputs: [output, change],
  fee: ethers.utils.parseEther('0.1'),
  recipient: recipientAddress,
  relayer: relayerAddress
})

// args is Proof type
// extData is ExtData type
Dummy UTXOs: When creating dummy UTXOs for padding, use new Utxo() with no parameters. This creates a UTXO with amount 0 and random blinding/keypair.
Index Required: The UTXO index field must be set before calling getNullifier() for spending. Get the index from the NewCommitment event.
Type Safety: When using TypeScript, import type definitions:
import type { ExtData, Proof, Account } from './types'

Build docs developers (and LLMs) love