Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

@enc-protocol/client — API Reference

Complete API reference for @enc-protocol/client@0.2.0, the network client package for the ENC Protocol. Provides HTTP, WebSocket, session management, wallet utilities, and a high-level SDK interface.

Registry: https://npm-registry.ocrybit.workers.dev/ Production node: https://enc-node.ocrybit.workers.dev

Installation

npm install @enc-protocol/client --registry https://npm-registry.ocrybit.workers.dev/

Peer dependency:

npm install @enc-protocol/core --registry https://npm-registry.ocrybit.workers.dev/

Package Structure

@enc-protocol/client
  index.js              Re-exports everything from all modules
  http.js               NodeClient — HTTP client for node communication
  ws.js                 NodeWebSocket — WebSocket client for real-time streaming
  session.js            SessionManager — auto-refreshing session tokens
  sdk.js                High-level SDK functions, factory exports, state management
  registry-client.js    RegistryClient — registry node lookups
  wallet.js             ENC pubkey to EVM address utilities

Each module is importable individually:

import { NodeClient } from '@enc-protocol/client/http.js'
import { NodeWebSocket } from '@enc-protocol/client/ws.js'

Or import everything from the barrel:

import { NodeClient, createIdentity, SessionManager } from '@enc-protocol/client'

http.js — NodeClient

HTTP client for communicating with an ENC node. All requests go through a single multiplexed POST / endpoint (except STH and liveness). Supports both ECDH-encrypted and plaintext modes.

Constructor

import { NodeClient } from '@enc-protocol/client/http.js'
 
const client = new NodeClient(baseUrl, opts?)
Parameters:
ParameterTypeDescription
baseUrlstringNode base URL (trailing slash stripped automatically)
opts.enclaveIdstringEnclave ID (64 hex) for ECDH requests
opts.identityPubHexstringIdentity public key (64 hex)
opts.seqPubHexstringSequencer public key (64 hex)
opts.sessionManagerSessionManagerSession manager instance for ECDH

Plaintext mode (no opts or partial opts):

const client = new NodeClient('https://enc-node.ocrybit.workers.dev')

Encrypted mode (all four opts provided):

import { SessionManager } from '@enc-protocol/client/session.js'
 
const sm = new SessionManager(myPrivateKey)
const client = new NodeClient('https://enc-node.ocrybit.workers.dev', {
  enclaveId: '...',
  identityPubHex: '...',
  seqPubHex: '...',
  sessionManager: sm,
})

.encrypted (getter)

Returns true when all four ECDH credentials are configured.

client.encrypted → boolean

.submitCommit(commit)

Submit a signed commit to the node.

client.submitCommit(commit: Object) → Promise<Object>

Sends POST / with the commit JSON. The node detects commits by the presence of the exp field.

Returns a receipt object on success:

{
  type: 'Receipt',
  seq: 0,
  id: '...',          // 64 hex event ID
  hash: '...',        // 64 hex commit hash
  timestamp: 1234567, // ms
  sig: '...',         // 128 hex author signature
  seq_sig: '...',     // 128 hex sequencer signature
  sequencer: '...',   // 64 hex sequencer pubkey
}

Or an error:

{
  type: 'Error',
  code: 'UNAUTHORIZED' | 'EXPIRED' | 'DUPLICATE' | ...,
  message: '...',
}
import { mkCommit, signCommit } from '@enc-protocol/core/event.js'
 
const commit = mkCommit(enclaveId, pubHex, 'post', '{"body":"hi"}', Date.now() + 300000, [])
const signed = signCommit(commit, privateKey)
const receipt = await client.submitCommit(signed)
console.log(receipt.id) // event ID

.query(filter, auth?)

Query events. Uses ECDH encryption when credentials are configured, plaintext otherwise.

client.query(filter: Object, auth?: Object) → Promise<Object>
Encrypted mode:

The filter is encrypted with ECDH (label: 'enc:query'). The request sends:

{ "type": "Query", "enclave": "...", "from": "...", "content": "session.ciphertext" }

The response content is decrypted with label 'enc:response'.

Plaintext mode:
const result = await client.query(
  { enclave: enclaveId, type: 'post', limit: 50 },
  { identity: pubHex, session: sessionToken }  // optional auth
)
Filter fields:
FieldTypeDescription
enclavestringEnclave ID (required in plaintext mode)
typestringEvent type filter
fromstringAuthor pubkey filter
idstringSpecific event ID
seqnumberSpecific sequence number
timestampnumberTimestamp filter
limitnumberMax events to return
reversebooleanReverse order

.pull(afterSeq, opts?)

Pull events after a sequence number. Uses ECDH encryption when configured.

client.pull(afterSeq: number, opts?: Object) → Promise<Object>
Parameters:
ParameterTypeDescription
afterSeqnumberPull events after this seq (-1 for all)
opts.identitystringIdentity pubkey (plaintext mode)
opts.sessionstringSession token (plaintext mode)
opts.limitnumberMax events
Returns:
{
  type: 'Events',
  events: [...],     // array of event objects
}
// Pull all events from the beginning
const result = await client.pull(-1, { limit: 100 })
for (const event of result.events) {
  console.log(event.seq, event.type, event.content)
}

.info()

Get enclave info. Requires ECDH credentials.

client.info() → Promise<Object>

Returns { type: 'Error', code: 'NO_CREDENTIALS', message: '...' } without ECDH credentials.

.getSTH(enclaveId?)

Get the Signed Tree Head for an enclave.

client.getSTH(enclaveId?: string) → Promise<{ t: number, ts: number, r: string, sig: string }>

Sends GET /:enclaveId/sth. Uses the configured enclaveId, the provided parameter, or '_' as fallback.

Response fields:
FieldTypeDescription
tnumberTree size (number of leaves)
tsnumberTimestamp
rstringRoot hash (64 hex)
sigstringSchnorr signature (128 hex)
import { verifySTH, hexToBytes } from '@enc-protocol/core/crypto.js'
 
const sth = await client.getSTH()
const valid = verifySTH(sth.t, sth.ts, hexToBytes(sth.r), sth.sig, hexToBytes(seqPubHex))

.getInclusion(leafIndex)

Get an inclusion proof for a leaf (event). Uses ECDH when configured.

client.getInclusion(leafIndex: number) → Promise<Object>

Encrypted mode: sends POST /inclusion with encrypted content containing { leaf_index }.

Plaintext mode: sends POST /inclusion with { seq: leafIndex }.

.getState(key)

Get a state proof for a key. Uses ECDH when configured.

client.getState(key: string) → Promise<Object>

Encrypted mode: sends POST /state with encrypted content containing { key }.

Plaintext mode: sends POST /state with { key }.

// Get RBAC state for an identity
const result = await client.getState(`rbac:${pubHex}`)

.pingLiveness()

Check if the node is alive via CORS preflight.

client.pingLiveness() → Promise<boolean>

Sends OPTIONS /. Returns true if the node responds with HTTP 204.


ws.js — NodeWebSocket

WebSocket client for real-time event streaming. Follows a Nostr-like protocol with typed JSON messages.

Constructor

import { NodeWebSocket } from '@enc-protocol/client/ws.js'
 
const ws = new NodeWebSocket(url)
ParameterTypeDescription
urlstringWebSocket URL (ws:// or wss://)

.on(event, handler)

Register an event handler. Returns this for chaining.

ws.on(event: string, handler: Function) → NodeWebSocket
Events:
EventHandler SignatureDescription
'event'(event, subId)New event received
'eose'(subId)End of stored events for subscription
'receipt'(receipt)Commit receipt (after submitting via WS)
'error'({ code, message })Error from server
'closed'(subId, reason)Subscription closed by server
'notice'(msg)Server notice (unrecognized message type)
'open'()WebSocket connection opened
'close'(code, reason)WebSocket connection closed

.connect()

Open the WebSocket connection. Returns this for chaining.

ws.connect() → NodeWebSocket

.close()

Close the WebSocket connection.

ws.close()

.connected (getter)

Check if the WebSocket is open.

ws.connected → boolean

.subscribe(enclave, from, session, filter?)

Subscribe to events on an enclave.

ws.subscribe(
  enclave: string,       // enclave ID (64 hex)
  from: string | null,   // identity pubkey for auth (or null)
  session: string | null, // session token (or null)
  filter?: Object        // { type?, from?, id?, seq?, timestamp?, limit? }
) → null

Sends a Query message over the WebSocket. The subscription ID is assigned by the server and delivered via 'event' and 'eose' callbacks. Returns null (not the sub ID).

.unsubscribe(subId)

Close a subscription.

ws.unsubscribe(subId: string)

Sends a Close message with the subscription ID.

.commit(commit)

Submit a signed commit via WebSocket.

ws.commit(commit: Object)

Sends the raw signed commit JSON. The server detects it by the exp field. The receipt is delivered via the 'receipt' event handler.

Throws Error('WebSocket not connected') if not connected.

Full WebSocket Example

import { NodeWebSocket } from '@enc-protocol/client/ws.js'
import { generateKeypair, bytesToHex, generateSession } from '@enc-protocol/core/crypto.js'
import { mkCommit, signCommit } from '@enc-protocol/core/event.js'
 
const kp = generateKeypair()
const pub = bytesToHex(kp.publicKey)
const { session } = generateSession(kp.privateKey)
 
const ws = new NodeWebSocket('wss://enc-node.ocrybit.workers.dev/ws')
 
ws.on('open', () => {
  console.log('Connected')
 
  // Subscribe to all events
  ws.subscribe(enclaveId, pub, session, {})
})
 
ws.on('event', (event, subId) => {
  console.log(`[${subId}] Event ${event.seq}: ${event.type}`)
  console.log('Content:', event.content)
})
 
ws.on('eose', (subId) => {
  console.log(`[${subId}] End of stored events — now streaming live`)
})
 
ws.on('receipt', (receipt) => {
  console.log('Commit accepted, event ID:', receipt.id)
})
 
ws.on('error', (err) => {
  console.error('Error:', err.code, err.message)
})
 
ws.on('close', (code, reason) => {
  console.log('Disconnected:', code, reason)
})
 
ws.connect()
 
// Submit a commit after connection
setTimeout(() => {
  const commit = mkCommit(enclaveId, pub, 'post', '{"body":"via ws"}', Date.now() + 300000, [])
  const signed = signCommit(commit, kp.privateKey)
  ws.commit(signed)
}, 1000)

session.js — SessionManager

Auto-refreshing session token manager. Generates cryptographic session tokens and automatically refreshes them before expiry.

Constructor

import { SessionManager } from '@enc-protocol/client/session.js'
 
const sm = new SessionManager(identityPriv, opts?)
ParameterTypeDefaultDescription
identityPrivUint8Arrayrequired32-byte identity private key
opts.durationnumber3600Session duration in seconds (capped at 7200)
opts.refreshBeforenumber300Refresh this many seconds before expiry

.getSession()

Get a valid session, auto-refreshing if needed.

sm.getSession() → {
  session: string,         // 136 hex char session token
  sessionPriv: Uint8Array, // 32-byte session private key
  expires: number          // Unix timestamp (seconds)
}

Auto-refreshes when now >= expires - refreshBefore.

.token (getter)

Get just the session token string (calls getSession() internally).

sm.token → string  // 136 hex chars

.valid (getter)

Check if the current session is still valid (not expired).

sm.valid → boolean

Returns false if no session has been generated yet.

.refresh()

Force immediate session refresh.

sm.refresh()

Example

import { SessionManager } from '@enc-protocol/client/session.js'
import { generateKeypair } from '@enc-protocol/core/crypto.js'
 
const kp = generateKeypair()
const sm = new SessionManager(kp.privateKey, {
  duration: 3600,      // 1 hour sessions
  refreshBefore: 300,  // refresh 5 min before expiry
})
 
// First call generates a session
const { session, sessionPriv, expires } = sm.getSession()
console.log(sm.valid) // true
 
// Subsequent calls return cached session (until refresh needed)
const same = sm.getSession()
console.log(same.session === session) // true (same token)
 
// Force refresh
sm.refresh()
const fresh = sm.getSession()
console.log(fresh.session === session) // false (new token)

sdk.js — High-Level SDK

High-level functions for identity management, connection state, commit creation, and factory constructors. Re-exports key crypto primitives for convenience.

Identity Management

createIdentity(seed?)

Create a new identity, optionally from a seed.

createIdentity(seed?: Uint8Array) → {
  privateKey: Uint8Array,
  publicKey: Uint8Array,
  publicKeyHex: string
}
  • Without seed: generates a random keypair
  • With seed: derives the private key as sha256(seed)
import { createIdentity } from '@enc-protocol/client/sdk.js'
 
// Random identity
const alice = createIdentity()
 
// Deterministic identity from seed
const bob = createIdentity(new TextEncoder().encode('bob-seed-phrase'))
console.log(bob.publicKeyHex) // always the same for same seed

loadIdentity(privateKey)

Load an identity from an existing private key.

loadIdentity(privateKey: Uint8Array) → {
  privateKey: Uint8Array,
  publicKey: Uint8Array,
  publicKeyHex: string
}

signWithIdentity(identity, data)

Sign data with an identity's private key (Schnorr).

signWithIdentity(identity: Object, data: Uint8Array) → Uint8Array(64)

Connection State

ConnectionStatus

Frozen enum of connection states.

import { ConnectionStatus } from '@enc-protocol/client/sdk.js'
 
ConnectionStatus.disconnected  // 'disconnected'
ConnectionStatus.connecting    // 'connecting'
ConnectionStatus.connected     // 'connected'
ConnectionStatus.error         // 'error'

createConnection(host, port, nodePubHex)

Create a connection config object.

createConnection(host: string, port: number, nodePubHex: string) → {
  host: string,
  port: number,
  nodePubHex: string,
  status: 'disconnected'
}

connect(conn) / disconnect(conn)

Update connection status (pure functions, return new objects).

connect(conn: Object) → Object     // { ...conn, status: 'connected' }
disconnect(conn: Object) → Object  // { ...conn, status: 'disconnected' }

isConnected(conn)

isConnected(conn: Object) → boolean

Commit Creation

createCommit(identity, enclave, type, content, exp?, tags?)

Create and sign a commit in one call.

createCommit(
  identity: Object,    // { privateKey, publicKey, publicKeyHex }
  enclave: string,     // enclave ID (64 hex)
  type: string,        // event type
  content: string,     // JSON string content
  exp?: number,        // expiration ms (default: now + 5 min)
  tags?: string[][]    // tags (default: [])
) → Object            // signed commit (has sig field)
import { createIdentity, createCommit } from '@enc-protocol/client/sdk.js'
 
const id = createIdentity()
const signed = createCommit(id, enclaveId, 'post', '{"body":"hi"}')
// signed is ready to submit to a node

createManifestCommit(identity, manifestContent, exp?, tags?)

Create and sign a manifest commit with a derived enclave ID.

createManifestCommit(
  identity: Object,          // { privateKey, publicKey, publicKeyHex }
  manifestContent: string,   // manifest JSON string
  exp?: number,              // expiration (default: now + 5 min)
  tags?: string[][]          // tags (default: [])
) → Object                  // signed commit with derived enclave
const manifest = JSON.stringify({
  enc_v: 2, nonce: Date.now(),
  RBAC: {
    use_temp: 'none',
    schema: [{ event: '*', role: 'Public', ops: ['R', 'C'] }],
    states: [], traits: [],
    initial_state: {},
  },
})
 
const signed = createManifestCommit(id, manifest)
console.log(signed.enclave) // derived enclave ID

buildRegEnclaveContent(manifestEvent, opts?)

Build content for a registry Reg_Enclave commit.

buildRegEnclaveContent(
  manifestEvent: Object,   // full finalized manifest event
  opts?: {
    app?: string,          // application name
    desc?: string,         // description
    meta?: Object,         // arbitrary metadata
    owner_proof?: string,  // ownership proof
  }
) → string               // JSON string for Reg_Enclave content

Client State Management

Functional state management for SDK client lifecycle.

SubmitResult

SubmitResult.success       // 'success'
SubmitResult.notConnected  // 'notConnected'
SubmitResult.error         // 'error'

initClient(identity)

Initialize client state.

initClient(identity: Object) → {
  identity: Object,
  connection: null,
  cachedSTH: null,
  pendingReceipts: []
}

connectClient(state, host, port, nodePubHex)

Connect client to a node (updates state).

connectClient(state, host, port, nodePubHex) → Object  // new state with connected connection

disconnectClient(state)

Disconnect client.

disconnectClient(state) → Object  // new state with disconnected connection

clientIsConnected(state)

clientIsConnected(state) → boolean

getCachedSTH(state) / cacheSTH(state, sth, timestamp)

Manage cached Signed Tree Head.

getCachedSTH(state) → Object | null
cacheSTH(state, sth, timestamp) → Object  // new state with cached STH

Receipt Verification

verifyNodeReceipt(receipt, nodePubHex)

Verify a receipt from the node by checking the sequencer's co-signature.

verifyNodeReceipt(receipt: Object, nodePubHex: string) → boolean
import { verifyNodeReceipt } from '@enc-protocol/client/sdk.js'
 
const receipt = await client.submitCommit(signed)
const valid = verifyNodeReceipt(receipt, seqPubHex)

Health Check

healthCheck(state)

healthCheck(state) → { connected: true, latency: 0, version: '1.0.0' } | null

Returns null if not connected.

SDK Interface Metadata

SDKInterface / SDKOperation

Enums describing the SDK's operation categories.

SDKInterface.node_api      // 'node_api'
SDKInterface.registry_api  // 'registry_api'
SDKInterface.local_api     // 'local_api'
 
SDKOperation.createIdentity       // 'createIdentity'
SDKOperation.loadIdentity         // 'loadIdentity'
SDKOperation.derivePublicKey      // 'derivePublicKey'
SDKOperation.generatePrivateKey   // 'generatePrivateKey'
SDKOperation.signWithIdentity     // 'signWithIdentity'
SDKOperation.createCommit         // 'createCommit'
SDKOperation.submitCommit         // 'submitCommit'
SDKOperation.queryEvents          // 'queryEvents'
SDKOperation.verifyNodeReceipt    // 'verifyNodeReceipt'
SDKOperation.verifyLogInclusion   // 'verifyLogInclusion'
SDKOperation.verifyLogConsistency // 'verifyLogConsistency'
SDKOperation.cacheSTH             // 'cacheSTH'
SDKOperation.healthCheck          // 'healthCheck'
SDKOperation.lookupNode           // 'lookupNode'
SDKOperation.lookupEnclave        // 'lookupEnclave'
SDKOperation.resolveEnclave       // 'resolveEnclave'

sdkOperationInterface(op)

Map an operation to its interface category.

sdkOperationInterface(op: string) → string | null
// 'createIdentity' → 'local_api'
// 'submitCommit'   → 'node_api'
// 'lookupNode'     → 'registry_api'

allSDKOperations

Frozen array of all operation names.

allSDKOperations → string[]  // all 16 operation names

Factory Functions

Convenience constructors for all client classes.

createNodeClient(url)

createNodeClient(url: string) → NodeClient

createRegistryClient(url)

createRegistryClient(url: string) → RegistryClient

createWebSocket(url, opts?)

createWebSocket(url: string, opts?: Object) → NodeWebSocket

createSessionManager(identityPriv, opts?)

createSessionManager(identityPriv: Uint8Array, opts?: Object) → SessionManager

Re-exports

sdk.js re-exports the following from @enc-protocol/core/crypto.js:

export {
  hexToBytes, bytesToHex, derivePublicKey, schnorrSign,
  generateSession, sha256Hash, taggedHash,
  computeContentHash, computeCommitHash, generateKeypair,
  ecdh, deriveKey, encrypt, decrypt,
} from '@enc-protocol/core/crypto.js'

And re-exports all client classes:

export { NodeClient, NodeWebSocket, RegistryClient, SessionManager }

registry-client.js — RegistryClient

Client for the ENC registry node. Used for enclave discovery and node lookup.

Constructor

import { RegistryClient } from '@enc-protocol/client/registry-client.js'
 
const registry = new RegistryClient(registryUrl)

.lookupNode(seqPub)

Look up a node by its sequencer public key.

registry.lookupNode(seqPub: string) → Promise<Object | null>

Sends GET /nodes/:seqPub. Returns null on 404.

.lookupEnclave(enclaveId)

Look up an enclave by ID.

registry.lookupEnclave(enclaveId: string) → Promise<Object | null>

Sends GET /enclaves/:enclaveId. Returns null on 404.

.resolveEnclave(enclaveId)

Resolve an enclave to its hosting node and endpoints.

registry.resolveEnclave(enclaveId: string) → Promise<{ enclave: Object, node: Object } | null>

Sends GET /resolve/:enclaveId. Returns null on 404.

.listNodes()

List all active nodes.

registry.listNodes() → Promise<Object[]>

.lookupIdentity(pubkey)

Look up an identity.

registry.lookupIdentity(pubkey: string) → Promise<Object | null>

.listIdentities()

List all active identities.

registry.listIdentities() → Promise<Object[]>

.connectToEnclave(enclaveId)

Resolve an enclave and create a NodeClient pointing to its hosting node.

registry.connectToEnclave(enclaveId: string) → Promise<NodeClient | null>

Returns null if the enclave is not found or has no endpoints.

const client = await registry.connectToEnclave(enclaveId)
if (client) {
  const sth = await client.getSTH()
  console.log('Tree size:', sth.t)
}

.submitCommit(commit)

Submit a commit to the registry node.

registry.submitCommit(commit: Object) → Promise<Object>

.query(filter)

Query events on the registry.

registry.query(filter: Object) → Promise<Object>

.pingLiveness()

registry.pingLiveness() → Promise<boolean>

wallet.js

Utilities for converting ENC secp256k1 x-only public keys to EVM (Ethereum) addresses.

The Y-Parity Problem

ENC uses x-only public keys (32 bytes). An x-coordinate corresponds to two possible full public keys (even-y and odd-y), which produce two different EVM addresses. These functions help resolve the ambiguity.

encPubToEvmAddress(encPubHex)

Convert an ENC x-only public key to both possible EVM addresses.

encPubToEvmAddress(encPubHex: string) → [string, string]
// Returns [evenYAddress, oddYAddress]

Each address is a checksumless 0x-prefixed 40-hex-char Ethereum address, derived via keccak256(uncompressedPubkey).

import { encPubToEvmAddress } from '@enc-protocol/client/wallet.js'
 
const [even, odd] = encPubToEvmAddress('abcd1234...')
console.log(even)  // '0x...' — address assuming even y-coordinate
console.log(odd)   // '0x...' — address assuming odd y-coordinate

resolveEvmAddress(encPubHex, rpcUrl)

Resolve the correct EVM address by checking on-chain balances.

resolveEvmAddress(encPubHex: string, rpcUrl: string) → Promise<string>

Calls eth_getBalance on both possible addresses. Returns the one with the higher balance. Falls back to the even-y address on error or equal balances.

import { resolveEvmAddress } from '@enc-protocol/client/wallet.js'
 
const addr = await resolveEvmAddress(pubHex, 'https://eth.llamarpc.com')
console.log(addr) // '0x...' — the address with higher balance

Complete Example: Identity to Proof Verification

End-to-end flow using the high-level SDK: create identity, create enclave, submit events, verify proofs.

import {
  createIdentity, createManifestCommit, createCommit,
  createNodeClient, createSessionManager, verifyNodeReceipt,
} from '@enc-protocol/client'
import { verifySTH, hexToBytes, verifySession } from '@enc-protocol/core/crypto.js'
import { verifyEvent } from '@enc-protocol/core/event.js'
import { verify, wireToProof, buildRBACKey, decodeRoleBitmask } from '@enc-protocol/core/smt.js'
import { verifyInclusionProof } from '@enc-protocol/core/ct.js'
import { isOwner } from '@enc-protocol/core/rbac.js'
 
const NODE = 'https://enc-node.ocrybit.workers.dev'
 
// ── 1. Create identity ──
const id = createIdentity()
console.log('Public key:', id.publicKeyHex)
 
// ── 2. Create enclave ──
const manifest = JSON.stringify({
  enc_v: 2,
  nonce: Date.now(),
  RBAC: {
    use_temp: 'none',
    schema: [
      { event: 'post', role: 'owner', ops: ['C', 'U', 'D'] },
      { event: '*', role: 'Public', ops: ['R'] },
    ],
    states: [],
    traits: ['owner(0)'],
    initial_state: { owner: [id.publicKeyHex] },
  },
})
 
const manifestCommit = createManifestCommit(id, manifest)
const client = createNodeClient(NODE)
const createResult = await client.submitCommit(manifestCommit)
 
const enclaveId = manifestCommit.enclave
const seqPubHex = createResult.sequencer
console.log('Enclave:', enclaveId)
 
// ── 3. Submit a post ──
const postCommit = createCommit(id, enclaveId, 'post', JSON.stringify({ body: 'hello world' }))
const receipt = await client.submitCommit(postCommit)
 
// Verify the receipt
const receiptValid = verifyNodeReceipt(receipt, seqPubHex)
console.log('Receipt valid:', receiptValid)
 
// ── 4. Set up encrypted client ──
const sm = createSessionManager(id.privateKey, { duration: 3600 })
const encClient = new (await import('@enc-protocol/client/http.js')).NodeClient(NODE, {
  enclaveId,
  identityPubHex: id.publicKeyHex,
  seqPubHex,
  sessionManager: sm,
})
console.log('Encrypted mode:', encClient.encrypted) // true
 
// ── 5. Verify Signed Tree Head ──
const sth = await client.getSTH(enclaveId)
const sthValid = verifySTH(sth.t, sth.ts, hexToBytes(sth.r), sth.sig, hexToBytes(seqPubHex))
console.log('STH valid:', sthValid, '| Tree size:', sth.t)
 
// ── 6. Pull events and verify each ──
const pullResult = await client.pull(-1, { enclave: enclaveId, limit: 100 })
for (const event of pullResult.events) {
  const ok = verifyEvent(event)
  console.log(`  seq=${event.seq} type=${event.type} verified=${ok}`)
}
 
// ── 7. Verify inclusion proof ──
const inclResult = await encClient.getInclusion(0)
if (inclResult.ct_proof) {
  const path = inclResult.ct_proof.p.map(h => hexToBytes(h))
  const leafHash = hexToBytes(inclResult.leaf_hash)
  const inclValid = verifyInclusionProof(leafHash, 0, sth.t, path, hexToBytes(sth.r))
  console.log('Inclusion proof valid:', inclValid)
}
 
// ── 8. Verify RBAC state proof ──
const stateResult = await encClient.getState(`rbac:${id.publicKeyHex}`)
if (stateResult.proof) {
  const proof = wireToProof(stateResult.proof)
  const smtRoot = hexToBytes(stateResult.smt_root)
  const proofValid = verify(proof, smtRoot)
  console.log('State proof valid:', proofValid)
 
  if (proof.value) {
    const bitmask = decodeRoleBitmask(proof.value)
    console.log('Is owner:', isOwner(bitmask))
  }
}

Encrypted vs Plaintext Summary

FeaturePlaintextEncrypted (ECDH)
submitCommitAlways plaintextAlways plaintext
queryDirect JSON filterSession-encrypted filter + response
pullDirect JSONSession-encrypted
infoNot availableSession-encrypted
getSTHAlways plaintext GETAlways plaintext GET
getInclusionPlaintext POSTSession-encrypted
getStatePlaintext POSTSession-encrypted
pingLivenessAlways OPTIONSAlways OPTIONS

ECDH encryption uses:

  • Request label: 'enc:query' (HKDF info)
  • Response label: 'enc:response' (HKDF info)
  • Wire format: "session_hex.ciphertext_base64" in the content field
  • Key derivation: deriveSignerPriv(sessionPriv, sessionPub, seqPub, enclaveId) then ecdh(signerPriv, seqPub) then deriveKey(shared, label)