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]>
73 lines
2.4 KiB
Go
73 lines
2.4 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package entropy_test
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"errors"
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/luxfi/crypto/entropy"
|
|
"github.com/luxfi/crypto/mldsa"
|
|
)
|
|
|
|
// TestKeyGenerationTakesASource is the whole integration claim: adopting a
|
|
// hardware entropy source is supplying a different reader. No consensus
|
|
// change, no fork, no new chain — the seam every key generator here already
|
|
// has is an io.Reader.
|
|
func TestKeyGenerationTakesASource(t *testing.T) {
|
|
src := entropy.Combine(entropy.OS())
|
|
|
|
priv, err := mldsa.GenerateKey(src, mldsa.MLDSA65)
|
|
if err != nil {
|
|
t.Fatalf("generate a key from an entropy source: %v", err)
|
|
}
|
|
if priv == nil {
|
|
t.Fatal("no key")
|
|
}
|
|
|
|
// The key works, which is the only thing that distinguishes a real source
|
|
// from one that returns zeros.
|
|
msg := []byte("attested")
|
|
sig, err := priv.Sign(src, msg, nil)
|
|
if err != nil {
|
|
t.Fatalf("sign: %v", err)
|
|
}
|
|
if !priv.PublicKey.Verify(msg, sig, nil) {
|
|
t.Fatal("a signature made with entropy-sourced material did not verify")
|
|
}
|
|
}
|
|
|
|
// unhealthy stands for a source whose health tests have failed.
|
|
type unhealthy struct{}
|
|
|
|
func (unhealthy) Health() error { return entropy.ErrUnhealthy }
|
|
func (unhealthy) Read([]byte) (int, error) { return 0, entropy.ErrUnhealthy }
|
|
|
|
// TestAKeyIsNotGeneratedFromABrokenSource. The failure this guards is the
|
|
// quiet one: a degraded source emits bytes of the right length, and a key made
|
|
// from them verifies and signs and behaves correctly in every respect except
|
|
// being unpredictable. Nothing downstream can detect it, so the refusal has to
|
|
// happen here.
|
|
func TestAKeyIsNotGeneratedFromABrokenSource(t *testing.T) {
|
|
src := entropy.Combine(entropy.OS(), unhealthy{})
|
|
if _, err := mldsa.GenerateKey(src, mldsa.MLDSA65); !errors.Is(err, entropy.ErrUnhealthy) {
|
|
t.Fatalf("a key was generated from an unhealthy source: err = %v", err)
|
|
}
|
|
}
|
|
|
|
// A source is a drop-in for crypto/rand.Reader wherever one is taken.
|
|
func TestASourceIsAnIOReader(t *testing.T) {
|
|
var r io.Reader = entropy.Combine(entropy.OS())
|
|
if _, err := io.ReadFull(r, make([]byte, 64)); err != nil {
|
|
t.Fatalf("read: %v", err)
|
|
}
|
|
// And the platform reader still works beside it, since a deployment always
|
|
// holds both.
|
|
if _, err := io.ReadFull(rand.Reader, make([]byte, 64)); err != nil {
|
|
t.Fatalf("platform read: %v", err)
|
|
}
|
|
}
|