npm package: @sahilw/dust-core
Description: Dust Protocol core crypto primitives — Poseidon hashing, key derivation, BN254 field arithmetic
Author: Dust Protocol
Latest version: 0.1.0
Deprecated: Moved to dust-core (unscoped). Install: npm i dust-core

# @dust/core

[![npm version](https://img.shields.io/npm/v/@dust/core)](https://www.npmjs.com/package/@dust/core)

Core cryptographic primitives for the Dust Protocol. This package provides the foundational types, Poseidon hashing, ZK note operations, key derivation, chain configuration, and provider abstractions used by all other Dust packages.

## Installation

```bash
npm install @dust/core
```

## Quick Start

```typescript
import {
  fromRpcUrl,
  deriveV2Keys,
  createNote,
  computeNoteCommitment,
  getChainConfig,
} from '@dust/core'

// Connect to a chain
const provider = fromRpcUrl('https://sepolia.drpc.org')

// Derive ZK keys (spending key + nullifier key)
const keys = await deriveV2Keys(walletSignature, '123456')

// Create a note for a deposit
const ownerPubKey = await computeOwnerPubKey(keys.spendingKey)
const note = createNote(ownerPubKey, 100000000000000000n, assetId, 11155111)
const commitment = await computeNoteCommitment(note)
```

## API Reference

### Provider Adapters

`DustProvider` is a chain-agnostic interface that wraps any Ethereum provider. All pool and stealth operations accept a `DustProvider`.

#### `fromRpcUrl(url: string): DustProvider`

Create a provider from a raw JSON-RPC endpoint. Uses `fetch` internally — works in both Node.js and browsers.

```typescript
import { fromRpcUrl } from '@dust/core'

const provider = fromRpcUrl('https://sepolia.drpc.org')
const chainId = await provider.getChainId() // 11155111
const balance = await provider.getBalance('0xYourAddress')
```

#### `fromEthers(provider: EthersProvider, signer?: EthersSigner): DustProvider`

Wrap an ethers.js v5 provider (and optional signer).

```typescript
import { ethers } from 'ethers'
import { fromEthers } from '@dust/core'

const ethersProvider = new ethers.providers.JsonRpcProvider('https://sepolia.drpc.org')
const signer = ethersProvider.getSigner()
const provider = fromEthers(ethersProvider, signer)
```

#### `fromViem(client: ViemPublicClient, wallet?: ViemWalletClient): DustProvider`

Wrap a viem `PublicClient` (and optional `WalletClient`).

```typescript
import { createPublicClient, createWalletClient, http } from 'viem'
import { sepolia } from 'viem/chains'
import { fromViem } from '@dust/core'

const publicClient = createPublicClient({ chain: sepolia, transport: http() })
const provider = fromViem(publicClient)
```

#### `DustProvider` interface

```typescript
interface DustProvider {
  getChainId(): Promise<number>
  call(to: Address, data: `0x${string}`): Promise<`0x${string}`>
  sendTransaction(tx: TransactionRequest): Promise<string>
  getLogs(filter: LogFilter): Promise<Log[]>
  waitForTransaction(hash: string): Promise<TransactionReceipt>
  getBlockNumber(): Promise<number>
  getBalance(address: Address): Promise<bigint>
}
```

---

### Key Derivation

#### `deriveV2Keys(signature: string, pin: string): Promise<V2Keys>`

Derive the ZK spending and nullifier keys from a wallet signature and 6-digit PIN. Uses PBKDF2 with 100,000 iterations and domain-separated salts. Keys are reduced modulo BN254.

```typescript
import { deriveV2Keys } from '@dust/core'

const keys = await deriveV2Keys(walletSignature, '123456')
// keys.spendingKey  — bigint, used to compute ownerPubKey
// keys.nullifierKey — bigint, used to compute nullifiers
```

#### `deriveSpendingSeed(signature: string, pin: string): Promise<string>`

Derive only the spending seed (32-byte hex string) without key reduction. Used by `@dust/stealth` for stealth key derivation.

#### `deriveViewingSeed(signature: string, pin: string): Promise<string>`

Derive only the viewing/nullifier seed (32-byte hex string).

---

### Poseidon Hashing

#### `poseidonHash(inputs: bigint[]): Promise<bigint>`

Compute a Poseidon hash over BN254. Lazily initializes `circomlibjs` on first call.

```typescript
import { poseidonHash } from '@dust/core'

const hash = await poseidonHash([1n, 2n, 3n])
```

---

### Note Operations

A *note* is the fundamental private UTXO unit: `{ owner, amount, asset, chainId, blinding }`.

#### `createNote(owner: bigint, amount: bigint, asset: bigint, chainId: number): NoteV2`

Create a new note with a cryptographically random blinding factor.

```typescript
import { createNote } from '@dust/core'

const note = createNote(ownerPubKey, 100000000000000000n, assetId, 11155111)
```

#### `createDummyNote(): NoteV2`

Create a zero-value dummy note. Required when a 2-input circuit has only one real input.

#### `isDummyNote(note: NoteV2): boolean`

Returns `true` if `note.amount === 0n`.

#### `generateBlinding(): bigint`

Generate a uniformly random blinding factor within BN254.

---

### Commitment and Nullifier

#### `computeNoteCommitment(note: NoteV2): Promise<bigint>`

Compute the Poseidon commitment: `Poseidon(owner, amount, asset, chainId, blinding)`.

```typescript
import { computeNoteCommitment } from '@dust/core'

const commitment = await computeNoteCommitment(note)
```

#### `computeNullifier(nullifierKey: bigint, commitment: bigint, leafIndex: number): Promise<bigint>`

Compute a nullifier: `Poseidon(nullifierKey, commitment, leafIndex)`. Spending a note reveals this nullifier, preventing double-spends.

#### `computeAssetId(chainId: number, tokenAddress: string): Promise<bigint>`

Compute the asset identifier: `Poseidon(chainId, tokenAddress)`. Used to identify which token a note represents.

```typescript
import { computeAssetId, ETH_ADDRESS } from '@dust/core'

// Native ETH
const ethAssetId = await computeAssetId(11155111, ETH_ADDRESS)

// ERC-20 token
const usdcAssetId = await computeAssetId(11155111, '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238')
```

#### `computeOwnerPubKey(spendingKey: bigint): Promise<bigint>`

Compute the owner public key: `Poseidon(spendingKey)`. This is stored in notes as the `owner` field.

---

### Chain Configuration

Chains are registered globally. Ethereum Sepolia (11155111), Arbitrum Sepolia (421614), and Base Sepolia (84532) are registered automatically when `@dust/core` is imported.

#### `registerChain(config: ChainConfig): void`

Register a custom chain. Call this before any chain-specific operations.

```typescript
import { registerChain } from '@dust/core'

registerChain({
  id: 1,
  name: 'Ethereum Mainnet',
  rpcUrl: 'https://eth.drpc.org',
  rpcUrls: ['https://eth.drpc.org'],
  nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
  blockExplorerUrl: 'https://etherscan.io',
  contracts: { /* ... */ },
  creationCodes: { /* ... */ },
  deploymentBlock: 0,
  dustPoolDeploymentBlock: null,
  supportsEIP7702: false,
  canonicalForNaming: false,
  testnet: false,
})
```

#### `getChainConfig(chainId?: number): ChainConfig`

Get the config for a chain. Throws if the chain is not registered. Defaults to chain ID 11155111 when `chainId` is omitted.

#### `getSupportedChains(): ChainConfig[]`

List all registered chains.

#### `isChainSupported(chainId: number): boolean`

Returns `true` if the chain has been registered.

#### `getCanonicalNamingChain(): ChainConfig`

Returns the chain marked as `canonicalForNaming: true` (Ethereum Sepolia). .dust names are resolved on this chain when the active chain has no name registry.

#### `getMinClaimableBalance(chainId: number): number`

Minimum ETH balance (in ETH) required to claim on a chain. Returns `0.0000001` for L2s (Arbitrum, Optimism, Base) and `0.0001` for L1s.

#### `DEFAULT_CHAIN_ID`

`11155111` (Ethereum Sepolia).

---

### Error Classes

All Dust errors extend `DustError extends Error`. Catch specific subtypes for precise error handling.

```typescript
import { DustError, ProviderError, ProofError, RelayerError, extractRelayerError, errorToUserMessage } from '@dust/core'

try {
  await provider.sendTransaction(tx)
} catch (e) {
  if (e instanceof ProviderError) {
    console.error('RPC error:', e.message)
  }
}
```

| Class | When thrown |
|-------|-------------|
| `DustError` | Base class for all Dust errors |
| `ProviderError` | RPC call failures |
| `ProofError` | FFLONK proof generation or calldata parsing failures |
| `StorageError` | Note storage read/write failures |
| `RelayerError` | Relayer HTTP errors (includes `.status` and `.body`) |
| `KeyDerivationError` | Key derivation produces a zero key |
| `ComplianceError` | Compliance screening failures |

#### `extractRelayerError(e: unknown, fallback: string): string`

Extract a human-readable error message from a `RelayerError`, parsing the JSON body when present.

#### `errorToUserMessage(raw: string): string`

Map a raw error string to a user-friendly message. Covers common patterns like insufficient balance, unknown Merkle root, and user rejection.

---

### Constants

```typescript
import {
  BN254_FIELD_SIZE,   // 21888242871839275222246405745257275088548364400416034343698204186575808495617n
  TREE_DEPTH,         // 20
  MAX_AMOUNT,         // (1n << 64n) - 1n — maximum note amount
  ZERO_VALUE,         // 0n
  COMPLIANCE_COOLDOWN_THRESHOLD_USD, // 10_000
  SCHEME_ID,          // { SECP256K1: 1 }
  ETH_ADDRESS,        // '0x0000000000000000000000000000000000000000'
} from '@dust/core'
```

---

### Types

```typescript
// ZK-UTXO note
interface NoteV2 {
  owner: bigint       // Poseidon(spendingKey)
  amount: bigint      // token amount in smallest unit
  asset: bigint       // Poseidon(chainId, tokenAddress)
  chainId: number
  blinding: bigint    // random field element
}

// Stored note with serialization-safe string fields
interface StoredNoteV2 {
  id: string          // hex commitment
  walletAddress: string
  chainId: number
  commitment: string
  owner: string
  amount: string
  asset: string
  blinding: string
  leafIndex: number   // -1 while pending
  spent: boolean
  createdAt: number   // unix ms
  status?: 'pending' | 'confirmed'
  complianceStatus?: 'unverified' | 'verified' | 'inherited'
  blockNumber?: number
}

// V2 ZK keys
interface V2Keys {
  spendingKey: bigint
  nullifierKey: bigint
}

// View key for selective disclosure
interface ViewKey {
  ownerPubKey: bigint
  nullifierKey: bigint
}

// View key scoped to a block range
interface ScopedViewKey extends ViewKey {
  startBlock: number
  endBlock: number
}

// Chain configuration
interface ChainConfig {
  id: number
  name: string
  rpcUrl: string
  rpcUrls: string[]
  nativeCurrency: { name: string; symbol: string; decimals: number }
  blockExplorerUrl: string
  contracts: ChainContracts
  creationCodes: ChainCreationCodes
  deploymentBlock: number
  dustPoolDeploymentBlock: number | null
  supportsEIP7702: boolean
  canonicalForNaming: boolean
  testnet: boolean
}
```

See `src/types.ts` for the complete `ChainContracts`, `ProofInputs`, `SplitProofInputs`, and `ProofResult` types.

## License

MIT