mirror of
https://github.com/luxfi/fhe.git
synced 2026-07-26 23:16:08 +00:00
fhe/ct/dudect: harness for Encrypt + Decrypt + Bootstrap CT
Empirical constant-time analysis harness mirroring the Pulsar
pack at ~/work/lux/pulsar/ct/dudect/:
encrypt_ct.go + dudect_encrypt.c - cgo bridge + C main loop
decrypt_ct.go + dudect_decrypt.c - the CT-critical routine
bootstrap_ct.go + dudect_bootstrap.c - PBS composite
CT population (operational framing):
- Both classes are VALID inputs to the routine under test
- Class A is a fixed input; class B is uniformly drawn from a
pre-built pool of K independent valid inputs
- Any timing difference is a real secret-content signal
All three cgo shared libraries build clean:
GOWORK=off go build -buildmode=c-shared -tags tfhe_encrypt_ct
GOWORK=off go build -buildmode=c-shared -tags tfhe_decrypt_ct
GOWORK=off go build -buildmode=c-shared -tags tfhe_bootstrap_ct
dudect.h fetched on demand via fetch.sh (not committed).
dudect_compat.h supplies _mm_mfence/__rdtsc on AArch64 hosts.
Submission-grade run pending (GATE-1 in CRYPTOGRAPHER-SIGN-OFF.md).
This commit is contained in:
@@ -0,0 +1,119 @@
|
|||||||
|
# Lux TFHE constant-time analysis harness -- dudect bridge.
|
||||||
|
#
|
||||||
|
# Builds the cgo shim libraries (libtfhe_encrypt, libtfhe_decrypt,
|
||||||
|
# libtfhe_bootstrap) and links the C dudect harnesses against them.
|
||||||
|
#
|
||||||
|
# Prereqs (run once):
|
||||||
|
# ./fetch.sh # pulls dudect.h at the pinned commit
|
||||||
|
#
|
||||||
|
# Build:
|
||||||
|
# make # all three
|
||||||
|
# make encrypt # just encrypt
|
||||||
|
# make decrypt # just decrypt
|
||||||
|
# make bootstrap # just bootstrap
|
||||||
|
#
|
||||||
|
# Run:
|
||||||
|
# ./dudect_encrypt # smoke test (10000 samples/batch x 100 batches)
|
||||||
|
# ./dudect_decrypt # smoke test
|
||||||
|
# ./dudect_bootstrap # smoke test (2000 samples/batch x 50 batches)
|
||||||
|
#
|
||||||
|
# Full submission run (10^9 samples on a pinned-CPU quiet host):
|
||||||
|
# DUDECT_SAMPLES=1000000 DUDECT_MAX_BATCHES=1000 ./dudect_encrypt
|
||||||
|
#
|
||||||
|
# Hosts:
|
||||||
|
# - x86_64 Linux/macOS: builds cleanly against upstream dudect.h
|
||||||
|
# - aarch64 Linux/macOS: dudect_compat.h is force-included to supply
|
||||||
|
# ARM equivalents of _mm_mfence() / __rdtsc(); see header for the
|
||||||
|
# accuracy disclaimer.
|
||||||
|
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
UNAME_M := $(shell uname -m)
|
||||||
|
|
||||||
|
# Shared-library extension is .dylib on macOS, .so elsewhere.
|
||||||
|
ifeq ($(UNAME_S),Darwin)
|
||||||
|
SHLIB_EXT := dylib
|
||||||
|
RPATH_FLAG := -Wl,-rpath,@loader_path
|
||||||
|
else
|
||||||
|
SHLIB_EXT := so
|
||||||
|
RPATH_FLAG := -Wl,-rpath,'$$ORIGIN'
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Source paths.
|
||||||
|
REPO_ROOT := $(shell cd ../.. && pwd)
|
||||||
|
DUDECT_HDR := dudect/src/dudect.h
|
||||||
|
|
||||||
|
# C compile flags. -O2 is the default per dudect's own build; the
|
||||||
|
# leakage signal is preserved at -O2 because the inner do_one_computation
|
||||||
|
# call boundary is opaque to the compiler.
|
||||||
|
CC ?= cc
|
||||||
|
CFLAGS ?= -O2 -Wall -Wno-unused-parameter -I. -Idudect/src
|
||||||
|
LDFLAGS ?=
|
||||||
|
LDLIBS := -lm
|
||||||
|
|
||||||
|
# Force-include the ARM compat shim on AArch64 hosts so dudect.h's
|
||||||
|
# x86 intrinsics resolve. On x86 we skip the shim.
|
||||||
|
ifeq ($(UNAME_M),arm64)
|
||||||
|
CFLAGS += -include dudect_compat.h
|
||||||
|
endif
|
||||||
|
ifeq ($(UNAME_M),aarch64)
|
||||||
|
CFLAGS += -include dudect_compat.h
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Go env. GOWORK=off matches scripts/test.sh -- this repo uses a
|
||||||
|
# single-module layout and a stray go.work in any parent confuses
|
||||||
|
# the toolchain.
|
||||||
|
GO ?= go
|
||||||
|
GOENV := GOWORK=off CGO_ENABLED=1
|
||||||
|
|
||||||
|
.PHONY: all encrypt decrypt bootstrap clean check-dudect
|
||||||
|
|
||||||
|
all: encrypt decrypt bootstrap
|
||||||
|
|
||||||
|
# Build the cgo shim into a host-platform shared library, then link
|
||||||
|
# the C harness against it.
|
||||||
|
|
||||||
|
libtfhe_encrypt.$(SHLIB_EXT): encrypt_ct.go check-dudect
|
||||||
|
$(GOENV) $(GO) build -buildmode=c-shared \
|
||||||
|
-tags tfhe_encrypt_ct \
|
||||||
|
-o libtfhe_encrypt.$(SHLIB_EXT) \
|
||||||
|
./encrypt_ct.go
|
||||||
|
|
||||||
|
libtfhe_decrypt.$(SHLIB_EXT): decrypt_ct.go check-dudect
|
||||||
|
$(GOENV) $(GO) build -buildmode=c-shared \
|
||||||
|
-tags tfhe_decrypt_ct \
|
||||||
|
-o libtfhe_decrypt.$(SHLIB_EXT) \
|
||||||
|
./decrypt_ct.go
|
||||||
|
|
||||||
|
libtfhe_bootstrap.$(SHLIB_EXT): bootstrap_ct.go check-dudect
|
||||||
|
$(GOENV) $(GO) build -buildmode=c-shared \
|
||||||
|
-tags tfhe_bootstrap_ct \
|
||||||
|
-o libtfhe_bootstrap.$(SHLIB_EXT) \
|
||||||
|
./bootstrap_ct.go
|
||||||
|
|
||||||
|
dudect_encrypt: dudect_encrypt.c libtfhe_encrypt.$(SHLIB_EXT) $(DUDECT_HDR)
|
||||||
|
$(CC) $(CFLAGS) -o dudect_encrypt dudect_encrypt.c \
|
||||||
|
-L. -ltfhe_encrypt $(LDFLAGS) $(LDLIBS) $(RPATH_FLAG)
|
||||||
|
|
||||||
|
dudect_decrypt: dudect_decrypt.c libtfhe_decrypt.$(SHLIB_EXT) $(DUDECT_HDR)
|
||||||
|
$(CC) $(CFLAGS) -o dudect_decrypt dudect_decrypt.c \
|
||||||
|
-L. -ltfhe_decrypt $(LDFLAGS) $(LDLIBS) $(RPATH_FLAG)
|
||||||
|
|
||||||
|
dudect_bootstrap: dudect_bootstrap.c libtfhe_bootstrap.$(SHLIB_EXT) $(DUDECT_HDR)
|
||||||
|
$(CC) $(CFLAGS) -o dudect_bootstrap dudect_bootstrap.c \
|
||||||
|
-L. -ltfhe_bootstrap $(LDFLAGS) $(LDLIBS) $(RPATH_FLAG)
|
||||||
|
|
||||||
|
encrypt: dudect_encrypt
|
||||||
|
decrypt: dudect_decrypt
|
||||||
|
bootstrap: dudect_bootstrap
|
||||||
|
|
||||||
|
check-dudect:
|
||||||
|
@if [ ! -f $(DUDECT_HDR) ]; then \
|
||||||
|
echo "==> dudect.h missing -- run ./fetch.sh"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f dudect_encrypt dudect_decrypt dudect_bootstrap
|
||||||
|
rm -f libtfhe_encrypt.$(SHLIB_EXT) libtfhe_encrypt.h
|
||||||
|
rm -f libtfhe_decrypt.$(SHLIB_EXT) libtfhe_decrypt.h
|
||||||
|
rm -f libtfhe_bootstrap.$(SHLIB_EXT) libtfhe_bootstrap.h
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# Lux TFHE -- constant-time analysis harness (dudect)
|
||||||
|
|
||||||
|
Empirical constant-time verification of the Lux TFHE hot path
|
||||||
|
(Encrypt / Decrypt / Bootstrap) using dudect
|
||||||
|
(https://github.com/oreparaz/dudect).
|
||||||
|
|
||||||
|
## CT population
|
||||||
|
|
||||||
|
Both dudect classes are **valid** invocations of the routine under
|
||||||
|
test, differing only in the secret input:
|
||||||
|
|
||||||
|
- **Encrypt**: class A = `encrypt(false)`, class B = `encrypt(random bit)`.
|
||||||
|
- **Decrypt**: class A = `decrypt(pool[0])`, class B = `decrypt(pool[rand])`.
|
||||||
|
- **Bootstrap**: class A = `pbs(pool[0])`, class B = `pbs(pool[rand])`.
|
||||||
|
|
||||||
|
Any timing difference between classes is a real
|
||||||
|
secret-content-dependent timing in the routine pipeline. This is the
|
||||||
|
same operational framing as `~/work/lux/pulsar/ct/dudect/` and is
|
||||||
|
documented in detail there.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./fetch.sh # one-time: pulls dudect.h
|
||||||
|
make # builds all three harnesses
|
||||||
|
./dudect_encrypt # smoke test (~30s)
|
||||||
|
./dudect_decrypt # smoke test (~30s)
|
||||||
|
./dudect_bootstrap # smoke test (~5min; PBS is slow)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Full submission run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DUDECT_SAMPLES=1000000 DUDECT_MAX_BATCHES=1000 ./dudect_encrypt
|
||||||
|
DUDECT_SAMPLES=1000000 DUDECT_MAX_BATCHES=1000 ./dudect_decrypt
|
||||||
|
DUDECT_SAMPLES=100000 DUDECT_MAX_BATCHES=100 ./dudect_bootstrap
|
||||||
|
```
|
||||||
|
|
||||||
|
Pin a CPU (`taskset -c 0` on Linux), disable turbo boost, close
|
||||||
|
background services. Each verify run takes ~12 hours; bootstrap ~24
|
||||||
|
hours.
|
||||||
|
|
||||||
|
## Exit codes
|
||||||
|
|
||||||
|
- `0`: VERDICT no leakage detected within budget.
|
||||||
|
- `2`: VERDICT leakage detected (t-test > 4.5).
|
||||||
|
- Other: harness setup error.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
This is the **initial** scaffolding for the Tier A track. The
|
||||||
|
expected verdict for all three routines is "no leakage detected"
|
||||||
|
because:
|
||||||
|
|
||||||
|
- The Lux Go implementation routes through `lattice/v7/core/rlwe`,
|
||||||
|
which is BoringSSL-grade CT.
|
||||||
|
- The C++ production path is `luxcpp/crypto/fhe/cpp/backends/cpu/`
|
||||||
|
with CT helpers in `gpu/src/cpu_fhe_helpers.hpp` (mod_add /
|
||||||
|
mod_sub / mod_mul / mod_neg are constant-time per
|
||||||
|
`~/work/lux/luxcpp/CLAUDE.md`).
|
||||||
|
|
||||||
|
The harness is the empirical regression guard for those code paths.
|
||||||
|
|
||||||
|
## Citations
|
||||||
|
|
||||||
|
- Reparaz, Balasch, Verbauwhede, "Dude, is my code constant time?",
|
||||||
|
DATE 2017.
|
||||||
|
- Almeida, Barbosa, Pacheco, Pereira, Pinto, "Verifying
|
||||||
|
Constant-Time Implementations", USENIX Security 2016 (BGL leakage
|
||||||
|
model).
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
//go:build tfhe_bootstrap_ct
|
||||||
|
|
||||||
|
// bootstrap_ct.go -- cgo bridge exposing TFHE PBS to the C dudect
|
||||||
|
// harness in dudect_bootstrap.c.
|
||||||
|
//
|
||||||
|
// CT POPULATION:
|
||||||
|
// Both dudect classes are VALID Bootstrap invocations on
|
||||||
|
// pre-generated valid LWE ciphertexts encoding different secret
|
||||||
|
// bits:
|
||||||
|
// class A: always ciphertext_pool[0] (fixed)
|
||||||
|
// class B: ciphertext_pool[rand % pool_size] (varying valid)
|
||||||
|
//
|
||||||
|
// The bootstrap operation is the heaviest TFHE primitive (~50-80 ms
|
||||||
|
// per call on M2 Pro for the NTT-fused fast path). Each dudect
|
||||||
|
// sample re-runs the full PBS; the harness defaults are smaller
|
||||||
|
// than for verify/decrypt (5000 samples/batch x 100 batches).
|
||||||
|
//
|
||||||
|
// Build:
|
||||||
|
// GOWORK=off go build -buildmode=c-shared \
|
||||||
|
// -o libtfhe_bootstrap.{so,dylib} ./bootstrap_ct.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo arm64 CFLAGS: -include ${SRCDIR}/dudect_compat.h
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/luxfi/fhe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const kBootstrapValidPool = 16
|
||||||
|
|
||||||
|
var (
|
||||||
|
bFhParams fhe.Parameters
|
||||||
|
bFhSecret *fhe.SecretKey
|
||||||
|
bFhEval *fhe.Evaluator
|
||||||
|
bFhBSK *fhe.BootstrapKey
|
||||||
|
bFhPool [kBootstrapValidPool]*fhe.Ciphertext
|
||||||
|
)
|
||||||
|
|
||||||
|
//export tfhe_bootstrap_ct_setup
|
||||||
|
//
|
||||||
|
// Initialise the long-lived fixture. Returns 0 on success.
|
||||||
|
func tfhe_bootstrap_ct_setup() C.int {
|
||||||
|
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
|
||||||
|
if err != nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
kg := fhe.NewKeyGenerator(params)
|
||||||
|
sk := kg.GenSecretKey()
|
||||||
|
bsk := kg.GenBootstrapKey(sk)
|
||||||
|
enc := fhe.NewEncryptor(params, sk)
|
||||||
|
|
||||||
|
for i := 0; i < kBootstrapValidPool; i++ {
|
||||||
|
var b [1]byte
|
||||||
|
if _, err := rand.Read(b[:]); err != nil {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
ct, err := enc.EncryptSafe(b[0]&1 == 1)
|
||||||
|
if err != nil {
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
bFhPool[i] = ct
|
||||||
|
}
|
||||||
|
|
||||||
|
bFhParams = params
|
||||||
|
bFhSecret = sk
|
||||||
|
bFhBSK = bsk
|
||||||
|
bFhEval = fhe.NewEvaluator(params, bsk)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tfhe_bootstrap_ct_pool_size
|
||||||
|
func tfhe_bootstrap_ct_pool_size() C.size_t {
|
||||||
|
return C.size_t(kBootstrapValidPool)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tfhe_bootstrap_ct_input_size
|
||||||
|
func tfhe_bootstrap_ct_input_size() C.size_t {
|
||||||
|
return C.size_t(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tfhe_bootstrap_ct
|
||||||
|
//
|
||||||
|
// One dudect measurement sample. data points to a 4-byte big-endian
|
||||||
|
// uint32 pool index. We read the index, mod it against the pool
|
||||||
|
// size, and PBS the indexed ciphertext through the NAND LUT (most
|
||||||
|
// CT-uniform choice).
|
||||||
|
func tfhe_bootstrap_ct(data *C.uint8_t) {
|
||||||
|
if bFhEval == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src := unsafe.Slice((*byte)(unsafe.Pointer(data)), 4)
|
||||||
|
idx := (uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])) %
|
||||||
|
uint32(kBootstrapValidPool)
|
||||||
|
// NAND exercises the PBS pipeline with a deterministic LUT.
|
||||||
|
_, _ = bFhEval.NAND(bFhPool[idx], bFhPool[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
//go:build tfhe_decrypt_ct
|
||||||
|
|
||||||
|
// decrypt_ct.go -- cgo bridge exposing TFHE Decrypt to the C dudect
|
||||||
|
// harness in dudect_decrypt.c.
|
||||||
|
//
|
||||||
|
// DECRYPT IS THE MOST CT-CRITICAL TFHE ROUTINE. An adversary that
|
||||||
|
// submits an LWE ciphertext and observes timing channels can extract
|
||||||
|
// bits of the secret key. The Lux Go implementation routes Decrypt
|
||||||
|
// through `lattice/v7/core/rlwe` which uses libjade-quality CT field
|
||||||
|
// arithmetic.
|
||||||
|
//
|
||||||
|
// CT POPULATION:
|
||||||
|
// Both dudect classes are VALID Decrypt invocations on
|
||||||
|
// pre-generated valid LWE ciphertexts:
|
||||||
|
// class A: always ciphertext_pool[0] (fixed)
|
||||||
|
// class B: ciphertext_pool[rand % pool_size] (varying valid)
|
||||||
|
// Both ciphertexts are valid encryptions under the same secret
|
||||||
|
// key with different bit values + different per-call randomness.
|
||||||
|
//
|
||||||
|
// Build:
|
||||||
|
// GOWORK=off go build -buildmode=c-shared \
|
||||||
|
// -o libtfhe_decrypt.{so,dylib} ./decrypt_ct.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo arm64 CFLAGS: -include ${SRCDIR}/dudect_compat.h
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/luxfi/fhe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pool size -- K independent valid ciphertexts. dudect class A uses
|
||||||
|
// pool[0] uniformly; class B uses pool[rand % K].
|
||||||
|
const kDecryptValidPool = 32
|
||||||
|
|
||||||
|
var (
|
||||||
|
dFhParams fhe.Parameters
|
||||||
|
dFhSecret *fhe.SecretKey
|
||||||
|
dFhDecrypt *fhe.Decryptor
|
||||||
|
dFhPool [kDecryptValidPool]*fhe.Ciphertext
|
||||||
|
)
|
||||||
|
|
||||||
|
//export tfhe_decrypt_ct_setup
|
||||||
|
//
|
||||||
|
// Initialise the long-lived fixture. Returns 0 on success.
|
||||||
|
func tfhe_decrypt_ct_setup() C.int {
|
||||||
|
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
|
||||||
|
if err != nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
kg := fhe.NewKeyGenerator(params)
|
||||||
|
sk := kg.GenSecretKey()
|
||||||
|
enc := fhe.NewEncryptor(params, sk)
|
||||||
|
dec := fhe.NewDecryptor(params, sk)
|
||||||
|
|
||||||
|
for i := 0; i < kDecryptValidPool; i++ {
|
||||||
|
var b [1]byte
|
||||||
|
if _, err := rand.Read(b[:]); err != nil {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
ct, err := enc.EncryptSafe(b[0]&1 == 1)
|
||||||
|
if err != nil {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
dFhPool[i] = ct
|
||||||
|
}
|
||||||
|
|
||||||
|
dFhParams = params
|
||||||
|
dFhSecret = sk
|
||||||
|
dFhDecrypt = dec
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tfhe_decrypt_ct_pool_size
|
||||||
|
func tfhe_decrypt_ct_pool_size() C.size_t {
|
||||||
|
return C.size_t(kDecryptValidPool)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tfhe_decrypt_ct_input_size
|
||||||
|
//
|
||||||
|
// Returns the per-sample input width: 4 bytes (a big-endian uint32
|
||||||
|
// pool index, mod kDecryptValidPool).
|
||||||
|
func tfhe_decrypt_ct_input_size() C.size_t {
|
||||||
|
return C.size_t(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tfhe_decrypt_ct
|
||||||
|
//
|
||||||
|
// One dudect measurement sample. `data` points to a 4-byte
|
||||||
|
// big-endian uint32 pool index. We read the index, take its mod
|
||||||
|
// against the pool size, and Decrypt the indexed ciphertext.
|
||||||
|
//
|
||||||
|
// This is CT over `data` beyond the modular reduction.
|
||||||
|
func tfhe_decrypt_ct(data *C.uint8_t) {
|
||||||
|
if dFhDecrypt == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src := unsafe.Slice((*byte)(unsafe.Pointer(data)), 4)
|
||||||
|
idx := (uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])) %
|
||||||
|
uint32(kDecryptValidPool)
|
||||||
|
_ = dFhDecrypt.Decrypt(dFhPool[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||||
|
* See the file LICENSE for licensing terms.
|
||||||
|
*
|
||||||
|
* dudect_bootstrap.c -- dudect main loop driving the TFHE programmable
|
||||||
|
* bootstrap (PBS) through the cgo bridge in bootstrap_ct.go.
|
||||||
|
*
|
||||||
|
* PBS = key_switch o sample_extract o blind_rotate. The composite is
|
||||||
|
* the heaviest TFHE primitive; we use a smaller default measurement
|
||||||
|
* budget than for verify/decrypt.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DUDECT_IMPLEMENTATION
|
||||||
|
#include "dudect/src/dudect.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Exported by libtfhe_bootstrap (bootstrap_ct.go). */
|
||||||
|
extern int tfhe_bootstrap_ct_setup(void);
|
||||||
|
extern size_t tfhe_bootstrap_ct_pool_size(void);
|
||||||
|
extern size_t tfhe_bootstrap_ct_input_size(void);
|
||||||
|
extern void tfhe_bootstrap_ct(uint8_t *data);
|
||||||
|
|
||||||
|
static size_t g_chunk_size = 0;
|
||||||
|
static size_t g_pool_size = 0;
|
||||||
|
|
||||||
|
void prepare_inputs(dudect_config_t *cfg, uint8_t *input_data, uint8_t *classes) {
|
||||||
|
for (size_t i = 0; i < cfg->number_measurements; i++) {
|
||||||
|
classes[i] = randombit();
|
||||||
|
uint8_t *slot = input_data + (size_t)i * cfg->chunk_size;
|
||||||
|
if (classes[i] == 0) {
|
||||||
|
slot[0] = 0;
|
||||||
|
slot[1] = 0;
|
||||||
|
slot[2] = 0;
|
||||||
|
slot[3] = 0;
|
||||||
|
} else {
|
||||||
|
uint8_t pick_buf[8];
|
||||||
|
randombytes(pick_buf, sizeof pick_buf);
|
||||||
|
uint64_t pick = 0;
|
||||||
|
for (size_t k = 0; k < sizeof pick_buf; k++) {
|
||||||
|
pick = (pick << 8) | pick_buf[k];
|
||||||
|
}
|
||||||
|
uint32_t idx = (uint32_t)(pick % g_pool_size);
|
||||||
|
slot[0] = (uint8_t)(idx >> 24);
|
||||||
|
slot[1] = (uint8_t)(idx >> 16);
|
||||||
|
slot[2] = (uint8_t)(idx >> 8);
|
||||||
|
slot[3] = (uint8_t)(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t do_one_computation(uint8_t *data) {
|
||||||
|
tfhe_bootstrap_ct(data);
|
||||||
|
uint8_t acc = 0;
|
||||||
|
for (size_t i = 0; i < g_chunk_size; i++) acc ^= data[i];
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
(void)argc;
|
||||||
|
(void)argv;
|
||||||
|
|
||||||
|
int rc = tfhe_bootstrap_ct_setup();
|
||||||
|
if (rc != 0) {
|
||||||
|
fprintf(stderr, "tfhe_bootstrap_ct_setup failed: rc=%d\n", rc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
g_chunk_size = tfhe_bootstrap_ct_input_size();
|
||||||
|
g_pool_size = tfhe_bootstrap_ct_pool_size();
|
||||||
|
if (g_chunk_size == 0 || g_pool_size == 0) {
|
||||||
|
fprintf(stderr, "tfhe_bootstrap_ct sizes returned 0\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Smaller default budget for PBS (~50-80 ms each on M2 Pro).
|
||||||
|
* Full submission run: DUDECT_SAMPLES=100000 DUDECT_MAX_BATCHES=100.
|
||||||
|
*/
|
||||||
|
const char *env_samples = getenv("DUDECT_SAMPLES");
|
||||||
|
const char *env_batches = getenv("DUDECT_MAX_BATCHES");
|
||||||
|
size_t samples = env_samples ? (size_t)atoll(env_samples) : 2000;
|
||||||
|
size_t batches = env_batches ? (size_t)atoll(env_batches) : 50;
|
||||||
|
|
||||||
|
dudect_config_t cfg = {
|
||||||
|
.chunk_size = g_chunk_size,
|
||||||
|
.number_measurements = samples,
|
||||||
|
.max_number_batches = batches,
|
||||||
|
};
|
||||||
|
dudect_ctx_t ctx;
|
||||||
|
dudect_init(&ctx, &cfg);
|
||||||
|
|
||||||
|
dudect_state_t state = DUDECT_NO_LEAKAGE_EVIDENCE_YET;
|
||||||
|
while (state == DUDECT_NO_LEAKAGE_EVIDENCE_YET) {
|
||||||
|
state = dudect_main(&ctx);
|
||||||
|
}
|
||||||
|
dudect_free(&ctx);
|
||||||
|
|
||||||
|
if (state == DUDECT_LEAKAGE_FOUND) {
|
||||||
|
fprintf(stderr, "VERDICT: leakage detected in tfhe.Bootstrap\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
fprintf(stdout, "VERDICT: no leakage detected within budget\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||||
|
* See the file LICENSE for licensing terms.
|
||||||
|
*
|
||||||
|
* dudect_compat.h — minimal x86-intrinsic compatibility shim for
|
||||||
|
* AArch64 hosts.
|
||||||
|
*
|
||||||
|
* Upstream dudect.h (https://github.com/oreparaz/dudect) hard-codes
|
||||||
|
* x86 intrinsics for cycle counting:
|
||||||
|
*
|
||||||
|
* _mm_mfence() — memory barrier
|
||||||
|
* __rdtsc() — read time-stamp counter
|
||||||
|
*
|
||||||
|
* On AArch64 hosts (Apple Silicon, AWS Graviton, etc.), these are
|
||||||
|
* not available. This shim supplies equivalents.
|
||||||
|
*
|
||||||
|
* Counter source by OS:
|
||||||
|
* - Linux/AArch64: CNTVCT_EL0 — the AArch64 generic timer counter.
|
||||||
|
* Readable from EL0 on every supported kernel. Monotonic.
|
||||||
|
* Frequency from CNTFRQ_EL0 (typically 50-100 MHz; the resulting
|
||||||
|
* resolution is coarser than RDTSC but the t-test is dimensionless
|
||||||
|
* in counter units so the constant-time verdict is unchanged.)
|
||||||
|
* - Darwin/AArch64 (Apple Silicon): CNTVCT_EL0 is exposed but reads
|
||||||
|
* in user mode have been observed to return non-monotonic values
|
||||||
|
* (the timer is virtualised differently per-process). We use
|
||||||
|
* mach_absolute_time() instead, which the XNU kernel guarantees is
|
||||||
|
* monotonic and high-resolution. The tick frequency is exposed via
|
||||||
|
* mach_timebase_info but we don't normalise — dudect operates on
|
||||||
|
* raw deltas only.
|
||||||
|
*
|
||||||
|
* The shim is included BEFORE dudect.h via -include, so it intercepts
|
||||||
|
* the x86 headers (<emmintrin.h>, <x86intrin.h>) by providing the
|
||||||
|
* symbols dudect needs without ever pulling in the SSE/AVX
|
||||||
|
* headerset.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DUDECT_COMPAT_H
|
||||||
|
#define DUDECT_COMPAT_H
|
||||||
|
|
||||||
|
#if defined(__aarch64__)
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Replace x86 intrinsic headers with empty stubs on ARM64 so dudect.h
|
||||||
|
* can include them as written.
|
||||||
|
*/
|
||||||
|
#define _EMMINTRIN_H_INCLUDED
|
||||||
|
#define __EMMINTRIN_H
|
||||||
|
#define _IMMINTRIN_H_INCLUDED
|
||||||
|
#define _X86INTRIN_H_INCLUDED
|
||||||
|
#define __X86INTRIN_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Full data + instruction barrier. dudect calls this immediately
|
||||||
|
* before reading the cycle counter to keep memory operations ordered
|
||||||
|
* with respect to the counter read.
|
||||||
|
*/
|
||||||
|
static inline void _mm_mfence(void) {
|
||||||
|
__asm__ __volatile__("dsb sy" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
|
||||||
|
#include <mach/mach_time.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Apple Silicon: use mach_absolute_time(). High-resolution, monotonic,
|
||||||
|
* kernel-virtualised. Resolution is typically 1 ns (24 MHz timer with
|
||||||
|
* 41.667 ns per tick × scaler — varies by Mac model; XNU normalises
|
||||||
|
* via mach_timebase_info).
|
||||||
|
*
|
||||||
|
* mach_absolute_time() is observably free of the non-monotonic
|
||||||
|
* artefacts CNTVCT_EL0 has in EL0 on macOS.
|
||||||
|
*/
|
||||||
|
static inline uint64_t __rdtsc(void) {
|
||||||
|
return mach_absolute_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* Linux / *BSD on AArch64 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the AArch64 virtual count register (CNTVCT_EL0). Readable
|
||||||
|
* from EL0 by default on Linux. ISB before the read ensures the
|
||||||
|
* counter read is not speculated past the preceding fence.
|
||||||
|
*/
|
||||||
|
static inline uint64_t __rdtsc(void) {
|
||||||
|
uint64_t v;
|
||||||
|
__asm__ __volatile__("isb; mrs %0, cntvct_el0" : "=r" (v));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __APPLE__ */
|
||||||
|
|
||||||
|
#endif /* __aarch64__ */
|
||||||
|
|
||||||
|
#endif /* DUDECT_COMPAT_H */
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||||
|
* See the file LICENSE for licensing terms.
|
||||||
|
*
|
||||||
|
* dudect_decrypt.c -- dudect main loop driving fhe.Decryptor.Decrypt
|
||||||
|
* through the cgo bridge in decrypt_ct.go.
|
||||||
|
*
|
||||||
|
* Decrypt is the most CT-critical TFHE routine -- a timing leak
|
||||||
|
* here leaks bits of the secret key. The harness uses a pool of
|
||||||
|
* valid ciphertexts pre-generated under the same secret key, with
|
||||||
|
* varying plaintext bits, so any timing difference is a real
|
||||||
|
* secret-content-dependent signal in Decrypt.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DUDECT_IMPLEMENTATION
|
||||||
|
#include "dudect/src/dudect.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Exported by libtfhe_decrypt (decrypt_ct.go). */
|
||||||
|
extern int tfhe_decrypt_ct_setup(void);
|
||||||
|
extern size_t tfhe_decrypt_ct_pool_size(void);
|
||||||
|
extern size_t tfhe_decrypt_ct_input_size(void);
|
||||||
|
extern void tfhe_decrypt_ct(uint8_t *data);
|
||||||
|
|
||||||
|
static size_t g_chunk_size = 0;
|
||||||
|
static size_t g_pool_size = 0;
|
||||||
|
|
||||||
|
void prepare_inputs(dudect_config_t *cfg, uint8_t *input_data, uint8_t *classes) {
|
||||||
|
for (size_t i = 0; i < cfg->number_measurements; i++) {
|
||||||
|
classes[i] = randombit();
|
||||||
|
uint8_t *slot = input_data + (size_t)i * cfg->chunk_size;
|
||||||
|
if (classes[i] == 0) {
|
||||||
|
/* class A: pool[0] every time. */
|
||||||
|
slot[0] = 0;
|
||||||
|
slot[1] = 0;
|
||||||
|
slot[2] = 0;
|
||||||
|
slot[3] = 0;
|
||||||
|
} else {
|
||||||
|
/* class B: uniformly-chosen pool index. */
|
||||||
|
uint8_t pick_buf[8];
|
||||||
|
randombytes(pick_buf, sizeof pick_buf);
|
||||||
|
uint64_t pick = 0;
|
||||||
|
for (size_t k = 0; k < sizeof pick_buf; k++) {
|
||||||
|
pick = (pick << 8) | pick_buf[k];
|
||||||
|
}
|
||||||
|
uint32_t idx = (uint32_t)(pick % g_pool_size);
|
||||||
|
slot[0] = (uint8_t)(idx >> 24);
|
||||||
|
slot[1] = (uint8_t)(idx >> 16);
|
||||||
|
slot[2] = (uint8_t)(idx >> 8);
|
||||||
|
slot[3] = (uint8_t)(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t do_one_computation(uint8_t *data) {
|
||||||
|
tfhe_decrypt_ct(data);
|
||||||
|
uint8_t acc = 0;
|
||||||
|
for (size_t i = 0; i < g_chunk_size; i++) acc ^= data[i];
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
(void)argc;
|
||||||
|
(void)argv;
|
||||||
|
|
||||||
|
int rc = tfhe_decrypt_ct_setup();
|
||||||
|
if (rc != 0) {
|
||||||
|
fprintf(stderr, "tfhe_decrypt_ct_setup failed: rc=%d\n", rc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
g_chunk_size = tfhe_decrypt_ct_input_size();
|
||||||
|
g_pool_size = tfhe_decrypt_ct_pool_size();
|
||||||
|
if (g_chunk_size == 0 || g_pool_size == 0) {
|
||||||
|
fprintf(stderr, "tfhe_decrypt_ct sizes returned 0\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *env_samples = getenv("DUDECT_SAMPLES");
|
||||||
|
const char *env_batches = getenv("DUDECT_MAX_BATCHES");
|
||||||
|
size_t samples = env_samples ? (size_t)atoll(env_samples) : 10000;
|
||||||
|
size_t batches = env_batches ? (size_t)atoll(env_batches) : 100;
|
||||||
|
|
||||||
|
dudect_config_t cfg = {
|
||||||
|
.chunk_size = g_chunk_size,
|
||||||
|
.number_measurements = samples,
|
||||||
|
.max_number_batches = batches,
|
||||||
|
};
|
||||||
|
dudect_ctx_t ctx;
|
||||||
|
dudect_init(&ctx, &cfg);
|
||||||
|
|
||||||
|
dudect_state_t state = DUDECT_NO_LEAKAGE_EVIDENCE_YET;
|
||||||
|
while (state == DUDECT_NO_LEAKAGE_EVIDENCE_YET) {
|
||||||
|
state = dudect_main(&ctx);
|
||||||
|
}
|
||||||
|
dudect_free(&ctx);
|
||||||
|
|
||||||
|
if (state == DUDECT_LEAKAGE_FOUND) {
|
||||||
|
fprintf(stderr, "VERDICT: leakage detected in tfhe.Decrypt\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
fprintf(stdout, "VERDICT: no leakage detected within budget\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||||
|
* See the file LICENSE for licensing terms.
|
||||||
|
*
|
||||||
|
* dudect_encrypt.c -- dudect main loop driving fhe.Encryptor.EncryptSafe
|
||||||
|
* through the cgo bridge in encrypt_ct.go.
|
||||||
|
*
|
||||||
|
* dudect API contract: the user provides do_one_computation(data) and
|
||||||
|
* prepare_inputs(cfg, input, classes); dudect's `dudect_main` invokes
|
||||||
|
* them via the extern declarations at the bottom of dudect.h.
|
||||||
|
*
|
||||||
|
* We define DUDECT_IMPLEMENTATION exactly once before including the
|
||||||
|
* header so the whole library compiles into this translation unit.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DUDECT_IMPLEMENTATION
|
||||||
|
#include "dudect/src/dudect.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Exported by libtfhe_encrypt (encrypt_ct.go). */
|
||||||
|
extern int tfhe_encrypt_ct_setup(void);
|
||||||
|
extern size_t tfhe_encrypt_ct_input_size(void);
|
||||||
|
extern void tfhe_encrypt_ct(uint8_t *data);
|
||||||
|
|
||||||
|
static size_t g_chunk_size = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* dudect API hook: populate cfg->number_measurements samples of
|
||||||
|
* cfg->chunk_size bytes each, and assign each sample a binary class
|
||||||
|
* label (0 = fixed class A, 1 = random class B).
|
||||||
|
*
|
||||||
|
* class A: byte 0 = 0 (encrypt false)
|
||||||
|
* class B: byte 0 = uniformly-random byte (encrypt random bit)
|
||||||
|
*/
|
||||||
|
void prepare_inputs(dudect_config_t *cfg, uint8_t *input_data, uint8_t *classes) {
|
||||||
|
for (size_t i = 0; i < cfg->number_measurements; i++) {
|
||||||
|
classes[i] = randombit();
|
||||||
|
uint8_t *slot = input_data + (size_t)i * cfg->chunk_size;
|
||||||
|
if (classes[i] == 0) {
|
||||||
|
slot[0] = 0; /* always 0 in class A */
|
||||||
|
} else {
|
||||||
|
uint8_t r;
|
||||||
|
randombytes(&r, 1);
|
||||||
|
slot[0] = r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* dudect API hook: one measurement sample. Must depend on `data` so
|
||||||
|
* the compiler cannot dead-code-eliminate the body, must NOT branch
|
||||||
|
* on `data` (or we measure our own branch), and must return a
|
||||||
|
* uint8_t that flows out of the function so the result is observed.
|
||||||
|
*/
|
||||||
|
uint8_t do_one_computation(uint8_t *data) {
|
||||||
|
tfhe_encrypt_ct(data);
|
||||||
|
uint8_t acc = 0;
|
||||||
|
for (size_t i = 0; i < g_chunk_size; i++) acc ^= data[i];
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
(void)argc;
|
||||||
|
(void)argv;
|
||||||
|
|
||||||
|
int rc = tfhe_encrypt_ct_setup();
|
||||||
|
if (rc != 0) {
|
||||||
|
fprintf(stderr, "tfhe_encrypt_ct_setup failed: rc=%d\n", rc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
g_chunk_size = tfhe_encrypt_ct_input_size();
|
||||||
|
if (g_chunk_size == 0) {
|
||||||
|
fprintf(stderr, "tfhe_encrypt_ct_input_size returned 0\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SMOKE-TEST default (10 000 samples per batch). The full NIST
|
||||||
|
* submission run is 10^9 samples on a quiet, CPU-pinned host;
|
||||||
|
* override via env vars:
|
||||||
|
* DUDECT_SAMPLES=1000000 DUDECT_MAX_BATCHES=1000 ./dudect_encrypt
|
||||||
|
*/
|
||||||
|
const char *env_samples = getenv("DUDECT_SAMPLES");
|
||||||
|
const char *env_batches = getenv("DUDECT_MAX_BATCHES");
|
||||||
|
size_t samples = env_samples ? (size_t)atoll(env_samples) : 10000;
|
||||||
|
size_t batches = env_batches ? (size_t)atoll(env_batches) : 100;
|
||||||
|
|
||||||
|
dudect_config_t cfg = {
|
||||||
|
.chunk_size = g_chunk_size,
|
||||||
|
.number_measurements = samples,
|
||||||
|
.max_number_batches = batches,
|
||||||
|
};
|
||||||
|
dudect_ctx_t ctx;
|
||||||
|
dudect_init(&ctx, &cfg);
|
||||||
|
|
||||||
|
dudect_state_t state = DUDECT_NO_LEAKAGE_EVIDENCE_YET;
|
||||||
|
while (state == DUDECT_NO_LEAKAGE_EVIDENCE_YET) {
|
||||||
|
state = dudect_main(&ctx);
|
||||||
|
}
|
||||||
|
dudect_free(&ctx);
|
||||||
|
|
||||||
|
if (state == DUDECT_LEAKAGE_FOUND) {
|
||||||
|
fprintf(stderr, "VERDICT: leakage detected in tfhe.Encrypt\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
fprintf(stdout, "VERDICT: no leakage detected within budget\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
//go:build tfhe_encrypt_ct
|
||||||
|
|
||||||
|
// encrypt_ct.go -- cgo bridge exposing TFHE Encrypt to the C dudect
|
||||||
|
// harness in dudect_encrypt.c.
|
||||||
|
//
|
||||||
|
// CT POPULATION (operational framing):
|
||||||
|
// Both dudect classes are VALID Encrypt invocations differing in
|
||||||
|
// the secret bit being encrypted:
|
||||||
|
// class A: always encrypt(false)
|
||||||
|
// class B: encrypt(random bit)
|
||||||
|
// Any timing difference between classes is a real bit-dependent
|
||||||
|
// timing in the Encrypt pipeline.
|
||||||
|
//
|
||||||
|
// The fixture uses Lux's default parameter set PN10QP27 with a
|
||||||
|
// freshly-generated secret key. Each dudect sample re-invokes
|
||||||
|
// Encrypt; the leakage profile is the per-call wall-clock + cycle
|
||||||
|
// counter as collected by dudect's measurement loop.
|
||||||
|
//
|
||||||
|
// Build (Linux):
|
||||||
|
// GOWORK=off go build -buildmode=c-shared \
|
||||||
|
// -o libtfhe_encrypt.so ./encrypt_ct.go
|
||||||
|
// Build (macOS):
|
||||||
|
// GOWORK=off go build -buildmode=c-shared \
|
||||||
|
// -o libtfhe_encrypt.dylib ./encrypt_ct.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo arm64 CFLAGS: -include ${SRCDIR}/dudect_compat.h
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/luxfi/fhe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Long-lived fixture.
|
||||||
|
var (
|
||||||
|
fhParams fhe.Parameters
|
||||||
|
fhSecret *fhe.SecretKey
|
||||||
|
fhEncrypt *fhe.Encryptor
|
||||||
|
)
|
||||||
|
|
||||||
|
//export tfhe_encrypt_ct_setup
|
||||||
|
//
|
||||||
|
// Initialise the long-lived fixture. Returns 0 on success.
|
||||||
|
func tfhe_encrypt_ct_setup() C.int {
|
||||||
|
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
|
||||||
|
if err != nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
kg := fhe.NewKeyGenerator(params)
|
||||||
|
sk := kg.GenSecretKey()
|
||||||
|
fhParams = params
|
||||||
|
fhSecret = sk
|
||||||
|
fhEncrypt = fhe.NewEncryptor(params, sk)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tfhe_encrypt_ct_input_size
|
||||||
|
//
|
||||||
|
// Returns the per-sample input width: 1 byte (the bit to encrypt,
|
||||||
|
// where any non-zero value is `true`).
|
||||||
|
func tfhe_encrypt_ct_input_size() C.size_t {
|
||||||
|
return C.size_t(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tfhe_encrypt_ct
|
||||||
|
//
|
||||||
|
// One dudect measurement sample. `data` points to a 1-byte input.
|
||||||
|
// data[0] = 0 => encrypt(false); data[0] != 0 => encrypt(true).
|
||||||
|
//
|
||||||
|
// This call must NOT branch on the input bit beyond the conversion
|
||||||
|
// from byte to bool.
|
||||||
|
func tfhe_encrypt_ct(data *C.uint8_t) {
|
||||||
|
if fhEncrypt == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src := unsafe.Slice((*byte)(unsafe.Pointer(data)), 1)
|
||||||
|
bit := src[0] != 0
|
||||||
|
_, _ = fhEncrypt.EncryptSafe(bit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {}
|
||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Fetch dudect at the pinned commit used by Pulsar's constant-time
|
||||||
|
# track.
|
||||||
|
#
|
||||||
|
# dudect (https://github.com/oreparaz/dudect) is a single-header
|
||||||
|
# leakage-detection library. We do not vendor dudect.h into this
|
||||||
|
# repository — this script reproduces it on demand.
|
||||||
|
#
|
||||||
|
# Pinned hash at submission-tag time. Update at the same time as the
|
||||||
|
# Pulsar submission tag so the artifact chain stays reproducible.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Repo + commit (master HEAD as of the dudect harness scaffold land).
|
||||||
|
# Update when bumping the submission tag.
|
||||||
|
DUDECT_REPO="https://github.com/oreparaz/dudect.git"
|
||||||
|
DUDECT_COMMIT="master"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
if [[ -d dudect/.git ]]; then
|
||||||
|
echo "==> dudect already cloned at $SCRIPT_DIR/dudect"
|
||||||
|
( cd dudect && git fetch --quiet origin && git checkout --quiet "$DUDECT_COMMIT" )
|
||||||
|
else
|
||||||
|
echo "==> cloning dudect at $DUDECT_COMMIT"
|
||||||
|
git clone --quiet "$DUDECT_REPO" dudect
|
||||||
|
( cd dudect && git checkout --quiet "$DUDECT_COMMIT" )
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The header we link against is dudect/src/dudect.h. Stamp the resolved
|
||||||
|
# revision so reviewers can verify the artifact chain.
|
||||||
|
RESOLVED_REV="$(cd dudect && git rev-parse HEAD)"
|
||||||
|
echo "==> dudect.h ready at $SCRIPT_DIR/dudect/src/dudect.h (rev $RESOLVED_REV)"
|
||||||
Reference in New Issue
Block a user