Inspiration

In the digital asset ecosystem, 24 words stand between sovereign wealth and irreversible loss. A single BIP-39 seed phrase safeguards cryptographic master private keys holding millions — or billions — of dollars. Yet the prevailing methods for handling, backing up, and restoring these seeds are shockingly fragile:

  1. Physical & Human Vulnerability — A single smudged letter on paper, a misplaced word in a steel capsule, or a transcription error invalidates the 8-bit checksum and permanently locks users out of their funds.
  2. Microarchitectural Side-Channel Attacks — Most cryptographic software relies on naive branching, data-dependent loops, or precomputed lookup tables (e.g., AES S-boxes). Attackers on co-located VMs or physical hardware can measure cache-line latencies (Flush+Reload, Prime+Probe) to extract private key bits in microseconds.
  3. Memory Remanence & Cold-Boot Exposure — Decrypted seed phrases held in standard heap memory can be paged out to unencrypted swap, dumped during core crashes, or physically read from frozen DRAM chips.

We asked ourselves:

What if a seed phrase wasn't just stored — but protected by a multi-layered, fault-tolerant, side-channel immune mathematical fortress, from the silicon up?

That question became Aegis-Seed.


What it does

Aegis-Seed is an enterprise-grade, side-channel immune master seed protection engine written in Rust. It executes a rigorous 5-layer sequential pipeline to transform, distribute, repair, encapsulate, and hardware-lock a 24-word BIP-39 master seed:

$$\text{[BIP-39 Mnemonic]} \xrightarrow{\text{L1}} \text{SHA-256 Vectorization} \xrightarrow{\text{L2}} \text{GF}(2^8)\text{ Secret Sharing} \xrightarrow{\text{L3}} \text{GF}(2^{11})\text{ Reed-Solomon} \xrightarrow{\text{L4}} \text{CC-KEM} \xrightarrow{\text{L5}} \text{Hardware Lock}$$

Layer 1 — BIP-39 Entropy Vectorization

Parses 24 mnemonic words using constant-time string comparisons, re-evaluates the SHA-256 checksum over the 256-bit entropy using a clean-room, lookup-table-free implementation, and recovers the 32-byte master seed vector.

Layer 2 — Linear-Algebraic Secret Sharing over $\mathrm{GF}(2^8)$

Implements a $(k, n)$ Shamir-style secret sharing scheme using polynomial evaluation over the Rijndael field $\mathbb{F}_{2^8}$, defined by the irreducible polynomial

$$P(x) = x^8 + x^4 + x^3 + x + 1 \quad (\texttt{0x11B})$$

Secret reconstruction uses branchless Gaussian elimination, ensuring no intermediate value leaks through execution timing.

Layer 3 — Systematic Reed-Solomon Codec over $\mathrm{GF}(2^{11})$

Maps each 11-bit BIP-39 word index to an element of $\mathbb{F}_{2^{11}}$ modulo

$$Q(x) = x^{11} + x^2 + 1 \quad (\texttt{0x805})$$

Encodes the 24 words into a systematic $(25, 24)$ codeword. If an index is corrupted, illegible, or dropped, Aegis-Seed calculates the syndrome polynomial and automatically reconstructs the missing word in constant time.

Layer 4 — Conic Curve Key Encapsulation (CC-KEM)

Performs key encapsulation over the 256-bit prime field $\mathbb{F}_p$ with

$$p = 2^{256} - 189$$

on the conic curve $C(d): Y^2 = X^2 - dZ^2 \pmod{p}$. Scalar multiplication uses a 256-step branchless Montgomery ladder in projective coordinates $(X : Y : Z)$ with constant-time implicit rejection for invalid ciphertexts.

Layer 5 — OS-Level Hardware Memory Hardening

Pins secret memory directly into physical RAM (VirtualLock on Windows, memfd_secret/mlock on Linux), strips pages from swap and crash dumps (MADV_DONTDUMP), and performs cryptographic zeroization on drop using atomic compiler barriers:

core::sync::atomic::compiler_fence(Ordering::SeqCst);
self.inner.fill(0u8);
core::sync::atomic::compiler_fence(Ordering::SeqCst);

How we built it

Finite Field Arithmetic — Clean-Room, No LUTs

All core algorithms live in core-math, a zero-allocation #![no_std] crate. Every multiplication in $\mathrm{GF}(2^8)$ and $\mathrm{GF}(2^{11})$ is computed via branchless carryless shift-and-XOR — no precomputed tables, no data-dependent memory accesses:

$$a \cdot b = \bigoplus_{i=0}^{7} \left( b_i \cdot \left( a \ll i \right) \right) \pmod{P(x)}$$

Conic Curve Group Law

The group law on $C(d)$ is closed and associative. Two projective points $P_1 = (X_1 : Y_1 : Z_1)$ and $P_2 = (X_2 : Y_2 : Z_2)$ compose by:

$$X_3 = X_1 Y_2 + Y_1 X_2, \quad Y_3 = Y_1 Y_2 + d \cdot X_1 X_2, \quad Z_3 = Z_1 Z_2$$

Conditional coordinate swaps during scalar multiplication use bitwise selection to remain constant-time:

$$\Delta = (a \oplus b) \cdot \text{bit}, \quad a \gets a \oplus \Delta, \quad b \gets b \oplus \Delta$$

Dual-Engine Side-Channel Verification

DudeCT Statistical Timing Gate — Compares latency distributions of fixed vs. random secrets using Welford's online algorithm and Welch's two-sample $t$-test:

$$t = \frac{\bar{x}_1 - \bar{x}_2}{\sqrt{\dfrac{s_1^2}{n_1} + \dfrac{s_2^2}{n_2}}}$$

The test enforces $|t| < 4.5$ across millions of clock cycles.

Valgrind Memcheck & TIMECOP — Secret inputs are poisoned with VALGRIND_MAKE_MEM_UNDEFINED. Valgrind instruments every conditional jump (jcc) and memory address to guarantee zero secret-dependent branches or memory dereferences at the binary level.

Continuous Integration — 4 Parallel Jobs

┌──────────────────────────────────┬──────────────────────────────────┐
│  Build & Test (ubuntu-latest)    │  Build & Test (windows-latest)   │
│  cargo test --workspace          │  cargo test --workspace          │
│  cargo clippy -D warnings        │  cargo clippy -D warnings        │
├──────────────────────────────────┼──────────────────────────────────┤
│  DudeCT Statistical Gate         │  Valgrind TIMECOP Analysis       │
│  |t| < 4.5 verified              │  0 secret-dependent branches     │
└──────────────────────────────────┴──────────────────────────────────┘

Challenges we ran into

1 — The Secret-Dependent Carry in Montgomery Reduction

During Valgrind taint analysis on CC-KEM decapsulation, CI flagged an unexpected conditional jump in our multi-limb Montgomery reduction:

while carry > 0 && k < 8 { ... }   // ← carry derives from secret scalar!

Under Valgrind's shadow memory, carry was tainted — making the while condition a secret-dependent branch, a textbook side-channel.

Fix: Replace the dynamic loop with an unconditional, constant-time scan:

for k in (i + 4)..8 {
    let sum = (c[k] as u128) + (carry as u128);
    c[k] = sum as u64;
    carry = (sum >> 64) as u64;
}

2 — libtest Threading Noise Under Valgrind

Running integration tests with Valgrind produced dozens of leak warnings from the default Rust test harness (libtest), which spawns pthreads and allocates TLS — none of which are cryptographically relevant.

Fix: Set harness = false in core-math/Cargo.toml and write a standalone fn main() in tests/timecop_kem.rs. This yielded a noise-free binary with exactly the allocations we intend to analyze.

3 — Cross-Platform Hardware Memory Isolation

memfd_secret(2) (Linux syscall 447) is unimplemented in Valgrind, causing unhandled-syscall aborts. We had to surgically isolate the Valgrind CI job to run only the TIMECOP KEM binary — not the full cli-app pipeline — to avoid triggering the syscall.


Accomplishments that we're proud of

  • ✅ Verified Zero Secret-Dependent Branches — Confirmed by Valgrind Memcheck/TIMECOP dynamic taint analysis at the machine-instruction level.
  • ✅ DudeCT Timing Invariance — $|t| < 4.5$ sustained across all cryptographic operations over millions of sample pairs.
  • ✅ Mathematical Self-Healing — Systematic $(25, 24)$ Reed-Solomon coding over $\mathrm{GF}(2^{11})$ that restores a damaged or missing BIP-39 word from syndrome-only information, with no lookup tables.
  • ✅ Strict #![no_std] Core — The entire mathematical engine is zero-allocation, stack-only, and firmware-portable (HSMs, Cortex-M4, RISC-V).
  • ✅ Clean-Room Cryptography — SHA-256, $\mathrm{GF}$ field arithmetic, and Conic Curve KEM engineered from first principles — zero external crypto dependencies.
  • ✅ All 4 CI Jobs Green — Cross-platform Clippy, format, security-gate, and TIMECOP checks all passing.

What we learned

Constant-time is harder than it looks. Modern optimizing compilers (LLVM) are tuned for average-case throughput. A seemingly innocent if carry != 0 or short-circuit && can compile to a conditional branch that Valgrind marks as a timing oracle. Constant-time code must be expressed in bitwise arithmetic and guarded by compiler_fence barriers.

Field size should match domain structure. Because BIP-39 uses an 11-bit word dictionary ($2^{11} = 2048$ words), Reed-Solomon coding over $\mathbb{F}_{2^{11}}$ gives a perfect 1:1 mapping — no byte-splitting, no padding, no information loss.

Layer your verification. Statistical timing tests (DudeCT) catch aggregate latency leaks; dynamic taint analysis (TIMECOP) catches individual branching leaks. Neither alone is sufficient. The combination provides meaningful security confidence.


What's next for Aegis-Seed

  • [ ] Hardware Wallet Firmware Port — Flash core-math onto ARM Cortex-M4 / RISC-V silicon (STM32, ESP32-S3) to benchmark constant-time guarantees on physical embedded hardware with cycle-accurate measurements.
  • [ ] Multi-Word Fault Tolerance — Expand to a $(28, 24)$ Reed-Solomon codec capable of recovering up to 2 corrupted words or 4 full erasures from partially damaged physical seed backups.
  • [ ] Post-Quantum Hybrid KEM — Combine the Conic Curve KEM with ML-KEM (Kyber-768) in a hybrid construction: $\text{SS} = \text{KDF}(\text{CC-KEM.SS} \,|\, \text{ML-KEM.SS})$, providing harvest-now-decrypt-later resistance against quantum adversaries.
  • [ ] Zero-Knowledge Seed Proofs — Implement a non-interactive zero-knowledge (NIZK) proof demonstrating possession of a valid $(k, n)$ secret share without revealing any bits of the underlying entropy:

Built With

  • bip39
  • conic-curve
  • cryptography
  • dudect
  • galois-field
  • github-actions
  • kem
  • memory-hardening
  • no-std
  • reed-solomon
  • rust
  • side-channel-resistance
  • timecop
  • valgrind
Share this project:

Updates

Submission history