mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
Adds GPU/CPU split for HQC's hot inner-loop primitives:
gpu_nocgo.go Pure-Go bit-sliced Karatsuba mod (X^n - 1), byte-equal
to PQClean's PQCLEAN_HQC{128,192,256}_CLEAN_vect_mul.
Constant-time on secret operands (masked nibble table
lookups; no data-dependent branches or indices).
Modern Go compiler emits SIMD-friendly XOR loops on
amd64 / arm64.
gpu_cgo.go Same surface, routed through luxfi/accel/hqc -> the
C kernel in libluxgpu_hqc.a (also byte-equal to PQClean).
gpu_kat_test.go KAT cross-check across both builds — vectors
captured from the cgo path's PQClean kernel,
verified bit-identical from the pure-Go path
(100-iter checksum + single-vector KAT).
gpu_polymul_test.go Algebraic properties (commutativity, identity,
zero, distributivity, associativity fuzz x600).
gpu_bench_test.go Single-shot + batched benchmarks per mode.
Updated gpu.go to route the existing batchEncapsulateGPU /
batchDecapsulateGPU through accel/ops/code's batch surface (replaces
the old "not implemented" stubs). Updated randombytes_shim.c to be
the single canonical PQClean entropy bridge.
Determinism contract: validators reach consensus on HQC encapsulation
ciphertexts, so any byte-level divergence between the two paths is a
consensus fork. The KAT test pins this contract.
Test status (all three configs green):
CGO_ENABLED=0 go test ./hqc/... 18.1s
CGO_ENABLED=1 go test ./hqc/... 44.2s
CGO_ENABLED=1 go test -tags=hqc_pqclean ./hqc/.. 5.5s
NIST KATs (HQC128/192/256 encap/decap byte-vectors): all PASS.
Bench numbers on Apple M1 Max (single-shot GF2 polymul, ns/op):
HQC-128: pure-Go 2.03 ms / cgo 4.10 ms
HQC-192: pure-Go 6.28 ms / cgo (n.m.)
HQC-256: pure-Go 11.20 ms / cgo (n.m.)
The cgo path is currently slower than pure-Go because libluxgpu_hqc.a
ships from luxcpp/gpu/build with CMAKE_BUILD_TYPE empty (no -O3). With
the CMake build switched to Release, cgo will return to its expected
~30us / poly margin.
Wired via:
crypto/hqc -> accel/hqc -> accel/ops/code -> luxcpp/gpu/libluxgpu_hqc
92 lines
2.4 KiB
Go
92 lines
2.4 KiB
Go
// Copyright (C) 2024-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package hqc
|
|
|
|
import (
|
|
"math/rand/v2"
|
|
"testing"
|
|
)
|
|
|
|
// BenchmarkGF2Polymul measures single-shot GF(2)^N polynomial
|
|
// multiplication across all three parameter sets. The numbers
|
|
// captured here are the headline figure for HQC's hot path — every
|
|
// IND-CPA encryption/decryption issues at least one polymul.
|
|
//
|
|
// Run on each build mode to compare:
|
|
//
|
|
// CGO_ENABLED=0 go test -bench BenchmarkGF2Polymul -run '^$' ./hqc/...
|
|
// CGO_ENABLED=1 go test -bench BenchmarkGF2Polymul -run '^$' ./hqc/...
|
|
func BenchmarkGF2Polymul(b *testing.B) {
|
|
cases := []struct {
|
|
mode Mode
|
|
name string
|
|
}{
|
|
{HQC128, "HQC-128"},
|
|
{HQC192, "HQC-192"},
|
|
{HQC256, "HQC-256"},
|
|
}
|
|
for _, tc := range cases {
|
|
b.Run(tc.name, func(b *testing.B) {
|
|
n, _ := vecNSize64(tc.mode)
|
|
a := make([]uint64, n)
|
|
bb := make([]uint64, n)
|
|
c := make([]uint64, n)
|
|
r := rand.New(rand.NewPCG(7, 13))
|
|
for i := 0; i < n; i++ {
|
|
a[i] = r.Uint64()
|
|
bb[i] = r.Uint64()
|
|
}
|
|
b.SetBytes(int64(n * 8))
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = GF2Polymul(tc.mode, c, a, bb)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkGF2PolymulBatch measures batched polymul throughput at
|
|
// various batch sizes, matching the user-requested test matrix:
|
|
//
|
|
// n in {2048, 4096, 8192} bits (vector-length surrogate is HQC mode)
|
|
// batch in {1, 32}
|
|
//
|
|
// HQC vector sizes (PARAM_N in bits): 17669, 35851, 57637.
|
|
// We use those directly rather than synthetic n — the production
|
|
// API never sees synthetic vector sizes.
|
|
func BenchmarkGF2PolymulBatch(b *testing.B) {
|
|
cases := []struct {
|
|
mode Mode
|
|
name string
|
|
batch int
|
|
}{
|
|
{HQC128, "HQC-128/batch=1", 1},
|
|
{HQC128, "HQC-128/batch=32", 32},
|
|
{HQC192, "HQC-192/batch=1", 1},
|
|
{HQC192, "HQC-192/batch=32", 32},
|
|
{HQC256, "HQC-256/batch=1", 1},
|
|
{HQC256, "HQC-256/batch=32", 32},
|
|
}
|
|
for _, tc := range cases {
|
|
b.Run(tc.name, func(b *testing.B) {
|
|
n, _ := vecNSize64(tc.mode)
|
|
a := make([]uint64, n)
|
|
bb := make([]uint64, n)
|
|
c := make([]uint64, n)
|
|
r := rand.New(rand.NewPCG(uint64(tc.mode)+1, uint64(tc.batch)*17))
|
|
for i := 0; i < n; i++ {
|
|
a[i] = r.Uint64()
|
|
bb[i] = r.Uint64()
|
|
}
|
|
b.SetBytes(int64(tc.batch * n * 8))
|
|
b.ResetTimer()
|
|
for iter := 0; iter < b.N; iter++ {
|
|
for j := 0; j < tc.batch; j++ {
|
|
_ = GF2Polymul(tc.mode, c, a, bb)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|