diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..25da9d3 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Test +on: + pull_request: + merge_group: + push: + paths: + - "**.go" + branches: + - main + - release/** + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/setup-go@v5 + with: + go-version: "1.21" + - uses: actions/checkout@v4 + + - name: Run Go Tests + run: | + go test -v -race ./... -tags bls12381 diff --git a/READEME.md b/READEME.md new file mode 100644 index 0000000..9e57f13 --- /dev/null +++ b/READEME.md @@ -0,0 +1,3 @@ +# Crypto + +The Cosmos crypto repository serves as an alignment point for the Cosmos crypto users (Cosmos SDK, CometBFT, etc..). diff --git a/curves/bls12381/alias.go b/curves/bls12381/alias.go new file mode 100644 index 0000000..6682f30 --- /dev/null +++ b/curves/bls12381/alias.go @@ -0,0 +1,9 @@ +//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && bls12381 + +package blst + +import blst "github.com/supranational/blst/bindings/go" + +// Internal types for blst. +type blstPublicKey = blst.P1Affine +type blstSignature = blst.P2Affine diff --git a/curves/bls12381/doc.go b/curves/bls12381/doc.go new file mode 100644 index 0000000..b5f8850 --- /dev/null +++ b/curves/bls12381/doc.go @@ -0,0 +1,6 @@ +// Package blst implements a go-wrapper around a library implementing the +// BLS12-381 curve and signature scheme. This package exposes a public API for +// verifying and aggregating BLS signatures used by Ethereum. +// +// This implementation uses the library written by Supranational, blst. +package blst diff --git a/curves/bls12381/helper_test.go b/curves/bls12381/helper_test.go new file mode 100644 index 0000000..20cf48c --- /dev/null +++ b/curves/bls12381/helper_test.go @@ -0,0 +1,15 @@ +//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && bls12381 + +package blst + +// Note: These functions are for tests to access private globals, such as pubkeyCache. + +// DisableCaches sets the cache sizes to 0. +func DisableCaches() { + pubkeyCache.Resize(0) +} + +// EnableCaches sets the cache sizes to the default values. +func EnableCaches() { + pubkeyCache.Resize(maxKeys) +} diff --git a/curves/bls12381/init.go b/curves/bls12381/init.go new file mode 100644 index 0000000..6a4fe26 --- /dev/null +++ b/curves/bls12381/init.go @@ -0,0 +1,27 @@ +//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && bls12381 + +package blst + +import ( + "fmt" + "runtime" + + blst "github.com/supranational/blst/bindings/go" + + "github.com/cosmos/crypto/utils/cache" +) + +func init() { + // Reserve 1 core for general application work + maxProcs := runtime.GOMAXPROCS(0) - 1 + if maxProcs <= 0 { + maxProcs = 1 + } + blst.SetMaxProcs(maxProcs) + onEvict := func(_ [48]byte, _ PubKey) {} + keysCache, err := cache.NewLRU(maxKeys, onEvict) + if err != nil { + panic(fmt.Sprintf("Could not initiate public keys cache: %v", err)) + } + pubkeyCache = keysCache +} diff --git a/curves/bls12381/interface.go b/curves/bls12381/interface.go new file mode 100644 index 0000000..1e8265c --- /dev/null +++ b/curves/bls12381/interface.go @@ -0,0 +1,21 @@ +package blst + +type PubKey interface { + Marshal() []byte + Copy() PubKey + Equals(p2 PubKey) bool +} + +// SignatureI represents a BLS signature. +type SignatureI interface { + Verify(pubKey PubKey, msg []byte) bool + Marshal() []byte + Copy() SignatureI +} + +// SecretKey represents a BLS secret or private key. +type SecretKey interface { + PublicKey() PubKey + Sign(msg []byte) SignatureI + Marshal() []byte +} diff --git a/curves/bls12381/pubkey.go b/curves/bls12381/pubkey.go new file mode 100644 index 0000000..1643f36 --- /dev/null +++ b/curves/bls12381/pubkey.go @@ -0,0 +1,76 @@ +//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && bls12381 + +package blst + +import ( + "errors" + "fmt" + + "github.com/cosmos/crypto/utils/cache" +) + +const ( + SignatureLength = 96 + PubkeyLength = 48 // PubkeyLength defines the byte length of a BLSSignature. +) + +var maxKeys = 2_000_000 +var pubkeyCache *cache.LRU[[48]byte, PubKey] + +// PublicKey used in the BLS signature scheme. +type PublicKey struct { + p *blstPublicKey +} + +// Marshal a public key into a LittleEndian byte slice. +func (p *PublicKey) Marshal() []byte { + return p.p.Compress() +} + +// Copy the public key to a new pointer reference. +func (p *PublicKey) Copy() PubKey { + np := *p.p + return &PublicKey{p: &np} +} + +// Equals checks if the provided public key is equal to +// the current one. +func (p *PublicKey) Equals(p2 PubKey) bool { + return p.p.Equals(p2.(*PublicKey).p) +} + +// PublicKeyFromBytes creates a BLS public key from a BigEndian byte slice. +func PublicKeyFromBytes(pubKey []byte) (PubKey, error) { + return publicKeyFromBytes(pubKey, true) +} + +func publicKeyFromBytes(pubKey []byte, cacheCopy bool) (PubKey, error) { + if len(pubKey) != PubkeyLength { //TODO: make this a parameter + return nil, fmt.Errorf("public key must be %d bytes", PubkeyLength) + } + + newKey := (*[PubkeyLength]byte)(pubKey) + if cv, ok := pubkeyCache.Get(*newKey); ok { + if cacheCopy { + return cv.Copy(), nil + } + return cv, nil + } + + // Subgroup check NOT done when decompressing pubkey. + p := new(blstPublicKey).Uncompress(pubKey) + if p == nil { + return nil, errors.New("could not unmarshal bytes into public key") + } + // Subgroup and infinity check + if !p.KeyValidate() { + // NOTE: the error is not quite accurate since it includes group check + return nil, errors.New("publickey is infinite") + } + + pubKeyObj := &PublicKey{p: p} + copiedKey := pubKeyObj.Copy() + cacheKey := *newKey + pubkeyCache.Add(cacheKey, copiedKey) + return pubKeyObj, nil +} diff --git a/curves/bls12381/pubkey_test.go b/curves/bls12381/pubkey_test.go new file mode 100644 index 0000000..fd656ea --- /dev/null +++ b/curves/bls12381/pubkey_test.go @@ -0,0 +1,97 @@ +//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && bls12381 + +package blst_test + +import ( + "bytes" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + blst "github.com/cosmos/crypto/curves/bls12381" +) + +func TestPublicKeyFromBytes(t *testing.T) { + tests := []struct { + name string + input []byte + err error + }{ + { + name: "Nil", + err: errors.New("public key must be 48 bytes"), + }, + { + name: "Empty", + input: []byte{}, + err: errors.New("public key must be 48 bytes"), + }, + { + name: "Short", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("public key must be 48 bytes"), + }, + { + name: "Long", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("public key must be 48 bytes"), + }, + { + name: "Bad", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("could not unmarshal bytes into public key"), + }, + { + name: "Good", + input: []byte{0xa9, 0x9a, 0x76, 0xed, 0x77, 0x96, 0xf7, 0xbe, 0x22, 0xd5, 0xb7, 0xe8, 0x5d, 0xee, 0xb7, 0xc5, 0x67, 0x7e, 0x88, 0xe5, 0x11, 0xe0, 0xb3, 0x37, 0x61, 0x8f, 0x8c, 0x4e, 0xb6, 0x13, 0x49, 0xb4, 0xbf, 0x2d, 0x15, 0x3f, 0x64, 0x9f, 0x7b, 0x53, 0x35, 0x9f, 0xe8, 0xb9, 0x4a, 0x38, 0xe4, 0x4c}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + res, err := blst.PublicKeyFromBytes(test.input) + if test.err != nil { + assert.NotEqual(t, nil, err, "No error returned") + assert.ErrorContains(t, test.err, err.Error(), "Unexpected error returned") + } else { + assert.NoError(t, err) + assert.Equal(t, 0, bytes.Compare(res.Marshal(), test.input)) + } + }) + } +} + +func TestPublicKey_Copy(t *testing.T) { + priv, err := blst.RandKey() + require.NoError(t, err) + pubkeyA := priv.PublicKey() + pubkeyBytes := pubkeyA.Marshal() + + require.Equal(t, pubkeyA.Marshal(), pubkeyBytes, "Pubkey was mutated after copy") +} + +func BenchmarkPublicKeyFromBytes(b *testing.B) { + priv, err := blst.RandKey() + require.NoError(b, err) + pubkey := priv.PublicKey() + pubkeyBytes := pubkey.Marshal() + + b.Run("cache on", func(b *testing.B) { + blst.EnableCaches() + for i := 0; i < b.N; i++ { + _, err := blst.PublicKeyFromBytes(pubkeyBytes) + require.NoError(b, err) + } + }) + + b.Run("cache off", func(b *testing.B) { + // blst.DisableCaches() + for i := 0; i < b.N; i++ { + _, err := blst.PublicKeyFromBytes(pubkeyBytes) + require.NoError(b, err) + } + }) + +} diff --git a/curves/bls12381/secret_key.go b/curves/bls12381/secret_key.go new file mode 100644 index 0000000..5fd2876 --- /dev/null +++ b/curves/bls12381/secret_key.go @@ -0,0 +1,72 @@ +//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && bls12381 + +package blst + +import ( + "crypto/subtle" + "errors" + "fmt" + + blst "github.com/supranational/blst/bindings/go" + + "github.com/cosmos/crypto/utils/rand" +) + +// bls12SecretKey used in the BLS signature scheme. +type bls12SecretKey struct { + p *blst.SecretKey +} + +// RandKey creates a new private key using a random method provided as an io.Reader. +func RandKey() (SecretKey, error) { + // Generate 32 bytes of randomness + var ikm [32]byte + if _, err := rand.NewGenerator().Read(ikm[:]); err != nil { + return nil, err + } + // Defensive check, that we have not generated a secret key, + secKey := &bls12SecretKey{blst.KeyGen(ikm[:])} + if IsZero(secKey.Marshal()) { + return nil, errors.New("received secret key is zero") + } + return secKey, nil +} + +// SecretKeyFromBytes creates a BLS private key from a BigEndian byte slice. +func SecretKeyFromBytes(privKey []byte) (SecretKey, error) { + if len(privKey) != 32 { + return nil, fmt.Errorf("secret key must be %d bytes", 32) + } + if IsZero(privKey) { + return nil, errors.New("received secret key is zero") + } + secKey := new(blst.SecretKey).Deserialize(privKey) + if secKey == nil { + return nil, errors.New("could not unmarshal bytes into secret key") + } + wrappedKey := &bls12SecretKey{p: secKey} + return wrappedKey, nil +} + +// IsZero checks if the secret key is a zero key. +func IsZero(sKey []byte) bool { + b := byte(0) + for _, s := range sKey { + b |= s + } + return subtle.ConstantTimeByteEq(b, 0) == 1 +} + +func (s *bls12SecretKey) Sign(msg []byte) SignatureI { + return &Signature{s: new(blstSignature).Sign(s.p, msg, dst)} +} + +// Marshal a secret key into a LittleEndian byte slice. +func (s *bls12SecretKey) Marshal() []byte { + return s.p.Serialize() +} + +// PublicKey obtains the public key corresponding to the BLS secret key. +func (s *bls12SecretKey) PublicKey() PubKey { + return &PublicKey{p: new(blstPublicKey).From(s.p)} +} diff --git a/curves/bls12381/secret_key_test.go b/curves/bls12381/secret_key_test.go new file mode 100644 index 0000000..bfea916 --- /dev/null +++ b/curves/bls12381/secret_key_test.go @@ -0,0 +1,113 @@ +//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && bls12381 + +package blst_test + +import ( + "bytes" + "crypto/rand" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + blst "github.com/cosmos/crypto/curves/bls12381" +) + +func TestMarshalUnmarshal(t *testing.T) { + priv, err := blst.RandKey() + require.NoError(t, err) + b := priv.Marshal() + b32 := ToBytes32(b) + pk, err := blst.SecretKeyFromBytes(b32[:]) + require.NoError(t, err) + pk2, err := blst.SecretKeyFromBytes(b32[:]) + require.NoError(t, err) + assert.Equal(t, pk.Marshal(), pk2.Marshal(), "Keys not equal") +} + +func TestSecretKeyFromBytes(t *testing.T) { + tests := []struct { + name string + input []byte + err error + }{ + { + name: "Nil", + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Empty", + input: []byte{}, + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Short", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Long", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("secret key must be 32 bytes"), + }, + { + name: "Bad", + input: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + err: errors.New("could not unmarshal bytes into secret key"), + }, + { + name: "Good", + input: []byte{0x25, 0x29, 0x5f, 0x0d, 0x1d, 0x59, 0x2a, 0x90, 0xb3, 0x33, 0xe2, 0x6e, 0x85, 0x14, 0x97, 0x08, 0x20, 0x8e, 0x9f, 0x8e, 0x8b, 0xc1, 0x8f, 0x6c, 0x77, 0xbd, 0x62, 0xf8, 0xad, 0x7a, 0x68, 0x66}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + res, err := blst.SecretKeyFromBytes(test.input) + if test.err != nil { + assert.NotEqual(t, nil, err, "No error returned") + assert.ErrorContains(t, test.err, err.Error(), "Unexpected error returned") + } else { + assert.NoError(t, err) + assert.Equal(t, 0, bytes.Compare(res.Marshal(), test.input)) + } + }) + } +} + +func TestSerialize(t *testing.T) { + rk, err := blst.RandKey() + require.NoError(t, err) + b := rk.Marshal() + + _, err = blst.SecretKeyFromBytes(b) + assert.NoError(t, err) +} + +func TestZeroKey(t *testing.T) { + // Is Zero + var zKey [32]byte + assert.Equal(t, true, blst.IsZero(zKey[:])) + + // Is Not Zero + _, err := rand.Read(zKey[:]) + assert.NoError(t, err) + assert.Equal(t, false, blst.IsZero(zKey[:])) +} + +// PadTo pads a byte slice to the given size. If the byte slice is larger than the given size, the +// original slice is returned. +func PadTo(b []byte, size int) []byte { + if len(b) >= size { + return b + } + return append(b, make([]byte, size-len(b))...) +} + +// ToBytes32 is a convenience method for converting a byte slice to a fix +// sized 32 byte array. This method will truncate the input if it is larger +// than 32 bytes. +func ToBytes32(x []byte) [32]byte { + return [32]byte(PadTo(x, 32)) +} diff --git a/curves/bls12381/signature.go b/curves/bls12381/signature.go new file mode 100644 index 0000000..a5a8655 --- /dev/null +++ b/curves/bls12381/signature.go @@ -0,0 +1,77 @@ +//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && bls12381 + +package blst + +import ( + "errors" + "fmt" +) + +var dst = []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_") + +// Signature used in the BLS signature scheme. +type Signature struct { + s *blstSignature +} + +// Marshal a signature into a LittleEndian byte slice. +func (s *Signature) Marshal() []byte { + return s.s.Compress() +} + +// Copy returns a full deep copy of a signature. +func (s *Signature) Copy() SignatureI { + sign := *s.s + return &Signature{s: &sign} +} + +func (s *Signature) Verify(pubKey PubKey, msg []byte) bool { + // Signature and PKs are assumed to have been validated upon decompression! + return s.s.Verify(false, pubKey.(*PublicKey).p, false, msg, dst) +} + +// VerifySignature verifies a single signature using public key and message. +func VerifySignature(sig []byte, msg [32]byte, pubKey PubKey) (bool, error) { + rSig, err := SignatureFromBytes(sig) + if err != nil { + return false, err + } + return rSig.Verify(pubKey, msg[:]), nil +} + +// signatureFromBytesNoValidation creates a BLS signature from a LittleEndian +// byte slice. It does not validate that the signature is in the BLS group +func signatureFromBytesNoValidation(sig []byte) (*blstSignature, error) { + if len(sig) != SignatureLength { + return nil, fmt.Errorf("signature must be %d bytes", SignatureLength) + } + signature := new(blstSignature).Uncompress(sig) + if signature == nil { + return nil, errors.New("could not unmarshal bytes into signature") + } + return signature, nil +} + +// SignatureFromBytesNoValidation creates a BLS signature from a LittleEndian +// byte slice. It does not validate that the signature is in the BLS group +func SignatureFromBytesNoValidation(sig []byte) (SignatureI, error) { + signature, err := signatureFromBytesNoValidation(sig) + if err != nil { + return nil, fmt.Errorf("could not create signature from byte slice: %w", err) + } + return &Signature{s: signature}, nil +} + +// SignatureFromBytes creates a BLS signature from a LittleEndian byte slice. +func SignatureFromBytes(sig []byte) (SignatureI, error) { + signature, err := signatureFromBytesNoValidation(sig) + if err != nil { + return nil, fmt.Errorf("could not create signature from byte slice: %w", err) + } + // Group check signature. Do not check for infinity since an aggregated signature + // could be infinite. + if !signature.SigValidate(false) { + return nil, errors.New("signature not in group") + } + return &Signature{s: signature}, nil +} diff --git a/curves/bls12381/signature_test.go b/curves/bls12381/signature_test.go new file mode 100644 index 0000000..ddfa229 --- /dev/null +++ b/curves/bls12381/signature_test.go @@ -0,0 +1,174 @@ +//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && bls12381 + +package blst + +import ( + "bytes" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSignVerify(t *testing.T) { + priv, err := RandKey() + require.NoError(t, err) + pub := priv.PublicKey() + msg := []byte("hello") + sig := priv.Sign(msg) + assert.Equal(t, true, sig.Verify(pub, msg), "Signature did not verify") +} + +func TestVerifySingleSignature_InvalidSignature(t *testing.T) { + priv, err := RandKey() + require.NoError(t, err) + pub := priv.PublicKey() + msgA := [32]byte{'h', 'e', 'l', 'l', 'o'} + msgB := [32]byte{'o', 'l', 'l', 'e', 'h'} + sigA := priv.Sign(msgA[:]).Marshal() + valid, err := VerifySignature(sigA, msgB, pub) + assert.NoError(t, err) + assert.Equal(t, false, valid, "Signature did verify") +} + +func TestVerifySingleSignature_ValidSignature(t *testing.T) { + priv, err := RandKey() + require.NoError(t, err) + pub := priv.PublicKey() + msg := [32]byte{'h', 'e', 'l', 'l', 'o'} + sig := priv.Sign(msg[:]).Marshal() + valid, err := VerifySignature(sig, msg, pub) + assert.NoError(t, err) + assert.Equal(t, true, valid, "Signature did not verify") +} + +func TestSignatureFromBytes(t *testing.T) { + tests := []struct { + name string + input []byte + err error + }{ + { + name: "Nil", + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Empty", + input: []byte{}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Short", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Long", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Bad", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("could not unmarshal bytes into signature"), + }, + { + input: []byte{0xac, 0xb0, 0x12, 0x4c, 0x75, 0x74, 0xf2, 0x81, 0xa2, 0x93, 0xf4, 0x18, 0x5c, 0xad, 0x3c, 0xb2, 0x26, 0x81, 0xd5, 0x20, 0x91, 0x7c, 0xe4, 0x66, 0x65, 0x24, 0x3e, 0xac, 0xb0, 0x51, 0x00, 0x0d, 0x8b, 0xac, 0xf7, 0x5e, 0x14, 0x51, 0x87, 0x0c, 0xa6, 0xb3, 0xb9, 0xe6, 0xc9, 0xd4, 0x1a, 0x7b, 0x02, 0xea, 0xd2, 0x68, 0x5a, 0x84, 0x18, 0x8a, 0x4f, 0xaf, 0xd3, 0x82, 0x5d, 0xaf, 0x6a, 0x98, 0x96, 0x25, 0xd7, 0x19, 0xcc, 0xd2, 0xd8, 0x3a, 0x40, 0x10, 0x1f, 0x4a, 0x45, 0x3f, 0xca, 0x62, 0x87, 0x8c, 0x89, 0x0e, 0xca, 0x62, 0x23, 0x63, 0xf9, 0xdd, 0xb8, 0xf3, 0x67, 0xa9, 0x1e, 0x84}, + name: "Not in group", + err: errors.New("signature not in group"), + }, + { + name: "Good", + input: []byte{0xab, 0xb0, 0x12, 0x4c, 0x75, 0x74, 0xf2, 0x81, 0xa2, 0x93, 0xf4, 0x18, 0x5c, 0xad, 0x3c, 0xb2, 0x26, 0x81, 0xd5, 0x20, 0x91, 0x7c, 0xe4, 0x66, 0x65, 0x24, 0x3e, 0xac, 0xb0, 0x51, 0x00, 0x0d, 0x8b, 0xac, 0xf7, 0x5e, 0x14, 0x51, 0x87, 0x0c, 0xa6, 0xb3, 0xb9, 0xe6, 0xc9, 0xd4, 0x1a, 0x7b, 0x02, 0xea, 0xd2, 0x68, 0x5a, 0x84, 0x18, 0x8a, 0x4f, 0xaf, 0xd3, 0x82, 0x5d, 0xaf, 0x6a, 0x98, 0x96, 0x25, 0xd7, 0x19, 0xcc, 0xd2, 0xd8, 0x3a, 0x40, 0x10, 0x1f, 0x4a, 0x45, 0x3f, 0xca, 0x62, 0x87, 0x8c, 0x89, 0x0e, 0xca, 0x62, 0x23, 0x63, 0xf9, 0xdd, 0xb8, 0xf3, 0x67, 0xa9, 0x1e, 0x84}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + res, err := SignatureFromBytes(test.input) + if test.err != nil { + assert.NotEqual(t, nil, err, "No error returned") + assert.ErrorContains(t, err, test.err.Error(), "Unexpected error returned") + } else { + assert.NoError(t, err) + assert.Equal(t, 0, bytes.Compare(res.Marshal(), test.input)) + } + }) + } +} + +func TestSignatureFromBytesNoValidation(t *testing.T) { + tests := []struct { + name string + input []byte + err error + }{ + { + name: "Nil", + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Empty", + input: []byte{}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Short", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Long", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("signature must be 96 bytes"), + }, + { + name: "Bad", + input: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + err: errors.New("could not unmarshal bytes into signature"), + }, + { + name: "Not in group", + input: []byte{0xac, 0xb0, 0x12, 0x4c, 0x75, 0x74, 0xf2, 0x81, 0xa2, 0x93, 0xf4, 0x18, 0x5c, 0xad, 0x3c, 0xb2, 0x26, 0x81, 0xd5, 0x20, 0x91, 0x7c, 0xe4, 0x66, 0x65, 0x24, 0x3e, 0xac, 0xb0, 0x51, 0x00, 0x0d, 0x8b, 0xac, 0xf7, 0x5e, 0x14, 0x51, 0x87, 0x0c, 0xa6, 0xb3, 0xb9, 0xe6, 0xc9, 0xd4, 0x1a, 0x7b, 0x02, 0xea, 0xd2, 0x68, 0x5a, 0x84, 0x18, 0x8a, 0x4f, 0xaf, 0xd3, 0x82, 0x5d, 0xaf, 0x6a, 0x98, 0x96, 0x25, 0xd7, 0x19, 0xcc, 0xd2, 0xd8, 0x3a, 0x40, 0x10, 0x1f, 0x4a, 0x45, 0x3f, 0xca, 0x62, 0x87, 0x8c, 0x89, 0x0e, 0xca, 0x62, 0x23, 0x63, 0xf9, 0xdd, 0xb8, 0xf3, 0x67, 0xa9, 0x1e, 0x84}, + }, + { + name: "Good", + input: []byte{0xab, 0xb0, 0x12, 0x4c, 0x75, 0x74, 0xf2, 0x81, 0xa2, 0x93, 0xf4, 0x18, 0x5c, 0xad, 0x3c, 0xb2, 0x26, 0x81, 0xd5, 0x20, 0x91, 0x7c, 0xe4, 0x66, 0x65, 0x24, 0x3e, 0xac, 0xb0, 0x51, 0x00, 0x0d, 0x8b, 0xac, 0xf7, 0x5e, 0x14, 0x51, 0x87, 0x0c, 0xa6, 0xb3, 0xb9, 0xe6, 0xc9, 0xd4, 0x1a, 0x7b, 0x02, 0xea, 0xd2, 0x68, 0x5a, 0x84, 0x18, 0x8a, 0x4f, 0xaf, 0xd3, 0x82, 0x5d, 0xaf, 0x6a, 0x98, 0x96, 0x25, 0xd7, 0x19, 0xcc, 0xd2, 0xd8, 0x3a, 0x40, 0x10, 0x1f, 0x4a, 0x45, 0x3f, 0xca, 0x62, 0x87, 0x8c, 0x89, 0x0e, 0xca, 0x62, 0x23, 0x63, 0xf9, 0xdd, 0xb8, 0xf3, 0x67, 0xa9, 0x1e, 0x84}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + res, err := SignatureFromBytesNoValidation(test.input) + if test.err != nil { + assert.NotEqual(t, nil, err, "No error returned") + assert.ErrorContains(t, err, test.err.Error(), "Unexpected error returned") + } else { + assert.NoError(t, err) + assert.Equal(t, 0, bytes.Compare(res.Marshal(), test.input)) + } + }) + } +} + +// TestCopy is a unit test function that tests the Copy method of the Signature struct. +// It generates a random private key, creates two signatures using the key, and verifies +// that the Copy method creates a deep copy of the original signature. It then modifies +// one of the signatures and verifies that the modified signature is not equal to the +// original signature. +func TestCopy(t *testing.T) { + priv, err := RandKey() + require.NoError(t, err) + key, ok := priv.(*bls12SecretKey) + require.Equal(t, true, ok) + + signatureA := &Signature{s: new(blstSignature).Sign(key.p, []byte("foo"), dst)} + signatureB, ok := signatureA.Copy().(*Signature) + require.Equal(t, true, ok) + + assert.Equal(t, signatureA, signatureB) + + signatureA.s.Sign(key.p, []byte("bar"), dst) + assert.NotEqual(t, signatureA, signatureB) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bb96dcd --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module github.com/cosmos/crypto + +go 1.21 + +require ( + github.com/stretchr/testify v1.9.0 + github.com/supranational/blst v0.3.11 + go.uber.org/automaxprocs v1.5.3 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e6315f5 --- /dev/null +++ b/go.sum @@ -0,0 +1,22 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/utils/cache/cache.go b/utils/cache/cache.go new file mode 100644 index 0000000..066e8b8 --- /dev/null +++ b/utils/cache/cache.go @@ -0,0 +1,144 @@ +package cache + +import ( + "errors" + "sync" +) + +// EvictCallback is used to get a callback when a cache entry is evicted. +type EvictCallback[K comparable, V any] func(key K, value V) + +// LRU implements a non-thread safe fixed size LRU cache. +type LRU[K comparable, V any] struct { + itemsLock sync.RWMutex + evictListLock sync.RWMutex + size int + evictList *lruList[K, V] + items map[K]*entry[K, V] + onEvict EvictCallback[K, V] + getChan chan *entry[K, V] +} + +// NewLRU constructs an LRU of the given size. +func NewLRU[K comparable, V any](size int, onEvict EvictCallback[K, V]) (*LRU[K, V], error) { + if size <= 0 { + return nil, errors.New("must provide a positive size") + } + // Initialize the channel buffer size as being 10% of the cache size. + chanSize := size / 10 + + c := &LRU[K, V]{ + size: size, + evictList: newList[K, V](), + items: make(map[K]*entry[K, V]), + onEvict: onEvict, + getChan: make(chan *entry[K, V], chanSize), + } + // Spin off separate go-routine to handle evict list + // operations. + go c.handleGetRequests() + return c, nil +} + +// Add adds a value to the cache. Returns true if an eviction occurred. +func (c *LRU[K, V]) Add(key K, value V) (evicted bool) { + // Check for existing item + c.itemsLock.RLock() + if ent, ok := c.items[key]; ok { + c.itemsLock.RUnlock() + + c.evictListLock.Lock() + c.evictList.moveToFront(ent) + c.evictListLock.Unlock() + ent.value = value + return false + } + c.itemsLock.RUnlock() + + // Add new item + c.evictListLock.Lock() + ent := c.evictList.pushFront(key, value) + c.evictListLock.Unlock() + + c.itemsLock.Lock() + c.items[key] = ent + c.itemsLock.Unlock() + + c.evictListLock.RLock() + evict := c.evictList.length() > c.size + c.evictListLock.RUnlock() + + // Verify size not exceeded + if evict { + c.removeOldest() + } + return evict +} + +// Get looks up a key's value from the cache. +func (c *LRU[K, V]) Get(key K) (value V, ok bool) { + c.itemsLock.RLock() + if ent, ok := c.items[key]; ok { + c.itemsLock.RUnlock() + + // Make this get function non-blocking for multiple readers. + c.getChan <- ent + return ent.value, true + } + c.itemsLock.RUnlock() + return +} + +// Len returns the number of items in the cache. +func (c *LRU[K, V]) Len() int { + c.evictListLock.RLock() + defer c.evictListLock.RUnlock() + return c.evictList.length() +} + +// Resize changes the cache size. +func (c *LRU[K, V]) Resize(size int) (evicted int) { + diff := c.Len() - size + if diff < 0 { + diff = 0 + } + for i := 0; i < diff; i++ { + c.removeOldest() + } + c.size = size + return diff +} + +// removeOldest removes the oldest item from the cache. +func (c *LRU[K, V]) removeOldest() { + c.evictListLock.RLock() + if ent := c.evictList.back(); ent != nil { + c.evictListLock.RUnlock() + c.removeElement(ent) + return + } + c.evictListLock.RUnlock() +} + +// removeElement is used to remove a given list element from the cache. +func (c *LRU[K, V]) removeElement(e *entry[K, V]) { + c.evictListLock.Lock() + c.evictList.remove(e) + c.evictListLock.Unlock() + + c.itemsLock.Lock() + delete(c.items, e.key) + c.itemsLock.Unlock() + if c.onEvict != nil { + c.onEvict(e.key, e.value) + } +} + +func (c *LRU[K, V]) handleGetRequests() { + for { + entry := <-c.getChan + c.evictListLock.Lock() + c.evictList.moveToFront(entry) + c.evictListLock.Unlock() + } +} diff --git a/utils/cache/list.go b/utils/cache/list.go new file mode 100644 index 0000000..5bf4f56 --- /dev/null +++ b/utils/cache/list.go @@ -0,0 +1,123 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE_list file. +package cache + +// entry is an LRU entry. +type entry[K comparable, V any] struct { + // Next and previous pointers in the doubly-linked list of elements. + // To simplify the implementation, internally a list l is implemented + // as a ring, such that &l.root is both the next element of the last + // list element (l.Back()) and the previous element of the first list + // element (l.Front()). + next, prev *entry[K, V] + + // The list to which this element belongs. + list *lruList[K, V] + + // The LRU key of this element. + key K + + // The value stored with this element. + value V +} + +// lruList represents a doubly linked list. +// The zero value for lruList is an empty list ready to use. +type lruList[K comparable, V any] struct { + root entry[K, V] // sentinel list element, only &root, root.prev, and root.next are used + len int // current list length excluding (this) sentinel element +} + +// init initializes or clears list l. +func (l *lruList[K, V]) init() *lruList[K, V] { + l.root.next = &l.root + l.root.prev = &l.root + l.len = 0 + return l +} + +// newList returns an initialized list. +func newList[K comparable, V any]() *lruList[K, V] { return new(lruList[K, V]).init() } + +// length returns the number of elements of list l. +// The complexity is O(1). +func (l *lruList[K, V]) length() int { return l.len } + +// back returns the last element of list l or nil if the list is empty. +func (l *lruList[K, V]) back() *entry[K, V] { + if l.len == 0 { + return nil + } + return l.root.prev +} + +// lazyInit lazily initializes a zero List value. +func (l *lruList[K, V]) lazyInit() { + if l.root.next == nil { + l.init() + } +} + +// insert inserts e after at, increments l.len, and returns e. +func (l *lruList[K, V]) insert(e, at *entry[K, V]) *entry[K, V] { + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e + e.list = l + l.len++ + return e +} + +// insertValue is a convenience wrapper for insert(&Element{Value: v}, at). +func (l *lruList[K, V]) insertValue(k K, v V, at *entry[K, V]) *entry[K, V] { + return l.insert(&entry[K, V]{value: v, key: k}, at) +} + +// remove removes e from its list, decrements l.len. +func (l *lruList[K, V]) remove(e *entry[K, V]) V { + // If already removed, do nothing. + if e.prev == nil && e.next == nil { + return e.value + } + e.prev.next = e.next + e.next.prev = e.prev + e.next = nil // avoid memory leaks + e.prev = nil // avoid memory leaks + e.list = nil + l.len-- + + return e.value +} + +// move moves e to next to at. +func (*lruList[K, V]) move(e, at *entry[K, V]) { + if e == at { + return + } + e.prev.next = e.next + e.next.prev = e.prev + + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e +} + +// pushFront inserts a new element e with value v at the front of list l and returns e. +func (l *lruList[K, V]) pushFront(k K, v V) *entry[K, V] { + l.lazyInit() + return l.insertValue(k, v, &l.root) +} + +// moveToFront moves element e to the front of list l. +// If e is not an element of l, the list is not modified. +// The element must not be nil. +func (l *lruList[K, V]) moveToFront(e *entry[K, V]) { + if e.list != l || l.root.next == e { + return + } + // see comment in List.Remove about initialization of l + l.move(e, &l.root) +} diff --git a/utils/rand/rand.go b/utils/rand/rand.go new file mode 100644 index 0000000..3fe4a94 --- /dev/null +++ b/utils/rand/rand.go @@ -0,0 +1,45 @@ +package rand + +import ( + "crypto/rand" + "encoding/binary" + mrand "math/rand" + "sync" +) + +type source struct{} + +var lock sync.RWMutex +var _ mrand.Source64 = (*source)(nil) // #nosec G404 -- This ensures we meet the interface + +// Seed does nothing when crypto/rand is used as source. +func (_ *source) Seed(_ int64) {} + +// Int63 returns uniformly-distributed random (as in CSPRNG) int64 value within [0, 1<<63) range. +// Panics if random generator reader cannot return data. +func (s *source) Int63() int64 { + return int64(s.Uint64() & ^uint64(1<<63)) +} + +// Uint64 returns uniformly-distributed random (as in CSPRNG) uint64 value within [0, 1<<64) range. +// Panics if random generator reader cannot return data. +func (_ *source) Uint64() (val uint64) { + lock.RLock() + defer lock.RUnlock() + if err := binary.Read(rand.Reader, binary.BigEndian, &val); err != nil { + panic(err) + } + return +} + +// Rand is alias for underlying random generator. +type Rand = mrand.Rand // #nosec G404 + +// NewGenerator returns a new generator that uses random values from crypto/rand as a source +// (cryptographically secure random number generator). +// Panics if crypto/rand input cannot be read. +// Use it for everything where crypto secure non-deterministic randomness is required. Performance +// takes a hit, so use sparingly. +func NewGenerator() *Rand { + return mrand.New(&source{}) // #nosec G404 -- excluded +}