mirror of
https://github.com/luxfi/crypto.git
synced 2026-10-30 16:59:13 +00:00
Every secret here is a draw — ML-DSA and ML-KEM keys, TLS material, signing nonces, threshold dealer seeds — and each is exactly as unpredictable as the draw that made it. This is the layer that produces those draws and says whether they are worth anything. A source is bytes plus a verdict on them, and the verdict is the part usually left out. A physical source that has drifted, saturated or stuck still emits bytes of the expected length; a key made from them verifies, signs and behaves correctly in every respect except being unpredictable, and nothing downstream can tell. So Read fails closed, and the two continuous tests of SP 800-90B §4.4 run over every sample: repetition count for a source that has stopped changing, adaptive proportion for one still changing but drawing from far fewer values than it claims. Cutoffs come from the device's own min-entropy claim, so a weaker claim is allowed longer runs rather than being refused for being honest. Nothing is released until a startup run has passed. Sources combine so that adding one cannot weaken the result: predicting the output means predicting every contribution, so one unpredictable source carries the whole combination. That is what makes it safe to put an unproven device into a running deployment — at worst it contributes nothing. The platform generator is always among the sources, because a deployment whose only source is a device it cannot audit has moved its trust rather than removed it. Mixing is separated from reading so the property it exists for can be tested directly. It needs to be: with a fixed draw per source the contributions are always the same length, so a test of injectivity through Combine passes with or without the length prefixes and proves nothing. Against mix, ["ab","c"] and ["a","bc"] collide the moment the prefixes are removed. Lives beside mldsa and mlkem rather than in a module of its own. LP-176 names luxfi/qrng, but a separate module means a separate go.mod, CI, and release cadence for one package whose only callers are its neighbours here. Integration is the claim LP-187 makes: mldsa.GenerateKey(src, …) — adopting a hardware source is supplying a different reader. No consensus change and no fork. A key is generated, signed with and verified in the tests, and refused outright when a source is unhealthy. Co-authored-by: Hanzo Dev <[email protected]>
66 lines
2.5 KiB
Go
66 lines
2.5 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package entropy supplies the secret material every other primitive here
|
|
// draws from: ML-DSA and ML-KEM key generation, TLS keys, signing nonces,
|
|
// threshold dealer seeds. Each is exactly as unpredictable as the draw that
|
|
// produced it, and no more.
|
|
//
|
|
// A source is bytes plus a verdict on whether those bytes are worth anything.
|
|
// The verdict is the part that is easy to leave out: a physical noise source
|
|
// that has drifted, saturated or stuck still emits bytes of the expected
|
|
// length, and nothing downstream can tell. A key generated from it verifies,
|
|
// signs and behaves correctly in every respect except the one that matters.
|
|
// So a source here reports its own health, and refuses to emit rather than
|
|
// emit weakly.
|
|
//
|
|
// Sources compose (see Combine), and the composition is chosen so that adding
|
|
// one can never make the result worse. That is what allows an unproven device
|
|
// to be introduced to a running deployment: at worst it contributes nothing.
|
|
//
|
|
// What this package is not for: consensus. Values drawn here are known to one
|
|
// party by construction, which is what a secret needs and what agreement
|
|
// forbids — a value only the proposer can produce is one it can produce
|
|
// repeatedly. Public randomness comes from a threshold protocol whose output
|
|
// is verifiable because it is a signature. See LP-187.
|
|
package entropy
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// ErrUnhealthy is returned by Read when a source's health tests are failing.
|
|
// It is deliberately not recoverable by retrying: a caller that receives it
|
|
// must fail the operation rather than generate a key from a source that has
|
|
// stopped being random.
|
|
var ErrUnhealthy = errors.New("entropy: source is not healthy")
|
|
|
|
// Source is bytes and a verdict on them.
|
|
//
|
|
// Read fails closed. It returns an error rather than short or low-quality
|
|
// output, because a caller cannot distinguish weak bytes from strong ones by
|
|
// looking at them.
|
|
type Source interface {
|
|
io.Reader
|
|
|
|
// Health reports nil while startup and continuous tests are passing.
|
|
Health() error
|
|
}
|
|
|
|
// Named is a Source that can say what it is, for operators reading logs and
|
|
// for evidence that names which sources a deployment held.
|
|
type Named interface {
|
|
Source
|
|
Name() string
|
|
}
|
|
|
|
// nameOf is the name a source reports, or a placeholder for one that does not.
|
|
func nameOf(s Source, i int) string {
|
|
if n, ok := s.(Named); ok {
|
|
return n.Name()
|
|
}
|
|
return fmt.Sprintf("source-%d", i)
|
|
}
|