diff --git a/beacon-chain/core/blocks/block_operations.go b/beacon-chain/core/blocks/block_operations.go index e11573a844..43e91b499f 100644 --- a/beacon-chain/core/blocks/block_operations.go +++ b/beacon-chain/core/blocks/block_operations.go @@ -97,21 +97,25 @@ func verifyDepositDataWithDomain(ctx context.Context, deps []*ethpb.Deposit, dom } msgs[i] = ctrRoot } - as := bls.AggregateSignatures(sigs) - if !as.AggregateVerify(pks, msgs) { - return errors.New("one or more deposit data signatures did not verify") + verify, err := bls.VerifyMultipleSignatures(sigs, msgs, pks) + if err != nil { + return errors.Errorf("could not verify multiple signatures: %v", err) + } + if !verify { + return errors.New("one or more deposit signatures did not verify") } return nil } -func verifySignature(signedData []byte, pub []byte, signature []byte, domain []byte) error { +// retrieves the signature set from the raw data, public key,signature and domain provided. +func retrieveSignatureSet(signedData []byte, pub []byte, signature []byte, domain []byte) (*bls.SignatureSet, error) { publicKey, err := bls.PublicKeyFromBytes(pub) if err != nil { - return errors.Wrap(err, "could not convert bytes to public key") + return nil, errors.Wrap(err, "could not convert bytes to public key") } sig, err := bls.SignatureFromBytes(signature) if err != nil { - return errors.Wrap(err, "could not convert bytes to signature") + return nil, errors.Wrap(err, "could not convert bytes to signature") } signingData := &pb.SigningData{ ObjectRoot: signedData, @@ -119,8 +123,28 @@ func verifySignature(signedData []byte, pub []byte, signature []byte, domain []b } root, err := ssz.HashTreeRoot(signingData) if err != nil { - return errors.Wrap(err, "could not hash container") + return nil, errors.Wrap(err, "could not hash container") } + return &bls.SignatureSet{ + Signatures: []bls.Signature{sig}, + PublicKeys: []bls.PublicKey{publicKey}, + Messages: [][32]byte{root}, + }, nil +} + +// verifies the signature from the raw data, public key and domain provided. +func verifySignature(signedData []byte, pub []byte, signature []byte, domain []byte) error { + set, err := retrieveSignatureSet(signedData, pub, signature, domain) + if err != nil { + return err + } + if len(set.Signatures) != 1 { + return errors.Errorf("signature set contains %d signatures instead of 1", len(set.Signatures)) + } + // We assume only one signature set is returned here. + sig := set.Signatures[0] + publicKey := set.PublicKeys[0] + root := set.Messages[0] if !sig.Verify(publicKey, root[:]) { return helpers.ErrSigFailedToVerify } @@ -248,6 +272,22 @@ func VerifyBlockSignature(beaconState *stateTrie.BeaconState, block *ethpb.Signe return helpers.VerifyBlockSigningRoot(block.Block, proposerPubKey[:], block.Signature, domain) } +// BlockSignatureSet retrieves the block signature set from the provided block and its corresponding state. +func BlockSignatureSet(beaconState *stateTrie.BeaconState, block *ethpb.SignedBeaconBlock) (*bls.SignatureSet, error) { + proposer, err := beaconState.ValidatorAtIndex(block.Block.ProposerIndex) + if err != nil { + return nil, err + } + + currentEpoch := helpers.SlotToEpoch(beaconState.Slot()) + domain, err := helpers.Domain(beaconState.Fork(), currentEpoch, params.BeaconConfig().DomainBeaconProposer, beaconState.GenesisValidatorRoot()) + if err != nil { + return nil, err + } + proposerPubKey := proposer.PublicKey + return helpers.RetrieveBlockSignatureSet(block.Block, proposerPubKey, block.Signature, domain) +} + // ProcessBlockHeaderNoVerify validates a block by its header but skips proposer // signature verification. // @@ -347,17 +387,7 @@ func ProcessRandao( beaconState *stateTrie.BeaconState, body *ethpb.BeaconBlockBody, ) (*stateTrie.BeaconState, error) { - proposerIdx, err := helpers.BeaconProposerIndex(beaconState) - if err != nil { - return nil, errors.Wrap(err, "could not get beacon proposer index") - } - proposerPub := beaconState.PubkeyAtIndex(proposerIdx) - - currentEpoch := helpers.SlotToEpoch(beaconState.Slot()) - buf := make([]byte, 32) - binary.LittleEndian.PutUint64(buf, currentEpoch) - - domain, err := helpers.Domain(beaconState.Fork(), currentEpoch, params.BeaconConfig().DomainRandao, beaconState.GenesisValidatorRoot()) + buf, proposerPub, domain, err := randaoSigningData(beaconState) if err != nil { return nil, err } @@ -372,6 +402,41 @@ func ProcessRandao( return beaconState, nil } +// RandaoSignatureSet retrieves the relevant randao specific signature set object +// from a block and its corresponding state. +func RandaoSignatureSet(beaconState *stateTrie.BeaconState, + body *ethpb.BeaconBlockBody, +) (*bls.SignatureSet, *stateTrie.BeaconState, error) { + buf, proposerPub, domain, err := randaoSigningData(beaconState) + if err != nil { + return nil, nil, err + } + set, err := retrieveSignatureSet(buf, proposerPub[:], body.RandaoReveal, domain) + if err != nil { + return nil, nil, err + } + return set, beaconState, nil +} + +// retrieves the randao related signing data from the state. +func randaoSigningData(beaconState *stateTrie.BeaconState) ([]byte, []byte, []byte, error) { + proposerIdx, err := helpers.BeaconProposerIndex(beaconState) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "could not get beacon proposer index") + } + proposerPub := beaconState.PubkeyAtIndex(proposerIdx) + + currentEpoch := helpers.SlotToEpoch(beaconState.Slot()) + buf := make([]byte, 32) + binary.LittleEndian.PutUint64(buf, currentEpoch) + + domain, err := helpers.Domain(beaconState.Fork(), currentEpoch, params.BeaconConfig().DomainRandao, beaconState.GenesisValidatorRoot()) + if err != nil { + return nil, nil, nil, err + } + return buf, proposerPub[:], domain, nil +} + // ProcessRandaoNoVerify generates a new randao mix to update // in the beacon state's latest randao mixes slice. // @@ -887,12 +952,84 @@ func VerifyAttestations(ctx context.Context, beaconState *stateTrie.BeaconState, return verifyAttestationsWithDomain(ctx, beaconState, postForkAtts, currDomain) } +// RetrieveAttestationSignatureSet retrieves all the related attestation signature data such as the relevant public keys, +// signatures and attestation signing data and collate it into a signature set object. +func RetrieveAttestationSignatureSet(ctx context.Context, beaconState *stateTrie.BeaconState, atts []*ethpb.Attestation) (*bls.SignatureSet, error) { + if len(atts) == 0 { + return bls.NewSet(), nil + } + + fork := beaconState.Fork() + gvr := beaconState.GenesisValidatorRoot() + dt := params.BeaconConfig().DomainBeaconAttester + + // Split attestations by fork. Note: the signature domain will differ based on the fork. + var preForkAtts []*ethpb.Attestation + var postForkAtts []*ethpb.Attestation + for _, a := range atts { + if helpers.SlotToEpoch(a.Data.Slot) < fork.Epoch { + preForkAtts = append(preForkAtts, a) + } else { + postForkAtts = append(postForkAtts, a) + } + } + set := bls.NewSet() + + // Check attestations from before the fork. + if fork.Epoch > 0 { // Check to prevent underflow. + prevDomain, err := helpers.Domain(fork, fork.Epoch-1, dt, gvr) + if err != nil { + return nil, err + } + aSet, err := createAttestationSignatureSet(ctx, beaconState, preForkAtts, prevDomain) + if err != nil { + return nil, err + } + set.Join(aSet) + } else if len(preForkAtts) > 0 { + // This is a sanity check that preForkAtts were not ignored when fork.Epoch == 0. This + // condition is not possible, but it doesn't hurt to check anyway. + return nil, errors.New("some attestations were not verified from previous fork before genesis") + } + + // Then check attestations from after the fork. + currDomain, err := helpers.Domain(fork, fork.Epoch, dt, gvr) + if err != nil { + return nil, err + } + + aSet, err := createAttestationSignatureSet(ctx, beaconState, postForkAtts, currDomain) + if err != nil { + return nil, err + } + return set.Join(aSet), nil +} + // Inner method to verify attestations. This abstraction allows for the domain to be provided as an // argument. func verifyAttestationsWithDomain(ctx context.Context, beaconState *stateTrie.BeaconState, atts []*ethpb.Attestation, domain []byte) error { if len(atts) == 0 { return nil } + set, err := createAttestationSignatureSet(ctx, beaconState, atts, domain) + if err != nil { + return err + } + verify, err := bls.VerifyMultipleSignatures(set.Signatures, set.Messages, set.PublicKeys) + if err != nil { + return errors.Errorf("got error in multiple verification: %v", err) + } + if !verify { + return errors.New("one or more attestation signatures did not verify") + } + return nil +} + +// Method to break down attestations of the same domain and collect them into a single signature set. +func createAttestationSignatureSet(ctx context.Context, beaconState *stateTrie.BeaconState, atts []*ethpb.Attestation, domain []byte) (*bls.SignatureSet, error) { + if len(atts) == 0 { + return nil, nil + } sigs := make([]bls.Signature, len(atts)) pks := make([]bls.PublicKey, len(atts)) @@ -900,12 +1037,12 @@ func verifyAttestationsWithDomain(ctx context.Context, beaconState *stateTrie.Be for i, a := range atts { sig, err := bls.SignatureFromBytes(a.Signature) if err != nil { - return err + return nil, err } sigs[i] = sig c, err := helpers.BeaconCommitteeFromState(beaconState, a.Data.Slot, a.Data.CommitteeIndex) if err != nil { - return err + return nil, err } ia := attestationutil.ConvertToIndexed(ctx, a, c) indices := ia.AttestingIndices @@ -914,7 +1051,7 @@ func verifyAttestationsWithDomain(ctx context.Context, beaconState *stateTrie.Be pubkeyAtIdx := beaconState.PubkeyAtIndex(indices[i]) p, err := bls.PublicKeyFromBytes(pubkeyAtIdx[:]) if err != nil { - return errors.Wrap(err, "could not deserialize validator public key") + return nil, errors.Wrap(err, "could not deserialize validator public key") } if pk == nil { pk = p @@ -926,15 +1063,15 @@ func verifyAttestationsWithDomain(ctx context.Context, beaconState *stateTrie.Be root, err := helpers.ComputeSigningRoot(ia.Data, domain) if err != nil { - return errors.Wrap(err, "could not get signing root of object") + return nil, errors.Wrap(err, "could not get signing root of object") } msgs[i] = root } - as := bls.AggregateSignatures(sigs) - if !as.AggregateVerify(pks, msgs) { - return errors.New("one or more attestation signatures did not verify") - } - return nil + return &bls.SignatureSet{ + Signatures: sigs, + PublicKeys: pks, + Messages: msgs, + }, nil } // ProcessDeposits is one of the operations performed on each processed diff --git a/beacon-chain/core/blocks/block_operations_test.go b/beacon-chain/core/blocks/block_operations_test.go index faf60a4e5a..e720efc0c1 100644 --- a/beacon-chain/core/blocks/block_operations_test.go +++ b/beacon-chain/core/blocks/block_operations_test.go @@ -349,6 +349,85 @@ func TestProcessBlockHeader_OK(t *testing.T) { } } +func TestBlockSignatureSet_OK(t *testing.T) { + validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount) + for i := 0; i < len(validators); i++ { + validators[i] = ðpb.Validator{ + ExitEpoch: params.BeaconConfig().FarFutureEpoch, + Slashed: true, + } + } + + state, err := stateTrie.InitializeFromProto(&pb.BeaconState{ + Validators: validators, + Slot: 10, + LatestBlockHeader: ðpb.BeaconBlockHeader{Slot: 9}, + Fork: &pb.Fork{ + PreviousVersion: []byte{0, 0, 0, 0}, + CurrentVersion: []byte{0, 0, 0, 0}, + }, + RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), + }) + if err != nil { + t.Fatal(err) + } + + latestBlockSignedRoot, err := stateutil.BlockHeaderRoot(state.LatestBlockHeader()) + if err != nil { + t.Error(err) + } + + currentEpoch := helpers.CurrentEpoch(state) + dt, err := helpers.Domain(state.Fork(), currentEpoch, params.BeaconConfig().DomainBeaconProposer, state.GenesisValidatorRoot()) + if err != nil { + t.Fatalf("Failed to get domain form state: %v", err) + } + priv := bls.RandKey() + pID, err := helpers.BeaconProposerIndex(state) + if err != nil { + t.Error(err) + } + block := ðpb.SignedBeaconBlock{ + Block: ðpb.BeaconBlock{ + ProposerIndex: pID, + Slot: 10, + Body: ðpb.BeaconBlockBody{ + RandaoReveal: []byte{'A', 'B', 'C'}, + }, + ParentRoot: latestBlockSignedRoot[:], + }, + } + signingRoot, err := helpers.ComputeSigningRoot(block.Block, dt) + if err != nil { + t.Fatalf("Failed to get signing root of block: %v", err) + } + blockSig := priv.Sign(signingRoot[:]) + block.Signature = blockSig.Marshal()[:] + + proposerIdx, err := helpers.BeaconProposerIndex(state) + if err != nil { + t.Fatal(err) + } + validators[proposerIdx].Slashed = false + validators[proposerIdx].PublicKey = priv.PublicKey().Marshal() + err = state.UpdateValidatorAtIndex(proposerIdx, validators[proposerIdx]) + if err != nil { + t.Fatal(err) + } + set, err := blocks.BlockSignatureSet(state, block) + if err != nil { + t.Fatal(err) + } + + verified, err := set.Verify() + if err != nil { + t.Fatal(err) + } + if !verified { + t.Error("Block signature set returned a set which was unable to be verified") + } +} + func TestProcessBlockHeader_ImproperBlockSlot(t *testing.T) { validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount) for i := 0; i < len(validators); i++ { @@ -489,6 +568,34 @@ func TestProcessRandao_SignatureVerifiesAndUpdatesLatestStateMixes(t *testing.T) } } +func TestRandaoSignatureSet_OK(t *testing.T) { + beaconState, privKeys := testutil.DeterministicGenesisState(t, 100) + + epoch := helpers.CurrentEpoch(beaconState) + epochSignature, err := testutil.RandaoReveal(beaconState, epoch, privKeys) + if err != nil { + t.Fatal(err) + } + + block := ðpb.BeaconBlock{ + Body: ðpb.BeaconBlockBody{ + RandaoReveal: epochSignature, + }, + } + + set, _, err := blocks.RandaoSignatureSet(beaconState, block.Body) + if err != nil { + t.Fatal(err) + } + verified, err := set.Verify() + if err != nil { + t.Fatal(err) + } + if !verified { + t.Error("Unable to verify randao signature set") + } +} + func TestProcessEth1Data_SetsCorrectly(t *testing.T) { beaconState, err := stateTrie.InitializeFromProto(&pb.BeaconState{ Eth1DataVotes: []*ethpb.Eth1Data{}, @@ -2219,6 +2326,96 @@ func TestVerifyAttestations_HandlesPlannedFork(t *testing.T) { } } +func TestRetrieveAttestationSignatureSet_VerifiesMultipleAttestations(t *testing.T) { + ctx := context.Background() + numOfValidators := 4 * params.BeaconConfig().SlotsPerEpoch + validators := make([]*ethpb.Validator, numOfValidators) + _, keys, err := testutil.DeterministicDepositsAndKeys(numOfValidators) + if err != nil { + t.Fatal(err) + } + for i := 0; i < len(validators); i++ { + validators[i] = ðpb.Validator{ + ExitEpoch: params.BeaconConfig().FarFutureEpoch, + PublicKey: keys[i].PublicKey().Marshal(), + } + } + + st, err := stateTrie.InitializeFromProto(&pb.BeaconState{ + Slot: 5, + Validators: validators, + Fork: &pb.Fork{ + Epoch: 0, + CurrentVersion: params.BeaconConfig().GenesisForkVersion, + PreviousVersion: params.BeaconConfig().GenesisForkVersion, + }, + RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), + }) + + comm1, err := helpers.BeaconCommitteeFromState(st, 1 /*slot*/, 0 /*committeeIndex*/) + if err != nil { + t.Fatal(err) + } + att1 := ðpb.Attestation{ + AggregationBits: bitfield.NewBitlist(uint64(len(comm1))), + Data: ðpb.AttestationData{ + Slot: 1, + CommitteeIndex: 0, + }, + Signature: nil, + } + domain, err := helpers.Domain(st.Fork(), st.Fork().Epoch, params.BeaconConfig().DomainBeaconAttester, st.GenesisValidatorRoot()) + if err != nil { + t.Fatal(err) + } + root, err := helpers.ComputeSigningRoot(att1.Data, domain) + if err != nil { + t.Fatal(err) + } + var sigs []bls.Signature + for i, u := range comm1 { + att1.AggregationBits.SetBitAt(uint64(i), true) + sigs = append(sigs, keys[u].Sign(root[:])) + } + att1.Signature = bls.AggregateSignatures(sigs).Marshal() + + comm2, err := helpers.BeaconCommitteeFromState(st, 1 /*slot*/, 1 /*committeeIndex*/) + if err != nil { + t.Fatal(err) + } + att2 := ðpb.Attestation{ + AggregationBits: bitfield.NewBitlist(uint64(len(comm2))), + Data: ðpb.AttestationData{ + Slot: 1, + CommitteeIndex: 1, + }, + Signature: nil, + } + root, err = helpers.ComputeSigningRoot(att2.Data, domain) + if err != nil { + t.Fatal(err) + } + sigs = nil + for i, u := range comm2 { + att2.AggregationBits.SetBitAt(uint64(i), true) + sigs = append(sigs, keys[u].Sign(root[:])) + } + att2.Signature = bls.AggregateSignatures(sigs).Marshal() + + set, err := blocks.RetrieveAttestationSignatureSet(ctx, st, []*ethpb.Attestation{att1, att2}) + if err != nil { + t.Fatal(err) + } + verified, err := set.Verify() + if err != nil { + t.Fatal(err) + } + if !verified { + t.Error("Multiple signatures were unable to be verified.") + } + +} + func TestAreEth1DataEqual(t *testing.T) { type args struct { a *ethpb.Eth1Data diff --git a/beacon-chain/core/helpers/signing_root.go b/beacon-chain/core/helpers/signing_root.go index 456615ebd2..b7392de24b 100644 --- a/beacon-chain/core/helpers/signing_root.go +++ b/beacon-chain/core/helpers/signing_root.go @@ -83,25 +83,44 @@ func VerifySigningRoot(obj interface{}, pub []byte, signature []byte, domain []b // VerifyBlockSigningRoot verifies the signing root of a block given it's public key, signature and domain. func VerifyBlockSigningRoot(blk *ethpb.BeaconBlock, pub []byte, signature []byte, domain []byte) error { + set, err := RetrieveBlockSignatureSet(blk, pub, signature, domain) + if err != nil { + return err + } + // We assume only one signature set is returned here. + sig := set.Signatures[0] + publicKey := set.PublicKeys[0] + root := set.Messages[0] + + if !sig.Verify(publicKey, root[:]) { + return ErrSigFailedToVerify + } + return nil +} + +// RetrieveBlockSignatureSet retrieves the relevant signature, message and pubkey data from a block and collating it +// into a signature set object. +func RetrieveBlockSignatureSet(blk *ethpb.BeaconBlock, pub []byte, signature []byte, domain []byte) (*bls.SignatureSet, error) { publicKey, err := bls.PublicKeyFromBytes(pub) if err != nil { - return errors.Wrap(err, "could not convert bytes to public key") + return nil, errors.Wrap(err, "could not convert bytes to public key") } sig, err := bls.SignatureFromBytes(signature) if err != nil { - return errors.Wrap(err, "could not convert bytes to signature") + return nil, errors.Wrap(err, "could not convert bytes to signature") } root, err := signingData(func() ([32]byte, error) { // utilize custom block hashing function return stateutil.BlockRoot(blk) }, domain) if err != nil { - return errors.Wrap(err, "could not compute signing root") + return nil, errors.Wrap(err, "could not compute signing root") } - if !sig.Verify(publicKey, root[:]) { - return ErrSigFailedToVerify - } - return nil + return &bls.SignatureSet{ + Signatures: []bls.Signature{sig}, + PublicKeys: []bls.PublicKey{publicKey}, + Messages: [][32]byte{root}, + }, nil } // VerifyBlockHeaderSigningRoot verifies the signing root of a block header given it's public key, signature and domain. diff --git a/beacon-chain/core/state/BUILD.bazel b/beacon-chain/core/state/BUILD.bazel index 8f4a18195e..cc6703f800 100644 --- a/beacon-chain/core/state/BUILD.bazel +++ b/beacon-chain/core/state/BUILD.bazel @@ -31,6 +31,7 @@ go_library( "//beacon-chain/state:go_default_library", "//beacon-chain/state/stateutil:go_default_library", "//proto/beacon/p2p/v1:go_default_library", + "//shared/bls:go_default_library", "//shared/mathutil:go_default_library", "//shared/params:go_default_library", "//shared/traceutil:go_default_library", diff --git a/beacon-chain/core/state/transition.go b/beacon-chain/core/state/transition.go index 1ffd782553..15942455df 100644 --- a/beacon-chain/core/state/transition.go +++ b/beacon-chain/core/state/transition.go @@ -18,6 +18,7 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/core/state/interop" stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state" "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/mathutil" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/traceutil" @@ -129,6 +130,52 @@ func ExecuteStateTransitionNoVerifyAttSigs( return state, nil } +// ExecuteStateTransitionNoVerify defines the procedure for a state transition function. +// This does not validate any BLS signatures of attestations, block proposer signature, randao signature, +// it is used for performing a state transition as quickly as possible. This function also returns a signature +// set of all signatures not verified, so that they can be stored and verified later. +// +// WARNING: This method does not validate any signatures in a block. This method also modifies the passed in state. +// +// Spec pseudocode definition: +// def state_transition(state: BeaconState, block: BeaconBlock, validate_state_root: bool=False) -> BeaconState: +// # Process slots (including those with no blocks) since block +// process_slots(state, block.slot) +// # Process block +// process_block(state, block) +// # Return post-state +// return state +func ExecuteStateTransitionNoVerify( + ctx context.Context, + state *stateTrie.BeaconState, + signed *ethpb.SignedBeaconBlock, +) (*bls.SignatureSet, *stateTrie.BeaconState, error) { + if ctx.Err() != nil { + return nil, nil, ctx.Err() + } + if signed == nil || signed.Block == nil { + return nil, nil, errors.New("nil block") + } + + ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.ExecuteStateTransitionNoVerifyAttSigs") + defer span.End() + var err error + + // Execute per slots transition. + state, err = ProcessSlots(ctx, state, signed.Block.Slot) + if err != nil { + return nil, nil, errors.Wrap(err, "could not process slot") + } + + // Execute per block transition. + set, state, err := ProcessBlockNoVerify(ctx, state, signed) + if err != nil { + return nil, nil, errors.Wrap(err, "could not process block") + } + + return set, state, nil +} + // CalculateStateRoot defines the procedure for a state transition function. // This does not validate any BLS signatures in a block, it is used for calculating the // state root of the state for the block proposer to use. @@ -427,6 +474,68 @@ func ProcessBlockNoVerifyAttSigs( return state, nil } +// ProcessBlockNoVerify creates a new, modified beacon state by applying block operation +// transformations as defined in the Ethereum Serenity specification. It does not validate +// any block signature except for deposit and slashing signatures. It also returns the relevant +// signature set from all the respective methods. +// +// Spec pseudocode definition: +// +// def process_block(state: BeaconState, block: BeaconBlock) -> None: +// process_block_header(state, block) +// process_randao(state, block.body) +// process_eth1_data(state, block.body) +// process_operations(state, block.body) +func ProcessBlockNoVerify( + ctx context.Context, + state *stateTrie.BeaconState, + signed *ethpb.SignedBeaconBlock, +) (*bls.SignatureSet, *stateTrie.BeaconState, error) { + ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.state.ProcessBlock") + defer span.End() + + // Empty signature set. + set := bls.NewSet() + + state, err := b.ProcessBlockHeaderNoVerify(state, signed.Block) + if err != nil { + traceutil.AnnotateError(span, err) + return nil, nil, errors.Wrap(err, "could not process block header") + } + bSet, err := b.BlockSignatureSet(state, signed) + if err != nil { + traceutil.AnnotateError(span, err) + return nil, nil, errors.Wrap(err, "could not retrieve block signature set") + } + rSet, state, err := b.RandaoSignatureSet(state, signed.Block.Body) + if err != nil { + traceutil.AnnotateError(span, err) + return nil, nil, errors.Wrap(err, "could not retrieve randao signature set") + } + state, err = b.ProcessRandaoNoVerify(state, signed.Block.Body) + if err != nil { + traceutil.AnnotateError(span, err) + return nil, nil, errors.Wrap(err, "could not verify and process randao") + } + + state, err = b.ProcessEth1DataInBlock(state, signed.Block) + if err != nil { + traceutil.AnnotateError(span, err) + return nil, nil, errors.Wrap(err, "could not process eth1 data") + } + + aSet, state, err := ProcessOperationsNoVerifySignatureSet(ctx, state, signed.Block.Body) + if err != nil { + traceutil.AnnotateError(span, err) + return nil, nil, errors.Wrap(err, "could not process block operation") + } + + // Merge all signature sets + set.Join(bSet).Join(rSet).Join(aSet) + + return set, state, nil +} + // ProcessOperations processes the operations in the beacon block and updates beacon state // with the operations in block. // @@ -547,6 +656,71 @@ func ProcessOperationsNoVerify( return state, nil } +// ProcessOperationsNoVerifySignatureSet processes the operations in the beacon block and updates beacon state +// with the operations in block. It does not verify attestation signatures. It instead +// returns the relevant signature set for each of the operations +// +// WARNING: This method does not verify attestation signatures. +// This is used to perform the block operations as fast as possible. +// +// Spec pseudocode definition: +// +// def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: +// # Verify that outstanding deposits are processed up to the maximum number of deposits +// assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) +// # Verify that there are no duplicate transfers +// assert len(body.transfers) == len(set(body.transfers)) +// +// all_operations = ( +// (body.proposer_slashings, process_proposer_slashing), +// (body.attester_slashings, process_attester_slashing), +// (body.attestations, process_attestation), +// (body.deposits, process_deposit), +// (body.voluntary_exits, process_voluntary_exit), +// (body.transfers, process_transfer), +// ) # type: Sequence[Tuple[List, Callable]] +// for operations, function in all_operations: +// for operation in operations: +// function(state, operation) +func ProcessOperationsNoVerifySignatureSet( + ctx context.Context, + state *stateTrie.BeaconState, + body *ethpb.BeaconBlockBody) (*bls.SignatureSet, *stateTrie.BeaconState, error) { + ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.state.ProcessOperations") + defer span.End() + + if err := verifyOperationLengths(state, body); err != nil { + return nil, nil, errors.Wrap(err, "could not verify operation lengths") + } + + state, err := b.ProcessProposerSlashings(ctx, state, body) + if err != nil { + return nil, nil, errors.Wrap(err, "could not process block proposer slashings") + } + state, err = b.ProcessAttesterSlashings(ctx, state, body) + if err != nil { + return nil, nil, errors.Wrap(err, "could not process block attester slashings") + } + state, err = b.ProcessAttestationsNoVerify(ctx, state, body) + if err != nil { + return nil, nil, errors.Wrap(err, "could not process block attestations") + } + aSet, err := b.RetrieveAttestationSignatureSet(ctx, state, body.Attestations) + if err != nil { + return nil, nil, errors.Wrap(err, "could not retrieve attestation signature set") + } + state, err = b.ProcessDeposits(ctx, state, body.Deposits) + if err != nil { + return nil, nil, errors.Wrap(err, "could not process block validator deposits") + } + state, err = b.ProcessVoluntaryExits(ctx, state, body) + if err != nil { + return nil, nil, errors.Wrap(err, "could not process validator exits") + } + + return aSet, state, nil +} + func verifyOperationLengths(state *stateTrie.BeaconState, body *ethpb.BeaconBlockBody) error { if uint64(len(body.ProposerSlashings)) > params.BeaconConfig().MaxProposerSlashings { return fmt.Errorf( diff --git a/beacon-chain/core/state/transition_test.go b/beacon-chain/core/state/transition_test.go index cc1974bcd0..6cea441c6e 100644 --- a/beacon-chain/core/state/transition_test.go +++ b/beacon-chain/core/state/transition_test.go @@ -144,6 +144,91 @@ func TestExecuteStateTransition_FullProcess(t *testing.T) { } } +func TestExecuteStateTransitionNoVerify_FullProcess(t *testing.T) { + beaconState, privKeys := testutil.DeterministicGenesisState(t, 100) + + eth1Data := ðpb.Eth1Data{ + DepositCount: 100, + DepositRoot: []byte{2}, + } + if err := beaconState.SetSlot(params.BeaconConfig().SlotsPerEpoch - 1); err != nil { + t.Fatal(err) + } + e := beaconState.Eth1Data() + e.DepositCount = 100 + if err := beaconState.SetEth1Data(e); err != nil { + t.Fatal(err) + } + if err := beaconState.SetLatestBlockHeader(ðpb.BeaconBlockHeader{Slot: beaconState.Slot()}); err != nil { + t.Fatal(err) + } + if err := beaconState.SetEth1DataVotes([]*ethpb.Eth1Data{eth1Data}); err != nil { + t.Fatal(err) + } + parentRoot, err := stateutil.BlockHeaderRoot(beaconState.LatestBlockHeader()) + if err != nil { + t.Error(err) + } + + if err := beaconState.SetSlot(beaconState.Slot() + 1); err != nil { + t.Fatal(err) + } + epoch := helpers.CurrentEpoch(beaconState) + randaoReveal, err := testutil.RandaoReveal(beaconState, epoch, privKeys) + if err != nil { + t.Fatal(err) + } + if err := beaconState.SetSlot(beaconState.Slot() - 1); err != nil { + t.Fatal(err) + } + + nextSlotState := beaconState.Copy() + if err := nextSlotState.SetSlot(beaconState.Slot() + 1); err != nil { + t.Fatal(err) + } + proposerIdx, err := helpers.BeaconProposerIndex(nextSlotState) + if err != nil { + t.Error(err) + } + block := ðpb.SignedBeaconBlock{ + Block: ðpb.BeaconBlock{ + ProposerIndex: proposerIdx, + Slot: beaconState.Slot() + 1, + ParentRoot: parentRoot[:], + Body: ðpb.BeaconBlockBody{ + RandaoReveal: randaoReveal, + Eth1Data: eth1Data, + }, + }, + } + + stateRoot, err := state.CalculateStateRoot(context.Background(), beaconState, block) + if err != nil { + t.Fatal(err) + } + + block.Block.StateRoot = stateRoot[:] + + sig, err := testutil.BlockSignature(beaconState, block.Block, privKeys) + if err != nil { + t.Fatal(err) + } + block.Signature = sig.Marshal() + + set, beaconState, err := state.ExecuteStateTransitionNoVerify(context.Background(), beaconState, block) + if err != nil { + t.Error(err) + } + verified, err := set.Verify() + if err != nil { + t.Error(err) + } + if !verified { + t.Error("Could not verify signature set") + } + +} + func TestProcessBlock_IncorrectProposerSlashing(t *testing.T) { beaconState, privKeys := testutil.DeterministicGenesisState(t, 100) @@ -347,7 +432,8 @@ func TestProcessBlock_IncorrectProcessExits(t *testing.T) { } } -func TestProcessBlock_PassesProcessingConditions(t *testing.T) { +func createFullBlockWithOperations(t *testing.T) (*beaconstate.BeaconState, + *ethpb.SignedBeaconBlock, []*ethpb.Attestation, []*ethpb.ProposerSlashing, []*ethpb.SignedVoluntaryExit) { beaconState, privKeys := testutil.DeterministicGenesisState(t, 32) genesisBlock := blocks.NewGenesisBlock([]byte{}) bodyRoot, err := stateutil.BlockRoot(genesisBlock.Block) @@ -587,7 +673,14 @@ func TestProcessBlock_PassesProcessingConditions(t *testing.T) { if beaconState.SetSlot(block.Block.Slot) != nil { t.Fatal(err) } - beaconState, err = state.ProcessBlock(context.Background(), beaconState, block) + return beaconState, block, []*ethpb.Attestation{blockAtt}, proposerSlashings, []*ethpb.SignedVoluntaryExit{exit} +} + +func TestProcessBlock_PassesProcessingConditions(t *testing.T) { + beaconState, block, _, proposerSlashings, exits := createFullBlockWithOperations(t) + exit := exits[0] + + beaconState, err := state.ProcessBlock(context.Background(), beaconState, block) if err != nil { t.Fatalf("Expected block to pass processing conditions: %v", err) } @@ -618,6 +711,23 @@ func TestProcessBlock_PassesProcessingConditions(t *testing.T) { } } +func TestProcessBlockNoVerify_PassesProcessingConditions(t *testing.T) { + beaconState, block, _, _, _ := createFullBlockWithOperations(t) + set, _, err := state.ProcessBlockNoVerify(context.Background(), beaconState, block) + if err != nil { + t.Fatal(err) + } + // Test Signature set verifies. + verified, err := set.Verify() + if err != nil { + t.Fatal(err) + } + if !verified { + t.Error("Could not verify signature set.") + } + +} + func TestProcessEpochPrecompute_CanProcess(t *testing.T) { epoch := uint64(1) diff --git a/shared/bls/signature_set.go b/shared/bls/signature_set.go index 33cd2c64d2..2fe75fea5d 100644 --- a/shared/bls/signature_set.go +++ b/shared/bls/signature_set.go @@ -9,9 +9,24 @@ type SignatureSet struct { Messages [][32]byte } +// NewSet constructs an empty signature set object. +func NewSet() *SignatureSet { + return &SignatureSet{ + Signatures: []Signature{}, + PublicKeys: []PublicKey{}, + Messages: [][32]byte{}, + } +} + // Join merges the provided signature set to out current one. -func (s *SignatureSet) Join(set *SignatureSet) { +func (s *SignatureSet) Join(set *SignatureSet) *SignatureSet { s.Signatures = append(s.Signatures, set.Signatures...) s.PublicKeys = append(s.PublicKeys, set.PublicKeys...) s.Messages = append(s.Messages, set.Messages...) + return s +} + +// Verify the current signature set using the batch verify algorithm. +func (s *SignatureSet) Verify() (bool, error) { + return VerifyMultipleSignatures(s.Signatures, s.Messages, s.PublicKeys) }