mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2025-01-06 01:32:18 +00:00
59575bcac9
* fuzz core/blocks package * gaz goimports * fix test * terence feedback * terence feedback * add error to domain. halfway through * adding error to domain * goimports * added error handling to test * error instead of continue * terence and nishant feedback * domain error handling * domain error handling * handle nil validator in ReadOnlyValidator creation * goinmports * [4]byte domain type * [4]byte domain type * [4]byte domain type fix tests * fix tests Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> Co-authored-by: terence tsao <terence@prysmaticlabs.com>
314 lines
9.6 KiB
Go
314 lines
9.6 KiB
Go
// Package bls implements a go-wrapper around a library implementing the
|
|
// the BLS12-381 curve and signature scheme. This package exposes a public API for
|
|
// verifying and aggregating BLS signatures used by Ethereum 2.0.
|
|
package bls
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/dgraph-io/ristretto"
|
|
bls12 "github.com/herumi/bls-eth-go-binary/bls"
|
|
"github.com/pkg/errors"
|
|
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
|
"github.com/prysmaticlabs/prysm/shared/featureconfig"
|
|
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
|
)
|
|
|
|
func init() {
|
|
err := bls12.Init(bls12.BLS12_381)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
bls12.SetETHserialization(true)
|
|
}
|
|
|
|
// DomainByteLength length of domain byte array.
|
|
const DomainByteLength = 4
|
|
|
|
// ForkVersionByteLength length of fork version byte array.
|
|
const ForkVersionByteLength = 4
|
|
|
|
var maxKeys = int64(100000)
|
|
var pubkeyCache, _ = ristretto.NewCache(&ristretto.Config{
|
|
NumCounters: maxKeys,
|
|
MaxCost: 1 << 19, // 500 kb is cache max size
|
|
BufferItems: 64,
|
|
})
|
|
|
|
// CurveOrder for the BLS12-381 curve.
|
|
const CurveOrder = "52435875175126190479447740508185965837690552500527637822603658699938581184513"
|
|
|
|
// The size would be a combination of both the message(32 bytes) and domain(8 bytes) size.
|
|
const concatMsgDomainSize = 40
|
|
|
|
// Signature used in the BLS signature scheme.
|
|
type Signature struct {
|
|
s *bls12.Sign
|
|
}
|
|
|
|
// PublicKey used in the BLS signature scheme.
|
|
type PublicKey struct {
|
|
p *bls12.PublicKey
|
|
}
|
|
|
|
// SecretKey used in the BLS signature scheme.
|
|
type SecretKey struct {
|
|
p *bls12.SecretKey
|
|
}
|
|
|
|
// RandKey creates a new private key using a random method provided as an io.Reader.
|
|
func RandKey() *SecretKey {
|
|
secKey := &bls12.SecretKey{}
|
|
secKey.SetByCSPRNG()
|
|
return &SecretKey{secKey}
|
|
}
|
|
|
|
// SecretKeyFromBytes creates a BLS private key from a BigEndian byte slice.
|
|
func SecretKeyFromBytes(priv []byte) (*SecretKey, error) {
|
|
if len(priv) != params.BeaconConfig().BLSSecretKeyLength {
|
|
return nil, fmt.Errorf("secret key must be %d bytes", params.BeaconConfig().BLSSecretKeyLength)
|
|
}
|
|
secKey := &bls12.SecretKey{}
|
|
err := secKey.Deserialize(priv)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not unmarshal bytes into secret key")
|
|
}
|
|
return &SecretKey{p: secKey}, err
|
|
}
|
|
|
|
// PublicKeyFromBytes creates a BLS public key from a BigEndian byte slice.
|
|
func PublicKeyFromBytes(pub []byte) (*PublicKey, error) {
|
|
if featureconfig.Get().SkipBLSVerify {
|
|
return &PublicKey{}, nil
|
|
}
|
|
if len(pub) != params.BeaconConfig().BLSPubkeyLength {
|
|
return nil, fmt.Errorf("public key must be %d bytes", params.BeaconConfig().BLSPubkeyLength)
|
|
}
|
|
cv, ok := pubkeyCache.Get(string(pub))
|
|
if ok {
|
|
return cv.(*PublicKey).Copy()
|
|
}
|
|
pubKey := &bls12.PublicKey{}
|
|
err := pubKey.Deserialize(pub)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not unmarshal bytes into public key")
|
|
}
|
|
pubkeyObj := &PublicKey{p: pubKey}
|
|
copiedKey, err := pubkeyObj.Copy()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not copy pubkey")
|
|
}
|
|
pubkeyCache.Set(string(pub), copiedKey, 48)
|
|
return pubkeyObj, nil
|
|
}
|
|
|
|
// SignatureFromBytes creates a BLS signature from a LittleEndian byte slice.
|
|
func SignatureFromBytes(sig []byte) (*Signature, error) {
|
|
if featureconfig.Get().SkipBLSVerify {
|
|
return &Signature{}, nil
|
|
}
|
|
if len(sig) != params.BeaconConfig().BLSSignatureLength {
|
|
return nil, fmt.Errorf("signature must be %d bytes", params.BeaconConfig().BLSSignatureLength)
|
|
}
|
|
signature := &bls12.Sign{}
|
|
err := signature.Deserialize(sig)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not unmarshal bytes into signature")
|
|
}
|
|
return &Signature{s: signature}, nil
|
|
}
|
|
|
|
// PublicKey obtains the public key corresponding to the BLS secret key.
|
|
func (s *SecretKey) PublicKey() *PublicKey {
|
|
return &PublicKey{p: s.p.GetPublicKey()}
|
|
}
|
|
|
|
func concatMsgAndDomain(msg []byte, domain uint64) []byte {
|
|
b := [concatMsgDomainSize]byte{}
|
|
binary.LittleEndian.PutUint64(b[32:], domain)
|
|
copy(b[0:32], msg)
|
|
return b[:]
|
|
}
|
|
|
|
// Sign a message using a secret key - in a beacon/validator client.
|
|
func (s *SecretKey) Sign(msg []byte, domain uint64) *Signature {
|
|
if featureconfig.Get().SkipBLSVerify {
|
|
return &Signature{}
|
|
}
|
|
signature := s.p.SignHashWithDomain(concatMsgAndDomain(msg, domain))
|
|
return &Signature{s: signature}
|
|
}
|
|
|
|
// Marshal a secret key into a LittleEndian byte slice.
|
|
func (s *SecretKey) Marshal() []byte {
|
|
keyBytes := s.p.Serialize()
|
|
if len(keyBytes) < params.BeaconConfig().BLSSecretKeyLength {
|
|
emptyBytes := make([]byte, params.BeaconConfig().BLSSecretKeyLength-len(keyBytes))
|
|
keyBytes = append(emptyBytes, keyBytes...)
|
|
}
|
|
return keyBytes
|
|
}
|
|
|
|
// Marshal a public key into a LittleEndian byte slice.
|
|
func (p *PublicKey) Marshal() []byte {
|
|
rawBytes := p.p.Serialize()
|
|
return rawBytes
|
|
}
|
|
|
|
// Copy the public key to a new pointer reference.
|
|
func (p *PublicKey) Copy() (*PublicKey, error) {
|
|
np := *p.p
|
|
return &PublicKey{p: &np}, nil
|
|
}
|
|
|
|
// Aggregate two public keys.
|
|
func (p *PublicKey) Aggregate(p2 *PublicKey) *PublicKey {
|
|
if featureconfig.Get().SkipBLSVerify {
|
|
return p
|
|
}
|
|
p.p.Add(p2.p)
|
|
return p
|
|
}
|
|
|
|
// Verify a bls signature given a public key, a message, and a domain.
|
|
func (s *Signature) Verify(msg []byte, pub *PublicKey, domain uint64) bool {
|
|
if featureconfig.Get().SkipBLSVerify {
|
|
return true
|
|
}
|
|
return s.s.VerifyHashWithDomain(pub.p, concatMsgAndDomain(msg, domain))
|
|
}
|
|
|
|
// VerifyAggregate verifies each public key against its respective message.
|
|
// This is vulnerable to rogue public-key attack. Each user must
|
|
// provide a proof-of-knowledge of the public key.
|
|
func (s *Signature) VerifyAggregate(pubKeys []*PublicKey, msg [][32]byte, domain uint64) bool {
|
|
if featureconfig.Get().SkipBLSVerify {
|
|
return true
|
|
}
|
|
size := len(pubKeys)
|
|
if size == 0 {
|
|
return false
|
|
}
|
|
if size != len(msg) {
|
|
return false
|
|
}
|
|
b := [8]byte{}
|
|
binary.LittleEndian.PutUint64(b[:], domain)
|
|
hashWithDomains := make([]byte, 0, size*concatMsgDomainSize)
|
|
var rawKeys []bls12.PublicKey
|
|
for i := 0; i < size; i++ {
|
|
hashWithDomains = append(hashWithDomains, concatMsgAndDomain(msg[i][:], domain)...)
|
|
rawKeys = append(rawKeys, *pubKeys[i].p)
|
|
}
|
|
return s.s.VerifyAggregateHashWithDomain(rawKeys, hashWithDomains)
|
|
}
|
|
|
|
// VerifyAggregateCommon verifies each public key against its respective message.
|
|
// This is vulnerable to rogue public-key attack. Each user must
|
|
// provide a proof-of-knowledge of the public key.
|
|
func (s *Signature) VerifyAggregateCommon(pubKeys []*PublicKey, msg [32]byte, domain uint64) bool {
|
|
if featureconfig.Get().SkipBLSVerify {
|
|
return true
|
|
}
|
|
if len(pubKeys) == 0 {
|
|
return false
|
|
}
|
|
//#nosec G104
|
|
aggregated, _ := pubKeys[0].Copy()
|
|
|
|
for i := 1; i < len(pubKeys); i++ {
|
|
aggregated.p.Add(pubKeys[i].p)
|
|
}
|
|
|
|
return s.s.VerifyHashWithDomain(aggregated.p, concatMsgAndDomain(msg[:], domain))
|
|
}
|
|
|
|
// NewAggregateSignature creates a blank aggregate signature.
|
|
func NewAggregateSignature() *Signature {
|
|
return &Signature{s: bls12.HashAndMapToSignature([]byte{'m', 'o', 'c', 'k'})}
|
|
}
|
|
|
|
// NewAggregatePubkey creates a blank public key.
|
|
func NewAggregatePubkey() *PublicKey {
|
|
return &PublicKey{p: RandKey().PublicKey().p}
|
|
}
|
|
|
|
// AggregateSignatures converts a list of signatures into a single, aggregated sig.
|
|
func AggregateSignatures(sigs []*Signature) *Signature {
|
|
if len(sigs) == 0 {
|
|
return nil
|
|
}
|
|
if featureconfig.Get().SkipBLSVerify {
|
|
return sigs[0]
|
|
}
|
|
marshalled := sigs[0].s.Serialize()
|
|
signature := &bls12.Sign{}
|
|
//#nosec G104
|
|
signature.Deserialize(marshalled)
|
|
|
|
for i := 1; i < len(sigs); i++ {
|
|
signature.Add(sigs[i].s)
|
|
}
|
|
return &Signature{s: signature}
|
|
}
|
|
|
|
// Marshal a signature into a LittleEndian byte slice.
|
|
func (s *Signature) Marshal() []byte {
|
|
if featureconfig.Get().SkipBLSVerify {
|
|
return make([]byte, params.BeaconConfig().BLSSignatureLength)
|
|
}
|
|
|
|
rawBytes := s.s.Serialize()
|
|
return rawBytes
|
|
}
|
|
|
|
// Domain returns the bls domain given by the domain type and the operation 4 byte fork version.
|
|
//
|
|
// Spec pseudocode definition:
|
|
// def get_domain(state: BeaconState, domain_type: DomainType, message_epoch: Epoch=None) -> Domain:
|
|
// """
|
|
// Return the signature domain (fork version concatenated with domain type) of a message.
|
|
// """
|
|
// epoch = get_current_epoch(state) if message_epoch is None else message_epoch
|
|
// fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version
|
|
// return compute_domain(domain_type, fork_version)
|
|
func Domain(domainType [DomainByteLength]byte, forkVersion [ForkVersionByteLength]byte) uint64 {
|
|
|
|
b := []byte{}
|
|
b = append(b, domainType[:4]...)
|
|
b = append(b, forkVersion[:4]...)
|
|
return bytesutil.FromBytes8(b)
|
|
}
|
|
|
|
// ComputeDomain returns the domain version for BLS private key to sign and verify with a zeroed 4-byte
|
|
// array as the fork version.
|
|
//
|
|
// def compute_domain(domain_type: DomainType, fork_version: Version=Version()) -> Domain:
|
|
// """
|
|
// Return the domain for the ``domain_type`` and ``fork_version``.
|
|
// """
|
|
// return Domain(domain_type + fork_version)
|
|
func ComputeDomain(domainType [DomainByteLength]byte) uint64 {
|
|
return Domain(domainType, [4]byte{0, 0, 0, 0})
|
|
}
|
|
|
|
// HashWithDomain hashes 32 byte message and uint64 domain parameters a Fp2 element
|
|
func HashWithDomain(messageHash [32]byte, domain [8]byte) []byte {
|
|
xReBytes := [41]byte{}
|
|
xImBytes := [41]byte{}
|
|
xBytes := make([]byte, 96)
|
|
copy(xReBytes[:32], messageHash[:])
|
|
copy(xReBytes[32:40], domain[:])
|
|
copy(xReBytes[40:41], []byte{0x01})
|
|
copy(xImBytes[:32], messageHash[:])
|
|
copy(xImBytes[32:40], domain[:])
|
|
copy(xImBytes[40:41], []byte{0x02})
|
|
hashedxImBytes := hashutil.Hash(xImBytes[:])
|
|
copy(xBytes[16:48], hashedxImBytes[:])
|
|
hashedxReBytes := hashutil.Hash(xReBytes[:])
|
|
copy(xBytes[64:], hashedxReBytes[:])
|
|
return xBytes
|
|
}
|