mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
Add compatibility layer for node crypto API: BLS helpers, secp256k1 keys, recover cache, and ethereum address support
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package bls
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSignVerify(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
sk, err := NewSecretKey()
|
||||
require.NoError(err)
|
||||
|
||||
pk := sk.PublicKey()
|
||||
require.NotNil(pk)
|
||||
|
||||
msg := make([]byte, 32)
|
||||
_, err = rand.Read(msg)
|
||||
require.NoError(err)
|
||||
|
||||
sig := sk.Sign(msg)
|
||||
require.NotNil(sig)
|
||||
|
||||
valid := Verify(pk, sig, msg)
|
||||
require.True(valid)
|
||||
|
||||
// Wrong message should fail
|
||||
msg[0]++
|
||||
valid = Verify(pk, sig, msg)
|
||||
require.False(valid)
|
||||
}
|
||||
|
||||
func TestProofOfPossession(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
sk, err := NewSecretKey()
|
||||
require.NoError(err)
|
||||
|
||||
pk := sk.PublicKey()
|
||||
require.NotNil(pk)
|
||||
|
||||
msg := make([]byte, 32)
|
||||
_, err = rand.Read(msg)
|
||||
require.NoError(err)
|
||||
|
||||
sig := sk.SignProofOfPossession(msg)
|
||||
require.NotNil(sig)
|
||||
|
||||
valid := VerifyProofOfPossession(pk, sig, msg)
|
||||
require.True(valid)
|
||||
}
|
||||
|
||||
func TestSecretKeyFromBytes(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
sk1, err := NewSecretKey()
|
||||
require.NoError(err)
|
||||
|
||||
bytes := SecretKeyToBytes(sk1)
|
||||
require.Len(bytes, SecretKeyLen)
|
||||
|
||||
sk2, err := SecretKeyFromBytes(bytes)
|
||||
require.NoError(err)
|
||||
|
||||
require.Equal(SecretKeyToBytes(sk1), SecretKeyToBytes(sk2))
|
||||
}
|
||||
|
||||
func TestPublicKeyFromBytes(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
sk, err := NewSecretKey()
|
||||
require.NoError(err)
|
||||
|
||||
pk1 := sk.PublicKey()
|
||||
bytes := PublicKeyToCompressedBytes(pk1)
|
||||
require.Len(bytes, PublicKeyLen)
|
||||
|
||||
pk2, err := PublicKeyFromCompressedBytes(bytes)
|
||||
require.NoError(err)
|
||||
|
||||
require.Equal(PublicKeyToCompressedBytes(pk1), PublicKeyToCompressedBytes(pk2))
|
||||
}
|
||||
|
||||
func TestSignatureFromBytes(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
sk, err := NewSecretKey()
|
||||
require.NoError(err)
|
||||
|
||||
msg := []byte("test message")
|
||||
sig1 := sk.Sign(msg)
|
||||
|
||||
bytes := SignatureToBytes(sig1)
|
||||
require.Len(bytes, SignatureLen)
|
||||
|
||||
sig2, err := SignatureFromBytes(bytes)
|
||||
require.NoError(err)
|
||||
|
||||
require.Equal(SignatureToBytes(sig1), SignatureToBytes(sig2))
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package bls
|
||||
|
||||
// Helper functions to maintain compatibility with node/utils/crypto/bls
|
||||
|
||||
// PublicFromSecretKey returns the public key for a secret key
|
||||
func PublicFromSecretKey(sk *SecretKey) *PublicKey {
|
||||
if sk == nil {
|
||||
return nil
|
||||
}
|
||||
return sk.PublicKey()
|
||||
}
|
||||
|
||||
// Sign signs a message with a secret key
|
||||
func Sign(sk *SecretKey, msg []byte) *Signature {
|
||||
if sk == nil {
|
||||
return nil
|
||||
}
|
||||
return sk.Sign(msg)
|
||||
}
|
||||
|
||||
// SignProofOfPossession signs a proof of possession
|
||||
func SignProofOfPossession(sk *SecretKey, msg []byte) *Signature {
|
||||
if sk == nil {
|
||||
return nil
|
||||
}
|
||||
return sk.SignProofOfPossession(msg)
|
||||
}
|
||||
|
||||
// PublicKeyBytes is a helper that returns the compressed bytes of a public key
|
||||
func PublicKeyBytes(pk *PublicKey) []byte {
|
||||
return PublicKeyToCompressedBytes(pk)
|
||||
}
|
||||
|
||||
// AggregatePublicKeyFromBytes converts bytes to an aggregate public key
|
||||
func AggregatePublicKeyFromBytes(pkBytes []byte) (*AggregatePublicKey, error) {
|
||||
return PublicKeyFromCompressedBytes(pkBytes)
|
||||
}
|
||||
|
||||
// AggregatePublicKeyToBytes converts an aggregate public key to bytes
|
||||
func AggregatePublicKeyToBytes(apk *AggregatePublicKey) []byte {
|
||||
return PublicKeyToCompressedBytes(apk)
|
||||
}
|
||||
|
||||
// AggregateSignatureFromBytes converts bytes to an aggregate signature
|
||||
func AggregateSignatureFromBytes(sigBytes []byte) (*AggregateSignature, error) {
|
||||
return SignatureFromBytes(sigBytes)
|
||||
}
|
||||
|
||||
// AggregateSignatureToBytes converts an aggregate signature to bytes
|
||||
func AggregateSignatureToBytes(asig *AggregateSignature) []byte {
|
||||
return SignatureToBytes(asig)
|
||||
}
|
||||
|
||||
// VerifyAggregate verifies an aggregate signature
|
||||
func VerifyAggregate(apk *AggregatePublicKey, asig *AggregateSignature, msg []byte) bool {
|
||||
return Verify(apk, asig, msg)
|
||||
}
|
||||
|
||||
// VerifyAggregateProofOfPossession verifies an aggregate proof of possession
|
||||
func VerifyAggregateProofOfPossession(apk *AggregatePublicKey, asig *AggregateSignature, msg []byte) bool {
|
||||
return VerifyProofOfPossession(apk, asig, msg)
|
||||
}
|
||||
Vendored
+107
@@ -0,0 +1,107 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// LRU is a thread-safe least recently used cache with a fixed size.
|
||||
type LRU[K comparable, V any] struct {
|
||||
Size int
|
||||
|
||||
mu sync.Mutex
|
||||
items map[K]*list.Element
|
||||
eviction *list.List
|
||||
}
|
||||
|
||||
// entry is the internal struct stored in the eviction list
|
||||
type entry[K comparable, V any] struct {
|
||||
key K
|
||||
value V
|
||||
}
|
||||
|
||||
// NewLRU creates a new LRU cache with the given size
|
||||
func NewLRU[K comparable, V any](size int) *LRU[K, V] {
|
||||
if size <= 0 {
|
||||
size = 1
|
||||
}
|
||||
return &LRU[K, V]{
|
||||
Size: size,
|
||||
items: make(map[K]*list.Element),
|
||||
eviction: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// Put adds or updates a key-value pair in the cache
|
||||
func (c *LRU[K, V]) Put(key K, value V) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// Check if key already exists
|
||||
if elem, ok := c.items[key]; ok {
|
||||
// Update value and move to front
|
||||
c.eviction.MoveToFront(elem)
|
||||
elem.Value.(*entry[K, V]).value = value
|
||||
return
|
||||
}
|
||||
|
||||
// Add new entry
|
||||
elem := c.eviction.PushFront(&entry[K, V]{key: key, value: value})
|
||||
c.items[key] = elem
|
||||
|
||||
// Evict oldest if over capacity
|
||||
if c.eviction.Len() > c.Size {
|
||||
oldest := c.eviction.Back()
|
||||
if oldest != nil {
|
||||
c.eviction.Remove(oldest)
|
||||
delete(c.items, oldest.Value.(*entry[K, V]).key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves a value from the cache
|
||||
func (c *LRU[K, V]) Get(key K) (V, bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
var zero V
|
||||
elem, ok := c.items[key]
|
||||
if !ok {
|
||||
return zero, false
|
||||
}
|
||||
|
||||
// Move to front (mark as recently used)
|
||||
c.eviction.MoveToFront(elem)
|
||||
return elem.Value.(*entry[K, V]).value, true
|
||||
}
|
||||
|
||||
// Evict removes a key from the cache
|
||||
func (c *LRU[K, V]) Evict(key K) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if elem, ok := c.items[key]; ok {
|
||||
c.eviction.Remove(elem)
|
||||
delete(c.items, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Flush removes all entries from the cache
|
||||
func (c *LRU[K, V]) Flush() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.items = make(map[K]*list.Element)
|
||||
c.eviction.Init()
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache
|
||||
func (c *LRU[K, V]) Len() int {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
return len(c.items)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package secp256k1
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
)
|
||||
|
||||
// PubkeyToAddress returns the Ethereum address for the given public key
|
||||
func PubkeyToAddress(p ecdsa.PublicKey) common.Address {
|
||||
pubBytes := make([]byte, 65)
|
||||
pubBytes[0] = 0x04 // uncompressed point
|
||||
copy(pubBytes[1:33], p.X.Bytes())
|
||||
copy(pubBytes[33:65], p.Y.Bytes())
|
||||
|
||||
// Ethereum address is last 20 bytes of Keccak256 hash of public key (excluding prefix)
|
||||
hash := Keccak256(pubBytes[1:])
|
||||
return common.BytesToAddress(hash[12:])
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package secp256k1
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// Keccak256 calculates and returns the Keccak256 hash of the input data.
|
||||
func Keccak256(data ...[]byte) []byte {
|
||||
d := sha3.NewLegacyKeccak256()
|
||||
for _, b := range data {
|
||||
d.Write(b)
|
||||
}
|
||||
return d.Sum(nil)
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package secp256k1
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/luxfi/crypto/cb58"
|
||||
"github.com/luxfi/crypto/cache"
|
||||
"github.com/luxfi/crypto/hashing"
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
const (
|
||||
// SignatureLen is the number of bytes in a secp256k1 recoverable signature
|
||||
SignatureLen = 65
|
||||
|
||||
// PrivateKeyLen is the number of bytes in a secp256k1 private key
|
||||
PrivateKeyLen = 32
|
||||
|
||||
// PublicKeyLen is the number of bytes in a secp256k1 public key
|
||||
PublicKeyLen = 33
|
||||
|
||||
PrivateKeyPrefix = "PrivateKey-"
|
||||
nullStr = "null"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidSig = errors.New("invalid signature")
|
||||
errInvalidPrivateKeyLength = fmt.Errorf("private key has unexpected length, expected %d", PrivateKeyLen)
|
||||
errInvalidPublicKeyLength = fmt.Errorf("public key has unexpected length, expected %d", PublicKeyLen)
|
||||
errInvalidSigLen = errors.New("invalid signature length")
|
||||
|
||||
secp256k1N = S256().Params().N
|
||||
secp256k1halfN = new(big.Int).Div(secp256k1N, big.NewInt(2))
|
||||
)
|
||||
|
||||
// RecoverCache is a cache for recovered public keys
|
||||
var RecoverCache = cache.NewLRU[string, *PublicKey](2048)
|
||||
|
||||
// PrivateKey wraps an ecdsa.PrivateKey
|
||||
type PrivateKey struct {
|
||||
sk *ecdsa.PrivateKey
|
||||
bytes []byte
|
||||
}
|
||||
|
||||
// PublicKey wraps an ecdsa.PublicKey
|
||||
type PublicKey struct {
|
||||
pk *ecdsa.PublicKey
|
||||
bytes []byte
|
||||
}
|
||||
|
||||
// NewPrivateKey generates a new private key
|
||||
func NewPrivateKey() (*PrivateKey, error) {
|
||||
privKey, err := ecdsa.GenerateKey(S256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bytes := PaddedBigBytes(privKey.D, PrivateKeyLen)
|
||||
return &PrivateKey{
|
||||
sk: privKey,
|
||||
bytes: bytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ToPrivateKey converts bytes to a private key
|
||||
func ToPrivateKey(b []byte) (*PrivateKey, error) {
|
||||
if len(b) != PrivateKeyLen {
|
||||
return nil, errInvalidPrivateKeyLength
|
||||
}
|
||||
|
||||
priv := new(ecdsa.PrivateKey)
|
||||
priv.PublicKey.Curve = S256()
|
||||
priv.D = new(big.Int).SetBytes(b)
|
||||
|
||||
// The priv.D must < N
|
||||
if priv.D.Cmp(secp256k1N) >= 0 {
|
||||
return nil, errors.New("invalid private key, >=N")
|
||||
}
|
||||
// The priv.D must not be zero or negative.
|
||||
if priv.D.Sign() <= 0 {
|
||||
return nil, errors.New("invalid private key, zero or negative")
|
||||
}
|
||||
|
||||
priv.PublicKey.X, priv.PublicKey.Y = S256().ScalarBaseMult(b)
|
||||
if priv.PublicKey.X == nil {
|
||||
return nil, errors.New("invalid private key")
|
||||
}
|
||||
|
||||
return &PrivateKey{
|
||||
sk: priv,
|
||||
bytes: b,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ToPublicKey converts bytes to a public key
|
||||
func ToPublicKey(b []byte) (*PublicKey, error) {
|
||||
if len(b) != PublicKeyLen {
|
||||
return nil, errInvalidPublicKeyLength
|
||||
}
|
||||
|
||||
x, y := DecompressPubkey(b)
|
||||
if x == nil || y == nil {
|
||||
return nil, errors.New("invalid public key")
|
||||
}
|
||||
|
||||
pub := &ecdsa.PublicKey{
|
||||
Curve: S256(),
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
|
||||
return &PublicKey{
|
||||
pk: pub,
|
||||
bytes: b,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Sign signs a message with the private key
|
||||
func (k *PrivateKey) Sign(msg []byte) ([]byte, error) {
|
||||
sig, err := k.SignArray(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sig[:], nil
|
||||
}
|
||||
|
||||
// SignArray signs a message and returns a fixed-size array
|
||||
func (k *PrivateKey) SignArray(msg []byte) ([SignatureLen]byte, error) {
|
||||
return k.SignHashArray(hashing.ComputeHash256(msg))
|
||||
}
|
||||
|
||||
// SignHash signs a hash with the private key
|
||||
func (k *PrivateKey) SignHash(hash []byte) ([]byte, error) {
|
||||
sig, err := k.SignHashArray(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sig[:], nil
|
||||
}
|
||||
|
||||
// SignHashArray signs a hash and returns a fixed-size array
|
||||
func (k *PrivateKey) SignHashArray(hash []byte) ([SignatureLen]byte, error) {
|
||||
sig, err := Sign(hash, k.bytes)
|
||||
if err != nil {
|
||||
return [SignatureLen]byte{}, err
|
||||
}
|
||||
var result [SignatureLen]byte
|
||||
copy(result[:], sig)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// PublicKey returns the public key
|
||||
func (k *PrivateKey) PublicKey() *PublicKey {
|
||||
pubBytes := CompressPubkey(k.sk.PublicKey.X, k.sk.PublicKey.Y)
|
||||
return &PublicKey{
|
||||
pk: &k.sk.PublicKey,
|
||||
bytes: pubBytes,
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns the private key bytes
|
||||
func (k *PrivateKey) Bytes() []byte {
|
||||
return k.bytes
|
||||
}
|
||||
|
||||
// Address returns the address of the private key (via its public key)
|
||||
func (k *PrivateKey) Address() ids.ShortID {
|
||||
return k.PublicKey().Address()
|
||||
}
|
||||
|
||||
// Address returns the address of the public key as an ids.ShortID
|
||||
func (k *PublicKey) Address() ids.ShortID {
|
||||
// Compute Ethereum address from public key
|
||||
pubBytes := make([]byte, 65)
|
||||
pubBytes[0] = 0x04 // uncompressed point
|
||||
k.pk.X.FillBytes(pubBytes[1:33])
|
||||
k.pk.Y.FillBytes(pubBytes[33:65])
|
||||
|
||||
hash := Keccak256(pubBytes[1:])
|
||||
var addr ids.ShortID
|
||||
copy(addr[:], hash[12:])
|
||||
return addr
|
||||
}
|
||||
|
||||
// Bytes returns the public key bytes
|
||||
func (k *PublicKey) Bytes() []byte {
|
||||
return k.bytes
|
||||
}
|
||||
|
||||
// ToECDSA returns the underlying ECDSA public key
|
||||
func (k *PublicKey) ToECDSA() *ecdsa.PublicKey {
|
||||
return k.pk
|
||||
}
|
||||
|
||||
// VerifyHash verifies a signature against a hash
|
||||
func (k *PublicKey) VerifyHash(hash, sig []byte) bool {
|
||||
if len(sig) != SignatureLen {
|
||||
return false
|
||||
}
|
||||
return VerifySignature(k.bytes, hash, sig[:64])
|
||||
}
|
||||
|
||||
// Verify verifies a signature against a message
|
||||
func (k *PublicKey) Verify(msg, sig []byte) bool {
|
||||
return k.VerifyHash(hashing.ComputeHash256(msg), sig)
|
||||
}
|
||||
|
||||
// RecoverPublicKey recovers the public key from a message and signature
|
||||
func RecoverPublicKey(msg, sig []byte) (*PublicKey, error) {
|
||||
return RecoverPublicKeyFromHash(hashing.ComputeHash256(msg), sig)
|
||||
}
|
||||
|
||||
// RecoverPublicKeyFromHash recovers the public key from a hash and signature
|
||||
func RecoverPublicKeyFromHash(hash, sig []byte) (*PublicKey, error) {
|
||||
if len(sig) != SignatureLen {
|
||||
return nil, errInvalidSigLen
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
cacheKey := string(hash) + string(sig)
|
||||
if cached, found := RecoverCache.Get(cacheKey); found {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
pubBytes, err := RecoverPubkey(hash, sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
x, y := DecompressPubkey(pubBytes)
|
||||
if x == nil || y == nil {
|
||||
return nil, errors.New("invalid recovered public key")
|
||||
}
|
||||
|
||||
pub := &ecdsa.PublicKey{
|
||||
Curve: S256(),
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
|
||||
result := &PublicKey{
|
||||
pk: pub,
|
||||
bytes: pubBytes,
|
||||
}
|
||||
|
||||
RecoverCache.Put(cacheKey, result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler
|
||||
func (k *PrivateKey) MarshalText() ([]byte, error) {
|
||||
return []byte(k.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler
|
||||
func (k *PrivateKey) UnmarshalText(text []byte) error {
|
||||
str := string(text)
|
||||
if str == nullStr {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove quotes if present
|
||||
if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' {
|
||||
str = str[1 : len(str)-1]
|
||||
}
|
||||
|
||||
// Check and remove prefix
|
||||
if !strings.HasPrefix(str, PrivateKeyPrefix) {
|
||||
return fmt.Errorf("private key missing %s prefix", PrivateKeyPrefix)
|
||||
}
|
||||
str = str[len(PrivateKeyPrefix):]
|
||||
|
||||
// Decode from CB58
|
||||
bytes, err := cb58.Decode(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert to private key
|
||||
priv, err := ToPrivateKey(bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*k = *priv
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the string representation of the private key
|
||||
func (k *PrivateKey) String() string {
|
||||
if k == nil || k.sk == nil {
|
||||
return nullStr
|
||||
}
|
||||
encoded, _ := cb58.Encode(k.bytes)
|
||||
return PrivateKeyPrefix + encoded
|
||||
}
|
||||
|
||||
// String returns the string representation of the public key
|
||||
func (k *PublicKey) String() string {
|
||||
if k == nil || k.pk == nil {
|
||||
return nullStr
|
||||
}
|
||||
encoded, _ := cb58.Encode(k.bytes)
|
||||
return encoded
|
||||
}
|
||||
|
||||
// PaddedBigBytes encodes a big integer as a big-endian byte slice. The byte slice is padded with zeros.
|
||||
func PaddedBigBytes(bigint *big.Int, n int) []byte {
|
||||
if bigint.BitLen()/8 >= n {
|
||||
return bigint.Bytes()
|
||||
}
|
||||
ret := make([]byte, n)
|
||||
bigint.FillBytes(ret)
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package secp256k1
|
||||
|
||||
import (
|
||||
"github.com/luxfi/crypto/cache"
|
||||
)
|
||||
|
||||
// RecoverCacheType provides a cache for public key recovery with methods
|
||||
type RecoverCacheType struct {
|
||||
cache *cache.LRU[string, *PublicKey]
|
||||
}
|
||||
|
||||
// NewRecoverCache creates a new recover cache
|
||||
func NewRecoverCache(size int) RecoverCacheType {
|
||||
return RecoverCacheType{
|
||||
cache: cache.NewLRU[string, *PublicKey](size),
|
||||
}
|
||||
}
|
||||
|
||||
// RecoverPublicKey recovers a public key from a message and signature
|
||||
func (r RecoverCacheType) RecoverPublicKey(msg, sig []byte) (*PublicKey, error) {
|
||||
return RecoverPublicKey(msg, sig)
|
||||
}
|
||||
|
||||
// RecoverPublicKeyFromHash recovers a public key from a hash and signature
|
||||
func (r RecoverCacheType) RecoverPublicKeyFromHash(hash, sig []byte) (*PublicKey, error) {
|
||||
return RecoverPublicKeyFromHash(hash, sig)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package secp256k1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRecover(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
key, err := NewPrivateKey()
|
||||
require.NoError(err)
|
||||
|
||||
msg := []byte{1, 2, 3}
|
||||
sig, err := key.Sign(msg)
|
||||
require.NoError(err)
|
||||
|
||||
pub := key.PublicKey()
|
||||
pubRec, err := RecoverPublicKey(msg, sig[:])
|
||||
require.NoError(err)
|
||||
|
||||
require.Equal(pub.Bytes(), pubRec.Bytes())
|
||||
require.True(pub.Verify(msg, sig[:]))
|
||||
}
|
||||
|
||||
func TestPrivateKeySECP256K1Uncompressed(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
key, err := NewPrivateKey()
|
||||
require.NoError(err)
|
||||
|
||||
pubUncompressed := key.PublicKey()
|
||||
require.NotNil(pubUncompressed)
|
||||
}
|
||||
|
||||
func TestPrivateKeyMarshalText(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
key, err := NewPrivateKey()
|
||||
require.NoError(err)
|
||||
|
||||
text, err := key.MarshalText()
|
||||
require.NoError(err)
|
||||
|
||||
key2 := &PrivateKey{}
|
||||
err = key2.UnmarshalText(text)
|
||||
require.NoError(err)
|
||||
|
||||
require.Equal(key.Bytes(), key2.Bytes())
|
||||
}
|
||||
|
||||
func TestPublicKeyVerify(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
key, err := NewPrivateKey()
|
||||
require.NoError(err)
|
||||
|
||||
msg := []byte("hello world")
|
||||
sig, err := key.Sign(msg)
|
||||
require.NoError(err)
|
||||
|
||||
pub := key.PublicKey()
|
||||
require.True(pub.Verify(msg, sig[:]))
|
||||
|
||||
// Wrong message should fail
|
||||
require.False(pub.Verify([]byte("wrong message"), sig[:]))
|
||||
}
|
||||
|
||||
func TestInvalidSignatureLength(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
key, err := NewPrivateKey()
|
||||
require.NoError(err)
|
||||
|
||||
msg := []byte("test")
|
||||
pub := key.PublicKey()
|
||||
|
||||
// Too short signature
|
||||
shortSig := make([]byte, SignatureLen-1)
|
||||
require.False(pub.Verify(msg, shortSig))
|
||||
|
||||
// Too long signature
|
||||
longSig := make([]byte, SignatureLen+1)
|
||||
require.False(pub.Verify(msg, longSig))
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package secp256k1
|
||||
|
||||
import "github.com/luxfi/crypto/cb58"
|
||||
|
||||
// TestKeys returns a set of test keys for testing purposes
|
||||
func TestKeys() []*PrivateKey {
|
||||
var (
|
||||
keyStrings = []string{
|
||||
"24jUJ9vZexUM6expyMcT48LBx27k1m7xpraoV62oSQAHdziao5",
|
||||
"2MMvUMsxx6zsHSNXJdFD8yc5XkancvwyKPwpw4xUK3TCGDuNBY",
|
||||
"cxb7KpGWhDMALTjNNSJ7UQkkomPesyWAPUaWRGdyeBNzR6f35",
|
||||
"ewoqjP7PxY4yr3iLTpLisriqt94hdyDFNgchSxGGztUrTXtNN",
|
||||
"2RWLv6YVEXDiWLpaCbXhhqxtLbnFaKQsWPSSMSPhpWo47uJAeV",
|
||||
}
|
||||
keys = make([]*PrivateKey, len(keyStrings))
|
||||
)
|
||||
|
||||
for i, key := range keyStrings {
|
||||
privKeyBytes, err := cb58.Decode(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
keys[i], err = ToPrivateKey(privKeyBytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
Reference in New Issue
Block a user