Files
zeekay cb592d0d70 fix(lss/adapters): sample Dilithium/ML-DSA secrets and masks via crypto/rand
Item5, re-targeted onto SHIPPED threshold v1.12.1 (cloud go.mod pin), not
the stale on-disk main.

The LSS Dilithium adapter (dilithium.go: sampleSecret, sampleError,
sampleMask) and the true-threshold ML-DSA Shamir adapter
(mldsa_threshold.go: sampleMaskingVector) sampled secret-key, error, and
masking-polynomial coefficients with math/rand, a non-cryptographic PRNG.
A predictable mask y is the lattice-signature analogue of ECDSA nonce
reuse and can lead to key recovery. Both adapters are gated out of the
production Corona/LSS path (factory_corona_prod.go) and unreferenced by
any consumer, but the footgun is loaded for whoever wires them in next.

Route all such sampling through a single crypto/rand-backed randIntn
helper (csprng.go). crypto/rand.Int already does unbiased rejection
sampling internally, so this is the one and only sampling path.

Regression tests (csprng_test.go): randIntn bounds/panic behavior,
freshness (non-determinism) of sampled secrets/masks across independent
DKG and signing runs, and an AST-based source scan (TestPackage_NoMath
RandForSecrets) that fails the build if math/rand (v1 or v2) is imported
into this package again.

No go.mod/go.sum change.
2026-07-08 07:26:29 -07:00

38 lines
1.5 KiB
Go

// Package adapters - shared CSPRNG helper for LSS signature adapters.
package adapters
import (
"crypto/rand"
"fmt"
"math/big"
)
// randIntn returns a cryptographically secure uniform random integer in the
// half-open range [0, n).
//
// Every Dilithium/ML-DSA secret polynomial, error polynomial, and masking
// polynomial (y) coefficient sampled anywhere in this package MUST go
// through this function — never through math/rand or math/rand/v2, which
// are not cryptographically secure PRNGs. A predictable or low-entropy mask
// y is exactly the ECDSA-style nonce-reuse footgun for lattice signatures:
// leaking (or being able to guess) even a few bits of y across signatures
// enables the same class of key-recovery attacks nonce reuse gives against
// Schnorr/ECDSA (Bleichenbacher-style lattice attacks on biased nonces).
//
// crypto/rand.Int already performs its own unbiased rejection sampling
// against crypto/rand.Reader internally, so no additional rejection loop
// is needed here — this is intentionally the one and only sampling path.
func randIntn(n int) int {
if n <= 0 {
panic(fmt.Sprintf("adapters: randIntn requires n > 0, got %d", n))
}
v, err := rand.Int(rand.Reader, big.NewInt(int64(n)))
if err != nil {
// crypto/rand.Reader failing means the OS entropy source is broken.
// There is no safe fallback when sampling secret/nonce material —
// fail loudly rather than silently degrade to a weaker source.
panic(fmt.Sprintf("adapters: crypto/rand.Reader failed: %v", err))
}
return int(v.Int64())
}