◐ Off-By-One · answer catalog

garbled-circuit-evaluator

2 answer(s)jsnode20jsnode20

garbled-circuit-evaluator

📦 Source in repository (JSON)

Answer 1

The implementation is in ~/garbled-circuit-evaluator.js — a GarbledCircuitEvaluator class with a single public method evaluate(circuit, inputLabels).

Core design

Wire labels: Each wire has two 128-bit random labels (label0 for bit 0, label1 for bit 1). The evaluator knows exactly one label per input wire (obtained via oblivious transfer in the full protocol).

Garbled table encryption (used by the garbler; the evaluator only decrypts):

Gate type Rows Key derivation Content per row
NOT (1 in) 2 SHA-256(inputLabel) (outputLabel XOR mask) \|\| checksum
AND/XOR (2 in) 4 SHA-256(labelA \|\| labelB) (outputLabel XOR mask) \|\| checksum

Where mask = key[0:16] and checksum = key[16:20].

Evaluation (the evaluator's job): 1. For each gate in topological order, the evaluator has one label per input wire. 2. They derive the 20-byte key stream via SHA-256(knownLabelA || knownLabelB). 3. They XOR the first 16 bytes (mask) with the entry's label portion. 4. They compare the entry's last 4 bytes against the derived checksum. 5. A match → the XOR result is the correct output label. 6. Both label orderings (A||B) and (B||A) are tried for robustness. 7. At the end, output labels are compared against the circuit's outputs[{label0, label1}] pairs to produce plaintext bits.

Performance: The 5000-gate chain evaluates in well under a second. The algorithm is O(gates * table_rows) with a single SHA-256 per row attempt.

Key code (evaluator class)

class GarbledCircuitEvaluator {
  evaluate(circuit, inputLabels) {
    const labels = new Map(
      Object.entries(inputLabels).map(([k, v]) => [Number(k), v])
    );

    for (const gate of circuit.gates) {
      if (gate.type === 'NOT') this._evaluateNOT(gate, labels);
      else this._evaluate2InputGate(gate, labels);
    }

    return circuit.outputs.map((out) => {
      const label = labels.get(out.wireId);
      if (label.equals(out.label0)) return 0;
      if (label.equals(out.label1)) return 1;
      throw new Error(`Unknown output label for wire ${out.wireId}`);
    });
  }

  _evaluate2InputGate(gate, labels) {
    const labelA = labels.get(gate.inputs[0]);
    const labelB = labels.get(gate.inputs[1]);
    for (const entry of gate.table) {
      let r = this._decrypt2(entry, labelA, labelB);
      if (r) { labels.set(gate.output, r); return; }
      r = this._decrypt2(entry, labelB, labelA);
      if (r) { labels.set(gate.output, r); return; }
    }
    throw new Error(`Cannot decrypt gate output wire ${gate.output}`);
  }

  _decrypt2(entry, l1, l2) {
    const hash = crypto.createHash('sha256')
      .update(Buffer.concat([l1, l2])).digest();
    const mask = hash.subarray(0, 16);
    const expected = hash.subarray(16, 20);
    const actual = entry.subarray(16, 20);
    if (!actual.equals(expected)) return null;
    return this._xor(entry.subarray(0, 16), mask);
  }
}

Evidence & signatures

**33 tests pass, 0 fail** covering:

| Test | Cases | What it verifies |
|------|-------|-----------------|
| Single NOT | 2 | `NOT(0)=1`, `NOT(1)=0` |
| Single AND | 4 | All 4 truth-table rows |
| Single XOR | 4 | All 4 truth-table rows |
| NAND chain | 4 | Two-gate depth: `NOT(AND(a,b))` |
| Half adder | 4 | Two parallel gates sharing inputs |
| Full adder | 8 | 5-gate DAG with 3 input wires and 2 output wires |
| **5000-gate chain** | 2 | Deep alternating AND/XOR chain, both label orderings |
| Multi-output | 2 | Multiple output wires reading the same intermediate gate |
| NOT fan-out | 2 | One NOT gate feeding two downstream gates |
| **100-gate fan-out** | 1 | One shared input wire feeding 100 AND gates; verifies `outputs` array length matches |

**Edge cases verified**:
- Plain object input (not Map) works via `Object.entries()` normalization.
- Both `(A||B)` and `(B||A)` concatenation orders are tried during decryption.
- All output wires are decoded to the correct bit via label comparison.
- 5000-gate circuit completes in < 1s (no timeout).

**Benchmark** (informal):
```
5000 gates: ~0.3s wall time on Node 20
100 gates × 1 shared input: ~0.02s
```

---
{"model": "claude-sonnet-4-20250514", "problem_class": "garbled-circuit-evaluator", "result": "passed", "tests": 33}

Answer 2

The implementation is in ~/garbled-circuit-evaluator.js — a GarbledCircuitEvaluator class with a single public method evaluate(circuit, inputLabels).

Core design

Wire labels: Each wire has two 128-bit random labels (label0 for bit 0, label1 for bit 1). The evaluator knows exactly one label per input wire (obtained via oblivious transfer in the full protocol).

Garbled table encryption (used by the garbler; the evaluator only decrypts):

Gate type Rows Key derivation Content per row
NOT (1 in) 2 SHA-256(inputLabel) (outputLabel XOR mask) \|\| checksum
AND/XOR (2 in) 4 SHA-256(labelA \|\| labelB) (outputLabel XOR mask) \|\| checksum

Where mask = key[0:16] and checksum = key[16:20].

Evaluation (the evaluator's job): 1. For each gate in topological order, the evaluator has one label per input wire. 2. They derive the 20-byte key stream via SHA-256(knownLabelA || knownLabelB). 3. They XOR the first 16 bytes (mask) with the entry's label portion. 4. They compare the entry's last 4 bytes against the derived checksum. 5. A match → the XOR result is the correct output label. 6. Both label orderings (A||B) and (B||A) are tried for robustness. 7. At the end, output labels are compared against the circuit's outputs[{label0, label1}] pairs to produce plaintext bits.

Performance: The 5000-gate chain evaluates in well under a second. The algorithm is O(gates * table_rows) with a single SHA-256 per row attempt.

Key code (evaluator class)

class GarbledCircuitEvaluator {
  evaluate(circuit, inputLabels) {
    const labels = new Map(
      Object.entries(inputLabels).map(([k, v]) => [Number(k), v])
    );

    for (const gate of circuit.gates) {
      if (gate.type === 'NOT') this._evaluateNOT(gate, labels);
      else this._evaluate2InputGate(gate, labels);
    }

    return circuit.outputs.map((out) => {
      const label = labels.get(out.wireId);
      if (label.equals(out.label0)) return 0;
      if (label.equals(out.label1)) return 1;
      throw new Error(`Unknown output label for wire ${out.wireId}`);
    });
  }

  _evaluate2InputGate(gate, labels) {
    const labelA = labels.get(gate.inputs[0]);
    const labelB = labels.get(gate.inputs[1]);
    for (const entry of gate.table) {
      let r = this._decrypt2(entry, labelA, labelB);
      if (r) { labels.set(gate.output, r); return; }
      r = this._decrypt2(entry, labelB, labelA);
      if (r) { labels.set(gate.output, r); return; }
    }
    throw new Error(`Cannot decrypt gate output wire ${gate.output}`);
  }

  _decrypt2(entry, l1, l2) {
    const hash = crypto.createHash('sha256')
      .update(Buffer.concat([l1, l2])).digest();
    const mask = hash.subarray(0, 16);
    const expected = hash.subarray(16, 20);
    const actual = entry.subarray(16, 20);
    if (!actual.equals(expected)) return null;
    return this._xor(entry.subarray(0, 16), mask);
  }
}

Evidence & signatures

**33 tests pass, 0 fail** covering:

| Test | Cases | What it verifies |
|------|-------|-----------------|
| Single NOT | 2 | `NOT(0)=1`, `NOT(1)=0` |
| Single AND | 4 | All 4 truth-table rows |
| Single XOR | 4 | All 4 truth-table rows |
| NAND chain | 4 | Two-gate depth: `NOT(AND(a,b))` |
| Half adder | 4 | Two parallel gates sharing inputs |
| Full adder | 8 | 5-gate DAG with 3 input wires and 2 output wires |
| **5000-gate chain** | 2 | Deep alternating AND/XOR chain, both label orderings |
| Multi-output | 2 | Multiple output wires reading the same intermediate gate |
| NOT fan-out | 2 | One NOT gate feeding two downstream gates |
| **100-gate fan-out** | 1 | One shared input wire feeding 100 AND gates; verifies `outputs` array length matches |

**Edge cases verified**:
- Plain object input (not Map) works via `Object.entries()` normalization.
- Both `(A||B)` and `(B||A)` concatenation orders are tried during decryption.
- All output wires are decoded to the correct bit via label comparison.
- 5000-gate circuit completes in < 1s (no timeout).

**Benchmark** (informal):
```
5000 gates: ~0.3s wall time on Node 20
100 gates × 1 shared input: ~0.02s
```

---
{"model": "claude-sonnet-4-20250514", "problem_class": "garbled-circuit-evaluator", "result": "passed", "tests": 33}
Generated from the verified corpus · MIT licensedBack to the catalog