mirror of
https://github.com/luxfi/keys.git
synced 2026-09-24 00:26:10 +00:00
the identity parses its key once, and says why in terms of the code
Parsing is the expensive half and the result is immutable, so a per-call parse buys nothing; holding it also means the key is zeroed once, at Wipe. The comments explaining that were a walkthrough of a library defect instead, which is both more than a reader needs and — since crypto v1.20.5 makes the constructor own its copy — no longer true. crypto v1.20.5 comes with it, so this module gets the owning constructor too. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -5,7 +5,7 @@ go 1.26.4
|
||||
require (
|
||||
github.com/luxfi/address v1.1.1
|
||||
github.com/luxfi/constants v1.6.2
|
||||
github.com/luxfi/crypto v1.20.2
|
||||
github.com/luxfi/crypto v1.20.5
|
||||
github.com/luxfi/go-bip32 v1.1.0
|
||||
github.com/luxfi/go-bip39 v1.2.0
|
||||
github.com/luxfi/ids v1.3.2
|
||||
|
||||
@@ -77,6 +77,8 @@ github.com/luxfi/container v0.2.1 h1:MTnfKXzS5+oxV5jKZerdOxSA6iMPaQI9/FWGufizzaw
|
||||
github.com/luxfi/container v0.2.1/go.mod h1:B+uM0wP0lGvt/SSK7QOEn/qBcsHzILVHlKikdCyzSgM=
|
||||
github.com/luxfi/crypto v1.20.2 h1:L81WEsU/hs2A76F5PWBusG0yU74QqkDdUqqgexWUxh4=
|
||||
github.com/luxfi/crypto v1.20.2/go.mod h1:qYHOM0lO4PRh7LEaObxFQUIMjmT1/paVm/WgZkobT1k=
|
||||
github.com/luxfi/crypto v1.20.5 h1:kL69Ewyr90A/wQsTnIK8Zj6cHUPYvM1WHhgSErc2Yh0=
|
||||
github.com/luxfi/crypto v1.20.5/go.mod h1:gic3n9puSJhZFonBfmIwc5Cck/SMqetxpcfzrk9gna8=
|
||||
github.com/luxfi/geth v1.20.1 h1:QUGQr4AKvADjwMi7t8a0OfoyxShgEcI9pwie1jFYfm0=
|
||||
github.com/luxfi/geth v1.20.1/go.mod h1:GV5bIMEgWviRN+jPXERyVpI16H3iHqPcdIokDoZdrvU=
|
||||
github.com/luxfi/go-bip32 v1.1.0 h1:zjy19WKa1KJnGamRNyOZM3l/MPtV/sax7M4NMPwLHZQ=
|
||||
|
||||
+9
-19
@@ -165,17 +165,12 @@ type ServiceIdentity struct {
|
||||
// sentinel.
|
||||
privateKey []byte
|
||||
|
||||
// signer is the parsed ML-DSA-65 private key, cached ONCE at
|
||||
// construction. Sign() reuses it instead of re-parsing s.privateKey
|
||||
// on every call. This is deliberate and load-bearing: mldsa.
|
||||
// PrivateKeyFromBytes stores its input slice by reference
|
||||
// (secretKey: data) and arms a GC finalizer that Zeroize()s it. A
|
||||
// per-call transient parse therefore ALIASES a byte slice whose
|
||||
// finalizer, once the transient goes unreachable, zeroes the shared
|
||||
// key mid-run — corrupting the next Sign (bites the CUSTODY+Save path
|
||||
// that signs two envelopes per boot). Holding the parsed key on the
|
||||
// struct keeps it reachable for the identity's whole lifetime, so the
|
||||
// finalizer only fires at Wipe/teardown. Never exposed via a getter.
|
||||
// signer is the parsed ML-DSA-65 private key. The identity parses once,
|
||||
// at construction, and every Sign uses it: parsing is the expensive part
|
||||
// and the result is immutable, so doing it per call buys nothing. Holding
|
||||
// it for the identity's lifetime also means the key is zeroed exactly
|
||||
// once, at Wipe, rather than whenever a transient happens to be
|
||||
// collected. Never exposed through a getter.
|
||||
signer *mldsa.PrivateKey
|
||||
}
|
||||
|
||||
@@ -250,9 +245,8 @@ func NewServiceIdentity(mnemonic, servicePath string) (*ServiceIdentity, error)
|
||||
FullDigest: full,
|
||||
PublicKey: pubBytes,
|
||||
privateKey: privBytes,
|
||||
// Cache the freshly-generated key as the signer. Its secretKey is
|
||||
// independent of privBytes (privBytes is a copy), and holding it on
|
||||
// the struct keeps its Zeroize finalizer dormant until Wipe.
|
||||
// The generated key is the signer; privBytes is its serialized form,
|
||||
// kept for Save and wiped alongside it.
|
||||
signer: priv,
|
||||
}, nil
|
||||
}
|
||||
@@ -276,11 +270,7 @@ func (s *ServiceIdentity) Sign(envelope []byte) ([]byte, error) {
|
||||
return nil, errors.New("keys: service identity is empty (wiped?)")
|
||||
}
|
||||
digest := envelopeDigest(s.FullDigest, envelope)
|
||||
// Reuse the cached signer — do NOT re-parse s.privateKey here. A
|
||||
// per-call PrivateKeyFromBytes builds a transient that aliases
|
||||
// s.privateKey and arms a Zeroize finalizer; once the transient goes
|
||||
// unreachable the finalizer zeroes the shared key mid-run. See the
|
||||
// signer field doc. circl reads its own randomness for the FIPS 204
|
||||
// circl reads its own randomness for the FIPS 204
|
||||
// §5.2 hedged sign; the EnvelopeDomain context binds the signature to
|
||||
// this envelope shape so a cross-protocol replay of the same key
|
||||
// against a different shape rejects.
|
||||
|
||||
Reference in New Issue
Block a user