mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
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:
@@ -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
|
||||
|
||||
@@ -16,28 +16,28 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: ['1.25.5']
|
||||
|
||||
go-version: ['1.26.1']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache: true
|
||||
cache-dependency-path: crypto/go.sum
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd crypto
|
||||
go mod download
|
||||
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd crypto
|
||||
go test -v -short -coverprofile=coverage.out -covermode=atomic ./...
|
||||
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
|
||||
+26
-15
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user