mirror of
https://github.com/luxfi/accel.git
synced 2026-10-30 17:03:45 +00:00
cgo says a C compiler is available. It does not say libluxgpu_hqc and lux/gpu/hqc.h are installed. code_cpu.go declared the second fact as the first, so any consumer that turns cgo on for an unrelated reason — blst, say — had to carry a luxgpu install or fail to compile: ops/code/code_cpu.go:102:10: fatal error: lux/gpu/hqc.h: No such file or directory Two facts, two predicates. This restores the pairing ops/code carried through v1.2.4 and that every other optional native link in accel still uses: crypto_secp256k1_cgo.go and ops/crypto/crypto_msm_native.go behind lux_crypto_native, ops/fhe/cgo.go behind luxfhe, internal/capi/real.go behind lux_accel_real. ops/code was the lone file keyed on bare cgo. code_nocgo.go covers the complement, so a plain cgo build now reports ErrNativeHQCUnavailable — the error the API already documents — instead of failing to build. -tags=lux_hqc_native links the native oracle unchanged. Verified: build + test at CGO=0; CGO=1 with and without the tag; both again under -tags=accel. Full suite green.
227 lines
7.7 KiB
Go
227 lines
7.7 KiB
Go
// Copyright (C) 2024-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
//go:build cgo && lux_hqc_native
|
|
|
|
// CPU reference implementation for code-based crypto. Links directly
|
|
// against the canonical luxgpu_hqc static library (built by luxfi/mlx,
|
|
// aka luxcpp/gpu). OPT-IN via `-tags=lux_hqc_native` — cgo alone says
|
|
// a C compiler exists, not that luxgpu is installed, so the default
|
|
// build (even with cgo) takes the stub: no static link, no luxgpu
|
|
// install required, and HQC returns a clean "native not linked" error
|
|
// until a runtime backend provides it. With the tag set, install
|
|
// luxgpu (cmake --install, or the homebrew keg at
|
|
// /opt/homebrew/opt/luxgpu) and this file becomes the byte-equal
|
|
// oracle for every backend, including the GPU dispatch.
|
|
//
|
|
// The wire format and seed layout exposed by code.go matches the C
|
|
// API exposed in luxfi/mlx/include/lux/gpu/hqc.h. Slot indexing and
|
|
// buffer sizes are validated at the Go boundary so the C entry points
|
|
// can trust their inputs.
|
|
//
|
|
// Path discovery (backend-agnostic, no env var required)
|
|
// -----------------------------------------------------
|
|
// The #cgo CFLAGS / LDFLAGS lines below enumerate every standard
|
|
// install prefix where libluxgpu_hqc.a + lux/gpu/hqc.h might live.
|
|
// The C compiler silently skips -I / -L paths that don't exist, so
|
|
// listing them all is harmless — only the prefix that actually has
|
|
// the artefacts will contribute to the resolved build.
|
|
//
|
|
// Fallback chain (in compiler search order):
|
|
// 1. CGO_CFLAGS / CGO_LDFLAGS — caller override (env var)
|
|
// e.g. LUX_GPU_PREFIX=/opt/custom pkg-config-style flags
|
|
// 2. /usr/local/{include,lib} — POSIX system install
|
|
// 3. /opt/homebrew/{include,lib} — Homebrew on Apple Silicon
|
|
// 4. /opt/homebrew/opt/luxgpu/{...} — Homebrew keg-only install
|
|
// 5. /usr/local/opt/luxgpu/{...} — Homebrew on Intel Mac
|
|
// 6. /opt/lux/{include,lib} — Lux canonical prefix
|
|
// 7. ${SRCDIR}/../../../mlx/{include,build} — in-tree dev fallback
|
|
// (works only when the consuming module is in a workspace next
|
|
// to luxfi/mlx; in the Go module cache this resolves to a path
|
|
// that doesn't exist and is silently skipped)
|
|
//
|
|
// To override discovery at build time without editing this file:
|
|
//
|
|
// CGO_CFLAGS="-I/my/install/include" \
|
|
// CGO_LDFLAGS="-L/my/install/lib" \
|
|
// go build ./...
|
|
//
|
|
// Go cgo concatenates env-var flags AFTER the #cgo directives, so the
|
|
// override paths take precedence in linker order.
|
|
//
|
|
// For programmatic introspection (which path resolved?) see
|
|
// accel.Provenance().GPUPaths() in the parent package.
|
|
|
|
package code
|
|
|
|
/*
|
|
// Discovery: the #cgo directives enumerate every standard install
|
|
// prefix where libluxgpu_hqc.a + lux/gpu/hqc.h might live. The C
|
|
// compiler silently skips -I / -L paths that don't exist, so listing
|
|
// them all is harmless — only the prefix that actually has the
|
|
// artefacts will contribute to the resolved build.
|
|
//
|
|
// For pkg-config-driven builds, set the environment up so cgo picks
|
|
// it up before the fallback list:
|
|
//
|
|
// eval "export CGO_CFLAGS=\"$(pkg-config --cflags luxgpu)\""
|
|
// eval "export CGO_LDFLAGS=\"$(pkg-config --libs luxgpu)\""
|
|
//
|
|
// (cgo concatenates env-var flags AFTER the #cgo directives, so the
|
|
// env values win on the linker's first-match-wins resolution.) The
|
|
// `code_cpu_pkgconfig.go` sibling enables an explicit `#cgo
|
|
// pkg-config: luxgpu` directive under the `lux_gpu_pkgconfig` build
|
|
// tag, for callers that prefer the hard-failure mode (pkg-config
|
|
// missing = build fails immediately) instead of the fallback chain.
|
|
|
|
#cgo CFLAGS: -I/usr/local/include
|
|
#cgo CFLAGS: -I/opt/homebrew/include
|
|
#cgo CFLAGS: -I/opt/homebrew/opt/luxgpu/include
|
|
#cgo CFLAGS: -I/usr/local/opt/luxgpu/include
|
|
#cgo CFLAGS: -I/opt/lux/include
|
|
#cgo CFLAGS: -I${SRCDIR}/../../../mlx/include
|
|
|
|
#cgo darwin LDFLAGS: -L/usr/local/lib
|
|
#cgo darwin LDFLAGS: -L/opt/homebrew/lib
|
|
#cgo darwin LDFLAGS: -L/opt/homebrew/opt/luxgpu/lib
|
|
#cgo darwin LDFLAGS: -L/usr/local/opt/luxgpu/lib
|
|
#cgo darwin LDFLAGS: -L/opt/lux/lib
|
|
#cgo darwin LDFLAGS: -L${SRCDIR}/../../../mlx/build
|
|
#cgo darwin LDFLAGS: -lluxgpu_hqc -lc++ -framework Security
|
|
// macOS ld emits "search path '<X>' not found" warnings for any -L
|
|
// path that doesn't exist. The fallback chain above probes every
|
|
// standard prefix, so those warnings are expected and informational;
|
|
// they do NOT indicate a build failure. Linux's GNU ld silently
|
|
// skips missing -L paths so this is darwin-only noise.
|
|
|
|
#cgo !darwin LDFLAGS: -L/usr/local/lib
|
|
#cgo !darwin LDFLAGS: -L/opt/lux/lib
|
|
#cgo !darwin LDFLAGS: -L${SRCDIR}/../../../mlx/build
|
|
#cgo !darwin LDFLAGS: -lluxgpu_hqc -lstdc++ -lm
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include "lux/gpu/hqc.h"
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"fmt"
|
|
"unsafe"
|
|
)
|
|
|
|
// translateError maps LuxHQCError -> Go error.
|
|
func translateError(rc C.LuxHQCError) error {
|
|
switch rc {
|
|
case C.LUX_HQC_OK:
|
|
return nil
|
|
case C.LUX_HQC_ERROR_INVALID_ARG:
|
|
return ErrInvalidInput
|
|
case C.LUX_HQC_ERROR_INVALID_MODE:
|
|
return ErrInvalidMode
|
|
case C.LUX_HQC_ERROR_INVALID_LENGTH:
|
|
return ErrBufferSizeInvalid
|
|
case C.LUX_HQC_ERROR_RNG_FAILED:
|
|
return ErrSeedExhausted
|
|
default:
|
|
return fmt.Errorf("code: HQC error code %d", int(rc))
|
|
}
|
|
}
|
|
|
|
// cgoMode maps Mode -> LuxHQCMode (same bit pattern, kept explicit so
|
|
// the Go enum can change independent of the C enum if needed).
|
|
func cgoMode(mode Mode) C.LuxHQCMode {
|
|
switch mode {
|
|
case HQC128:
|
|
return C.LUX_HQC_128
|
|
case HQC192:
|
|
return C.LUX_HQC_192
|
|
case HQC256:
|
|
return C.LUX_HQC_256
|
|
default:
|
|
return C.LuxHQCMode(255) // intentionally invalid; C side rejects
|
|
}
|
|
}
|
|
|
|
func hqcKeypairCPU(mode Mode, pks, sks, seeds []byte, count int) error {
|
|
rc := C.lux_hqc_keypair_batch(
|
|
cgoMode(mode),
|
|
(*C.uint8_t)(unsafe.Pointer(&pks[0])),
|
|
(*C.uint8_t)(unsafe.Pointer(&sks[0])),
|
|
(*C.uint8_t)(unsafe.Pointer(&seeds[0])),
|
|
C.size_t(count),
|
|
)
|
|
return translateError(rc)
|
|
}
|
|
|
|
func hqcEncapsCPU(mode Mode, cts, sss, pks, seeds []byte, count int) error {
|
|
rc := C.lux_hqc_encaps_batch(
|
|
cgoMode(mode),
|
|
(*C.uint8_t)(unsafe.Pointer(&cts[0])),
|
|
(*C.uint8_t)(unsafe.Pointer(&sss[0])),
|
|
(*C.uint8_t)(unsafe.Pointer(&pks[0])),
|
|
(*C.uint8_t)(unsafe.Pointer(&seeds[0])),
|
|
C.size_t(count),
|
|
)
|
|
return translateError(rc)
|
|
}
|
|
|
|
func hqcDecapsCPU(mode Mode, sss, cts, sks []byte, count int) error {
|
|
rc := C.lux_hqc_decaps_batch(
|
|
cgoMode(mode),
|
|
(*C.uint8_t)(unsafe.Pointer(&sss[0])),
|
|
(*C.uint8_t)(unsafe.Pointer(&cts[0])),
|
|
(*C.uint8_t)(unsafe.Pointer(&sks[0])),
|
|
C.size_t(count),
|
|
)
|
|
return translateError(rc)
|
|
}
|
|
|
|
func gf2PolymulCPU(mode Mode, c, a, b []uint64, count int) error {
|
|
vecN, err := vecNSize64(mode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cMode := cgoMode(mode)
|
|
// Loop in Go so a per-slot kernel failure can short-circuit the
|
|
// batch. The C single-op kernel is constant-time and stateless,
|
|
// so this is no slower than a C-side loop (the only overhead is
|
|
// a per-slot cgo crossing — measured at ~150ns on Apple M1 Max).
|
|
for i := 0; i < count; i++ {
|
|
ai := a[i*vecN : (i+1)*vecN]
|
|
bi := b[i*vecN : (i+1)*vecN]
|
|
ci := c[i*vecN : (i+1)*vecN]
|
|
rc := C.lux_hqc_gf2_polymul(
|
|
cMode,
|
|
(*C.uint64_t)(unsafe.Pointer(&ci[0])),
|
|
(*C.uint64_t)(unsafe.Pointer(&ai[0])),
|
|
(*C.uint64_t)(unsafe.Pointer(&bi[0])),
|
|
)
|
|
if rc != C.LUX_HQC_OK {
|
|
return translateError(rc)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func rsDecodeCPU(mode Mode, msgs, cdws []byte, count int) error {
|
|
n1, k, err := rsDims(mode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cMode := cgoMode(mode)
|
|
for i := 0; i < count; i++ {
|
|
msgI := msgs[i*k : (i+1)*k]
|
|
cdwI := cdws[i*n1 : (i+1)*n1]
|
|
rc := C.lux_hqc_reed_solomon_decode(
|
|
cMode,
|
|
(*C.uint8_t)(unsafe.Pointer(&msgI[0])),
|
|
(*C.uint8_t)(unsafe.Pointer(&cdwI[0])),
|
|
)
|
|
if rc != C.LUX_HQC_OK {
|
|
return translateError(rc)
|
|
}
|
|
}
|
|
return nil
|
|
}
|