Crypto concepts · iwcrypto

The cryptography behind a confidential ledger.

How IronWeave hides every balance and amount while still letting anyone verify the math. You don't need any of this to use iwcrypto — but if you want to know what's happening under the hood, start here and read top to bottom.

01The confidential model

On IronWeave, an account's balance is never stored in the clear. Instead the chain holds a 32-byte commitment — a cryptographic blob that hides the balance but locks it in. Every operation ships zero-knowledge proofs that let a validator confirm the arithmetic is correct without ever seeing a number.

Four pieces work together throughout this page:

  • Commitments hide balances and amounts while remaining addable, so validators can check that value is conserved.
  • Proofs (equality & range) show the hidden numbers satisfy the rules — you know your balance, you're not going negative, the amount is positive.
  • Signatures (Ed25519) prove who authorized an operation.
  • Encryption (ECDH + AES) lets the owner — and only the owner — recover the secret blinding factors needed for future operations.
Every commitment, proof, and key in IronWeave is a Ristretto255 group element — a clean, prime-order abstraction over Curve25519. Equality of two points is just equality of their 32 bytes.

02Curves & Ristretto255

An elliptic curve gives you a set of points and a way to add them: draw a line through two points, it meets the curve at a third, reflect it — that's the sum. Adding a point to itself repeatedly is scalar multiplication, written n·P.

2·P = P + P 3·P = P + P + P given P and n·P, recovering n is infeasible ← ECDLP

That one-way property — the elliptic-curve discrete logarithm problem — is the foundation of everything. You can multiply forward easily but can't divide back. It's what makes a commitment hiding and a proof unforgeable.

Curve25519 → Ristretto255

IronWeave builds on Curve25519 (fast, conservatively designed, no suspicious constants — the same family behind Signal, SSH, and TLS). Raw Curve25519 has a cofactor of 8: a small subgroup of "bad" points that cause malleability and small-subgroup attacks.

Ristretto255 eliminates that cleanly. It defines a prime-order group on top of Curve25519 arithmetic with canonical encoding — every element has exactly one 32-byte representation, so no cofactor workarounds, no clamping, and equality is a byte comparison.

The two generators, G and H

Pedersen commitments use two independent points, G (the standard basepoint) and H (derived by hashing G through SHA3-512 onto the curve). Security requires that nobody knows a k with H = k·G — deriving H from a hash guarantees no one chose it with a known relationship to G.

Scalars are integers mod the group order (≈ 2²⁵²), so arithmetic wraps: 0 − 1 ≡ L − 1. A "negative" balance silently becomes a huge positive number — which is exactly why range proofs exist.

03The building blocks

Pedersen commitments — hiding a number in plain sight

Combine a secret value with a random blinding factor over G and H. The result is a single 32-byte point that looks like noise to everyone else.

C = value·G + blinding·H
  • Hiding. C reveals nothing about the value — infinitely many (value, blinding) pairs produce the same point.
  • Binding. Once committed, you can't find a different opening. You're locked in.
  • Homomorphic. Add two commitments and you get a commitment to the sum — so balances can be checked to add up without being revealed.
C₁ + C₂ = (v₁ + v₂)·G + (r₁ + r₂)·H ← add hidden numbers

The all-zero commitment — value 0, blinding 0 — is the identity point. Because everyone knows its opening, anyone can build proofs against it, which is how third parties send tokens to brand-new accounts.

Equality proofs — "these two boxes hold the same number"

Prove two commitments hide the same value without revealing it. If both hide the same value, their difference depends only on the blindings — the value terms cancel:

C_old − C_new = (r_old − r_new)·H

The prover shows they know that blinding difference with a Schnorr protocol (commit a random point, receive a challenge, respond). The whole proof is just 64 bytes — a 32-byte point plus a 32-byte scalar.

Range proofs — "my number isn't negative or astronomically large"

Prove a committed value lies in [0, 2⁶⁴) without revealing it. IronWeave uses Bulletproofs: decompose the value into bits, prove each bit is 0 or 1, and prove the bits reconstruct the committed value. No trusted setup, and about 1,350 bytes — far smaller than older schemes.

Without range proofs, someone could "spend" a negative amount that wraps into a huge positive number. A range proof forces honesty: a wrapped value has no valid 64-bit decomposition, so the proof fails.

Transcript binding — "this proof was made for THIS operation"

Every proof's Fiat–Shamir challenge is computed by hashing the public data together with five context fields. Reuse the proof anywhere the context differs and the hash changes, so it no longer verifies — replay is dead on arrival.

e = Hash(…, op_uuid, role, prev_op_uuid, token_addr, account_addr)
  • op_uuid — can't reuse a proof in a different operation.
  • role — can't swap a "sender balance" proof for a "transfer amount" proof.
  • prev_op_uuid — can't replay proofs from old operations.
  • token_addr — can't move proofs between token types.
  • account_addr — can't move proofs between accounts.

Any one field would stop most attacks; using all five is defense in depth.

04Signing & authorization

Proofs show the math is right; a signature shows who authorized it. IronWeave signs with Ed25519. Everything derives from a single 32-byte seed:

seed (32B) ├── Ed25519 signing key ├── Ed25519 verifying key = your account address └── X25519 keypair (for encryption)

Your 32-byte public key is your address — no hashing, no encoding. Signing is deterministic (no per-signature randomness to leak your key) and produces a 64-byte signature over the exact serialized operation bytes, wrapped in a SignedCryptoOperation envelope.

A validator checks the cheap thing first: size-limit the payload, confirm the signature type is allowed for the address type (today only (Ed25519, Ed25519); unknown pairs fail closed), verify the signature against the sender address — and only then spend cycles validating the ZK proofs.

The signature proves authorization, not freshness or solvency. Replay protection comes from op_uuid/prev_op_uuid at the consensus layer; sufficiency comes from the range proofs. Signatures prove who, proofs prove what.

05Private-data encryption

If balances and blindings are private, how does the owner recover them later — or a receiver learn the blinding they'll need? Each operation carries encrypted private data on-chain.

ECDH + HKDF + AES-256-GCM

Two parties derive a shared secret without meeting via X25519 ECDH (Ed25519 keys convert deterministically to X25519). That secret is run through HKDF — salted with the op_uuid — into a fresh AES-256 key and nonce, then the data is sealed with AES-256-GCM (confidentiality + a 16-byte integrity tag).

The nonce is derived, not random — safe only because validators enforce that each op_uuid is unique. HKDF domain labels keep encryption keys and derived blindings from ever colliding.

Three kinds of private data

  • Sender data — self-encrypted with (sender_priv, sender_pub). Only the sender can read their remaining balance. Crucially not shared, or the receiver could read the sender's balance.
  • Receiver data — cross-encrypted with (sender_priv, receiver_pub). Both parties can decrypt (ECDH is symmetric); carries the receiver's new blinding.
  • User data — arbitrary notes/metadata, shared the same way as receiver data.

Griefing recovery & multi-party sharing

Both parties can independently recompute a blinding with derive_blinding (ECDH + a distinct HKDF label), so a receiver never has to trust the sender's encrypted data — if it's garbage, they derive the correct blinding themselves. For sending one payload to many recipients, encrypt the payload once under a random AES key and wrap that 48-byte key per recipient — the payload is stored once regardless of audience size.

06The operations

Every confidential operation is assembled from the primitives above. From simplest to most complex:

Rerandomize & Reset — one equality proof

Rerandomize swaps your blinding for a fresh one without changing your balance (privacy hygiene): one equality proof that C_current and C_new hide the same value. Reset proves a zero balance and returns your commitment to the identity point so third parties can send to you again — one equality proof against identity. Neither needs a range proof.

Mint & Burn — public amount, two proofs

Because the amount is public, the validator subtracts it directly and checks the leftover commits to zero:

C_diff = C_new − C_old − amount·G → must commit to 0

One equality proof (the difference is a commitment to zero → the arithmetic is correct) plus one range proof on the new balance (no overflow). The public amount replaces the machinery a transfer needs.

Assertion — proof-of-solvency, no state change

Prove your balance is within [min, max] without revealing it, changing nothing on-chain. Two range proofs on shifted commitments:

C_low = C − min·G ≥ 0 ⟹ balance ≥ min C_high = max·G − C ≥ 0 ⟹ balance ≤ max

An assertion can be challenge-response: using a challenger's op_uuid (and binding to the account's last op) stops you from precomputing a flattering proof and replaying it after your balance changes.

Transfer — six proofs + a balance check

The full private transfer (Alice sends 30 to Bob) hides all four numbers — both balances, the amount, and every blinding. It carries 3 equality proofs, 3 range proofs, and a homomorphic balance check:

  • Equality #1 — sender knows their current balance (authorization to spend).
  • Equality #2 — receiver's starting balance is known (access control; for a third party the receiver must be at the identity commitment).
  • Range #1 — the transfer amount is ≥ 1 (no negative or zero transfers), via a shifted (v−1) ∈ [0, 2⁶⁴) proof.
  • Range #2 / #3 — sender's and receiver's new balances are ≥ 0 (no overspend, no overflow).
  • Equality #3 / #4 — final on-chain commitments are re-randomized to fresh blindings (privacy).

Then the validator verifies conservation with pure point arithmetic — no secrets needed:

C_alice_start − C_transfer == C_alice_end C_bob_start + C_transfer == C_bob_end C_alice_start + C_bob_start == C_alice_end + C_bob_end ← nothing created/destroyed
If a transfer drains the sender to zero, choosing a zero final blinding lands them on the identity point — an implicit reset in the same operation, no separate step needed.

07Sizes & composition

What each piece costs, and how the operations compose from proofs.

PrimitiveSizeNotes
Commitment / point32 BRistretto255, canonical
Equality proof64 BSchnorr (point + scalar)
Range proof~1,350 BBulletproof, no trusted setup
Ed25519 signature64 Bdeterministic
Public / private key32 Bpubkey = address
GCM auth tag16 Bintegrity + authenticity
OperationEquality proofsRange proofsBalance check
Transfer33Homomorphic
Mint11Public amount
Burn11Public amount
Reset10
Rerandomize10
Assertion02

Same building blocks throughout — the simplest operations use a single proof; a transfer uses six plus a homomorphic balance check.