Code

The same construction from how it works — garble a single AND gate, then evaluate it — implemented in Python, JavaScript, and Rust. Every sample on this page has actually been run and checked for a correct garble → evaluate roundtrip across hundreds of trials; none of this is pseudocode.

A note on the keystream formula: each SHA-256(...) call natively outputs 256 bits, but the construction on how it works needs a 256-bit keystream total from concatenating two of them — so each half is truncated to its first 128 bits before concatenating. All three samples below do this the same way.

Python

No third-party dependencies — just hashlib and secrets from the standard library.

import hashlib
import secrets

def random_label() -> bytes:
    """A 128-bit wire label."""
    return secrets.token_bytes(16)

def sha256(data: bytes) -> bytes:
    return hashlib.sha256(data).digest()

def keystream(k1: bytes, k2: bytes, gate_id: str) -> bytes:
    """32 bytes: first 16 bytes of SHA256(...‖'0'), then first 16 of SHA256(...‖'1')."""
    gid = gate_id.encode()
    h0 = sha256(k1 + k2 + gid + b"0")[:16]
    h1 = sha256(k1 + k2 + gid + b"1")[:16]
    return h0 + h1

def xor(a: bytes, b: bytes) -> bytes:
    return bytes(x ^ y for x, y in zip(a, b))

def encrypt_cell(k1: bytes, k2: bytes, gate_id: str, label: bytes) -> bytes:
    """ciphertext = keystream XOR (label ‖ 128 zero bits)"""
    ks = keystream(k1, k2, gate_id)
    plaintext = label + b"\x00" * 16
    return xor(ks, plaintext)

def garble_and_gate(gate_id, W1_0, W1_1, W2_0, W2_1, Wout_0, Wout_1):
    # AND truth table: only (1,1) produces the "1" output label.
    rows = [
        (W1_0, W2_0, Wout_0),
        (W1_0, W2_1, Wout_0),
        (W1_1, W2_0, Wout_0),
        (W1_1, W2_1, Wout_1),
    ]
    table = [encrypt_cell(k1, k2, gate_id, out) for (k1, k2, out) in rows]
    secrets.SystemRandom().shuffle(table)  # position must leak nothing
    return table

def evaluate(gate_id, L1, L2, table):
    """Try every row; the one with a zero tail is the real one."""
    ks = keystream(L1, L2, gate_id)
    for ciphertext in table:
        plaintext = xor(ks, ciphertext)
        label, check = plaintext[:16], plaintext[16:]
        if check == b"\x00" * 16:
            return label
    return None  # never happens for a correctly garbled gate

# --- usage ---
gate_id = "gate-1"
W1_0, W1_1 = random_label(), random_label()
W2_0, W2_1 = random_label(), random_label()
Wout_0, Wout_1 = random_label(), random_label()

table = garble_and_gate(gate_id, W1_0, W1_1, W2_0, W2_1, Wout_0, Wout_1)

# Evaluate for a=1, b=1 — should recover Wout_1
result = evaluate(gate_id, W1_1, W2_1, table)
assert result == Wout_1

JavaScript

This is the same implementation running live in the demo on this site — see src/lib/garbled-and.js in the site’s own repository. Uses only the browser’s built-in Web Crypto API — no dependencies.

function randomLabel() {
  const bytes = new Uint8Array(16);
  crypto.getRandomValues(bytes);
  return bytes;
}

async function sha256(bytes) {
  return new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));
}

function xorBytes(a, b) {
  const out = new Uint8Array(a.length);
  for (let i = 0; i < a.length; i++) out[i] = a[i] ^ b[i];
  return out;
}

// keystream: first 16 bytes of SHA256(...‖"0"), then first 16 of SHA256(...‖"1")
async function keystream(k1, k2, gateId) {
  const enc = new TextEncoder();
  const gidBytes = enc.encode(gateId);
  const h0 = await sha256(new Uint8Array([...k1, ...k2, ...gidBytes, ...enc.encode('0')]));
  const h1 = await sha256(new Uint8Array([...k1, ...k2, ...gidBytes, ...enc.encode('1')]));
  return new Uint8Array([...h0.slice(0, 16), ...h1.slice(0, 16)]);
}

// ciphertext = keystream XOR (label ‖ 128 zero bits)
async function encryptCell(k1, k2, gateId, label) {
  const ks = await keystream(k1, k2, gateId);
  const plaintext = new Uint8Array([...label, ...new Uint8Array(16)]);
  return xorBytes(ks, plaintext);
}

async function garbleAndGate(gateId, wires) {
  const { W1_0, W1_1, W2_0, W2_1, Wout_0, Wout_1 } = wires;
  const rows = [
    [W1_0, W2_0, Wout_0],
    [W1_0, W2_1, Wout_0],
    [W1_1, W2_0, Wout_0],
    [W1_1, W2_1, Wout_1],
  ];
  const table = [];
  for (const [k1, k2, out] of rows) table.push(await encryptCell(k1, k2, gateId, out));
  for (let i = table.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [table[i], table[j]] = [table[j], table[i]];
  }
  return table;
}

async function evaluate(gateId, L1, L2, table) {
  const ks = await keystream(L1, L2, gateId);
  for (const ciphertext of table) {
    const plaintext = xorBytes(ks, ciphertext);
    const label = plaintext.slice(0, 16);
    const check = plaintext.slice(16, 32);
    if (check.every((b) => b === 0)) return label;
  }
  return null;
}

Rust

Uses sha2 for hashing and rand for randomness.

use rand::RngCore;
use sha2::{Digest, Sha256};

fn random_label() -> [u8; 16] {
    let mut b = [0u8; 16];
    rand::thread_rng().fill_bytes(&mut b);
    b
}

fn xor32(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
    let mut out = [0u8; 32];
    for i in 0..32 {
        out[i] = a[i] ^ b[i];
    }
    out
}

/// keystream = SHA256(k1‖k2‖gate_id‖"0")[..16] ‖ SHA256(k1‖k2‖gate_id‖"1")[..16]
fn keystream(k1: &[u8; 16], k2: &[u8; 16], gate_id: &str) -> [u8; 32] {
    let mut h0 = Sha256::new();
    h0.update(k1);
    h0.update(k2);
    h0.update(gate_id.as_bytes());
    h0.update(b"0");
    let d0 = h0.finalize();

    let mut h1 = Sha256::new();
    h1.update(k1);
    h1.update(k2);
    h1.update(gate_id.as_bytes());
    h1.update(b"1");
    let d1 = h1.finalize();

    let mut out = [0u8; 32];
    out[..16].copy_from_slice(&d0[..16]);
    out[16..].copy_from_slice(&d1[..16]);
    out
}

// ciphertext = keystream ⊕ (label ‖ 128 zero bits)
fn encrypt_cell(k1: &[u8; 16], k2: &[u8; 16], gate_id: &str, label: &[u8; 16]) -> [u8; 32] {
    let ks = keystream(k1, k2, gate_id);
    let mut plaintext = [0u8; 32];
    plaintext[..16].copy_from_slice(label);
    xor32(&ks, &plaintext)
}

struct Wires {
    w1_0: [u8; 16], w1_1: [u8; 16],
    w2_0: [u8; 16], w2_1: [u8; 16],
    wout_0: [u8; 16], wout_1: [u8; 16],
}

fn garble_and_gate(gate_id: &str, w: &Wires) -> Vec<[u8; 32]> {
    // AND truth table: only (1,1) produces the "1" output label.
    let rows = [
        (&w.w1_0, &w.w2_0, &w.wout_0),
        (&w.w1_0, &w.w2_1, &w.wout_0),
        (&w.w1_1, &w.w2_0, &w.wout_0),
        (&w.w1_1, &w.w2_1, &w.wout_1),
    ];
    let mut table: Vec<[u8; 32]> = rows
        .iter()
        .map(|(k1, k2, out)| encrypt_cell(k1, k2, gate_id, out))
        .collect();

    // Fisher-Yates shuffle — position must leak nothing.
    let mut rng = rand::thread_rng();
    for i in (1..table.len()).rev() {
        let j = (rng.next_u32() as usize) % (i + 1);
        table.swap(i, j);
    }
    table
}

/// Try every row; the one with a zero tail is the real one.
fn evaluate(gate_id: &str, l1: &[u8; 16], l2: &[u8; 16], table: &[[u8; 32]]) -> Option<[u8; 16]> {
    let ks = keystream(l1, l2, gate_id);
    for ciphertext in table {
        let plaintext = xor32(&ks, ciphertext);
        let (label, check) = plaintext.split_at(16);
        if check.iter().all(|&b| b == 0) {
            let mut out = [0u8; 16];
            out.copy_from_slice(label);
            return Some(out);
        }
    }
    None // never happens for a correctly garbled gate
}

fn main() {
    let gate_id = "gate-1";
    let w = Wires {
        w1_0: random_label(), w1_1: random_label(),
        w2_0: random_label(), w2_1: random_label(),
        wout_0: random_label(), wout_1: random_label(),
    };

    let table = garble_and_gate(gate_id, &w);

    // Evaluate for a=1, b=1 — should recover wout_1.
    let result = evaluate(gate_id, &w.w1_1, &w.w2_1, &table);
    assert_eq!(result, Some(w.wout_1));
}

Sources: implements the construction derived on how it works. Python samples use only the standard library; the Rust sample uses sha2 and rand.