prysm-pulse/shared/attestationutil/attestation_utils.go
Preston Van Loon fb8be4d555
Refactor BLS (#6395)
* refactor BLS to expose an interface rather than a single implementation
* Split up tests, gofmt, goimports, docs
* godoc for signature.Copy()
* more godocs
* revert gomod / deps changes
* rm newline
* revert proto changes
* rename bls12 to herumi for clarity
2020-06-25 00:47:51 +00:00

211 lines
7.8 KiB
Go

// Package attestationutil contains useful helpers for converting
// attestations into indexed form.
package attestationutil
import (
"bytes"
"context"
"fmt"
"reflect"
"sort"
"github.com/pkg/errors"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/shared/bls"
"github.com/prysmaticlabs/prysm/shared/params"
"go.opencensus.io/trace"
)
// ConvertToIndexed converts attestation to (almost) indexed-verifiable form.
//
// Note about spec pseudocode definition. The state was used by get_attesting_indices to determine
// the attestation committee. Now that we provide this as an argument, we no longer need to provide
// a state.
//
// Spec pseudocode definition:
// def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation:
// """
// Return the indexed attestation corresponding to ``attestation``.
// """
// attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
//
// return IndexedAttestation(
// attesting_indices=sorted(attesting_indices),
// data=attestation.data,
// signature=attestation.signature,
// )
func ConvertToIndexed(ctx context.Context, attestation *ethpb.Attestation, committee []uint64) *ethpb.IndexedAttestation {
ctx, span := trace.StartSpan(ctx, "attestationutil.ConvertToIndexed")
defer span.End()
attIndices := AttestingIndices(attestation.AggregationBits, committee)
sort.Slice(attIndices, func(i, j int) bool {
return attIndices[i] < attIndices[j]
})
inAtt := &ethpb.IndexedAttestation{
Data: attestation.Data,
Signature: attestation.Signature,
AttestingIndices: attIndices,
}
return inAtt
}
// AttestingIndices returns the attesting participants indices from the attestation data. The
// committee is provided as an argument rather than a direct implementation from the spec definition.
// Having the committee as an argument allows for re-use of beacon committees when possible.
//
// Spec pseudocode definition:
// def get_attesting_indices(state: BeaconState,
// data: AttestationData,
// bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]:
// """
// Return the set of attesting indices corresponding to ``data`` and ``bits``.
// """
// committee = get_beacon_committee(state, data.slot, data.index)
// return set(index for i, index in enumerate(committee) if bits[i])
func AttestingIndices(bf bitfield.Bitfield, committee []uint64) []uint64 {
indices := make([]uint64, 0, len(committee))
for _, idx := range bf.BitIndices() {
if idx < len(committee) {
indices = append(indices, committee[idx])
}
}
return indices
}
// VerifyIndexedAttestationSig this helper function performs the last part of the
// spec indexed attestation validation starting at Verify aggregate signature
// comment.
//
// Spec pseudocode definition:
// def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool:
// """
// Check if ``indexed_attestation`` has sorted and unique indices and a valid aggregate signature.
// """
// # Verify indices are sorted and unique
// indices = indexed_attestation.attesting_indices
// if not indices == sorted(set(indices)):
// return False
// # Verify aggregate signature
// pubkeys = [state.validators[i].pubkey for i in indices]
// domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch)
// signing_root = compute_signing_root(indexed_attestation.data, domain)
// return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature)
func VerifyIndexedAttestationSig(ctx context.Context, indexedAtt *ethpb.IndexedAttestation, pubKeys []bls.PublicKey, domain []byte) error {
ctx, span := trace.StartSpan(ctx, "attestationutil.VerifyIndexedAttestationSig")
defer span.End()
indices := indexedAtt.AttestingIndices
messageHash, err := helpers.ComputeSigningRoot(indexedAtt.Data, domain)
if err != nil {
return errors.Wrap(err, "could not get signing root of object")
}
sig, err := bls.SignatureFromBytes(indexedAtt.Signature)
if err != nil {
return errors.Wrap(err, "could not convert bytes to signature")
}
voted := len(indices) > 0
if voted && !sig.FastAggregateVerify(pubKeys, messageHash) {
return helpers.ErrSigFailedToVerify
}
return nil
}
// IsValidAttestationIndices this helper function performs the first part of the
// spec indexed attestation validation starting at Check if ``indexed_attestation``
// comment and ends at Verify aggregate signature comment.
//
// Spec pseudocode definition:
// def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool:
// """
// Check if ``indexed_attestation``is not empty, has valid indices, and signature.
// """
// indices = indexed_attestation.attesting_indices
//
// # Verify max number of indices
// if not len(indices) <= MAX_VALIDATORS_PER_COMMITTEE:
// return False
// # Verify indices are sorted and unique
// if not indices == sorted(set(indices)):
// # Verify aggregate signature
// if not bls_verify(
// pubkey=bls_aggregate_pubkeys([state.validators[i].pubkey for i in indices]),
// message_hash=hash_tree_root(indexed_attestation.data),
// signature=indexed_attestation.signature,
// domain=get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch),
// ):
// return False
// return True
func IsValidAttestationIndices(ctx context.Context, indexedAttestation *ethpb.IndexedAttestation) error {
ctx, span := trace.StartSpan(ctx, "attestationutil.IsValidAttestationIndices")
defer span.End()
if indexedAttestation == nil || indexedAttestation.Data == nil || indexedAttestation.Data.Target == nil {
return errors.New("nil or missing indexed attestation data")
}
indices := indexedAttestation.AttestingIndices
if len(indices) == 0 {
return errors.New("expected non-empty attesting indices")
}
if uint64(len(indices)) > params.BeaconConfig().MaxValidatorsPerCommittee {
return fmt.Errorf("validator indices count exceeds MAX_VALIDATORS_PER_COMMITTEE, %d > %d", len(indices), params.BeaconConfig().MaxValidatorsPerCommittee)
}
set := make(map[uint64]bool)
setIndices := make([]uint64, 0, len(indices))
for _, i := range indices {
if ok := set[i]; ok {
continue
}
setIndices = append(setIndices, i)
set[i] = true
}
sort.SliceStable(setIndices, func(i, j int) bool {
return setIndices[i] < setIndices[j]
})
if !reflect.DeepEqual(setIndices, indices) {
return errors.New("attesting indices is not uniquely sorted")
}
return nil
}
// AttDataIsEqual this function performs an equality check between 2 attestation data, if they're unequal, it will return false.
func AttDataIsEqual(attData1 *ethpb.AttestationData, attData2 *ethpb.AttestationData) bool {
if attData1.Slot != attData2.Slot {
return false
}
if attData1.CommitteeIndex != attData2.CommitteeIndex {
return false
}
if !bytes.Equal(attData1.BeaconBlockRoot, attData2.BeaconBlockRoot) {
return false
}
if attData1.Source.Epoch != attData2.Source.Epoch {
return false
}
if !bytes.Equal(attData1.Source.Root, attData2.Source.Root) {
return false
}
if attData1.Target.Epoch != attData2.Target.Epoch {
return false
}
if !bytes.Equal(attData1.Target.Root, attData2.Target.Root) {
return false
}
return true
}
// CheckPointIsEqual performs an equality check between 2 check points, returns false if unequal.
func CheckPointIsEqual(checkPt1 *ethpb.Checkpoint, checkPt2 *ethpb.Checkpoint) bool {
if checkPt1.Epoch != checkPt2.Epoch {
return false
}
if !bytes.Equal(checkPt1.Root, checkPt2.Root) {
return false
}
return true
}