Class organization

August 3, 2026 · View on GitHub

The repo has two protocol families (OT extensions and sVOLE extensions) sharing one streaming skeleton, one inner sibling-OT gadget, one LPN amplifier, and one Half-Tree cGGM. This doc is the map: who inherits from whom, who templates over what, and where each responsibility lives.

Top-level layout

emp-ot/
├── common/                ← shared by OT-extension and sVOLE-extension layers
│   ├── streaming_extension.h    StreamingExtension<Element>
│   ├── mp_gadget.h              MultiPointGadget{Sender,Receiver}<AuthValue>
│   ├── lpn.h                    Lpn<AuthValue, d>
│   └── cggm.h                   namespace cggm { build_sender, eval_receiver }

├── ot.h                   OT / COT / RandomCOT abstract interfaces
├── tuning.h               PrimalLPNParameter + tuned constants

├── base_ot/               OT (chosen-input) implementations
│   ├── co.h                     CO         (Chou-Orlandi)
│   ├── csw.h                    CSW        (CSW "blazing-fast")
│   ├── pvw.h                    PVW        (Peikert-Vaikuntanathan-Waters)
│   ├── bmm.h                    BMM        (Badrinarayanan-Masny-Mukherjee; PQ, custom over ML-KEM-512 internals)
│   └── mlkem/                   vendored Kyber / ML-KEM-512 arithmetic + lattice OT impls

├── ot_extension/          RandomCOT extensions
│   ├── ot_extension.h           OTExtension base
│   ├── iknp.{h,cpp}             IKNP
│   ├── softspoken/              SoftSpoken<k, kChunkBlocks>
│   └── ferret/                  Ferret + SilentFerret (+ AuthValueFerret carrier)

└── svole/                 sVOLE
    ├── svole.h                  Svole<AuthValue> + svole_n / svole_M
    ├── f2k_vole.h               AuthValueF2k (carrier + Bootstrap)
    ├── fp_vole.h                AuthValueFp  (carrier + Bootstrap)
    ├── fp_base_svole.h          Base_svole + Cope (Fp-only bootstrap helpers)
    └── fp_utility.h             Mersenne $2^{61}$-1 arithmetic

The two protocol families share one streaming base

                  StreamingExtension<Element>
                ┌───────────────────────────────┐
                │  pure virtuals (subclass):    │
                │    begin / next / end /       │
                │    chunk_size                 │
                │  non-virtual one-shot:        │
                │    run(data, num)             │
                │  state:                       │
                │    party, malicious,          │
                │    setup_done,                │
                │    leftover buffer            │
                │  tripwire helpers (protected):│
                │    enter_session_ /           │
                │    exit_session_  /           │
                │    expect_in_session_         │
                └───────────────────────────────┘


              ┌───────────────┴───────────────┐
              │                               │
   public RandomCOT, public                Svole<AuthValue>
   StreamingExtension<block>               : public StreamingExtension<AuthValue>
   (OTExtension)

   ┌──────────┼──────────┐
   │          │          │
  IKNP    SoftSpoken  Ferret
                <k>

StreamingExtension<Element> (in common/streaming_extension.h) is the only place the begin/next/end lifecycle, leftover buffer, and FS plumbing live. Both protocol families share it; their concrete classes differ in Element (block for RCOT, AuthValueXxx for sVOLE) and the protocol-specific surface they add on top.

OTExtension and its subclasses

OTExtension : public RandomCOT, public StreamingExtension<block>
├── public
│   ├── base_ot                 (owned base OT for bootstrap)
│   ├── Δ, delta_bool[128]      (sender-side correlation)
│   ├── choice_prg              (receiver-side PRG)
│   ├── set_delta(bool*)        (override Δ pre-bootstrap)
│   ├── set_choice_seed(block)  (override choice PRG seed)
│   ├── Ferret::set_lpn_matrix_seed(block)
│   │                            (optional public matrix root, both roles)
│   ├── chunk_size()            (inherited; subclass overrides)
│   ├── begin/next/end          (inherited; subclass overrides no-arg begin)
│   ├── begin(total)            (counted next_n plan; OT-only virtual default)
│   ├── check()                 (mid-session certification; retains tail)
│   └── rcot(data, num)         (RandomCOT abstract; final override —
│                                forwards to inherited run())

└── four subclasses (each overrides begin / next / end directly —
    no NVI hooks; tripwire enforced via the inherited
    enter_session_ / exit_session_ / expect_in_session_ helpers)
    ├── IKNP                    (inline party-dispatch to
    │                            send/recv_{begin,next,end}_)
    ├── SoftSpoken<k, kChunkBlocks>  (same shape as IKNP)
    ├── Ferret                  (unified body per stage; party-dispatch
    │                            inside the per-tree helpers)
        └── SilentFerret        (public subclass of Ferret; prepays
                                 each round's corrections in begin())

Subclass choice — inline party-dispatch vs unified body. IKNP and SoftSpoken have genuinely different code paths for sender vs receiver (different base OT directions, different per-row work), so their begin/next/end each start with if (is_ot_sender()) and delegate to private non-virtual send_*_ / recv_*_ helpers. Ferret's send/recv bodies are the same shape up to a party-test (both run a multi-point gadget + LPN slice loop), so its begin/next/end have one unified body each, and the per-tree helpers (bootstrap_/inner_run_begin_/process_one_tree_/inner_run_end_/run_refill_) party-dispatch internally.

Ferret normally derives its public LPN matrix from the transcript session id. set_lpn_matrix_seed is the narrow exception for an outer protocol that needs several otherwise domain-separated Ferret instances to expose one identical receiver-choice pattern. Both roles set the same root before bootstrap; Ferret domain-separates it by the concrete LPN parameters and forwards it through a nested b10 bootstrap.

Svole — one template, two carriers, two policies

Svole<AuthValue> : public StreamingExtension<AuthValue>

                                  AuthValue determines │
                                  every protocol detail

              ┌────────────────────────────────────────┼────────────────────────────────┐
              │                                                                         │
       AuthValueF2k                                                              AuthValueFp
       (in f2k_vole.h)                                                          (in fp_vole.h)
       block val, mac                                                           uint64 val, mac
       F = block, F_2k arithmetic (XOR, gfmul)                                  F = uint64, Mersenne mod p
       Bootstrap: Galois packing of M·128 Ferret COTs                           Bootstrap: COPE seed sVOLE
       delta_holder = BOB                                                       + pre-stage MPFSS+LPN
                                                                                delta_holder = ALICE
                                                                                resolve_delta = random nonzero Fp element

F2kVOLE and FpVOLE are template aliases defaulting to Svole<AuthValueF2k> and Svole<AuthValueFp> respectively (each takes a carrier parameter, so callers write F2kVOLE<> / FpVOLE<>).

The carrier (AuthValueXxx) is the protocol description

Svole, MultiPointGadget, and Lpn are all templated on the carrier. The carrier provides everything those generic classes need in one place:

struct AuthValueXxx {
  using F = ...;                                    // field type
  F val;   F mac;                                   // storage (Ferret omits .val)

  // Field arithmetic
  static F f_zero / f_add / f_sub / f_mul ...

  // Wire-format / protocol traits
  static constexpr bool          kHasSecretSum;
  static constexpr bool          kClearLeafLSB;
  static constexpr ChiFoldFlavor kChiFoldFlavor;    // F2kPacked | FTyped

  // Chi-fold helpers
  static void expand_chi(block seed, F* out, int64_t n);
  static void accumulate_VW(F& acc, const F* chi,
                            const AuthValueXxx* leaves, int64_t n);

  // LPN ops (for Lpn<AuthValueXxx, d>)
  static constexpr int kLpnSafeAddsPerReduce;
  static void auth_add_into / auth_partial_reduce / auth_final_reduce;

  // cGGM leaf to element
  static AuthValueXxx auth_from_block(block leaf);

  // sVOLE-specific (Svole<AuthValue> only)
  static constexpr int delta_holder_party();
  static F resolve_delta(Ferret*);
  static void on_set_delta(F, Ferret*);
  struct Bootstrap { static void run(Svole<...>&); };
};

There are three concrete carriers:

CarrierLives inFvalmacTraits
AuthValueFerretot_extension/ferret/ferret.hblocknoyeskHasSecretSum=false, kClearLeafLSB=true, F2kPacked
AuthValueF2ksvole/f2k_vole.hblockyesyeskHasSecretSum=true, kClearLeafLSB=false, FTyped
AuthValueFpsvole/fp_vole.huint64yesyeskHasSecretSum=true, kClearLeafLSB=false, FTyped

Ferret's carrier is unusual: it's a single block mac field (layout-equivalent to block so the gadget reinterprets directly). There's no val because Ferret RCOT doesn't have a per-leaf value — the choice bit is encoded in the LSB.

The inner gadget: MultiPointGadget

Both Ferret and Svole run a sequence of cGGM trees and ship a per-tree correction on the wire. That logic lives in MultiPointGadget{Sender,Receiver}<AuthValue> (common/mp_gadget.h), templated on the carrier. The carrier's kHasSecretSum, kClearLeafLSB, and kChiFoldFlavor traits select compile-time branches:

                    MultiPointGadgetSender<AuthValue>

            ┌─────────────────┴─────────────────┐
            │                                   │
  cGGM build with                      kHasSecretSum?
  AuthValue::kClearLeafLSB             ├── true:  ship c[d] + secret_sum:F
                                       └── false: ship c[d] only


                              │ (malicious only)

                       AuthValue::expand_chi → chi[leave_n]
                       AuthValue::accumulate_VW → VW[tree_idx]

  run_end_packed   ◄── F2kPacked chi-fold (Ferret style)
                       sender ships bool[128] x_prime + 2-block hash;
                       receiver derives x_prime from XOR(chi_α) + chi-check region

  run_end_typed    ◄── FTyped chi-fold (Mpsvole style)
                       sender: vb = Δ·x_star + triple_t.mac + Σ VW
                       receiver: x_star = Σ chi_α·val + triple.val

Aliases on Ferret's side keep the legacy names:

using MPCOT_Sender   = MultiPointGadgetSender<AuthValueFerret>;
using MPCOT_Receiver = MultiPointGadgetReceiver<AuthValueFerret>;

LPN and cGGM are pure helpers

Lpn<AuthValue, d> (in common/lpn.h) is the linear-code amplifier that turns the sparse output of the multi-point gadget into a dense correlation. It's templated on the carrier; the carrier provides auth_add_into / auth_partial_reduce / auth_final_reduce and the kLpnSafeAddsPerReduce integer that controls reduce frequency. Its compute_slice_sink variant consumes the identical PRG range and produces the identical finalized values as compute_slice, but delivers them to a caller-supplied sink instead of storing them back to the initial buffer.

namespace emp::cggm (in common/cggm.h) is the Half-Tree cGGM tree build/eval. It's a free-function namespace, no class, no templating on AuthValue — it just writes block leaves. Used by MultiPointGadget and by SoftSpoken's PPRF tree.

Putting it together: data-flow for a single sVOLE round

   svole.run(out, num)


   StreamingExtension::run                                  (common/)
      ├── drain leftover
      └── begin → loop next → end

   Svole::begin / next / end                                (svole/svole.h)
      ├── begin:     bootstrap_ (lazy)
      │              swap carry_curr_/_next_
      │              inner_run_begin_ (pull base COTs from base_ferret_,
      │                                set Δ on gadget_send_)
      ├── next:      process_one_tree_(out)
      │                │
      │                ├── gadget_send_->run_next_tree       (common/mp_gadget.h)
      │                │       cGGM build, ship c[]+secret_sum, accumulate VW
      │                │
      │                └── lpn_->compute_slice               (common/lpn.h)
      │                       fold LPN secret from carry_curr_ into dst

      └── end:       run_refill_ (refill trees write into carry_next_)
                     inner_run_end_ (chi-fold check via gadget_send_->run_end_typed)

For Ferret RCOT the picture is the same with these substitutions: SvoleFerret, carry_*ot_pre_data_* (raw block storage), gadget_* is a MultiPointGadget<AuthValueFerret> (= MPCOT_*), lpn_ is Lpn<AuthValueFerret, 10>.

See also