mirror of
https://github.com/luxfi/fhe.git
synced 2026-07-26 23:16:08 +00:00
feat(keygen): real deterministic NewKeyGeneratorFromSeed (v1.8.0)
Replace the stub implementation with a real HKDF-SHA256 derivation that seeds blake2b KeyedPRNG streams driving ring.NewSampler over the secret distribution Xs. Same seed -> byte-identical secret keys, the consensus invariant validators rely on. Pipeline: prk = HKDF-Extract(SHA-256, salt="LUX_FHE_KEYGEN_v1", ikm=seed) keyLWE = HKDF-Expand(prk, "LUX_FHE_KEYGEN_v1:LWE", 32) keyBR = HKDF-Expand(prk, "LUX_FHE_KEYGEN_v1:BR", 32) Tests: - Deterministic (same seed -> same SK bytes) - DifferentSeeds (distinct seeds -> distinct SKs) - GoldenVector (sha256(SKBR) pinned for PN10QP27) - EmptySeed (rejected) Unblocks luxfi/precompile fhe/fhe_ops.go:49 which calls this symbol.
This commit is contained in:
@@ -14,13 +14,16 @@
|
|||||||
package fhe
|
package fhe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/luxfi/lattice/v7/core/rgsw/blindrot"
|
"github.com/luxfi/lattice/v7/core/rgsw/blindrot"
|
||||||
"github.com/luxfi/lattice/v7/core/rlwe"
|
"github.com/luxfi/lattice/v7/core/rlwe"
|
||||||
"github.com/luxfi/lattice/v7/ring"
|
"github.com/luxfi/lattice/v7/ring"
|
||||||
"github.com/luxfi/lattice/v7/utils"
|
"github.com/luxfi/lattice/v7/utils"
|
||||||
"github.com/luxfi/lattice/v7/utils/sampling"
|
"github.com/luxfi/lattice/v7/utils/sampling"
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parameters defines the FHE parameter set
|
// Parameters defines the FHE parameter set
|
||||||
@@ -203,6 +206,11 @@ type KeyGenerator struct {
|
|||||||
kgenBR *rlwe.KeyGenerator
|
kgenBR *rlwe.KeyGenerator
|
||||||
ringQBR *ring.Ring
|
ringQBR *ring.Ring
|
||||||
scaleBR float64
|
scaleBR float64
|
||||||
|
// prngLWE and prngBR are non-nil only when the generator was constructed
|
||||||
|
// via NewKeyGeneratorFromSeed. They drive deterministic sampling of the
|
||||||
|
// secret-key coefficients in GenSecretKey.
|
||||||
|
prngLWE sampling.PRNG
|
||||||
|
prngBR sampling.PRNG
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKeyGenerator creates a new key generator
|
// NewKeyGenerator creates a new key generator
|
||||||
@@ -216,71 +224,151 @@ func NewKeyGenerator(params Parameters) *KeyGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKeyGeneratorFromSeed creates a key generator using a deterministic PRNG
|
// keygenHKDFInfoLWE is the HKDF info string for the LWE secret-key stream.
|
||||||
// seeded with the given key. All validators using the same seed will produce
|
// Domain-separated from BR to ensure the two PRNG streams never collide.
|
||||||
// identical FHE keys, which is required for consensus.
|
const keygenHKDFInfoLWE = "LUX_FHE_KEYGEN_v1:LWE"
|
||||||
|
|
||||||
|
// keygenHKDFInfoBR is the HKDF info string for the blind-rotation secret-key
|
||||||
|
// stream. Domain-separated from LWE.
|
||||||
|
const keygenHKDFInfoBR = "LUX_FHE_KEYGEN_v1:BR"
|
||||||
|
|
||||||
|
// keygenHKDFSalt is a fixed salt used for HKDF-SHA256 extract. Treated as a
|
||||||
|
// network constant — changing it invalidates all keys derived from prior seeds.
|
||||||
|
var keygenHKDFSalt = []byte("LUX_FHE_KEYGEN_v1")
|
||||||
|
|
||||||
|
// NewKeyGeneratorFromSeed creates a key generator that deterministically
|
||||||
|
// derives the secret-key material from `seed`. All validators using the same
|
||||||
|
// seed produce identical secret keys (and therefore identical public/bootstrap
|
||||||
|
// keys), which is required for consensus.
|
||||||
|
//
|
||||||
|
// Derivation pipeline:
|
||||||
|
//
|
||||||
|
// prk = HKDF-Extract(SHA-256, salt=keygenHKDFSalt, ikm=seed)
|
||||||
|
// keyLWE = HKDF-Expand(prk, info="LUX_FHE_KEYGEN_v1:LWE", L=32)
|
||||||
|
// keyBR = HKDF-Expand(prk, info="LUX_FHE_KEYGEN_v1:BR", L=32)
|
||||||
|
//
|
||||||
|
// keyLWE and keyBR seed two independent blake2b-based KeyedPRNG streams which
|
||||||
|
// drive `ring.NewSampler` to fill the secret-key polynomial coefficients
|
||||||
|
// according to the parameter set's secret distribution `Xs`.
|
||||||
//
|
//
|
||||||
// WARNING: The seed is a network parameter. Changing it invalidates all
|
// WARNING: The seed is a network parameter. Changing it invalidates all
|
||||||
// existing ciphertexts. Use a domain-separated constant (e.g. "LUX_FHE_KEYGEN_v1").
|
// existing ciphertexts. Use a domain-separated constant
|
||||||
|
// (e.g. "LUX_FHE_KEYGEN_v1").
|
||||||
func NewKeyGeneratorFromSeed(params Parameters, seed []byte) (*KeyGenerator, error) {
|
func NewKeyGeneratorFromSeed(params Parameters, seed []byte) (*KeyGenerator, error) {
|
||||||
prng, err := sampling.NewKeyedPRNG(seed)
|
if len(seed) == 0 {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("fhe: NewKeyGeneratorFromSeed: empty seed")
|
||||||
return nil, fmt.Errorf("fhe: NewKeyedPRNG: %w", err)
|
|
||||||
}
|
}
|
||||||
kgenLWE, err := newRLWEKeyGeneratorFromPRNG(params.paramsLWE, prng)
|
|
||||||
|
// HKDF-SHA256 extract once, expand to two domain-separated 32-byte keys.
|
||||||
|
prk := hkdf.Extract(sha256.New, seed, keygenHKDFSalt)
|
||||||
|
keyLWE, err := hkdfExpand32(prk, keygenHKDFInfoLWE)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("fhe: LWE keygen: %w", err)
|
return nil, fmt.Errorf("fhe: HKDF-Expand LWE: %w", err)
|
||||||
}
|
}
|
||||||
// For the blind rotation key generator, create a second keyed PRNG
|
keyBR, err := hkdfExpand32(prk, keygenHKDFInfoBR)
|
||||||
// derived from the same seed but domain-separated so the two streams
|
if err != nil {
|
||||||
// never collide.
|
return nil, fmt.Errorf("fhe: HKDF-Expand BR: %w", err)
|
||||||
brSeed := append(seed, []byte(":BR")...)
|
}
|
||||||
prngBR, err := sampling.NewKeyedPRNG(brSeed)
|
|
||||||
|
prngLWE, err := sampling.NewKeyedPRNG(keyLWE)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fhe: NewKeyedPRNG LWE: %w", err)
|
||||||
|
}
|
||||||
|
prngBR, err := sampling.NewKeyedPRNG(keyBR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("fhe: NewKeyedPRNG BR: %w", err)
|
return nil, fmt.Errorf("fhe: NewKeyedPRNG BR: %w", err)
|
||||||
}
|
}
|
||||||
kgenBR, err := newRLWEKeyGeneratorFromPRNG(params.paramsBR, prngBR)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("fhe: BR keygen: %w", err)
|
|
||||||
}
|
|
||||||
return &KeyGenerator{
|
return &KeyGenerator{
|
||||||
params: params,
|
params: params,
|
||||||
kgenLWE: kgenLWE,
|
kgenLWE: rlwe.NewKeyGenerator(params.paramsLWE),
|
||||||
kgenBR: kgenBR,
|
kgenBR: rlwe.NewKeyGenerator(params.paramsBR),
|
||||||
ringQBR: params.paramsBR.RingQ(),
|
ringQBR: params.paramsBR.RingQ(),
|
||||||
scaleBR: float64(params.QBR()) / 8.0,
|
scaleBR: float64(params.QBR()) / 8.0,
|
||||||
|
// Stash the per-stream PRNGs; GenSecretKey consumes them when set so
|
||||||
|
// the resulting secret key is fully deterministic for a given seed.
|
||||||
|
prngLWE: prngLWE,
|
||||||
|
prngBR: prngBR,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRLWEKeyGeneratorFromPRNG creates an rlwe.KeyGenerator.
|
// hkdfExpand32 expands `prk` to a 32-byte key using HKDF-SHA256 with the
|
||||||
//
|
// given info string.
|
||||||
// Note: lattice/v7 does not expose a public FromPRNG constructor.
|
func hkdfExpand32(prk []byte, info string) ([]byte, error) {
|
||||||
// We create a standard generator; deterministic seeding happens at a
|
r := hkdf.Expand(sha256.New, prk, []byte(info))
|
||||||
// higher level via the evaluation key derivation pipeline. The PRNG
|
out := make([]byte, 32)
|
||||||
// parameter is accepted for API compatibility but currently unused.
|
if _, err := io.ReadFull(r, out); err != nil {
|
||||||
//
|
return nil, err
|
||||||
// TODO(luxfi/lattice#42): upstream a KeyGenerator.WithPRNG option so
|
}
|
||||||
// consensus validators can derive identical keys from the same seed.
|
return out, nil
|
||||||
func newRLWEKeyGeneratorFromPRNG(params rlwe.Parameters, _ *sampling.KeyedPRNG) (*rlwe.KeyGenerator, error) {
|
|
||||||
kg := rlwe.NewKeyGenerator(params)
|
|
||||||
return kg, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenSecretKey generates a new secret key pair
|
// sampleSecretKeyDeterministic fills `sk` in-place with secret-key
|
||||||
|
// coefficients drawn from the parameter set's `Xs` distribution using `prng`
|
||||||
|
// as the source of randomness. The polynomial is left in NTT + Montgomery
|
||||||
|
// form, matching the convention used by `rlwe.KeyGenerator.GenSecretKeyNew`.
|
||||||
|
func sampleSecretKeyDeterministic(params rlwe.Parameters, prng sampling.PRNG, sk *rlwe.SecretKey) error {
|
||||||
|
ringQP := params.RingQP()
|
||||||
|
|
||||||
|
// RingQ is always present; sample Xs into sk.Value.Q at level Q.
|
||||||
|
samplerQ, err := ring.NewSampler(prng, ringQP.RingQ, params.Xs(), false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fhe: ring.NewSampler Q: %w", err)
|
||||||
|
}
|
||||||
|
samplerQ.AtLevel(sk.LevelQ()).Read(sk.Value.Q)
|
||||||
|
ringQP.RingQ.AtLevel(sk.LevelQ()).NTT(sk.Value.Q, sk.Value.Q)
|
||||||
|
ringQP.RingQ.AtLevel(sk.LevelQ()).MForm(sk.Value.Q, sk.Value.Q)
|
||||||
|
|
||||||
|
// RingP is optional (only when special primes P are configured).
|
||||||
|
if ringQP.RingP != nil && sk.LevelP() >= 0 {
|
||||||
|
samplerP, err := ring.NewSampler(prng, ringQP.RingP, params.Xs(), false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fhe: ring.NewSampler P: %w", err)
|
||||||
|
}
|
||||||
|
samplerP.AtLevel(sk.LevelP()).Read(sk.Value.P)
|
||||||
|
ringQP.RingP.AtLevel(sk.LevelP()).NTT(sk.Value.P, sk.Value.P)
|
||||||
|
ringQP.RingP.AtLevel(sk.LevelP()).MForm(sk.Value.P, sk.Value.P)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenSecretKey generates a new secret key pair.
|
||||||
|
//
|
||||||
|
// When the generator was constructed via NewKeyGeneratorFromSeed the secret
|
||||||
|
// key coefficients are sampled from the seeded blake2b stream so the result
|
||||||
|
// is fully deterministic. Otherwise the standard cryptographically random
|
||||||
|
// sampler is used.
|
||||||
func (kg *KeyGenerator) GenSecretKey() *SecretKey {
|
func (kg *KeyGenerator) GenSecretKey() *SecretKey {
|
||||||
// When LWE and BR have the same dimension, use the same key for both
|
// When LWE and BR have the same dimension, use the same key for both
|
||||||
// This simplifies bootstrapping by eliminating key switching
|
// This simplifies bootstrapping by eliminating key switching.
|
||||||
if kg.params.N() == kg.params.NBR() {
|
if kg.params.N() == kg.params.NBR() {
|
||||||
sk := kg.kgenBR.GenSecretKeyNew()
|
sk := kg.kgenBR.GenSecretKeyNew()
|
||||||
|
if kg.prngBR != nil {
|
||||||
|
if err := sampleSecretKeyDeterministic(kg.params.paramsBR, kg.prngBR, sk); err != nil {
|
||||||
|
panic(fmt.Sprintf("fhe: deterministic BR sample: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
return &SecretKey{
|
return &SecretKey{
|
||||||
SKLWE: sk,
|
SKLWE: sk,
|
||||||
SKBR: sk,
|
SKBR: sk,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Different dimensions require separate keys
|
// Different dimensions require separate keys.
|
||||||
|
skLWE := kg.kgenLWE.GenSecretKeyNew()
|
||||||
|
skBR := kg.kgenBR.GenSecretKeyNew()
|
||||||
|
if kg.prngLWE != nil {
|
||||||
|
if err := sampleSecretKeyDeterministic(kg.params.paramsLWE, kg.prngLWE, skLWE); err != nil {
|
||||||
|
panic(fmt.Sprintf("fhe: deterministic LWE sample: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if kg.prngBR != nil {
|
||||||
|
if err := sampleSecretKeyDeterministic(kg.params.paramsBR, kg.prngBR, skBR); err != nil {
|
||||||
|
panic(fmt.Sprintf("fhe: deterministic BR sample: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
return &SecretKey{
|
return &SecretKey{
|
||||||
SKLWE: kg.kgenLWE.GenSecretKeyNew(),
|
SKLWE: skLWE,
|
||||||
SKBR: kg.kgenBR.GenSecretKeyNew(),
|
SKBR: skBR,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ require (
|
|||||||
github.com/luxfi/metric v1.5.0
|
github.com/luxfi/metric v1.5.0
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/urfave/cli/v3 v3.6.2
|
github.com/urfave/cli/v3 v3.6.2
|
||||||
|
golang.org/x/crypto v0.49.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -78,7 +79,6 @@ require (
|
|||||||
go.temporal.io/api v1.62.6 // indirect
|
go.temporal.io/api v1.62.6 // indirect
|
||||||
go.temporal.io/sdk v1.41.1 // indirect
|
go.temporal.io/sdk v1.41.1 // indirect
|
||||||
go.uber.org/mock v0.6.0 // indirect
|
go.uber.org/mock v0.6.0 // indirect
|
||||||
golang.org/x/crypto v0.49.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
|
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
|
||||||
golang.org/x/image v0.38.0 // indirect
|
golang.org/x/image v0.38.0 // indirect
|
||||||
golang.org/x/mod v0.34.0 // indirect
|
golang.org/x/mod v0.34.0 // indirect
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright (c) 2025, Lux Industries Inc
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package fhe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestNewKeyGeneratorFromSeed_Deterministic verifies that the same seed
|
||||||
|
// produces byte-identical secret keys across independent invocations.
|
||||||
|
// This is the consensus invariant: every validator must derive the same
|
||||||
|
// key from the same network seed.
|
||||||
|
func TestNewKeyGeneratorFromSeed_Deterministic(t *testing.T) {
|
||||||
|
params, err := NewParametersFromLiteral(PN10QP27)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewParametersFromLiteral: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
seed := []byte("LUX_FHE_KEYGEN_v1:test-seed-1")
|
||||||
|
|
||||||
|
kg1, err := NewKeyGeneratorFromSeed(params, seed)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewKeyGeneratorFromSeed #1: %v", err)
|
||||||
|
}
|
||||||
|
kg2, err := NewKeyGeneratorFromSeed(params, seed)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewKeyGeneratorFromSeed #2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sk1 := kg1.GenSecretKey()
|
||||||
|
sk2 := kg2.GenSecretKey()
|
||||||
|
|
||||||
|
b1, err := sk1.SKBR.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("sk1 marshal: %v", err)
|
||||||
|
}
|
||||||
|
b2, err := sk2.SKBR.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("sk2 marshal: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(b1, b2) {
|
||||||
|
t.Fatalf("same seed produced different SKBR: len1=%d len2=%d", len(b1), len(b2))
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.N() != params.NBR() {
|
||||||
|
bL1, err := sk1.SKLWE.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("sk1 LWE marshal: %v", err)
|
||||||
|
}
|
||||||
|
bL2, err := sk2.SKLWE.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("sk2 LWE marshal: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(bL1, bL2) {
|
||||||
|
t.Fatalf("same seed produced different SKLWE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewKeyGeneratorFromSeed_DifferentSeeds verifies distinct seeds yield
|
||||||
|
// distinct secret keys. This catches accidental constant-output bugs.
|
||||||
|
func TestNewKeyGeneratorFromSeed_DifferentSeeds(t *testing.T) {
|
||||||
|
params, err := NewParametersFromLiteral(PN10QP27)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewParametersFromLiteral: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kgA, err := NewKeyGeneratorFromSeed(params, []byte("seed-A"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("kgA: %v", err)
|
||||||
|
}
|
||||||
|
kgB, err := NewKeyGeneratorFromSeed(params, []byte("seed-B"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("kgB: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
skA := kgA.GenSecretKey()
|
||||||
|
skB := kgB.GenSecretKey()
|
||||||
|
|
||||||
|
bA, _ := skA.SKBR.MarshalBinary()
|
||||||
|
bB, _ := skB.SKBR.MarshalBinary()
|
||||||
|
if bytes.Equal(bA, bB) {
|
||||||
|
t.Fatalf("distinct seeds produced identical secret key (degenerate sampler)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewKeyGeneratorFromSeed_GoldenVector pins the SHA-256 of the marshalled
|
||||||
|
// secret key for a known seed under PN10QP27. Any change to the derivation
|
||||||
|
// pipeline (HKDF salt, info strings, sampler order, NTT/Montgomery form,
|
||||||
|
// underlying lattice library) will break this vector and must be reviewed
|
||||||
|
// as a network-breaking change.
|
||||||
|
func TestNewKeyGeneratorFromSeed_GoldenVector(t *testing.T) {
|
||||||
|
params, err := NewParametersFromLiteral(PN10QP27)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewParametersFromLiteral: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed network seed used for the golden vector.
|
||||||
|
seed := []byte("LUX_FHE_KEYGEN_v1:golden:0001")
|
||||||
|
|
||||||
|
kg, err := NewKeyGeneratorFromSeed(params, seed)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewKeyGeneratorFromSeed: %v", err)
|
||||||
|
}
|
||||||
|
sk := kg.GenSecretKey()
|
||||||
|
raw, err := sk.SKBR.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("MarshalBinary: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := sha256.Sum256(raw)
|
||||||
|
got := hex.EncodeToString(digest[:])
|
||||||
|
|
||||||
|
// Cross-process golden vector. Updating this requires explicit network
|
||||||
|
// review: any change here invalidates all keys derived under prior versions.
|
||||||
|
const goldenDigest = "75becea8a116b6f04469ba39bbcfe9ecbbe893c3fbedcc50f30c653a2004ec73"
|
||||||
|
if got != goldenDigest {
|
||||||
|
t.Fatalf("golden vector drift:\n want %s\n got %s", goldenDigest, got)
|
||||||
|
}
|
||||||
|
t.Logf("PN10QP27 SKBR sha256(seed=%q) = %s", seed, got)
|
||||||
|
|
||||||
|
// Determinism check — same seed, fresh generator, same digest.
|
||||||
|
kg2, _ := NewKeyGeneratorFromSeed(params, seed)
|
||||||
|
sk2 := kg2.GenSecretKey()
|
||||||
|
raw2, _ := sk2.SKBR.MarshalBinary()
|
||||||
|
digest2 := sha256.Sum256(raw2)
|
||||||
|
if !bytes.Equal(digest[:], digest2[:]) {
|
||||||
|
t.Fatalf("re-derivation differs:\n first %x\n second %x", digest, digest2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewKeyGeneratorFromSeed_EmptySeed asserts the empty-seed guard.
|
||||||
|
func TestNewKeyGeneratorFromSeed_EmptySeed(t *testing.T) {
|
||||||
|
params, err := NewParametersFromLiteral(PN10QP27)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewParametersFromLiteral: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := NewKeyGeneratorFromSeed(params, nil); err == nil {
|
||||||
|
t.Fatalf("expected error for empty seed")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user