github.com/onflow/flow-go@v0.33.17/consensus/hotstuff/verification/combined_verifier_v2.go (about) 1 package verification 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/onflow/flow-go/consensus/hotstuff" 8 "github.com/onflow/flow-go/consensus/hotstuff/model" 9 "github.com/onflow/flow-go/crypto/hash" 10 "github.com/onflow/flow-go/model/flow" 11 msig "github.com/onflow/flow-go/module/signature" 12 "github.com/onflow/flow-go/state/protocol" 13 ) 14 15 // CombinedVerifier is a verifier capable of verifying two signatures, one for each 16 // scheme. The first type is a signature from a staking signer, 17 // which verifies either a single or an aggregated signature. The second type is 18 // a signature from a random beacon signer, which verifies either the signature share or 19 // the reconstructed threshold signature. 20 type CombinedVerifier struct { 21 committee hotstuff.Replicas 22 stakingHasher hash.Hasher 23 timeoutObjectHasher hash.Hasher 24 beaconHasher hash.Hasher 25 packer hotstuff.Packer 26 } 27 28 var _ hotstuff.Verifier = (*CombinedVerifier)(nil) 29 30 // NewCombinedVerifier creates a new combined verifier with the given dependencies. 31 // - the hotstuff committee's state is used to retrieve the public keys for the staking signature; 32 // - the merger is used to combine and split staking and random beacon signatures; 33 // - the packer is used to unpack QC for verification; 34 func NewCombinedVerifier(committee hotstuff.Replicas, packer hotstuff.Packer) *CombinedVerifier { 35 return &CombinedVerifier{ 36 committee: committee, 37 stakingHasher: msig.NewBLSHasher(msig.ConsensusVoteTag), 38 timeoutObjectHasher: msig.NewBLSHasher(msig.ConsensusTimeoutTag), 39 beaconHasher: msig.NewBLSHasher(msig.RandomBeaconTag), 40 packer: packer, 41 } 42 } 43 44 // VerifyVote verifies the validity of a combined signature from a vote. 45 // Usually this method is only used to verify the proposer's vote, which is 46 // the vote included in a block proposal. 47 // - model.InvalidFormatError if the signature has an incompatible format. 48 // - model.ErrInvalidSignature is the signature is invalid 49 // - model.InvalidSignerError if signer is _not_ part of the random beacon committee 50 // - model.ErrViewForUnknownEpoch if no epoch containing the given view is known 51 // - unexpected errors should be treated as symptoms of bugs or uncovered 52 // edge cases in the logic (i.e. as fatal) 53 func (c *CombinedVerifier) VerifyVote(signer *flow.Identity, sigData []byte, view uint64, blockID flow.Identifier) error { 54 55 // create the to-be-signed message 56 msg := MakeVoteMessage(view, blockID) 57 58 // split the two signatures from the vote 59 stakingSig, beaconShare, err := msig.DecodeDoubleSig(sigData) 60 if err != nil { 61 if errors.Is(err, msig.ErrInvalidSignatureFormat) { 62 return model.NewInvalidFormatErrorf("could not split signature for block %v: %w", blockID, err) 63 } 64 return fmt.Errorf("unexpected internal error while splitting signature for block %v: %w", blockID, err) 65 } 66 67 dkg, err := c.committee.DKG(view) 68 if err != nil { 69 return fmt.Errorf("could not get dkg: %w", err) 70 } 71 72 // verify each signature against the message 73 // TODO: check if using batch verification is faster (should be yes) 74 stakingValid, err := signer.StakingPubKey.Verify(stakingSig, msg, c.stakingHasher) 75 if err != nil { 76 return fmt.Errorf("internal error while verifying staking signature of node %x at block %v: %w", 77 signer.NodeID, blockID, err) 78 } 79 if !stakingValid { 80 return fmt.Errorf("invalid staking sig for block %v: %w", blockID, model.ErrInvalidSignature) 81 } 82 83 // there is no beacon share, no need to verify it 84 if beaconShare == nil { 85 return nil 86 } 87 88 // if there is beacon share, there should be beacon public key 89 beaconPubKey, err := dkg.KeyShare(signer.NodeID) 90 if err != nil { 91 if protocol.IsIdentityNotFound(err) { 92 return model.NewInvalidSignerErrorf("%v is not a random beacon participant: %w", signer.NodeID, err) 93 } 94 return fmt.Errorf("unexpected error retrieving random beacon key share for node %x at block %v: %w", 95 signer.NodeID, blockID, err) 96 } 97 98 beaconValid, err := beaconPubKey.Verify(beaconShare, msg, c.beaconHasher) 99 if err != nil { 100 return fmt.Errorf("internal error while verifying beacon signature at block %v: %w", 101 blockID, err) 102 } 103 if !beaconValid { 104 return fmt.Errorf("invalid beacon sig for block %v: %w", blockID, model.ErrInvalidSignature) 105 } 106 return nil 107 } 108 109 // VerifyQC checks the cryptographic validity of the QC's `sigData` for the 110 // given block. It is the responsibility of the calling code to ensure 111 // that all `signers` are authorized, without duplicates. Return values: 112 // - nil if `sigData` is cryptographically valid 113 // - model.InsufficientSignaturesError if `signers` is empty. 114 // Depending on the order of checks in the higher-level logic this error might 115 // be an indicator of an external byzantine input or an internal bug. 116 // - model.InvalidFormatError if `sigData` has an incompatible format 117 // - model.ErrInvalidSignature if a signature is invalid 118 // - model.ErrViewForUnknownEpoch if no epoch containing the given view is known 119 // - error if running into any unexpected exception (i.e. fatal error) 120 func (c *CombinedVerifier) VerifyQC(signers flow.IdentityList, sigData []byte, view uint64, blockID flow.Identifier) error { 121 dkg, err := c.committee.DKG(view) 122 if err != nil { 123 return fmt.Errorf("could not get dkg data: %w", err) 124 } 125 126 // unpack sig data using packer 127 blockSigData, err := c.packer.Unpack(signers, sigData) 128 if err != nil { 129 return fmt.Errorf("could not split signature: %w", err) 130 } 131 132 msg := MakeVoteMessage(view, blockID) 133 134 // verify the beacon signature first since it is faster to verify (no public key aggregation needed) 135 beaconValid, err := dkg.GroupKey().Verify(blockSigData.ReconstructedRandomBeaconSig, msg, c.beaconHasher) 136 if err != nil { 137 return fmt.Errorf("internal error while verifying beacon signature: %w", err) 138 } 139 if !beaconValid { 140 return fmt.Errorf("invalid reconstructed random beacon sig for block (%x): %w", blockID, model.ErrInvalidSignature) 141 } 142 143 err = verifyAggregatedSignatureOneMessage(signers.PublicStakingKeys(), blockSigData.AggregatedStakingSig, c.stakingHasher, msg) 144 if err != nil { 145 return fmt.Errorf("verifying aggregated staking signature failed for block %v: %w", blockID, err) 146 } 147 148 return nil 149 } 150 151 // VerifyTC checks cryptographic validity of the TC's `sigData` w.r.t. the 152 // given view. It is the responsibility of the calling code to ensure 153 // that all `signers` are authorized, without duplicates. Return values: 154 // - nil if `sigData` is cryptographically valid 155 // - model.InsufficientSignaturesError if `signers is empty. 156 // - model.InvalidFormatError if `signers`/`highQCViews` have differing lengths 157 // - model.ErrInvalidSignature if a signature is invalid 158 // - unexpected errors should be treated as symptoms of bugs or uncovered 159 // edge cases in the logic (i.e. as fatal) 160 func (c *CombinedVerifier) VerifyTC(signers flow.IdentityList, sigData []byte, view uint64, highQCViews []uint64) error { 161 stakingPks := signers.PublicStakingKeys() 162 return verifyTCSignatureManyMessages(stakingPks, sigData, view, highQCViews, c.timeoutObjectHasher) 163 }