mirror of
https://github.com/luxfi/pulsar.git
synced 2026-09-24 00:25:52 +00:00
gpu: pulsar wires onto shared luxfi/lattice/v7 dispatcher
Corona = rank-1 Pulsar (R_q^k specialised at k=1). Both share the lattice GPU NTT dispatcher; pulsar's gpu/ subpackage is a thin RegisterRing wrapper matching corona/gpu/. Byte-equal contract verified by TestPulsar_GPU_ByteEqual across (n,t) shapes (5,3), (7,4), (10,7) and a UseAcceleratorForce variant pinning the dispatcher at threshold=1. Decomplecting note: pulsar's FIPS 204 NTT in mldsa_lattice.go operates on [256]uint32 polynomials in 32-bit Montgomery form (q=8380417, R=2^32); the lattice/v7 dispatcher operates on []uint64 in 64-bit Montgomery form. Pulsar's NTT does not route through ring.SubRing today so the dispatcher is vacuous on pulsar's N=256 production sign path. The gpu/ subpackage mirrors corona/gpu's UseAccelerator / DisableAccelerator / RegisterRing / Stats surface verbatim so multi-primitive consumers see one opt-in. The byte-equal test is the regression guard for the v0.4 lift that widens mldsa_lattice.go to 64-bit Montgomery and routes through ring.SubRing. NIST-clean import discipline preserved. The reference package github.com/luxfi/pulsar/ref/go/pkg/pulsar carries ZERO non-test imports of lattice/v7 — go list -deps confirms only circl + golang.org/x/crypto + stdlib transitively. The gpu/ subpackage is a separate import path; reviewers compiling the reference for NIST submission with go build ./ref/go/pkg/pulsar do not pull lattice or cgo. Lattice/v7 enters the import graph only when go test runs (gpu surface tests + threshold_v03 byte-eq test); lattice is pure-Go on default builds, no cgo engaged unless -tags gpu && CGO_ENABLED=1 && Metal/CUDA link config. Threshold combine routes through lattice/v7/gpu when a batched dispatcher is engaged (UseAccelerator); single-poly dispatch threshold is set ABOVE pulsar's production ring degree N=256 (defaultThreshold=1024, same default as corona) — operators with a true batched dispatcher should lower SetThreshold via the gpu subpackage's runtime knob. Benches mirror corona/dkg2's CPU/GPU pair pattern: 5of7, 7of11, 14of21 at the v0.3 algebraic-threshold sign path. The golang.org/x/crypto bump from v0.32.0 to v0.49.0 is a transitive constraint from lattice/[email protected]; the bump is SHA3-byte-stable (FIPS 202), verified by existing TestAlgebraic_ByteValid and KAT round-trip passing post-bump.
This commit is contained in:
@@ -4,7 +4,18 @@ go 1.26.3
|
||||
|
||||
require (
|
||||
github.com/cloudflare/circl v1.6.3
|
||||
golang.org/x/crypto v0.32.0
|
||||
github.com/luxfi/lattice/v7 v7.1.4
|
||||
golang.org/x/crypto v0.49.0
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.29.0 // indirect
|
||||
require (
|
||||
github.com/ALTree/bigfloat v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/luxfi/math v1.4.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,6 +1,33 @@
|
||||
github.com/ALTree/bigfloat v0.2.0 h1:AwNzawrpFuw55/YDVlcPw0F0cmmXrmngBHhVrvdXPvM=
|
||||
github.com/ALTree/bigfloat v0.2.0/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/luxfi/lattice/v7 v7.1.4 h1:hQR02M6cHTAV5+joOPi9gb9Gm+z/hKJnhJF4IlciIJs=
|
||||
github.com/luxfi/lattice/v7 v7.1.4/go.mod h1:DmIQFi3mJiehVsR235l1NKYEU0JhU649OX5p7gMEW2c=
|
||||
github.com/luxfi/math v1.4.1 h1:1t9bCCsEqnl9yIKrShlbs80DBKyYTWdnzkVfBqEeO7Q=
|
||||
github.com/luxfi/math v1.4.1/go.mod h1:QvbRxauQyE1w4lvbcLSe6c8yeJz2Zj1Bq1rayGgs2tA=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package gpu_test
|
||||
|
||||
// bench_test.go — accelerator surface microbenchmarks.
|
||||
//
|
||||
// The v0.3 algebraic-threshold benches that exercise the
|
||||
// CPU-vs-accelerator wall-clock comparison at production sizes
|
||||
// (Pulsar-5of7, Pulsar-7of11) live in the pulsar package
|
||||
// (ref/go/pkg/pulsar/threshold_v03_gpu_byte_eq_test.go's companion
|
||||
// bench file would, but pulsar's bench_test.go already covers Sign
|
||||
// timings — the per-mode signing cost is what matters operationally,
|
||||
// since the accelerator flag has no semantic effect at N=256 on
|
||||
// the dispatcher today).
|
||||
//
|
||||
// The microbenches here exercise:
|
||||
// BenchmarkUseAccelerator — flag flip cost (atomic store)
|
||||
// BenchmarkRegisterRing_N256 — per-ring SubRing registry add
|
||||
// BenchmarkLatticeRingNTT_N256 — direct lattice/v7 SubRing.NTT
|
||||
// call timing under the same
|
||||
// defaultThreshold the accel
|
||||
// wrapper installs (showing the
|
||||
// single-poly cost the dispatcher
|
||||
// would face if engaged).
|
||||
//
|
||||
// These are diagnostic; operators inspecting accelerator hot-loop
|
||||
// budgets read these to confirm the wrapper overhead is dwarfed by
|
||||
// the SubRing.NTT cost.
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/lattice/v7/ring"
|
||||
|
||||
"github.com/luxfi/pulsar/ref/go/pkg/pulsar/gpu"
|
||||
)
|
||||
|
||||
func BenchmarkUseAccelerator(b *testing.B) {
|
||||
b.Cleanup(gpu.DisableAccelerator)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = gpu.UseAccelerator()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRegisterRing_N256(b *testing.B) {
|
||||
b.Cleanup(gpu.DisableAccelerator)
|
||||
if err := gpu.UseAccelerator(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
r, err := ring.NewRing(256, []uint64{0x1000000004A01})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := gpu.RegisterRing(r); err != nil {
|
||||
b.Skipf("GPU registration unavailable on this build: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLatticeRingNTT_N256 measures the cost of one ring.SubRing
|
||||
// forward NTT at pulsar's production ring degree N=256 on the q used
|
||||
// by the surface tests. This is the throughput the lattice dispatcher
|
||||
// would have to beat for single-poly dispatch to be a net win at
|
||||
// N=256 — and the reason gpu.defaultThreshold is set to 1024 instead
|
||||
// of 256 on the wrapper.
|
||||
func BenchmarkLatticeRingNTT_N256(b *testing.B) {
|
||||
r, err := ring.NewRing(256, []uint64{0x1000000004A01})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
s := r.SubRings[0]
|
||||
src := make([]uint64, 256)
|
||||
for i := range src {
|
||||
src[i] = uint64(i)
|
||||
}
|
||||
dst := make([]uint64, 256)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.NTT(src, dst)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package gpu wires Pulsar (FIPS 204 M-LWE threshold signing) onto the
|
||||
// shared luxfi/lattice/v7 GPU NTT dispatcher. Pulsar and luxfi/corona
|
||||
// (R-LWE threshold signing) share this dispatcher by construction:
|
||||
// Corona is rank-1 Pulsar (the module shape R_q^k specialised at k=1),
|
||||
// so the polynomial arithmetic — NTT, sampling, multiplication — is
|
||||
// the same primitive. Sharing the dispatcher means one and only one
|
||||
// place owns the cgo plumbing: lattice/v7/gpu. Corona's
|
||||
// corona/gpu/gpu.go is the structural template for this package;
|
||||
// every export here has the matching corona counterpart.
|
||||
//
|
||||
// IMPORT-PATH DISCIPLINE — NIST-CLEAN REFERENCE
|
||||
//
|
||||
// The pulsar NIST reference implementation lives at the parent import
|
||||
// path github.com/luxfi/pulsar/ref/go/pkg/pulsar. That package's
|
||||
// import graph carries ZERO dependency on luxfi/lattice/v7 — its
|
||||
// only external imports are encoding/binary, crypto/rand,
|
||||
// crypto/elliptic, golang.org/x/crypto/sha3, and the standard library.
|
||||
// Reviewers compiling the reference do not pull lattice, cgo, or any
|
||||
// GPU plumbing.
|
||||
//
|
||||
// This subpackage is a SEPARATE import path —
|
||||
// github.com/luxfi/pulsar/ref/go/pkg/pulsar/gpu — that consumers
|
||||
// reach for explicitly when they want to opt into the shared
|
||||
// dispatcher. Compiling the reference (go test ./ref/go/pkg/pulsar/)
|
||||
// does not transitively pull this subpackage.
|
||||
//
|
||||
// DECOMPLECTING NOTE
|
||||
//
|
||||
// Pulsar's per-coefficient NTT (FIPS 204 §3.6) operates on
|
||||
// [256]uint32 polynomials in 32-bit Montgomery form with q=8380417
|
||||
// and R=2^32. The lattice/v7 ring.SubRing.NTT operates on []uint64
|
||||
// polynomials in 64-bit Montgomery form. The byte format and the
|
||||
// Montgomery constants differ, so pulsar's reference NTT in
|
||||
// mldsa_lattice.go is NOT byte-equal to ring.SubRing.NTT for the
|
||||
// same FIPS 204 input. This is by design — pulsar's NTT bytes are
|
||||
// pinned by the FIPS 204 KAT contract.
|
||||
//
|
||||
// The lattice/v7/gpu dispatcher operates at the ring.SubRing layer:
|
||||
// it is engaged when a ring.SubRing is registered with
|
||||
// gpu.RegisterSubRing and the SubRing's NTT call lands above the
|
||||
// gpu.NTTThreshold. Pulsar's per-poly FIPS 204 NTT does not flow
|
||||
// through ring.SubRing, so the dispatcher is not engaged by pulsar's
|
||||
// production sign path today.
|
||||
//
|
||||
// Why ship this surface anyway:
|
||||
//
|
||||
// 1. ONE WAY TO DO EVERYTHING. Corona and pulsar consumers see the
|
||||
// same UseAccelerator / DisableAccelerator / RegisterRing /
|
||||
// Stats surface. Multi-primitive harnesses (e.g. a research
|
||||
// environment exercising both corona R-LWE and pulsar M-LWE
|
||||
// against a shared lattice/v7 ring) opt in with one call.
|
||||
//
|
||||
// 2. BYTE-EQUALITY BY CONSTRUCTION. TestPulsar_GPU_ByteEqual asserts
|
||||
// that signatures emitted with UseAccelerator() enabled are
|
||||
// bit-identical to signatures emitted with the accelerator
|
||||
// disabled. Today this holds vacuously (the dispatcher is not
|
||||
// engaged) but the test is the load-bearing contract: when the
|
||||
// v0.4 lift widens pulsar's NTT to a 64-bit Montgomery form
|
||||
// routable through ring.SubRing, the test must continue to pass.
|
||||
//
|
||||
// 3. STATS + DIAGNOSTICS. CurrentStats() reports the active backend
|
||||
// (Metal / CUDA / CPU) so operators can confirm the lattice/v7
|
||||
// cgo plumbing is wired correctly without grepping the lattice
|
||||
// source.
|
||||
//
|
||||
// THRESHOLD GATING
|
||||
//
|
||||
// Pulsar's production ring degree is N=256 (FIPS 204 fixed). Single-
|
||||
// poly Metal NTT on Apple M1 Max is strictly slower than the pure-Go
|
||||
// path for every N up to 16384 (lattice/v7/gpu/gpu_montgomery_cgo.go).
|
||||
// The GPU dispatch win lives in BATCHED dispatch — many polynomials
|
||||
// submitted in one kernel launch. Engaging single-poly GPU dispatch
|
||||
// at N=256 regresses wall-clock by roughly 4x.
|
||||
//
|
||||
// Therefore UseAccelerator() picks defaultThreshold=1024, ABOVE
|
||||
// Pulsar's production ring degree. Operators with a batched
|
||||
// dispatcher available (future luxfi/accel batch NTT plumbed through
|
||||
// ring.NTT, or a v0.4 batched mldsa_lattice.go path) can lower the
|
||||
// threshold via SetThreshold; correctness tests can call
|
||||
// UseAcceleratorForce() to set threshold=1 and exercise the GPU path
|
||||
// at N=256 as a byte-equality gate.
|
||||
//
|
||||
// BUILD-TAG DISCIPLINE
|
||||
//
|
||||
// This package has NO build tags. It compiles identically across
|
||||
// cgo+gpu builds and pure-Go builds because the cgo plumbing lives
|
||||
// entirely in lattice/v7/gpu (gpu_montgomery_cgo.go vs
|
||||
// gpu_montgomery_purego.go). UseAccelerator() flips an atomic flag;
|
||||
// SetNTTThreshold writes to lattice's threshold; on a pure-Go build
|
||||
// lattice's dispatcher returns false and the canonical pure-Go
|
||||
// SubRing.NTT path runs. Output bytes are unchanged.
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/luxfi/lattice/v7/gpu"
|
||||
"github.com/luxfi/lattice/v7/ring"
|
||||
)
|
||||
|
||||
// defaultThreshold is the SubRing single-poly dispatch threshold
|
||||
// installed by UseAccelerator(). See the package doc above for the
|
||||
// rationale: pulsar's N=256 sits below the M1 Max GPU break-even on
|
||||
// single-poly NTT, so the default leaves single-poly dispatch off
|
||||
// while keeping the SubRing registry primed for future batched paths.
|
||||
const defaultThreshold uint32 = 1024
|
||||
|
||||
// accelEnabled is the global opt-in flag. Consumers invoke
|
||||
// UseAccelerator() once at startup; subsequent RegisterRing calls
|
||||
// from any caller (pulsar's future v0.4 batched NTT lift, a research
|
||||
// harness, or a cross-primitive multi-ring runtime) consult this via
|
||||
// Enabled().
|
||||
var accelEnabled atomic.Bool
|
||||
|
||||
// UseAccelerator opts every subsequent caller into the lattice/v7
|
||||
// GPU NTT dispatch path. Idempotent. Safe to call from package init
|
||||
// or from a runtime configuration step before any pulsar signer is
|
||||
// constructed.
|
||||
//
|
||||
// On a pure-Go (no cgo) build, the flag still flips and the lattice
|
||||
// threshold is set, but the lattice dispatcher's Available() returns
|
||||
// false so the canonical pure-Go SubRing.NTT path runs. Output bytes
|
||||
// are unchanged.
|
||||
func UseAccelerator() error {
|
||||
accelEnabled.Store(true)
|
||||
// See defaultThreshold doc above for the rationale: single-poly
|
||||
// GPU dispatch is slower than pure-Go at pulsar's production
|
||||
// N=256. The dispatcher stays armed (registered SubRings remain
|
||||
// bound) so any future batched dispatch path can engage it; the
|
||||
// threshold is conservative to keep single-poly NTT on CPU.
|
||||
gpu.SetNTTThreshold(defaultThreshold)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UseAcceleratorForce flips the opt-in flag and forces the SubRing
|
||||
// threshold to 1, dispatching every NTT call on a registered SubRing
|
||||
// to the GPU regardless of size. Useful for the byte-equal
|
||||
// correctness tests that need to exercise the GPU path on small
|
||||
// rings; production callers should use UseAccelerator() instead.
|
||||
func UseAcceleratorForce() error {
|
||||
accelEnabled.Store(true)
|
||||
gpu.SetNTTThreshold(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableAccelerator clears the opt-in. Subsequent RegisterRing /
|
||||
// MaybeRegister calls become no-ops. Existing registrations remain
|
||||
// in place (use UnregisterRing to detach them).
|
||||
func DisableAccelerator() {
|
||||
accelEnabled.Store(false)
|
||||
gpu.SetNTTThreshold(0)
|
||||
}
|
||||
|
||||
// Enabled reports whether the opt-in flag is set. Internal callers
|
||||
// consult this to decide whether to call RegisterRing.
|
||||
func Enabled() bool { return accelEnabled.Load() }
|
||||
|
||||
// SetThreshold overrides the lattice/v7/gpu single-poly dispatch
|
||||
// threshold. Pass 0 to disable single-poly GPU dispatch entirely.
|
||||
// See lattice/v7/gpu.SetNTTThreshold for the full contract.
|
||||
func SetThreshold(n uint32) { gpu.SetNTTThreshold(n) }
|
||||
|
||||
// Available reports whether the GPU NTT path is reachable on this
|
||||
// build (cgo + lattice library + Metal / CUDA at runtime). The CPU
|
||||
// fallback is always reachable.
|
||||
func Available() bool { return gpu.Available() }
|
||||
|
||||
// Backend returns the active GPU backend name ("Metal", "CUDA", or a
|
||||
// CPU descriptor) for diagnostic logging. Identical to the value
|
||||
// returned by lattice/v7/gpu.GetBackend(); re-exported here so
|
||||
// callers do not import lattice/v7/gpu directly.
|
||||
func Backend() string { return gpu.GetBackend() }
|
||||
|
||||
// registeredMu guards registeredRings.
|
||||
var (
|
||||
registeredMu sync.Mutex
|
||||
registeredRings = map[*ring.SubRing]struct{}{}
|
||||
)
|
||||
|
||||
// RegisterRing binds every SubRing of r into the lattice/v7/gpu
|
||||
// per-SubRing registry. Idempotent per SubRing pointer; safe to call
|
||||
// multiple times with the same ring.
|
||||
//
|
||||
// Callers exercising a luxfi/lattice/v7 ring (research harnesses,
|
||||
// multi-primitive runtimes sharing the dispatcher across pulsar +
|
||||
// corona, the v0.4 batched mldsa_lattice.go lift) register their
|
||||
// rings here. The pulsar reference implementation does NOT register
|
||||
// any ring today — its FIPS 204 NTT is in-package and uint32-Montgomery
|
||||
// so it does not route through ring.SubRing.
|
||||
func RegisterRing(r *ring.Ring) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
registeredMu.Lock()
|
||||
defer registeredMu.Unlock()
|
||||
for _, s := range r.SubRings {
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := registeredRings[s]; ok {
|
||||
continue
|
||||
}
|
||||
if _, err := gpu.RegisterSubRing(s); err != nil {
|
||||
return err
|
||||
}
|
||||
registeredRings[s] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnregisterRing removes the binding installed by RegisterRing. Used
|
||||
// by tests to ensure subsequent benches measure the pure-Go path.
|
||||
func UnregisterRing(r *ring.Ring) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
registeredMu.Lock()
|
||||
defer registeredMu.Unlock()
|
||||
for _, s := range r.SubRings {
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
gpu.UnregisterSubRing(s)
|
||||
delete(registeredRings, s)
|
||||
}
|
||||
}
|
||||
|
||||
// MaybeRegister is the convenience helper consumer code invokes from
|
||||
// its NewParams() / ring-construction site. If Enabled() is true it
|
||||
// binds the ring; otherwise no-op.
|
||||
func MaybeRegister(r *ring.Ring) {
|
||||
if !accelEnabled.Load() || r == nil {
|
||||
return
|
||||
}
|
||||
_ = RegisterRing(r) // best-effort; failure leaves CPU path engaged
|
||||
}
|
||||
|
||||
// Stats describes the active accelerator state for diagnostic
|
||||
// logging.
|
||||
type Stats struct {
|
||||
Enabled bool
|
||||
Available bool
|
||||
Backend string
|
||||
Threshold uint32
|
||||
RegisteredRings int
|
||||
}
|
||||
|
||||
// CurrentStats snapshots the accelerator state.
|
||||
func CurrentStats() Stats {
|
||||
registeredMu.Lock()
|
||||
n := len(registeredRings)
|
||||
registeredMu.Unlock()
|
||||
return Stats{
|
||||
Enabled: accelEnabled.Load(),
|
||||
Available: gpu.Available(),
|
||||
Backend: gpu.GetBackend(),
|
||||
Threshold: gpu.NTTThreshold(),
|
||||
RegisteredRings: n,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package gpu_test
|
||||
|
||||
// gpu_test.go — surface tests for the pulsar gpu subpackage.
|
||||
//
|
||||
// Mirrors corona/gpu/gpu_test.go: idempotency of UseAccelerator, the
|
||||
// flag-clear semantics of DisableAccelerator, RegisterRing /
|
||||
// UnregisterRing round-trip on a synthetic lattice/v7 ring, the
|
||||
// MaybeRegister no-op-when-disabled behaviour, and stats shape.
|
||||
//
|
||||
// The byte-equality contract — TestPulsar_GPU_ByteEqual — lives in the
|
||||
// pulsar package (ref/go/pkg/pulsar/threshold_v03_gpu_byte_eq_test.go)
|
||||
// because driving a full v0.3 algebraic threshold ceremony requires
|
||||
// the package-private polyVec / unpackPolyVec helpers that
|
||||
// Round2Sign's peer-W map consumes. The pulsar package's TEST import
|
||||
// graph is allowed to reference gpu (and thus lattice/v7) — the
|
||||
// non-test reference compile (go build ./ref/go/pkg/pulsar) does NOT
|
||||
// pull lattice/v7.
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/lattice/v7/ring"
|
||||
|
||||
"github.com/luxfi/pulsar/ref/go/pkg/pulsar/gpu"
|
||||
)
|
||||
|
||||
// TestUseAcceleratorIdempotent — opting in twice is harmless and leaves
|
||||
// the flag set.
|
||||
func TestUseAcceleratorIdempotent(t *testing.T) {
|
||||
t.Cleanup(gpu.DisableAccelerator)
|
||||
if err := gpu.UseAccelerator(); err != nil {
|
||||
t.Fatalf("UseAccelerator first call: %v", err)
|
||||
}
|
||||
if !gpu.Enabled() {
|
||||
t.Fatal("Enabled() false after first UseAccelerator")
|
||||
}
|
||||
if err := gpu.UseAccelerator(); err != nil {
|
||||
t.Fatalf("UseAccelerator second call: %v", err)
|
||||
}
|
||||
if !gpu.Enabled() {
|
||||
t.Fatal("Enabled() false after second UseAccelerator")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDisableAcceleratorClearsFlag — DisableAccelerator returns the
|
||||
// global to its baseline state.
|
||||
func TestDisableAcceleratorClearsFlag(t *testing.T) {
|
||||
t.Cleanup(gpu.DisableAccelerator)
|
||||
if err := gpu.UseAccelerator(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gpu.DisableAccelerator()
|
||||
if gpu.Enabled() {
|
||||
t.Fatal("Enabled() true after DisableAccelerator")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterRingIdempotent — calling RegisterRing twice with the same
|
||||
// ring does not double-register SubRings.
|
||||
func TestRegisterRingIdempotent(t *testing.T) {
|
||||
t.Cleanup(gpu.DisableAccelerator)
|
||||
if err := gpu.UseAccelerator(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, err := ring.NewRing(256, []uint64{0x1000000004A01})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { gpu.UnregisterRing(r) })
|
||||
|
||||
if err := gpu.RegisterRing(r); err != nil {
|
||||
// On non-GPU builds (no cgo / no Metal / no CUDA) lattice/v7/gpu
|
||||
// RegisterSubRing returns "GPU unavailable". The rest of the
|
||||
// surface is exercised by the other tests; skip this idempotency
|
||||
// check rather than failing.
|
||||
t.Skipf("GPU registration unavailable on this build: %v", err)
|
||||
}
|
||||
beforeStats := gpu.CurrentStats()
|
||||
if err := gpu.RegisterRing(r); err != nil {
|
||||
t.Fatalf("RegisterRing second: %v", err)
|
||||
}
|
||||
afterStats := gpu.CurrentStats()
|
||||
if beforeStats.RegisteredRings != afterStats.RegisteredRings {
|
||||
t.Fatalf("idempotency broken: %d -> %d", beforeStats.RegisteredRings, afterStats.RegisteredRings)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMaybeRegisterNoopWhenDisabled — when the accelerator is off,
|
||||
// MaybeRegister leaves the SubRing registry untouched.
|
||||
func TestMaybeRegisterNoopWhenDisabled(t *testing.T) {
|
||||
t.Cleanup(gpu.DisableAccelerator)
|
||||
gpu.DisableAccelerator()
|
||||
|
||||
r, err := ring.NewRing(256, []uint64{0x1000000004A01})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
before := gpu.CurrentStats()
|
||||
gpu.MaybeRegister(r)
|
||||
after := gpu.CurrentStats()
|
||||
if before.RegisteredRings != after.RegisteredRings {
|
||||
t.Fatalf("MaybeRegister mutated registry while disabled: %d -> %d",
|
||||
before.RegisteredRings, after.RegisteredRings)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnregisterRing — unbinding restores the pre-register state.
|
||||
func TestUnregisterRing(t *testing.T) {
|
||||
t.Cleanup(gpu.DisableAccelerator)
|
||||
if err := gpu.UseAccelerator(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, err := ring.NewRing(256, []uint64{0x1000000004A01})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
before := gpu.CurrentStats()
|
||||
if err := gpu.RegisterRing(r); err != nil {
|
||||
t.Skipf("GPU registration unavailable on this build: %v", err)
|
||||
}
|
||||
gpu.UnregisterRing(r)
|
||||
after := gpu.CurrentStats()
|
||||
if before.RegisteredRings != after.RegisteredRings {
|
||||
t.Fatalf("registry not restored: %d -> %d", before.RegisteredRings, after.RegisteredRings)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStatsShape — sanity check that CurrentStats returns a populated
|
||||
// struct. Backend string is informational; we only assert it is set
|
||||
// (lattice/v7/gpu always provides one even in the pure-Go build).
|
||||
func TestStatsShape(t *testing.T) {
|
||||
s := gpu.CurrentStats()
|
||||
if s.Backend == "" {
|
||||
t.Fatal("Backend is empty")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetThreshold — direct write to lattice/v7/gpu threshold via the
|
||||
// wrapper. Surface-only; no semantic side effect beyond stats round-trip.
|
||||
func TestSetThreshold(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
gpu.SetThreshold(0)
|
||||
})
|
||||
gpu.SetThreshold(2048)
|
||||
if got := gpu.CurrentStats().Threshold; got != 2048 {
|
||||
t.Fatalf("Threshold not honored: got %d want 2048", got)
|
||||
}
|
||||
gpu.SetThreshold(0)
|
||||
if got := gpu.CurrentStats().Threshold; got != 0 {
|
||||
t.Fatalf("Threshold not cleared: got %d want 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUseAcceleratorForce — the threshold=1 mode dispatches every
|
||||
// registered SubRing NTT to the GPU. Used by the byte-equal test in
|
||||
// the pulsar package to exercise the dispatcher at small N.
|
||||
func TestUseAcceleratorForce(t *testing.T) {
|
||||
t.Cleanup(gpu.DisableAccelerator)
|
||||
if err := gpu.UseAcceleratorForce(); err != nil {
|
||||
t.Fatalf("UseAcceleratorForce: %v", err)
|
||||
}
|
||||
if !gpu.Enabled() {
|
||||
t.Fatal("Enabled() false after UseAcceleratorForce")
|
||||
}
|
||||
if got := gpu.CurrentStats().Threshold; got != 1 {
|
||||
t.Fatalf("forced threshold not 1: got %d", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package pulsar
|
||||
|
||||
// threshold_v03_gpu_bench_test.go — CPU vs accelerator wall-clock
|
||||
// benchmarks for the v0.3 algebraic-threshold sign path at small
|
||||
// quorum shapes. Mirrors the corona/dkg2 bench discipline at
|
||||
// corona/dkg2/dkg2_gpu_test.go (BenchmarkDKG2_GPU_Round1_{5of7,
|
||||
// 7of11, 14of21}_{CPU,GPU}).
|
||||
//
|
||||
// Today the dispatcher is vacuous on pulsar's N=256 single-poly NTT
|
||||
// (gpu.defaultThreshold=1024 > 256) so the CPU and GPU legs report
|
||||
// equivalent ns/op modulo measurement noise. The benches exist as
|
||||
// the wall-clock regression guard for the v0.4 lift that widens
|
||||
// mldsa_lattice.go's NTT to 64-bit Montgomery and routes through
|
||||
// ring.SubRing — at that point the GPU leg measures the BATCHED
|
||||
// dispatch path and should win at production sizes.
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
pgpu "github.com/luxfi/pulsar/ref/go/pkg/pulsar/gpu"
|
||||
)
|
||||
|
||||
// 5-of-7 — the headline shape the task asks for (matches corona's
|
||||
// equivalent dkg2 bench).
|
||||
func BenchmarkPulsarSign_5of7_CPU(b *testing.B) {
|
||||
pgpu.DisableAccelerator()
|
||||
benchV03Ceremony(b, 7, 5)
|
||||
}
|
||||
|
||||
func BenchmarkPulsarSign_5of7_GPU(b *testing.B) {
|
||||
if err := pgpu.UseAccelerator(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.Cleanup(pgpu.DisableAccelerator)
|
||||
benchV03Ceremony(b, 7, 5)
|
||||
}
|
||||
|
||||
// 7-of-11 — intermediate.
|
||||
func BenchmarkPulsarSign_7of11_CPU(b *testing.B) {
|
||||
pgpu.DisableAccelerator()
|
||||
benchV03Ceremony(b, 11, 7)
|
||||
}
|
||||
|
||||
func BenchmarkPulsarSign_7of11_GPU(b *testing.B) {
|
||||
if err := pgpu.UseAccelerator(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.Cleanup(pgpu.DisableAccelerator)
|
||||
benchV03Ceremony(b, 11, 7)
|
||||
}
|
||||
|
||||
// 14-of-21 — production Lux consensus committee shape, matches
|
||||
// BenchmarkDKG2_GPU_Round1_14of21 in corona.
|
||||
func BenchmarkPulsarSign_14of21_CPU(b *testing.B) {
|
||||
pgpu.DisableAccelerator()
|
||||
benchV03Ceremony(b, 21, 14)
|
||||
}
|
||||
|
||||
func BenchmarkPulsarSign_14of21_GPU(b *testing.B) {
|
||||
if err := pgpu.UseAccelerator(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.Cleanup(pgpu.DisableAccelerator)
|
||||
benchV03Ceremony(b, 21, 14)
|
||||
}
|
||||
|
||||
// benchV03Ceremony drives the v0.3 ceremony to one successful
|
||||
// signature per iteration. The rejection-restart loop is part of the
|
||||
// measurement because operationally a higher restart probability
|
||||
// pulls the GPU-leg wall-clock down proportionally with the per-
|
||||
// attempt NTT cost — that is what we want the bench to reflect.
|
||||
func benchV03Ceremony(b *testing.B, n, threshold int) {
|
||||
b.Helper()
|
||||
msg := []byte("pulsar v03 bench")
|
||||
var sid [16]byte
|
||||
copy(sid[:], "v03-bench-001-aa")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Vary sid per iteration so the rejection-restart RNG path
|
||||
// doesn't latch onto a single deterministic acceptance.
|
||||
sid[8] = byte(i)
|
||||
sid[9] = byte(i >> 8)
|
||||
var (
|
||||
sig *Signature
|
||||
err error
|
||||
)
|
||||
for attempt := uint32(0); attempt < 64; attempt++ {
|
||||
sig, _, _, _, _, _, _, err = stageAlgebraic(b, n, threshold, msg, sid, attempt)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if err != ErrAlgebraicRestart {
|
||||
b.Fatalf("attempt %d unexpected err: %v", attempt, err)
|
||||
}
|
||||
}
|
||||
if sig == nil {
|
||||
b.Fatalf("no acceptance within 64 attempts")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package pulsar
|
||||
|
||||
// threshold_v03_gpu_byte_eq_test.go — byte-equality contract between
|
||||
// the pure-Go v0.3 algebraic-threshold ML-DSA path and the path with
|
||||
// gpu.UseAccelerator() opted in.
|
||||
//
|
||||
// The lattice/v7/gpu dispatcher is the shared GPU NTT entry point used
|
||||
// by luxfi/corona's R-LWE threshold signing and (by construction of
|
||||
// this subpackage) luxfi/pulsar's M-LWE threshold signing. Corona is
|
||||
// rank-1 Pulsar (R_q^k specialised at k=1); the polynomial arithmetic
|
||||
// is the same primitive. Sharing the dispatcher keeps one and only
|
||||
// one place — luxfi/lattice/v7/gpu — owning the cgo plumbing.
|
||||
//
|
||||
// TODAY (v0.3): pulsar's FIPS 204 NTT in mldsa_lattice.go operates on
|
||||
// [256]uint32 polynomials in 32-bit Montgomery form. The lattice/v7
|
||||
// dispatcher operates on []uint64 polynomials in 64-bit Montgomery
|
||||
// form. Pulsar's per-poly NTT does NOT route through ring.SubRing, so
|
||||
// the dispatcher is not engaged by pulsar's production sign path at
|
||||
// pulsar's N=256 production ring degree. The byte-equality contract
|
||||
// therefore reduces to "both legs run the canonical pure-Go FIPS 204
|
||||
// NTT" — vacuously equal.
|
||||
//
|
||||
// LOAD-BEARING SCOPE: this test is the regression guard for the v0.4
|
||||
// lift that widens mldsa_lattice.go's NTT to a 64-bit Montgomery form
|
||||
// routable through ring.SubRing. At that point the lattice
|
||||
// dispatcher's per-SubRing fast-path WILL engage on pulsar
|
||||
// signatures, and any byte divergence between the dispatched leg and
|
||||
// the pure-Go leg surfaces here as a test failure before it slips
|
||||
// into a KAT regression.
|
||||
//
|
||||
// IMPORT-GRAPH NOTE: this test file imports
|
||||
// github.com/luxfi/pulsar/ref/go/pkg/pulsar/gpu (the wrapper around
|
||||
// lattice/v7/gpu). The import lives in a _test.go file so the
|
||||
// non-test reference compile of github.com/luxfi/pulsar/ref/go/pkg/pulsar
|
||||
// does NOT pull lattice/v7. Reviewers building the reference for
|
||||
// NIST submission with `go build ./ref/go/pkg/pulsar` see only the
|
||||
// circl + golang.org/x/crypto + stdlib transitive graph. Reviewers
|
||||
// running `go test ./ref/go/pkg/pulsar` pull lattice/v7 as a pure-Go
|
||||
// dependency; no cgo is engaged unless `-tags gpu` AND `CGO_ENABLED=1`
|
||||
// AND a Metal / CUDA backend link config are all set.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
pgpu "github.com/luxfi/pulsar/ref/go/pkg/pulsar/gpu"
|
||||
)
|
||||
|
||||
// TestPulsar_GPU_ByteEqual asserts that the v0.3 algebraic threshold
|
||||
// ML-DSA signature produced via the pure-Go path is bit-identical to
|
||||
// the signature produced via the GPU-accelerator-enabled path for the
|
||||
// same committee, threshold, seed, message, and session id.
|
||||
//
|
||||
// Exercised shapes mirror the existing TestAlgebraic_FullCycle_*
|
||||
// coverage: (5, 3) is the canonical small fixture; (7, 4) and (10, 7)
|
||||
// add intermediate quorum sizes so a divergence in any code path
|
||||
// proportional to t, n, or t·N would surface.
|
||||
func TestPulsar_GPU_ByteEqual(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
n, t int
|
||||
}{
|
||||
{"n5_t3", 5, 3},
|
||||
{"n7_t4", 7, 4},
|
||||
{"n10_t7", 10, 7},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
msg := []byte("pulsar gpu byte-eq " + tc.name)
|
||||
var sid [16]byte
|
||||
copy(sid[:], "v03-gpu-byte-eq!")
|
||||
|
||||
runLeg := func(gpuOn bool) []byte {
|
||||
if gpuOn {
|
||||
if err := pgpu.UseAccelerator(); err != nil {
|
||||
t.Fatalf("UseAccelerator: %v", err)
|
||||
}
|
||||
} else {
|
||||
pgpu.DisableAccelerator()
|
||||
}
|
||||
sig := runV03Once(t, tc.n, tc.t, msg, sid)
|
||||
return sig.Bytes
|
||||
}
|
||||
|
||||
cpu := runLeg(false)
|
||||
gpu := runLeg(true)
|
||||
pgpu.DisableAccelerator() // clean global state for subsequent tests
|
||||
|
||||
if !bytes.Equal(cpu, gpu) {
|
||||
t.Fatalf("signature bytes mismatch: cpu_len=%d gpu_len=%d cpu_head=%x gpu_head=%x",
|
||||
len(cpu), len(gpu), cpu[:32], gpu[:32])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPulsar_GPU_ByteEqual_Force exercises the byte-equal contract
|
||||
// with the lattice/v7 single-poly dispatch threshold forced to 1. On
|
||||
// a cgo+gpu build with a Metal / CUDA backend live this would
|
||||
// dispatch every registered SubRing NTT to the GPU regardless of N —
|
||||
// but pulsar's FIPS 204 NTT does not flow through ring.SubRing today
|
||||
// so the contract still reduces to the pure-Go path. The test
|
||||
// exists so a future v0.4 batched lift that DOES route through
|
||||
// ring.SubRing is automatically gated on byte equality at the
|
||||
// smallest possible dispatch threshold.
|
||||
func TestPulsar_GPU_ByteEqual_Force(t *testing.T) {
|
||||
msg := []byte("pulsar gpu byte-eq forced")
|
||||
var sid [16]byte
|
||||
copy(sid[:], "v03-gpu-be-force")
|
||||
|
||||
pgpu.DisableAccelerator()
|
||||
cpu := runV03Once(t, 5, 3, msg, sid).Bytes
|
||||
|
||||
if err := pgpu.UseAcceleratorForce(); err != nil {
|
||||
t.Fatalf("UseAcceleratorForce: %v", err)
|
||||
}
|
||||
forced := runV03Once(t, 5, 3, msg, sid).Bytes
|
||||
pgpu.DisableAccelerator()
|
||||
|
||||
if !bytes.Equal(cpu, forced) {
|
||||
t.Fatalf("forced GPU signature differs: cpu_head=%x forced_head=%x",
|
||||
cpu[:32], forced[:32])
|
||||
}
|
||||
}
|
||||
|
||||
// runV03Once drives the v0.3 ceremony retry loop and returns the
|
||||
// emitted signature. It reuses the existing stageAlgebraic helper
|
||||
// from threshold_v03_test.go — that helper handles identity setup,
|
||||
// session-key derivation, the rejection-restart loop, and aggregator
|
||||
// call. Reusing it keeps THIS file focused on the byte-equality
|
||||
// assertion, not on re-deriving the ceremony fixture.
|
||||
func runV03Once(t testing.TB, n, threshold int, msg []byte, sid [16]byte) *Signature {
|
||||
t.Helper()
|
||||
var (
|
||||
sig *Signature
|
||||
err error
|
||||
)
|
||||
for attempt := uint32(0); attempt < 64; attempt++ {
|
||||
sig, _, _, _, _, _, _, err = stageAlgebraic(t, n, threshold, msg, sid, attempt)
|
||||
if err == nil {
|
||||
return sig
|
||||
}
|
||||
if err != ErrAlgebraicRestart {
|
||||
t.Fatalf("attempt %d unexpected err: %v", attempt, err)
|
||||
}
|
||||
}
|
||||
t.Fatalf("no acceptance within 64 attempts")
|
||||
return nil
|
||||
}
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
// stageAlgebraic runs a deterministic v0.3 algebraic-threshold ceremony
|
||||
// to produce a signature, returning the signature, group public key,
|
||||
// and the trusted-dealer setup. Used by every test below.
|
||||
func stageAlgebraic(t *testing.T, n, threshold int, msg []byte, sid [16]byte, attempt uint32) (
|
||||
func stageAlgebraic(t testing.TB, n, threshold int, msg []byte, sid [16]byte, attempt uint32) (
|
||||
*Signature,
|
||||
*PublicKey,
|
||||
*AlgebraicSetup,
|
||||
|
||||
Reference in New Issue
Block a user