feat: Go 1.26.1 crypto features + runtime/secret support

- Add secret/ package: wraps runtime/secret.Do() for secure key erasure
  when built with GOEXPERIMENT=runtimesecret, no-op stub otherwise
- Add encryption/hpke.go: HPKE encryption using Go 1.26 stdlib crypto/hpke
  with X25519 (classical) and ML-KEM-768+X25519 (post-quantum hybrid)
- Wrap BLS key generation (both CGO and pure Go) in secret.Do()
- Wrap HexToECDSA and LoadECDSA in secret.Do() for key byte cleanup
- Simplify random.go: crypto/rand.Read never errors in Go 1.26
- Update CI workflows to Go 1.26.1, add GOEXPERIMENT=runtimesecret job
- Update dependencies: circl 1.6.3, x/crypto 0.48.0, age 1.3.1
This commit is contained in:
Hanzo Dev
2025-12-27 13:26:02 -08:00
parent fbe2b4578e
commit b01d9e9e7b
13 changed files with 712 additions and 67 deletions
+41 -3
View File
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.25.5']
go-version: ['1.26.1']
cgo: ['0', '1']
steps:
@@ -84,6 +84,20 @@ jobs:
echo "Running integration tests"
go test -v . -count=1 || true
- name: Run secret package tests
env:
CGO_ENABLED: ${{ matrix.cgo }}
run: |
echo "Testing secret package (stub mode)"
go test -v ./secret/... -count=1
- name: Run encryption/HPKE tests
env:
CGO_ENABLED: ${{ matrix.cgo }}
run: |
echo "Testing HPKE encryption (Go 1.26 stdlib)"
go test -v ./encryption/... -count=1
- name: Run benchmarks
if: matrix.cgo == '1'
env:
@@ -92,6 +106,30 @@ jobs:
echo "Running performance benchmarks"
go test -bench=. -benchmem ./... || true
test-runtimesecret:
name: Test with GOEXPERIMENT=runtimesecret
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.26.1'
- name: Build with runtimesecret
env:
GOEXPERIMENT: runtimesecret
CGO_ENABLED: '0'
run: go build ./...
- name: Test with runtimesecret
env:
GOEXPERIMENT: runtimesecret
CGO_ENABLED: '0'
run: go test -v -count=1 ./secret/... ./encryption/... .
security:
name: Security Scan
runs-on: ubuntu-latest
@@ -103,7 +141,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25.5'
go-version: '1.26.1'
- name: Run Gosec Security Scanner
uses: securego/gosec@master
@@ -123,7 +161,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go-version: ['1.25.5']
go-version: ['1.26.1']
steps:
- name: Checkout code
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.25.5']
go-version: ['1.26.1']
steps:
- uses: actions/checkout@v4
+26 -15
View File
@@ -11,6 +11,7 @@ import (
"github.com/cloudflare/circl/ecc/bls12381"
blssign "github.com/cloudflare/circl/sign/bls"
"github.com/luxfi/crypto/secret"
)
// Domain separation tags - must match the CGO version (blst) exactly
@@ -37,17 +38,21 @@ type (
)
func NewSecretKey() (*SecretKey, error) {
ikm := make([]byte, 32)
if _, err := rand.Read(ikm); err != nil {
return nil, err
}
defer clear(ikm)
var result *SecretKey
var keyErr error
secret.Do(func() {
ikm := make([]byte, 32)
rand.Read(ikm)
defer clear(ikm)
sk, err := blssign.KeyGen[blssign.KeyG1SigG2](ikm, nil, nil)
if err != nil {
return nil, err
}
return &SecretKey{sk: sk}, nil
sk, err := blssign.KeyGen[blssign.KeyG1SigG2](ikm, nil, nil)
if err != nil {
keyErr = err
return
}
result = &SecretKey{sk: sk}
})
return result, keyErr
}
func SecretKeyToBytes(sk *SecretKey) []byte {
@@ -76,11 +81,17 @@ func SecretKeyFromBytes(skBytes []byte) (*SecretKey, error) {
if len(skBytes) != SecretKeyLen {
return nil, ErrFailedSecretKeyDeserialize
}
sk := new(blssign.PrivateKey[blssign.KeyG1SigG2])
if err := sk.UnmarshalBinary(skBytes); err != nil {
return nil, ErrFailedSecretKeyDeserialize
}
return &SecretKey{sk: sk}, nil
var result *SecretKey
var keyErr error
secret.Do(func() {
sk := new(blssign.PrivateKey[blssign.KeyG1SigG2])
if err := sk.UnmarshalBinary(skBytes); err != nil {
keyErr = ErrFailedSecretKeyDeserialize
return
}
result = &SecretKey{sk: sk}
})
return result, keyErr
}
func (sk *SecretKey) PublicKey() *PublicKey {
+16 -17
View File
@@ -10,6 +10,7 @@ import (
"errors"
blst "github.com/supranational/blst/bindings/go"
"github.com/luxfi/crypto/secret"
)
// Domain separation tags
@@ -39,20 +40,14 @@ type (
// NewSecretKey generates a new secret key from the local source of
// cryptographically secure randomness.
func NewSecretKey() (*SecretKey, error) {
ikm := make([]byte, 32)
_, err := rand.Read(ikm)
if err != nil {
return nil, err
}
sk := blst.KeyGen(ikm)
// Clear the ikm
for i := range ikm {
ikm[i] = 0
}
return &SecretKey{sk: sk}, nil
var result *SecretKey
secret.Do(func() {
ikm := make([]byte, 32)
rand.Read(ikm)
defer clear(ikm)
result = &SecretKey{sk: blst.KeyGen(ikm)}
})
return result, nil
}
// SecretKeyToBytes returns the big-endian format of the secret key.
@@ -91,9 +86,13 @@ func SecretKeyFromBytes(skBytes []byte) (*SecretKey, error) {
if allZero {
return nil, ErrFailedSecretKeyDeserialize
}
sk := new(blst.SecretKey)
sk.Deserialize(skBytes)
return &SecretKey{sk: sk}, nil
var result *SecretKey
secret.Do(func() {
sk := new(blst.SecretKey)
sk.Deserialize(skBytes)
result = &SecretKey{sk: sk}
})
return result, nil
}
// PublicKey returns the public key associated with the secret key.
+13 -2
View File
@@ -31,6 +31,7 @@ import (
"github.com/luxfi/crypto/common"
"github.com/luxfi/crypto/rlp"
"github.com/luxfi/crypto/secret"
)
// Re-export common types at the package level for convenience
@@ -181,7 +182,12 @@ func HexToECDSA(hexkey string) (*ecdsa.PrivateKey, error) {
} else if err != nil {
return nil, errors.New("invalid hex data for private key")
}
return ToECDSA(b)
var key *ecdsa.PrivateKey
secret.Do(func() {
key, err = ToECDSA(b)
clear(b)
})
return key, err
}
// LoadECDSA loads a secp256k1 private key from the given file.
@@ -204,7 +210,12 @@ func LoadECDSA(file string) (*ecdsa.PrivateKey, error) {
return nil, err
}
return HexToECDSA(string(buf))
var key *ecdsa.PrivateKey
secret.Do(func() {
key, err = HexToECDSA(string(buf))
clear(buf)
})
return key, err
}
// readASCII reads into 'buf', stopping when the buffer is full or
+164
View File
@@ -0,0 +1,164 @@
// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package encryption
import (
"crypto/ecdh"
"crypto/hpke"
"errors"
"fmt"
"github.com/luxfi/crypto/secret"
)
// HPKEKeyPair holds an HPKE key pair for X25519-based encryption.
type HPKEKeyPair struct {
private hpke.PrivateKey
public hpke.PublicKey
}
var (
// defaultKEM is X25519-based DHKEM, widely deployed and fast.
defaultKEM = hpke.DHKEM(ecdh.X25519())
// defaultKDF is HKDF-SHA256 per RFC 9180.
defaultKDF = hpke.HKDFSHA256()
// defaultAEAD is ChaCha20-Poly1305, fast on platforms without AES-NI.
defaultAEAD = hpke.ChaCha20Poly1305()
)
// GenerateHPKEKeyPair generates a new X25519 HPKE key pair.
func GenerateHPKEKeyPair() (*HPKEKeyPair, error) {
priv, err := defaultKEM.GenerateKey()
if err != nil {
return nil, fmt.Errorf("hpke: generate key: %w", err)
}
return &HPKEKeyPair{
private: priv,
public: priv.PublicKey(),
}, nil
}
// HPKEKeyPairFromBytes reconstructs an HPKE key pair from a serialized private key.
func HPKEKeyPairFromBytes(privBytes []byte) (*HPKEKeyPair, error) {
priv, err := defaultKEM.NewPrivateKey(privBytes)
if err != nil {
return nil, fmt.Errorf("hpke: parse private key: %w", err)
}
return &HPKEKeyPair{
private: priv,
public: priv.PublicKey(),
}, nil
}
// PublicKeyBytes returns the serialized public key.
func (kp *HPKEKeyPair) PublicKeyBytes() []byte {
return kp.public.Bytes()
}
// PrivateKeyBytes returns the serialized private key.
// The caller should clear the returned slice when done.
func (kp *HPKEKeyPair) PrivateKeyBytes() ([]byte, error) {
return kp.private.Bytes()
}
// HPKESeal encrypts plaintext to a recipient's public key using HPKE (RFC 9180).
//
// Returns the concatenation of the encapsulated key and ciphertext.
// The info parameter provides context binding (can be nil).
func HPKESeal(recipientPubKeyBytes []byte, info, plaintext []byte) ([]byte, error) {
pub, err := defaultKEM.NewPublicKey(recipientPubKeyBytes)
if err != nil {
return nil, fmt.Errorf("hpke seal: parse public key: %w", err)
}
return hpke.Seal(pub, defaultKDF, defaultAEAD, info, plaintext)
}
// HPKEOpen decrypts ciphertext using the recipient's private key.
//
// The ciphertext must be the concatenation of the encapsulated key and the
// actual ciphertext, as returned by HPKESeal.
// The info parameter must match what was used during encryption.
func HPKEOpen(kp *HPKEKeyPair, info, ciphertext []byte) ([]byte, error) {
if kp == nil {
return nil, errors.New("hpke open: nil key pair")
}
var plaintext []byte
var openErr error
secret.Do(func() {
plaintext, openErr = hpke.Open(kp.private, defaultKDF, defaultAEAD, info, ciphertext)
})
return plaintext, openErr
}
// HPKEPublicKeyFromBytes deserializes an HPKE public key from bytes.
func HPKEPublicKeyFromBytes(pubBytes []byte) (hpke.PublicKey, error) {
return defaultKEM.NewPublicKey(pubBytes)
}
// Hybrid HPKE: ML-KEM-768 + X25519 (post-quantum hybrid)
// HybridHPKEKeyPair holds a hybrid ML-KEM-768 + X25519 HPKE key pair.
type HybridHPKEKeyPair struct {
private hpke.PrivateKey
public hpke.PublicKey
}
var hybridKEM = hpke.MLKEM768X25519()
// GenerateHybridHPKEKeyPair generates a new hybrid ML-KEM-768+X25519 HPKE key pair.
// This provides post-quantum security combined with classical X25519.
func GenerateHybridHPKEKeyPair() (*HybridHPKEKeyPair, error) {
priv, err := hybridKEM.GenerateKey()
if err != nil {
return nil, fmt.Errorf("hybrid hpke: generate key: %w", err)
}
return &HybridHPKEKeyPair{
private: priv,
public: priv.PublicKey(),
}, nil
}
// HybridHPKEKeyPairFromBytes reconstructs a hybrid HPKE key pair from serialized bytes.
func HybridHPKEKeyPairFromBytes(privBytes []byte) (*HybridHPKEKeyPair, error) {
priv, err := hybridKEM.NewPrivateKey(privBytes)
if err != nil {
return nil, fmt.Errorf("hybrid hpke: parse private key: %w", err)
}
return &HybridHPKEKeyPair{
private: priv,
public: priv.PublicKey(),
}, nil
}
// PublicKeyBytes returns the serialized public key.
func (kp *HybridHPKEKeyPair) PublicKeyBytes() []byte {
return kp.public.Bytes()
}
// PrivateKeyBytes returns the serialized private key.
func (kp *HybridHPKEKeyPair) PrivateKeyBytes() ([]byte, error) {
return kp.private.Bytes()
}
// HybridHPKESeal encrypts plaintext using hybrid ML-KEM-768+X25519 HPKE.
func HybridHPKESeal(recipientPubKeyBytes []byte, info, plaintext []byte) ([]byte, error) {
pub, err := hybridKEM.NewPublicKey(recipientPubKeyBytes)
if err != nil {
return nil, fmt.Errorf("hybrid hpke seal: parse public key: %w", err)
}
return hpke.Seal(pub, defaultKDF, defaultAEAD, info, plaintext)
}
// HybridHPKEOpen decrypts ciphertext using hybrid ML-KEM-768+X25519 HPKE.
func HybridHPKEOpen(kp *HybridHPKEKeyPair, info, ciphertext []byte) ([]byte, error) {
if kp == nil {
return nil, errors.New("hybrid hpke open: nil key pair")
}
var plaintext []byte
var openErr error
secret.Do(func() {
plaintext, openErr = hpke.Open(kp.private, defaultKDF, defaultAEAD, info, ciphertext)
})
return plaintext, openErr
}
+289
View File
@@ -0,0 +1,289 @@
// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package encryption
import (
"bytes"
"testing"
)
func TestHPKESealOpen(t *testing.T) {
kp, err := GenerateHPKEKeyPair()
if err != nil {
t.Fatalf("GenerateHPKEKeyPair: %v", err)
}
plaintext := []byte("hello from Lux HPKE")
info := []byte("test-context")
ciphertext, err := HPKESeal(kp.PublicKeyBytes(), info, plaintext)
if err != nil {
t.Fatalf("HPKESeal: %v", err)
}
if bytes.Equal(plaintext, ciphertext) {
t.Fatal("ciphertext must differ from plaintext")
}
decrypted, err := HPKEOpen(kp, info, ciphertext)
if err != nil {
t.Fatalf("HPKEOpen: %v", err)
}
if !bytes.Equal(plaintext, decrypted) {
t.Fatalf("decrypted mismatch: got %q, want %q", decrypted, plaintext)
}
}
func TestHPKEWrongKey(t *testing.T) {
kp1, err := GenerateHPKEKeyPair()
if err != nil {
t.Fatalf("GenerateHPKEKeyPair: %v", err)
}
kp2, err := GenerateHPKEKeyPair()
if err != nil {
t.Fatalf("GenerateHPKEKeyPair: %v", err)
}
plaintext := []byte("secret message")
ciphertext, err := HPKESeal(kp1.PublicKeyBytes(), nil, plaintext)
if err != nil {
t.Fatalf("HPKESeal: %v", err)
}
_, err = HPKEOpen(kp2, nil, ciphertext)
if err == nil {
t.Fatal("expected error decrypting with wrong key")
}
}
func TestHPKEWrongInfo(t *testing.T) {
kp, err := GenerateHPKEKeyPair()
if err != nil {
t.Fatalf("GenerateHPKEKeyPair: %v", err)
}
plaintext := []byte("context-bound message")
ciphertext, err := HPKESeal(kp.PublicKeyBytes(), []byte("info-a"), plaintext)
if err != nil {
t.Fatalf("HPKESeal: %v", err)
}
_, err = HPKEOpen(kp, []byte("info-b"), ciphertext)
if err == nil {
t.Fatal("expected error decrypting with wrong info")
}
}
func TestHPKEEmptyPlaintext(t *testing.T) {
kp, err := GenerateHPKEKeyPair()
if err != nil {
t.Fatalf("GenerateHPKEKeyPair: %v", err)
}
ciphertext, err := HPKESeal(kp.PublicKeyBytes(), nil, []byte{})
if err != nil {
t.Fatalf("HPKESeal: %v", err)
}
decrypted, err := HPKEOpen(kp, nil, ciphertext)
if err != nil {
t.Fatalf("HPKEOpen: %v", err)
}
if len(decrypted) != 0 {
t.Fatalf("expected empty plaintext, got %d bytes", len(decrypted))
}
}
func TestHPKEKeyPairRoundTrip(t *testing.T) {
kp, err := GenerateHPKEKeyPair()
if err != nil {
t.Fatalf("GenerateHPKEKeyPair: %v", err)
}
privBytes, err := kp.PrivateKeyBytes()
if err != nil {
t.Fatalf("PrivateKeyBytes: %v", err)
}
defer clear(privBytes)
kp2, err := HPKEKeyPairFromBytes(privBytes)
if err != nil {
t.Fatalf("HPKEKeyPairFromBytes: %v", err)
}
if !bytes.Equal(kp.PublicKeyBytes(), kp2.PublicKeyBytes()) {
t.Fatal("public keys do not match after round-trip")
}
// Verify decryption works with reconstructed key
plaintext := []byte("round-trip test")
ciphertext, err := HPKESeal(kp.PublicKeyBytes(), nil, plaintext)
if err != nil {
t.Fatalf("HPKESeal: %v", err)
}
decrypted, err := HPKEOpen(kp2, nil, ciphertext)
if err != nil {
t.Fatalf("HPKEOpen with reconstructed key: %v", err)
}
if !bytes.Equal(plaintext, decrypted) {
t.Fatal("decrypted mismatch with reconstructed key")
}
}
func TestHPKEOpenNilKeyPair(t *testing.T) {
_, err := HPKEOpen(nil, nil, []byte("data"))
if err == nil {
t.Fatal("expected error with nil key pair")
}
}
func TestHPKELargeData(t *testing.T) {
kp, err := GenerateHPKEKeyPair()
if err != nil {
t.Fatalf("GenerateHPKEKeyPair: %v", err)
}
// 1MB of data
plaintext := make([]byte, 1024*1024)
for i := range plaintext {
plaintext[i] = byte(i % 256)
}
ciphertext, err := HPKESeal(kp.PublicKeyBytes(), nil, plaintext)
if err != nil {
t.Fatalf("HPKESeal: %v", err)
}
decrypted, err := HPKEOpen(kp, nil, ciphertext)
if err != nil {
t.Fatalf("HPKEOpen: %v", err)
}
if !bytes.Equal(plaintext, decrypted) {
t.Fatal("large data decryption mismatch")
}
}
func TestHybridHPKESealOpen(t *testing.T) {
kp, err := GenerateHybridHPKEKeyPair()
if err != nil {
t.Fatalf("GenerateHybridHPKEKeyPair: %v", err)
}
plaintext := []byte("post-quantum hybrid encryption")
info := []byte("hybrid-test")
ciphertext, err := HybridHPKESeal(kp.PublicKeyBytes(), info, plaintext)
if err != nil {
t.Fatalf("HybridHPKESeal: %v", err)
}
decrypted, err := HybridHPKEOpen(kp, info, ciphertext)
if err != nil {
t.Fatalf("HybridHPKEOpen: %v", err)
}
if !bytes.Equal(plaintext, decrypted) {
t.Fatalf("hybrid decrypted mismatch: got %q, want %q", decrypted, plaintext)
}
}
func TestHybridHPKEWrongKey(t *testing.T) {
kp1, err := GenerateHybridHPKEKeyPair()
if err != nil {
t.Fatalf("GenerateHybridHPKEKeyPair: %v", err)
}
kp2, err := GenerateHybridHPKEKeyPair()
if err != nil {
t.Fatalf("GenerateHybridHPKEKeyPair: %v", err)
}
ciphertext, err := HybridHPKESeal(kp1.PublicKeyBytes(), nil, []byte("secret"))
if err != nil {
t.Fatalf("HybridHPKESeal: %v", err)
}
_, err = HybridHPKEOpen(kp2, nil, ciphertext)
if err == nil {
t.Fatal("expected error decrypting with wrong hybrid key")
}
}
func TestHybridHPKEKeyPairRoundTrip(t *testing.T) {
kp, err := GenerateHybridHPKEKeyPair()
if err != nil {
t.Fatalf("GenerateHybridHPKEKeyPair: %v", err)
}
privBytes, err := kp.PrivateKeyBytes()
if err != nil {
t.Fatalf("PrivateKeyBytes: %v", err)
}
defer clear(privBytes)
kp2, err := HybridHPKEKeyPairFromBytes(privBytes)
if err != nil {
t.Fatalf("HybridHPKEKeyPairFromBytes: %v", err)
}
if !bytes.Equal(kp.PublicKeyBytes(), kp2.PublicKeyBytes()) {
t.Fatal("hybrid public keys do not match after round-trip")
}
plaintext := []byte("hybrid round-trip")
ciphertext, err := HybridHPKESeal(kp.PublicKeyBytes(), nil, plaintext)
if err != nil {
t.Fatalf("HybridHPKESeal: %v", err)
}
decrypted, err := HybridHPKEOpen(kp2, nil, ciphertext)
if err != nil {
t.Fatalf("HybridHPKEOpen with reconstructed key: %v", err)
}
if !bytes.Equal(plaintext, decrypted) {
t.Fatal("hybrid decrypted mismatch with reconstructed key")
}
}
func TestHybridHPKEOpenNilKeyPair(t *testing.T) {
_, err := HybridHPKEOpen(nil, nil, []byte("data"))
if err == nil {
t.Fatal("expected error with nil hybrid key pair")
}
}
func BenchmarkHPKESealOpen(b *testing.B) {
kp, err := GenerateHPKEKeyPair()
if err != nil {
b.Fatalf("GenerateHPKEKeyPair: %v", err)
}
plaintext := []byte("benchmark data for HPKE seal/open")
pubBytes := kp.PublicKeyBytes()
b.ResetTimer()
for b.Loop() {
ct, _ := HPKESeal(pubBytes, nil, plaintext)
HPKEOpen(kp, nil, ct)
}
}
func BenchmarkHybridHPKESealOpen(b *testing.B) {
kp, err := GenerateHybridHPKEKeyPair()
if err != nil {
b.Fatalf("GenerateHybridHPKEKeyPair: %v", err)
}
plaintext := []byte("benchmark data for hybrid HPKE seal/open")
pubBytes := kp.PublicKeyBytes()
b.ResetTimer()
for b.Loop() {
ct, _ := HybridHPKESeal(pubBytes, nil, plaintext)
HybridHPKEOpen(kp, nil, ct)
}
}
+7 -6
View File
@@ -5,12 +5,12 @@ go 1.26.1
exclude github.com/luxfi/geth v1.16.1
require (
filippo.io/age v1.2.1
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251230134950-44c893854e3f
filippo.io/age v1.3.1
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260215031811-a0ab0b218a81
github.com/btcsuite/btcd/btcutil v1.1.6
github.com/cloudflare/circl v1.6.2
github.com/cloudflare/circl v1.6.3
github.com/consensys/gnark-crypto v0.19.2
github.com/crate-crypto/go-eth-kzg v1.4.0
github.com/crate-crypto/go-eth-kzg v1.5.0
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
github.com/ethereum/c-kzg-4844/v2 v2.1.5
github.com/google/btree v1.1.3
@@ -26,12 +26,13 @@ require (
github.com/supranational/blst v0.3.16
github.com/zeebo/blake3 v0.2.4
go.uber.org/mock v0.6.0
golang.org/x/crypto v0.46.0
golang.org/x/crypto v0.48.0
golang.org/x/sync v0.19.0
golang.org/x/sys v0.39.0
golang.org/x/sys v0.41.0
)
require (
filippo.io/hpke v0.4.0 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
+16 -14
View File
@@ -1,5 +1,5 @@
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd h1:ZLsPO6WdZ5zatV4UfVpr7oAwLGRZ+sebTUruuM4Ra3M=
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -39,12 +39,14 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
filippo.io/age v1.3.1 h1:hbzdQOJkuaMEpRCLSN1/C5DX74RPcNCk6oqhKMXmZi0=
filippo.io/age v1.3.1/go.mod h1:EZorDTYUxt836i3zdori5IJX/v2Lj6kWFU0cfh6C0D4=
filippo.io/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251230134950-44c893854e3f h1:a5PUgHGinaD6XrLmIDLQmGHocjIjBsBAcR5gALjZvMU=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251230134950-44c893854e3f/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260215031811-a0ab0b218a81 h1:TBzelXBdnzDy+HCrBMcomEnhrmigkWOI1/mIPCi2u4M=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260215031811-a0ab0b218a81/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -82,8 +84,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -92,8 +94,8 @@ github.com/consensys/gnark-crypto v0.19.2/go.mod h1:rT23F0XSZqE0mUA0+pRtnL56IbPx
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
github.com/crate-crypto/go-eth-kzg v1.5.0 h1:FYRiJMJG2iv+2Dy3fi14SVGjcPteZ5HAAUe4YWlJygc=
github.com/crate-crypto/go-eth-kzg v1.5.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -371,8 +373,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -534,8 +536,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+4 -4
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package crypto
@@ -7,10 +7,10 @@ import (
"crypto/rand"
)
// RandomBytes returns a slice of n random bytes
// RandomBytes returns a slice of n random bytes from crypto/rand.
// In Go 1.26+, crypto/rand.Read always succeeds or panics.
func RandomBytes(n int) []byte {
bytes := make([]byte, n)
// #nosec G404 - we don't need cryptographic randomness for test data
_, _ = rand.Read(bytes)
rand.Read(bytes)
return bytes
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build goexperiment.runtimesecret
// Package secret provides helpers for secure handling of cryptographic key material.
//
// When built with GOEXPERIMENT=runtimesecret, secret key bytes are processed
// inside runtime/secret.Do(), which ensures the memory holding the key material
// is zeroed by the runtime after use and is not visible to core dumps or
// debuggers.
//
// Without the experiment flag, the package provides identical API with no-op
// wrappers so callers do not need build tags.
package secret
import (
"runtime/secret"
)
// Do executes f inside a runtime/secret context, ensuring any stack-allocated
// secret material in f is securely erased after f returns.
//
// Use this to wrap operations that handle raw private key bytes:
//
// secret.Do(func() {
// key := generateKeyBytes()
// defer clear(key)
// useKey(key)
// })
func Do(f func()) {
secret.Do(f)
}
// Enabled reports whether the runtime secret erasure support is active.
func Enabled() bool {
return secret.Enabled()
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build !goexperiment.runtimesecret
package secret
// Do executes f directly. Without GOEXPERIMENT=runtimesecret, this is a
// pass-through with no runtime secret memory protection.
//
// Callers should still clear(key) sensitive byte slices manually.
func Do(f func()) {
f()
}
// Enabled reports whether the runtime secret erasure support is active.
// Returns false when built without GOEXPERIMENT=runtimesecret.
func Enabled() bool {
return false
}
+72
View File
@@ -0,0 +1,72 @@
// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package secret
import (
"testing"
)
func TestDo(t *testing.T) {
called := false
Do(func() {
called = true
})
if !called {
t.Fatal("Do did not execute the function")
}
}
func TestDoWithKeyMaterial(t *testing.T) {
var keyBytes []byte
Do(func() {
keyBytes = make([]byte, 32)
for i := range keyBytes {
keyBytes[i] = byte(i)
}
// Simulate using the key then clearing it
clear(keyBytes)
})
// After Do, keyBytes should be zeroed
for _, b := range keyBytes {
if b != 0 {
t.Fatal("key material was not cleared inside Do")
}
}
}
func TestEnabled(t *testing.T) {
// This test works with both build tags.
// With goexperiment.runtimesecret: Enabled() == true
// Without: Enabled() == false
// We just verify it returns a bool without panicking.
_ = Enabled()
}
func TestDoNested(t *testing.T) {
outerCalled := false
innerCalled := false
Do(func() {
outerCalled = true
Do(func() {
innerCalled = true
})
})
if !outerCalled {
t.Fatal("outer Do not called")
}
if !innerCalled {
t.Fatal("inner Do not called")
}
}
func TestDoWithPanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("expected panic to propagate through Do")
}
}()
Do(func() {
panic("test panic")
})
}