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