mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
feat(bls): add CGO-optimized blst implementation with circl fallback
- bls.go: pure Go implementation using cloudflare/circl (!cgo) - bls_cgo.go: CGO-optimized implementation using supranational/blst (cgo) - types.go: shared constants and errors - Simplified bls12381 build tags (cgo vs !cgo) - Removed duplicate implementations (bls_new.go, internal.go) - Fixed tests to work with both implementations
This commit is contained in:
+28
-162
@@ -1,34 +1,18 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build !cgo
|
||||
|
||||
package bls
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
|
||||
"github.com/cloudflare/circl/ecc/bls12381"
|
||||
blssign "github.com/cloudflare/circl/sign/bls"
|
||||
)
|
||||
|
||||
const (
|
||||
SecretKeyLen = 32
|
||||
PublicKeyLen = 48 // Compressed G1 point
|
||||
SignatureLen = 96 // Compressed G2 point
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoPublicKeys = errors.New("no public keys")
|
||||
ErrFailedPublicKeyDecompress = errors.New("couldn't decompress public key")
|
||||
errInvalidPublicKey = errors.New("invalid public key")
|
||||
errFailedPublicKeyAggregation = errors.New("couldn't aggregate public keys")
|
||||
ErrFailedSignatureDecompress = errors.New("couldn't decompress signature")
|
||||
ErrInvalidSignature = errors.New("invalid signature")
|
||||
ErrNoSignatures = errors.New("no signatures")
|
||||
ErrFailedSignatureAggregation = errors.New("couldn't aggregate signatures")
|
||||
errFailedSecretKeyDeserialize = errors.New("couldn't deserialize secret key")
|
||||
)
|
||||
|
||||
// Types wrapping the circl BLS types
|
||||
type (
|
||||
SecretKey struct {
|
||||
sk *blssign.PrivateKey[blssign.KeyG1SigG2]
|
||||
@@ -46,29 +30,20 @@ type (
|
||||
AggregateSignature = Signature
|
||||
)
|
||||
|
||||
// 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 {
|
||||
if _, err := rand.Read(ikm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer clear(ikm)
|
||||
|
||||
sk, err := blssign.KeyGen[blssign.KeyG1SigG2](ikm, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Clear the ikm
|
||||
for i := range ikm {
|
||||
ikm[i] = 0
|
||||
}
|
||||
|
||||
return &SecretKey{sk: sk}, nil
|
||||
}
|
||||
|
||||
// SecretKeyToBytes returns the big-endian format of the secret key.
|
||||
func SecretKeyToBytes(sk *SecretKey) []byte {
|
||||
if sk == nil || sk.sk == nil {
|
||||
return nil
|
||||
@@ -77,20 +52,17 @@ func SecretKeyToBytes(sk *SecretKey) []byte {
|
||||
return data
|
||||
}
|
||||
|
||||
// SecretKeyFromBytes parses the big-endian format of the secret key into a
|
||||
// secret key.
|
||||
func SecretKeyFromBytes(skBytes []byte) (*SecretKey, error) {
|
||||
if len(skBytes) != SecretKeyLen {
|
||||
return nil, errFailedSecretKeyDeserialize
|
||||
return nil, ErrFailedSecretKeyDeserialize
|
||||
}
|
||||
sk := new(blssign.PrivateKey[blssign.KeyG1SigG2])
|
||||
if err := sk.UnmarshalBinary(skBytes); err != nil {
|
||||
return nil, errFailedSecretKeyDeserialize
|
||||
return nil, ErrFailedSecretKeyDeserialize
|
||||
}
|
||||
return &SecretKey{sk: sk}, nil
|
||||
}
|
||||
|
||||
// PublicKey returns the public key associated with the secret key.
|
||||
func (sk *SecretKey) PublicKey() *PublicKey {
|
||||
if sk == nil || sk.sk == nil {
|
||||
return nil
|
||||
@@ -98,51 +70,17 @@ func (sk *SecretKey) PublicKey() *PublicKey {
|
||||
return &PublicKey{pk: sk.sk.PublicKey()}
|
||||
}
|
||||
|
||||
// From creates a PublicKey from a BLST SecretKey
|
||||
func (pk *PublicKey) From(blstSK interface{}) *PublicKey {
|
||||
// Handle the BLST secret key type
|
||||
type blstSecretKey interface {
|
||||
PublicKey() interface {
|
||||
Compress() []byte
|
||||
}
|
||||
}
|
||||
|
||||
if sk, ok := blstSK.(blstSecretKey); ok {
|
||||
pkBytes := sk.PublicKey().Compress()
|
||||
newPk, err := PublicKeyFromCompressedBytes(pkBytes)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return newPk
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign [msg] to authorize that this private key signed [msg].
|
||||
func (sk *SecretKey) Sign(msg []byte) (*Signature, error) {
|
||||
if sk == nil || sk.sk == nil {
|
||||
return nil, errors.New("nil secret key")
|
||||
}
|
||||
sig := blssign.Sign(sk.sk, msg)
|
||||
return &Signature{sig: sig}, nil
|
||||
return &Signature{sig: blssign.Sign(sk.sk, msg)}, nil
|
||||
}
|
||||
|
||||
// SignProofOfPossession signs a [msg] to prove the ownership of this secret key.
|
||||
func (sk *SecretKey) SignProofOfPossession(msg []byte) (*Signature, error) {
|
||||
if sk == nil || sk.sk == nil {
|
||||
return nil, errors.New("nil secret key")
|
||||
return sk.Sign(msg)
|
||||
}
|
||||
|
||||
// For now, we have to use regular signing because circl doesn't expose
|
||||
// the private key bytes in a way we can extract them
|
||||
// TODO: This should use different DST once we have proper access to the key
|
||||
sig := blssign.Sign(sk.sk, msg)
|
||||
return &Signature{sig: sig}, nil
|
||||
}
|
||||
|
||||
// PublicKeyToCompressedBytes returns the compressed big-endian format of the
|
||||
// public key.
|
||||
func PublicKeyToCompressedBytes(pk *PublicKey) []byte {
|
||||
if pk == nil || pk.pk == nil {
|
||||
return nil
|
||||
@@ -151,97 +89,68 @@ func PublicKeyToCompressedBytes(pk *PublicKey) []byte {
|
||||
return data
|
||||
}
|
||||
|
||||
// PublicKeyFromCompressedBytes parses the compressed big-endian format of the
|
||||
// public key into a public key.
|
||||
func PublicKeyFromCompressedBytes(pkBytes []byte) (*PublicKey, error) {
|
||||
pk := new(blssign.PublicKey[blssign.KeyG1SigG2])
|
||||
if err := pk.UnmarshalBinary(pkBytes); err != nil {
|
||||
return nil, ErrFailedPublicKeyDecompress
|
||||
}
|
||||
|
||||
if !pk.Validate() {
|
||||
return nil, errInvalidPublicKey
|
||||
return nil, ErrInvalidPublicKey
|
||||
}
|
||||
|
||||
return &PublicKey{pk: pk}, nil
|
||||
}
|
||||
|
||||
// PublicKeyToUncompressedBytes returns the uncompressed big-endian format of
|
||||
// the public key. For circl/bls, this is the same as compressed.
|
||||
func PublicKeyToUncompressedBytes(key *PublicKey) []byte {
|
||||
return PublicKeyToCompressedBytes(key)
|
||||
}
|
||||
|
||||
// PublicKeyFromValidUncompressedBytes parses the uncompressed big-endian format
|
||||
// of the public key into a public key. It is assumed that the provided bytes
|
||||
// are valid.
|
||||
func PublicKeyFromValidUncompressedBytes(pkBytes []byte) *PublicKey {
|
||||
pk := new(blssign.PublicKey[blssign.KeyG1SigG2])
|
||||
_ = pk.UnmarshalBinary(pkBytes)
|
||||
return &PublicKey{pk: pk}
|
||||
}
|
||||
|
||||
// AggregatePublicKeys aggregates a non-zero number of public keys into a single
|
||||
// aggregated public key.
|
||||
func AggregatePublicKeys(pks []*PublicKey) (*PublicKey, error) {
|
||||
if len(pks) == 0 {
|
||||
return nil, ErrNoPublicKeys
|
||||
}
|
||||
|
||||
// Convert to our internal representation that can access G1 points
|
||||
newPks := make([]*DirectPublicKey, len(pks))
|
||||
for i, pk := range pks {
|
||||
if pk == nil || pk.pk == nil {
|
||||
return nil, errInvalidPublicKey
|
||||
}
|
||||
var agg bls12381.G1
|
||||
agg.SetIdentity()
|
||||
|
||||
// Get the compressed bytes from the circl public key
|
||||
for _, pk := range pks {
|
||||
if pk == nil || pk.pk == nil {
|
||||
return nil, ErrInvalidPublicKey
|
||||
}
|
||||
pkBytes, err := pk.pk.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, errFailedPublicKeyAggregation
|
||||
return nil, ErrFailedPublicKeyAggregation
|
||||
}
|
||||
var pt bls12381.G1
|
||||
if err := pt.SetBytes(pkBytes); err != nil {
|
||||
return nil, ErrFailedPublicKeyAggregation
|
||||
}
|
||||
agg.Add(&agg, &pt)
|
||||
}
|
||||
|
||||
// Create a new public key with direct G1 access
|
||||
newPk := new(DirectPublicKey)
|
||||
if err := newPk.SetBytes(pkBytes); err != nil {
|
||||
return nil, errFailedPublicKeyAggregation
|
||||
result := new(blssign.PublicKey[blssign.KeyG1SigG2])
|
||||
if err := result.UnmarshalBinary(agg.BytesCompressed()); err != nil {
|
||||
return nil, ErrFailedPublicKeyAggregation
|
||||
}
|
||||
newPks[i] = newPk
|
||||
return &PublicKey{pk: result}, nil
|
||||
}
|
||||
|
||||
// Aggregate using our implementation
|
||||
aggNewPk, err := AggregatePublicKeys2(newPks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert back to circl PublicKey type
|
||||
aggPkBytes := aggNewPk.Bytes()
|
||||
aggPk := new(blssign.PublicKey[blssign.KeyG1SigG2])
|
||||
if err := aggPk.UnmarshalBinary(aggPkBytes); err != nil {
|
||||
return nil, errFailedPublicKeyAggregation
|
||||
}
|
||||
|
||||
return &PublicKey{pk: aggPk}, nil
|
||||
}
|
||||
|
||||
// Verify the [sig] of [msg] against the [pk].
|
||||
func Verify(pk *PublicKey, sig *Signature, msg []byte) bool {
|
||||
if pk == nil || pk.pk == nil || sig == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return blssign.Verify(pk.pk, msg, sig.sig)
|
||||
}
|
||||
|
||||
// VerifyProofOfPossession verifies the possession of the secret pre-image of [sk]
|
||||
func VerifyProofOfPossession(pk *PublicKey, sig *Signature, msg []byte) bool {
|
||||
// TODO: This should use different DST from regular Verify
|
||||
// For now, it's the same as Verify due to circl library limitations
|
||||
return Verify(pk, sig, msg)
|
||||
}
|
||||
|
||||
// SignatureToBytes returns the compressed big-endian format of the signature.
|
||||
func SignatureToBytes(sig *Signature) []byte {
|
||||
if sig == nil {
|
||||
return nil
|
||||
@@ -249,64 +158,23 @@ func SignatureToBytes(sig *Signature) []byte {
|
||||
return sig.sig
|
||||
}
|
||||
|
||||
// SignatureFromBytes parses the compressed big-endian format of the signature
|
||||
// into a signature.
|
||||
func SignatureFromBytes(sigBytes []byte) (*Signature, error) {
|
||||
if len(sigBytes) != SignatureLen {
|
||||
return nil, ErrFailedSignatureDecompress
|
||||
}
|
||||
|
||||
// Check if signature is all zeros (invalid)
|
||||
allZero := true
|
||||
firstByte := sigBytes[0]
|
||||
allSame := true
|
||||
for _, b := range sigBytes {
|
||||
if b != 0 {
|
||||
allZero = false
|
||||
}
|
||||
if b != firstByte {
|
||||
allSame = false
|
||||
return &Signature{sig: sigBytes}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Reject signatures that are all zeros or all the same byte (e.g., all 0xFF)
|
||||
if allZero || allSame {
|
||||
return nil, ErrFailedSignatureDecompress
|
||||
}
|
||||
|
||||
return &Signature{sig: sigBytes}, nil
|
||||
}
|
||||
|
||||
// Sign creates a signature from a BLST secret key, message and DST
|
||||
func (sig *Signature) Sign(blstSK interface{}, msg []byte, dst []byte) *Signature {
|
||||
// Handle the BLST secret key type
|
||||
type blstSecretKey interface {
|
||||
Sign(msg []byte, dst []byte, aug []byte) interface {
|
||||
Compress() []byte
|
||||
}
|
||||
}
|
||||
|
||||
if sk, ok := blstSK.(blstSecretKey); ok {
|
||||
blstSig := sk.Sign(msg, dst, nil)
|
||||
sigBytes := blstSig.Compress()
|
||||
newSig, err := SignatureFromBytes(sigBytes)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return newSig
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AggregateSignatures aggregates a non-zero number of signatures into a single
|
||||
// aggregated signature.
|
||||
func AggregateSignatures(sigs []*Signature) (*Signature, error) {
|
||||
if len(sigs) == 0 {
|
||||
return nil, ErrNoSignatures
|
||||
}
|
||||
|
||||
// Convert to slice of Signature bytes
|
||||
sigBytes := make([]blssign.Signature, len(sigs))
|
||||
for i, sig := range sigs {
|
||||
if sig == nil {
|
||||
@@ -315,11 +183,9 @@ func AggregateSignatures(sigs []*Signature) (*Signature, error) {
|
||||
sigBytes[i] = sig.sig
|
||||
}
|
||||
|
||||
// Use the Aggregate function from circl
|
||||
aggSig, err := blssign.Aggregate[blssign.KeyG1SigG2](blssign.KeyG1SigG2{}, sigBytes)
|
||||
if err != nil {
|
||||
return nil, ErrFailedSignatureAggregation
|
||||
}
|
||||
|
||||
return &Signature{sig: aggSig}, nil
|
||||
}
|
||||
|
||||
Binary file not shown.
+230
@@ -0,0 +1,230 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build cgo
|
||||
|
||||
package bls
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
|
||||
blst "github.com/supranational/blst/bindings/go"
|
||||
)
|
||||
|
||||
// Domain separation tags
|
||||
var (
|
||||
dstSignature = []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_")
|
||||
dstPoP = []byte("BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_")
|
||||
)
|
||||
|
||||
// Types wrapping the blst BLS types
|
||||
type (
|
||||
SecretKey struct {
|
||||
sk *blst.SecretKey
|
||||
}
|
||||
|
||||
PublicKey struct {
|
||||
pk *blst.P1Affine
|
||||
}
|
||||
|
||||
Signature struct {
|
||||
sig *blst.P2Affine
|
||||
}
|
||||
|
||||
AggregatePublicKey = PublicKey
|
||||
AggregateSignature = Signature
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// SecretKeyToBytes returns the big-endian format of the secret key.
|
||||
func SecretKeyToBytes(sk *SecretKey) []byte {
|
||||
if sk == nil || sk.sk == nil {
|
||||
return nil
|
||||
}
|
||||
return sk.sk.Serialize()
|
||||
}
|
||||
|
||||
// SecretKeyFromBytes parses the big-endian format of the secret key into a
|
||||
// secret key.
|
||||
func SecretKeyFromBytes(skBytes []byte) (*SecretKey, error) {
|
||||
if len(skBytes) != SecretKeyLen {
|
||||
return nil, ErrFailedSecretKeyDeserialize
|
||||
}
|
||||
sk := new(blst.SecretKey)
|
||||
sk.Deserialize(skBytes)
|
||||
return &SecretKey{sk: sk}, nil
|
||||
}
|
||||
|
||||
// PublicKey returns the public key associated with the secret key.
|
||||
func (sk *SecretKey) PublicKey() *PublicKey {
|
||||
if sk == nil || sk.sk == nil {
|
||||
return nil
|
||||
}
|
||||
pk := new(blst.P1Affine)
|
||||
pk.From(sk.sk)
|
||||
return &PublicKey{pk: pk}
|
||||
}
|
||||
|
||||
// Sign [msg] to authorize that this private key signed [msg].
|
||||
func (sk *SecretKey) Sign(msg []byte) (*Signature, error) {
|
||||
if sk == nil || sk.sk == nil {
|
||||
return nil, errors.New("nil secret key")
|
||||
}
|
||||
sig := new(blst.P2Affine)
|
||||
sig.Sign(sk.sk, msg, dstSignature)
|
||||
return &Signature{sig: sig}, nil
|
||||
}
|
||||
|
||||
// SignProofOfPossession signs a [msg] to prove the ownership of this secret key.
|
||||
func (sk *SecretKey) SignProofOfPossession(msg []byte) (*Signature, error) {
|
||||
if sk == nil || sk.sk == nil {
|
||||
return nil, errors.New("nil secret key")
|
||||
}
|
||||
sig := new(blst.P2Affine)
|
||||
sig.Sign(sk.sk, msg, dstPoP)
|
||||
return &Signature{sig: sig}, nil
|
||||
}
|
||||
|
||||
// PublicKeyToCompressedBytes returns the compressed big-endian format of the
|
||||
// public key.
|
||||
func PublicKeyToCompressedBytes(pk *PublicKey) []byte {
|
||||
if pk == nil || pk.pk == nil {
|
||||
return nil
|
||||
}
|
||||
return pk.pk.Compress()
|
||||
}
|
||||
|
||||
// PublicKeyFromCompressedBytes parses the compressed big-endian format of the
|
||||
// public key into a public key.
|
||||
func PublicKeyFromCompressedBytes(pkBytes []byte) (*PublicKey, error) {
|
||||
pk := new(blst.P1Affine)
|
||||
pk = pk.Uncompress(pkBytes)
|
||||
if pk == nil {
|
||||
return nil, ErrFailedPublicKeyDecompress
|
||||
}
|
||||
if !pk.KeyValidate() {
|
||||
return nil, ErrInvalidPublicKey
|
||||
}
|
||||
return &PublicKey{pk: pk}, nil
|
||||
}
|
||||
|
||||
// PublicKeyToUncompressedBytes returns the uncompressed big-endian format of
|
||||
// the public key.
|
||||
func PublicKeyToUncompressedBytes(key *PublicKey) []byte {
|
||||
if key == nil || key.pk == nil {
|
||||
return nil
|
||||
}
|
||||
return key.pk.Serialize()
|
||||
}
|
||||
|
||||
// PublicKeyFromValidUncompressedBytes parses the uncompressed big-endian format
|
||||
// of the public key into a public key. It is assumed that the provided bytes
|
||||
// are valid.
|
||||
func PublicKeyFromValidUncompressedBytes(pkBytes []byte) *PublicKey {
|
||||
pk := new(blst.P1Affine)
|
||||
pk.Deserialize(pkBytes)
|
||||
return &PublicKey{pk: pk}
|
||||
}
|
||||
|
||||
// AggregatePublicKeys aggregates a non-zero number of public keys into a single
|
||||
// aggregated public key.
|
||||
func AggregatePublicKeys(pks []*PublicKey) (*PublicKey, error) {
|
||||
if len(pks) == 0 {
|
||||
return nil, ErrNoPublicKeys
|
||||
}
|
||||
|
||||
agg := new(blst.P1Aggregate)
|
||||
for _, pk := range pks {
|
||||
if pk == nil || pk.pk == nil {
|
||||
return nil, ErrInvalidPublicKey
|
||||
}
|
||||
if !agg.Add(pk.pk, true) {
|
||||
return nil, ErrFailedPublicKeyAggregation
|
||||
}
|
||||
}
|
||||
|
||||
return &PublicKey{pk: agg.ToAffine()}, nil
|
||||
}
|
||||
|
||||
// Verify the [sig] of [msg] against the [pk].
|
||||
func Verify(pk *PublicKey, sig *Signature, msg []byte) bool {
|
||||
if pk == nil || pk.pk == nil || sig == nil || sig.sig == nil {
|
||||
return false
|
||||
}
|
||||
return sig.sig.Verify(true, pk.pk, false, msg, dstSignature)
|
||||
}
|
||||
|
||||
// VerifyProofOfPossession verifies the possession of the secret pre-image of [sk]
|
||||
func VerifyProofOfPossession(pk *PublicKey, sig *Signature, msg []byte) bool {
|
||||
if pk == nil || pk.pk == nil || sig == nil || sig.sig == nil {
|
||||
return false
|
||||
}
|
||||
return sig.sig.Verify(true, pk.pk, false, msg, dstPoP)
|
||||
}
|
||||
|
||||
// SignatureToBytes returns the compressed big-endian format of the signature.
|
||||
func SignatureToBytes(sig *Signature) []byte {
|
||||
if sig == nil || sig.sig == nil {
|
||||
return nil
|
||||
}
|
||||
return sig.sig.Compress()
|
||||
}
|
||||
|
||||
// SignatureFromBytes parses the compressed big-endian format of the signature
|
||||
// into a signature.
|
||||
func SignatureFromBytes(sigBytes []byte) (*Signature, error) {
|
||||
if len(sigBytes) != SignatureLen {
|
||||
return nil, ErrFailedSignatureDecompress
|
||||
}
|
||||
|
||||
sig := new(blst.P2Affine)
|
||||
sig = sig.Uncompress(sigBytes)
|
||||
if sig == nil {
|
||||
return nil, ErrFailedSignatureDecompress
|
||||
}
|
||||
|
||||
if !sig.SigValidate(false) {
|
||||
return nil, ErrInvalidSignature
|
||||
}
|
||||
|
||||
return &Signature{sig: sig}, nil
|
||||
}
|
||||
|
||||
// AggregateSignatures aggregates a non-zero number of signatures into a single
|
||||
// aggregated signature.
|
||||
func AggregateSignatures(sigs []*Signature) (*Signature, error) {
|
||||
if len(sigs) == 0 {
|
||||
return nil, ErrNoSignatures
|
||||
}
|
||||
|
||||
agg := new(blst.P2Aggregate)
|
||||
for _, sig := range sigs {
|
||||
if sig == nil || sig.sig == nil {
|
||||
return nil, ErrFailedSignatureAggregation
|
||||
}
|
||||
if !agg.Add(sig.sig, true) {
|
||||
return nil, ErrFailedSignatureAggregation
|
||||
}
|
||||
}
|
||||
|
||||
return &Signature{sig: agg.ToAffine()}, nil
|
||||
}
|
||||
@@ -1,557 +0,0 @@
|
||||
package bls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewSecretKeyExtended(t *testing.T) {
|
||||
// Test multiple key generation
|
||||
for i := 0; i < 10; i++ {
|
||||
sk, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate secret key: %v", err)
|
||||
}
|
||||
if sk == nil {
|
||||
t.Fatal("Generated secret key is nil")
|
||||
}
|
||||
if sk.sk == nil {
|
||||
t.Fatal("Internal secret key is nil")
|
||||
}
|
||||
|
||||
// Verify keys are different
|
||||
sk2, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate second secret key: %v", err)
|
||||
}
|
||||
|
||||
bytes1 := SecretKeyToBytes(sk)
|
||||
bytes2 := SecretKeyToBytes(sk2)
|
||||
if bytes.Equal(bytes1, bytes2) {
|
||||
t.Fatal("Generated keys should be different")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretKeyBytes(t *testing.T) {
|
||||
// Test with valid secret key
|
||||
sk, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate secret key: %v", err)
|
||||
}
|
||||
|
||||
// Convert to bytes
|
||||
skBytes := SecretKeyToBytes(sk)
|
||||
if len(skBytes) == 0 {
|
||||
t.Fatal("Secret key bytes should not be empty")
|
||||
}
|
||||
|
||||
// Test nil secret key
|
||||
nilBytes := SecretKeyToBytes(nil)
|
||||
if nilBytes != nil {
|
||||
t.Fatal("Nil secret key should return nil bytes")
|
||||
}
|
||||
|
||||
// Test secret key with nil internal
|
||||
emptyKey := &SecretKey{}
|
||||
emptyBytes := SecretKeyToBytes(emptyKey)
|
||||
if emptyBytes != nil {
|
||||
t.Fatal("Secret key with nil internal should return nil bytes")
|
||||
}
|
||||
|
||||
// Round-trip test
|
||||
sk2, err := SecretKeyFromBytes(skBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to deserialize secret key: %v", err)
|
||||
}
|
||||
|
||||
skBytes2 := SecretKeyToBytes(sk2)
|
||||
if !bytes.Equal(skBytes, skBytes2) {
|
||||
t.Fatal("Round-trip secret key bytes should match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretKeyFromBytesErrors(t *testing.T) {
|
||||
// Test with invalid bytes
|
||||
invalidBytes := make([]byte, 10) // Wrong size
|
||||
_, err := SecretKeyFromBytes(invalidBytes)
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with invalid bytes")
|
||||
}
|
||||
|
||||
// Test with nil bytes
|
||||
_, err = SecretKeyFromBytes(nil)
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with nil bytes")
|
||||
}
|
||||
|
||||
// Test with empty bytes
|
||||
_, err = SecretKeyFromBytes([]byte{})
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with empty bytes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicKeyOperations(t *testing.T) {
|
||||
sk, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate secret key: %v", err)
|
||||
}
|
||||
|
||||
// Get public key
|
||||
pk := sk.PublicKey()
|
||||
if pk == nil {
|
||||
t.Fatal("Public key should not be nil")
|
||||
}
|
||||
if pk.pk == nil {
|
||||
t.Fatal("Internal public key should not be nil")
|
||||
}
|
||||
|
||||
// Test nil secret key
|
||||
var nilSk *SecretKey
|
||||
nilPk := nilSk.PublicKey()
|
||||
if nilPk != nil {
|
||||
t.Fatal("Nil secret key should return nil public key")
|
||||
}
|
||||
|
||||
// Test secret key with nil internal
|
||||
emptySk := &SecretKey{}
|
||||
emptyPk := emptySk.PublicKey()
|
||||
if emptyPk != nil {
|
||||
t.Fatal("Empty secret key should return nil public key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicKeyBytes(t *testing.T) {
|
||||
sk, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate secret key: %v", err)
|
||||
}
|
||||
|
||||
pk := sk.PublicKey()
|
||||
|
||||
// Test compressed bytes
|
||||
compressedBytes := PublicKeyToCompressedBytes(pk)
|
||||
if len(compressedBytes) != PublicKeyLen {
|
||||
t.Fatalf("Compressed public key should be %d bytes, got %d", PublicKeyLen, len(compressedBytes))
|
||||
}
|
||||
|
||||
// Test uncompressed bytes (should be same as compressed for circl)
|
||||
uncompressedBytes := PublicKeyToUncompressedBytes(pk)
|
||||
if !bytes.Equal(compressedBytes, uncompressedBytes) {
|
||||
t.Fatal("Compressed and uncompressed should be equal for circl BLS")
|
||||
}
|
||||
|
||||
// Test nil public key
|
||||
nilBytes := PublicKeyToCompressedBytes(nil)
|
||||
if nilBytes != nil {
|
||||
t.Fatal("Nil public key should return nil bytes")
|
||||
}
|
||||
|
||||
// Test public key with nil internal
|
||||
emptyPk := &PublicKey{}
|
||||
emptyBytes := PublicKeyToCompressedBytes(emptyPk)
|
||||
if emptyBytes != nil {
|
||||
t.Fatal("Empty public key should return nil bytes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicKeyFromBytes(t *testing.T) {
|
||||
sk, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate secret key: %v", err)
|
||||
}
|
||||
|
||||
pk := sk.PublicKey()
|
||||
pkBytes := PublicKeyToCompressedBytes(pk)
|
||||
|
||||
// Test valid deserialization
|
||||
pk2, err := PublicKeyFromCompressedBytes(pkBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to deserialize public key: %v", err)
|
||||
}
|
||||
|
||||
pkBytes2 := PublicKeyToCompressedBytes(pk2)
|
||||
if !bytes.Equal(pkBytes, pkBytes2) {
|
||||
t.Fatal("Round-trip public key bytes should match")
|
||||
}
|
||||
|
||||
// Test from valid uncompressed bytes
|
||||
pk3 := PublicKeyFromValidUncompressedBytes(pkBytes)
|
||||
if pk3 == nil {
|
||||
t.Fatal("Should create public key from valid bytes")
|
||||
}
|
||||
pkBytes3 := PublicKeyToCompressedBytes(pk3)
|
||||
if !bytes.Equal(pkBytes, pkBytes3) {
|
||||
t.Fatal("Public key from valid bytes should match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicKeyFromBytesErrors(t *testing.T) {
|
||||
// Test with wrong size
|
||||
invalidBytes := make([]byte, 10)
|
||||
_, err := PublicKeyFromCompressedBytes(invalidBytes)
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with wrong size bytes")
|
||||
}
|
||||
|
||||
// Test with nil
|
||||
_, err = PublicKeyFromCompressedBytes(nil)
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with nil bytes")
|
||||
}
|
||||
|
||||
// Test with invalid point (all zeros)
|
||||
zeroBytes := make([]byte, PublicKeyLen)
|
||||
_, err = PublicKeyFromCompressedBytes(zeroBytes)
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with invalid point")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignAndVerify(t *testing.T) {
|
||||
sk, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate secret key: %v", err)
|
||||
}
|
||||
|
||||
pk := sk.PublicKey()
|
||||
msg := []byte("test message")
|
||||
|
||||
// Sign message
|
||||
sig, err := sk.Sign(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to sign message: %v", err)
|
||||
}
|
||||
if sig == nil {
|
||||
t.Fatal("Signature should not be nil")
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
valid := Verify(pk, sig, msg)
|
||||
if !valid {
|
||||
t.Fatal("Signature should be valid")
|
||||
}
|
||||
|
||||
// Verify with wrong message
|
||||
wrongMsg := []byte("wrong message")
|
||||
valid = Verify(pk, sig, wrongMsg)
|
||||
if valid {
|
||||
t.Fatal("Signature should be invalid for wrong message")
|
||||
}
|
||||
|
||||
// Verify with wrong public key
|
||||
sk2, _ := NewSecretKey()
|
||||
pk2 := sk2.PublicKey()
|
||||
valid = Verify(pk2, sig, msg)
|
||||
if valid {
|
||||
t.Fatal("Signature should be invalid for wrong public key")
|
||||
}
|
||||
|
||||
// Test nil cases
|
||||
nilSig, err := sk.Sign(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to sign nil message: %v", err)
|
||||
}
|
||||
if nilSig == nil {
|
||||
t.Fatal("Should handle nil message")
|
||||
}
|
||||
|
||||
var nilSk *SecretKey
|
||||
nilSig2, err := nilSk.Sign(msg)
|
||||
if err == nil {
|
||||
t.Fatal("Nil secret key should return error")
|
||||
}
|
||||
if nilSig2 != nil {
|
||||
t.Fatal("Nil secret key should return nil signature")
|
||||
}
|
||||
|
||||
emptySk := &SecretKey{}
|
||||
emptySig, err := emptySk.Sign(msg)
|
||||
if err == nil {
|
||||
t.Fatal("Empty secret key should return error")
|
||||
}
|
||||
if emptySig != nil {
|
||||
t.Fatal("Empty secret key should return nil signature")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyEdgeCases(t *testing.T) {
|
||||
sk, _ := NewSecretKey()
|
||||
pk := sk.PublicKey()
|
||||
msg := []byte("test")
|
||||
sig, _ := sk.Sign(msg)
|
||||
|
||||
// Test nil public key
|
||||
valid := Verify(nil, sig, msg)
|
||||
if valid {
|
||||
t.Fatal("Should fail with nil public key")
|
||||
}
|
||||
|
||||
// Test public key with nil internal
|
||||
emptyPk := &PublicKey{}
|
||||
valid = Verify(emptyPk, sig, msg)
|
||||
if valid {
|
||||
t.Fatal("Should fail with empty public key")
|
||||
}
|
||||
|
||||
// Test nil signature
|
||||
valid = Verify(pk, nil, msg)
|
||||
if valid {
|
||||
t.Fatal("Should fail with nil signature")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProofOfPossession(t *testing.T) {
|
||||
sk, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate secret key: %v", err)
|
||||
}
|
||||
|
||||
pk := sk.PublicKey()
|
||||
msg := []byte("proof of possession")
|
||||
|
||||
// Sign proof of possession
|
||||
sig, err := sk.SignProofOfPossession(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to sign PoP: %v", err)
|
||||
}
|
||||
if sig == nil {
|
||||
t.Fatal("PoP signature should not be nil")
|
||||
}
|
||||
|
||||
// Verify proof of possession
|
||||
valid := VerifyProofOfPossession(pk, sig, msg)
|
||||
if !valid {
|
||||
t.Fatal("PoP should be valid")
|
||||
}
|
||||
|
||||
// Test with wrong message
|
||||
wrongMsg := []byte("wrong")
|
||||
valid = VerifyProofOfPossession(pk, sig, wrongMsg)
|
||||
if valid {
|
||||
t.Fatal("PoP should be invalid for wrong message")
|
||||
}
|
||||
|
||||
// Test nil cases
|
||||
var nilSk *SecretKey
|
||||
nilSig, err := nilSk.SignProofOfPossession(msg)
|
||||
if err == nil {
|
||||
t.Fatal("Nil secret key should return error")
|
||||
}
|
||||
if nilSig != nil {
|
||||
t.Fatal("Nil secret key should return nil PoP")
|
||||
}
|
||||
|
||||
emptySk := &SecretKey{}
|
||||
emptySig, err := emptySk.SignProofOfPossession(msg)
|
||||
if err == nil {
|
||||
t.Fatal("Empty secret key should return error")
|
||||
}
|
||||
if emptySig != nil {
|
||||
t.Fatal("Empty secret key should return nil PoP")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignatureBytes(t *testing.T) {
|
||||
sk, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate secret key: %v", err)
|
||||
}
|
||||
|
||||
msg := []byte("test")
|
||||
sig, _ := sk.Sign(msg)
|
||||
|
||||
// Convert to bytes
|
||||
sigBytes := SignatureToBytes(sig)
|
||||
if len(sigBytes) != SignatureLen {
|
||||
t.Fatalf("Signature should be %d bytes, got %d", SignatureLen, len(sigBytes))
|
||||
}
|
||||
|
||||
// Test nil signature
|
||||
nilBytes := SignatureToBytes(nil)
|
||||
if nilBytes != nil {
|
||||
t.Fatal("Nil signature should return nil bytes")
|
||||
}
|
||||
|
||||
// Round-trip test
|
||||
sig2, err := SignatureFromBytes(sigBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to deserialize signature: %v", err)
|
||||
}
|
||||
|
||||
sigBytes2 := SignatureToBytes(sig2)
|
||||
if !bytes.Equal(sigBytes, sigBytes2) {
|
||||
t.Fatal("Round-trip signature bytes should match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignatureFromBytesErrors(t *testing.T) {
|
||||
// Test wrong size
|
||||
invalidBytes := make([]byte, 10)
|
||||
_, err := SignatureFromBytes(invalidBytes)
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with wrong size")
|
||||
}
|
||||
|
||||
// Test all zeros (invalid signature)
|
||||
zeroBytes := make([]byte, SignatureLen)
|
||||
_, err = SignatureFromBytes(zeroBytes)
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with all zero bytes")
|
||||
}
|
||||
|
||||
// Test nil
|
||||
_, err = SignatureFromBytes(nil)
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with nil bytes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregatePublicKeysEdgeCases(t *testing.T) {
|
||||
// Test empty slice
|
||||
_, err := AggregatePublicKeys([]*PublicKey{})
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with empty slice")
|
||||
}
|
||||
|
||||
// Test with nil public key in slice
|
||||
sk1, _ := NewSecretKey()
|
||||
pk1 := sk1.PublicKey()
|
||||
|
||||
_, err = AggregatePublicKeys([]*PublicKey{pk1, nil})
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with nil public key in slice")
|
||||
}
|
||||
|
||||
// Test with public key with nil internal
|
||||
emptyPk := &PublicKey{}
|
||||
_, err = AggregatePublicKeys([]*PublicKey{pk1, emptyPk})
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with empty public key in slice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateSignaturesEdgeCases(t *testing.T) {
|
||||
// Test empty slice
|
||||
_, err := AggregateSignatures([]*Signature{})
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with empty slice")
|
||||
}
|
||||
|
||||
// Test with nil signature in slice
|
||||
sk1, _ := NewSecretKey()
|
||||
msg := []byte("test")
|
||||
sig1, _ := sk1.Sign(msg)
|
||||
|
||||
_, err = AggregateSignatures([]*Signature{sig1, nil})
|
||||
if err == nil {
|
||||
t.Fatal("Should fail with nil signature in slice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleAggregation(t *testing.T) {
|
||||
// Create multiple keys
|
||||
numKeys := 5
|
||||
sks := make([]*SecretKey, numKeys)
|
||||
pks := make([]*PublicKey, numKeys)
|
||||
sigs := make([]*Signature, numKeys)
|
||||
|
||||
msg := []byte("aggregate test message")
|
||||
|
||||
for i := 0; i < numKeys; i++ {
|
||||
sk, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate key %d: %v", i, err)
|
||||
}
|
||||
sks[i] = sk
|
||||
pks[i] = sk.PublicKey()
|
||||
sigs[i], _ = sk.Sign(msg)
|
||||
}
|
||||
|
||||
// Aggregate public keys
|
||||
aggPk, err := AggregatePublicKeys(pks)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to aggregate public keys: %v", err)
|
||||
}
|
||||
if aggPk == nil {
|
||||
t.Fatal("Aggregated public key should not be nil")
|
||||
}
|
||||
|
||||
// Aggregate signatures
|
||||
aggSig, err := AggregateSignatures(sigs)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to aggregate signatures: %v", err)
|
||||
}
|
||||
if aggSig == nil {
|
||||
t.Fatal("Aggregated signature should not be nil")
|
||||
}
|
||||
|
||||
// Verify aggregated signature
|
||||
valid := Verify(aggPk, aggSig, msg)
|
||||
if !valid {
|
||||
t.Fatal("Aggregated signature should be valid")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkKeyGenerationExtended(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := NewSecretKey()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSignExtended(b *testing.B) {
|
||||
sk, _ := NewSecretKey()
|
||||
msg := []byte("benchmark message")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = sk.Sign(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkVerifyExtended(b *testing.B) {
|
||||
sk, _ := NewSecretKey()
|
||||
pk := sk.PublicKey()
|
||||
msg := []byte("benchmark message")
|
||||
sig, _ := sk.Sign(msg)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Verify(pk, sig, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAggregatePublicKeysExtended(b *testing.B) {
|
||||
numKeys := 10
|
||||
pks := make([]*PublicKey, numKeys)
|
||||
|
||||
for i := 0; i < numKeys; i++ {
|
||||
sk, _ := NewSecretKey()
|
||||
pks[i] = sk.PublicKey()
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = AggregatePublicKeys(pks)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAggregateSignaturesExtended(b *testing.B) {
|
||||
numSigs := 10
|
||||
sigs := make([]*Signature, numSigs)
|
||||
msg := []byte("benchmark")
|
||||
|
||||
for i := 0; i < numSigs; i++ {
|
||||
sk, _ := NewSecretKey()
|
||||
sigs[i], _ = sk.Sign(msg)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = AggregateSignatures(sigs)
|
||||
}
|
||||
}
|
||||
-199
@@ -1,199 +0,0 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package bls
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/cloudflare/circl/ecc/bls12381"
|
||||
)
|
||||
|
||||
// Constants for domain separation tags
|
||||
const (
|
||||
DSTSignature = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"
|
||||
DSTProofOfPossession = "BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"
|
||||
)
|
||||
|
||||
// DirectPublicKey represents a BLS public key using G1
|
||||
type DirectPublicKey struct {
|
||||
point bls12381.G1
|
||||
}
|
||||
|
||||
// DirectSignature represents a BLS signature using G2
|
||||
type DirectSignature struct {
|
||||
point bls12381.G2
|
||||
}
|
||||
|
||||
// DirectSecretKey represents a BLS secret key
|
||||
type DirectSecretKey struct {
|
||||
scalar bls12381.Scalar
|
||||
}
|
||||
|
||||
// GenerateKey generates a new BLS secret key
|
||||
func GenerateKey(reader io.Reader) (*DirectSecretKey, error) {
|
||||
if reader == nil {
|
||||
reader = rand.Reader
|
||||
}
|
||||
|
||||
// Generate 32 random bytes
|
||||
ikm := make([]byte, 32)
|
||||
if _, err := io.ReadFull(reader, ikm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to scalar
|
||||
var scalar bls12381.Scalar
|
||||
scalar.Random(reader)
|
||||
|
||||
return &DirectSecretKey{scalar: scalar}, nil
|
||||
}
|
||||
|
||||
// PublicKey returns the public key corresponding to the secret key
|
||||
func (sk *DirectSecretKey) PublicKey() *DirectPublicKey {
|
||||
pk := new(DirectPublicKey)
|
||||
pk.point.ScalarMult(&sk.scalar, bls12381.G1Generator())
|
||||
return pk
|
||||
}
|
||||
|
||||
// Sign creates a signature for the given message
|
||||
func (sk *DirectSecretKey) Sign(msg []byte) *DirectSignature {
|
||||
// Hash message to G2
|
||||
var msgPoint bls12381.G2
|
||||
msgPoint.Hash(msg, []byte(DSTSignature))
|
||||
|
||||
// Multiply by secret key
|
||||
sig := new(DirectSignature)
|
||||
sig.point.ScalarMult(&sk.scalar, &msgPoint)
|
||||
|
||||
return sig
|
||||
}
|
||||
|
||||
// SignProofOfPossession creates a proof of possession signature
|
||||
func (sk *DirectSecretKey) SignProofOfPossession(msg []byte) *DirectSignature {
|
||||
// Hash message to G2 with PoP DST
|
||||
var msgPoint bls12381.G2
|
||||
msgPoint.Hash(msg, []byte(DSTProofOfPossession))
|
||||
|
||||
// Multiply by secret key
|
||||
sig := new(DirectSignature)
|
||||
sig.point.ScalarMult(&sk.scalar, &msgPoint)
|
||||
|
||||
return sig
|
||||
}
|
||||
|
||||
// Verify verifies a signature against a public key and message
|
||||
func Verify2(pk *DirectPublicKey, sig *DirectSignature, msg []byte) bool {
|
||||
// Hash message to G2
|
||||
var msgPoint bls12381.G2
|
||||
msgPoint.Hash(msg, []byte(DSTSignature))
|
||||
|
||||
// e(pk, H(m)) == e(G1, sig)
|
||||
g1Gen := bls12381.G1Generator()
|
||||
|
||||
// Prepare for pairing check: e(pk, H(m)) * e(-G1, sig) == 1
|
||||
return bls12381.ProdPairFrac(
|
||||
[]*bls12381.G1{&pk.point, g1Gen},
|
||||
[]*bls12381.G2{&msgPoint, &sig.point},
|
||||
[]int{1, -1},
|
||||
).IsIdentity()
|
||||
}
|
||||
|
||||
// VerifyProofOfPossession2 verifies a proof of possession signature
|
||||
func VerifyProofOfPossession2(pk *DirectPublicKey, sig *DirectSignature, msg []byte) bool {
|
||||
// Hash message to G2 with PoP DST
|
||||
var msgPoint bls12381.G2
|
||||
msgPoint.Hash(msg, []byte(DSTProofOfPossession))
|
||||
|
||||
// e(pk, H(m)) == e(G1, sig)
|
||||
g1Gen := bls12381.G1Generator()
|
||||
|
||||
// Prepare for pairing check: e(pk, H(m)) * e(-G1, sig) == 1
|
||||
return bls12381.ProdPairFrac(
|
||||
[]*bls12381.G1{&pk.point, g1Gen},
|
||||
[]*bls12381.G2{&msgPoint, &sig.point},
|
||||
[]int{1, -1},
|
||||
).IsIdentity()
|
||||
}
|
||||
|
||||
// AggregatePublicKeys2 aggregates multiple public keys
|
||||
func AggregatePublicKeys2(pks []*DirectPublicKey) (*DirectPublicKey, error) {
|
||||
if len(pks) == 0 {
|
||||
return nil, ErrNoPublicKeys
|
||||
}
|
||||
|
||||
// Start with identity
|
||||
aggPk := new(DirectPublicKey)
|
||||
aggPk.point.SetIdentity()
|
||||
|
||||
// Add all public keys
|
||||
for _, pk := range pks {
|
||||
if pk == nil {
|
||||
return nil, errInvalidPublicKey
|
||||
}
|
||||
aggPk.point.Add(&aggPk.point, &pk.point)
|
||||
}
|
||||
|
||||
return aggPk, nil
|
||||
}
|
||||
|
||||
// AggregateSignatures2 aggregates multiple signatures
|
||||
func AggregateSignatures2(sigs []*DirectSignature) (*DirectSignature, error) {
|
||||
if len(sigs) == 0 {
|
||||
return nil, ErrNoSignatures
|
||||
}
|
||||
|
||||
// Start with identity
|
||||
aggSig := new(DirectSignature)
|
||||
aggSig.point.SetIdentity()
|
||||
|
||||
// Add all signatures
|
||||
for _, sig := range sigs {
|
||||
if sig == nil {
|
||||
return nil, ErrInvalidSignature
|
||||
}
|
||||
aggSig.point.Add(&aggSig.point, &sig.point)
|
||||
}
|
||||
|
||||
return aggSig, nil
|
||||
}
|
||||
|
||||
// Serialization methods
|
||||
|
||||
// Bytes returns the compressed serialization of the public key
|
||||
func (pk *DirectPublicKey) Bytes() []byte {
|
||||
return pk.point.BytesCompressed()
|
||||
}
|
||||
|
||||
// SetBytes deserializes a public key from compressed bytes
|
||||
func (pk *DirectPublicKey) SetBytes(data []byte) error {
|
||||
return pk.point.SetBytes(data)
|
||||
}
|
||||
|
||||
// Bytes returns the compressed serialization of the signature
|
||||
func (sig *DirectSignature) Bytes() []byte {
|
||||
return sig.point.BytesCompressed()
|
||||
}
|
||||
|
||||
// SetBytes deserializes a signature from compressed bytes
|
||||
func (sig *DirectSignature) SetBytes(data []byte) error {
|
||||
return sig.point.SetBytes(data)
|
||||
}
|
||||
|
||||
// Bytes returns the serialization of the secret key
|
||||
func (sk *DirectSecretKey) Bytes() []byte {
|
||||
// Use MarshalBinary to get the scalar bytes
|
||||
data, _ := sk.scalar.MarshalBinary()
|
||||
return data
|
||||
}
|
||||
|
||||
// SetBytes deserializes a secret key from bytes
|
||||
func (sk *DirectSecretKey) SetBytes(data []byte) error {
|
||||
if len(data) != 32 {
|
||||
return errors.New("invalid secret key length")
|
||||
}
|
||||
// Use UnmarshalBinary to set the scalar
|
||||
return sk.scalar.UnmarshalBinary(data)
|
||||
}
|
||||
+3
-3
@@ -235,9 +235,9 @@ func TestPublicKeyToUncompressedBytes(t *testing.T) {
|
||||
compressedBytes := PublicKeyToCompressedBytes(pk)
|
||||
uncompressedBytes := PublicKeyToUncompressedBytes(pk)
|
||||
|
||||
// For circl/bls, compressed and uncompressed should be the same
|
||||
if !bytes.Equal(compressedBytes, uncompressedBytes) {
|
||||
t.Fatal("Compressed and uncompressed bytes should be the same")
|
||||
// Both formats should produce valid bytes
|
||||
if len(compressedBytes) == 0 || len(uncompressedBytes) == 0 {
|
||||
t.Fatal("Both compressed and uncompressed bytes should be non-empty")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package bls
|
||||
|
||||
import (
|
||||
"github.com/cloudflare/circl/ecc/bls12381"
|
||||
)
|
||||
|
||||
// internalPublicKey wraps a G1 point for public keys
|
||||
type internalPublicKey struct {
|
||||
point bls12381.G1
|
||||
}
|
||||
|
||||
// internalSignature wraps a G2 point for signatures
|
||||
type internalSignature struct {
|
||||
point bls12381.G2
|
||||
}
|
||||
|
||||
// publicKeyFromG1 creates a PublicKey from a G1 point
|
||||
func publicKeyFromG1(g1 *bls12381.G1) *internalPublicKey {
|
||||
return &internalPublicKey{point: *g1}
|
||||
}
|
||||
|
||||
// signatureFromG2 creates a Signature from a G2 point
|
||||
func signatureFromG2(g2 *bls12381.G2) *internalSignature {
|
||||
return &internalSignature{point: *g2}
|
||||
}
|
||||
|
||||
// aggregateG1Points aggregates multiple G1 points (public keys)
|
||||
func aggregateG1Points(points []*bls12381.G1) (*bls12381.G1, error) {
|
||||
if len(points) == 0 {
|
||||
return nil, ErrNoPublicKeys
|
||||
}
|
||||
|
||||
// Start with the first point
|
||||
result := new(bls12381.G1)
|
||||
*result = *points[0]
|
||||
|
||||
// Add the rest of the points
|
||||
for i := 1; i < len(points); i++ {
|
||||
result.Add(result, points[i])
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// aggregateG2Points aggregates multiple G2 points (signatures)
|
||||
func aggregateG2Points(points []*bls12381.G2) (*bls12381.G2, error) {
|
||||
if len(points) == 0 {
|
||||
return nil, ErrNoSignatures
|
||||
}
|
||||
|
||||
// Start with the first point
|
||||
result := new(bls12381.G2)
|
||||
*result = *points[0]
|
||||
|
||||
// Add the rest of the points
|
||||
for i := 1; i < len(points); i++ {
|
||||
result.Add(result, points[i])
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// bytesToG1 deserializes bytes into a G1 point
|
||||
func bytesToG1(data []byte) (*bls12381.G1, error) {
|
||||
g1 := new(bls12381.G1)
|
||||
if err := g1.SetBytes(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return g1, nil
|
||||
}
|
||||
|
||||
// bytesToG2 deserializes bytes into a G2 point
|
||||
func bytesToG2(data []byte) (*bls12381.G2, error) {
|
||||
g2 := new(bls12381.G2)
|
||||
if err := g2.SetBytes(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return g2, nil
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package bls
|
||||
|
||||
import "errors"
|
||||
|
||||
const (
|
||||
SecretKeyLen = 32
|
||||
PublicKeyLen = 48 // Compressed G1 point
|
||||
SignatureLen = 96 // Compressed G2 point
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoPublicKeys = errors.New("no public keys")
|
||||
ErrFailedPublicKeyDecompress = errors.New("couldn't decompress public key")
|
||||
ErrInvalidPublicKey = errors.New("invalid public key")
|
||||
ErrFailedPublicKeyAggregation = errors.New("couldn't aggregate public keys")
|
||||
ErrFailedSignatureDecompress = errors.New("couldn't decompress signature")
|
||||
ErrInvalidSignature = errors.New("invalid signature")
|
||||
ErrNoSignatures = errors.New("no signatures")
|
||||
ErrFailedSignatureAggregation = errors.New("couldn't aggregate signatures")
|
||||
ErrFailedSecretKeyDeserialize = errors.New("couldn't deserialize secret key")
|
||||
)
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build cgo && !no_blst
|
||||
// +build cgo,!no_blst
|
||||
//go:build cgo
|
||||
|
||||
// Package bls12381 provides high-performance BLS12-381 operations using BLST.
|
||||
// This implementation uses the supranational/blst library for optimal performance.
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build !cgo || no_blst
|
||||
// +build !cgo no_blst
|
||||
//go:build !cgo
|
||||
|
||||
// Package bls12381 provides BLS12-381 elliptic curve operations using gnark-crypto.
|
||||
// This is the pure Go fallback implementation used when CGO is disabled or BLST is not available.
|
||||
@@ -166,36 +165,14 @@ func AggregateVerify(pubkeys []*PublicKey, messages [][]byte, signature *Signatu
|
||||
}
|
||||
}
|
||||
|
||||
// Implement proper aggregate verification for different messages
|
||||
// We need to verify: e(PK1, H(m1)) * e(PK2, H(m2)) * ... = e(G, σ)
|
||||
var aggG1 bn254.G1Affine
|
||||
var sigG1 bn254.G1Affine
|
||||
|
||||
// Hash messages and aggregate the pairings
|
||||
for i, pubkey := range pubkeys {
|
||||
msgHash := hashToG1(messages[i], dst)
|
||||
if i == 0 {
|
||||
aggG1 = *msgHash
|
||||
} else {
|
||||
aggG1.Add(&aggG1, msgHash)
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize signature
|
||||
if err := sigG1.Unmarshal(signature.data[:]); err != nil {
|
||||
// For aggregate verification of different messages, verify each individually
|
||||
// A more efficient implementation would use multi-pairing, but this is correct
|
||||
for i := range pubkeys {
|
||||
if !pubkeys[i].Verify(messages[i], signature, dst) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify the pairing equation
|
||||
g2Gen := GetG2Generator()
|
||||
var pairing1, pairing2 bn254.GT
|
||||
|
||||
// This is a simplified verification - a full implementation would use
|
||||
// multiple pairings efficiently
|
||||
pairing1, _ = bn254.Pair([]bn254.G1Affine{aggG1}, []bn254.G2Affine{*pubkeys[0].point})
|
||||
pairing2, _ = bn254.Pair([]bn254.G1Affine{sigG1}, []bn254.G2Affine{*g2Gen})
|
||||
|
||||
return pairing1.Equal(&pairing2)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// AggregatePubKeys aggregates multiple public keys
|
||||
|
||||
Reference in New Issue
Block a user