You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

219 lines
10 KiB

  1. # Light client attacks
  2. We define a light client attack as detection of conflicting headers for a given height that can be verified
  3. starting from the trusted light block. A light client attack is defined in the context of interactions of
  4. light client with two peers. One of the peers (called primary) defines a trace of verified light blocks
  5. (primary trace) that are being checked against trace of the other peer (called witness) that we call
  6. witness trace.
  7. A light client attack is defined by the primary and witness traces
  8. that have a common root (the same trusted light block for a common height) but forms
  9. conflicting branches (end of traces is for the same height but with different headers).
  10. Note that conflicting branches could be arbitrarily big as branches continue to diverge after
  11. a bifurcation point. We propose an approach that allows us to define a valid light client attack
  12. only with a common light block and a single conflicting light block. We rely on the fact that
  13. we assume that the primary is under suspicion (therefore not trusted) and that the witness plays
  14. support role to detect and process an attack (therefore trusted). Therefore, once a light client
  15. detects an attack, it needs to send to a witness only missing data (common height
  16. and conflicting light block) as it has its trace. Keeping light client attack data of constant size
  17. saves bandwidth and reduces an attack surface. As we will explain below, although in the context of
  18. light client core
  19. [verification](https://github.com/informalsystems/tendermint-rs/tree/master/docs/spec/lightclient/verification)
  20. the roles of primary and witness are clearly defined,
  21. in case of the attack, we run the same attack detection procedure twice where the roles are swapped.
  22. The rationale is that the light client does not know what peer is correct (on a right main branch)
  23. so it tries to create and submit an attack evidence to both peers.
  24. Light client attack evidence consists of a conflicting light block and a common height.
  25. ```go
  26. type LightClientAttackEvidence struct {
  27. ConflictingBlock LightBlock
  28. CommonHeight int64
  29. }
  30. ```
  31. Full node can validate a light client attack evidence by executing the following procedure:
  32. ```go
  33. func IsValid(lcaEvidence LightClientAttackEvidence, bc Blockchain) boolean {
  34. commonBlock = GetLightBlock(bc, lcaEvidence.CommonHeight)
  35. if commonBlock == nil return false
  36. // Note that trustingPeriod in ValidAndVerified is set to UNBONDING_PERIOD
  37. verdict = ValidAndVerified(commonBlock, lcaEvidence.ConflictingBlock)
  38. conflictingHeight = lcaEvidence.ConflictingBlock.Header.Height
  39. return verdict == OK and bc[conflictingHeight].Header != lcaEvidence.ConflictingBlock.Header
  40. }
  41. ```
  42. ## Light client attack creation
  43. Given a trusted light block `trusted`, a light node executes the bisection algorithm to verify header
  44. `untrusted` at some height `h`. If the bisection algorithm succeeds, then the header `untrusted` is verified.
  45. Headers that are downloaded as part of the bisection algorithm are stored in a store and they are also in
  46. the verified state. Therefore, after the bisection algorithm successfully terminates we have a trace of
  47. the light blocks ([] LightBlock) we obtained from the primary that we call primary trace.
  48. ### Primary trace
  49. The following invariant holds for the primary trace:
  50. - Given a `trusted` light block, target height `h`, and `primary_trace` ([] LightBlock):
  51. *primary_trace[0] == trusted* and *primary_trace[len(primary_trace)-1].Height == h* and
  52. successive light blocks are passing light client verification logic.
  53. ### Witness with a conflicting header
  54. The verified header at height `h` is cross-checked with every witness as part of
  55. [detection](https://github.com/informalsystems/tendermint-rs/tree/master/docs/spec/lightclient/detection).
  56. If a witness returns the conflicting header at the height `h` the following procedure is executed to verify
  57. if the conflicting header comes from the valid trace and if that's the case to create an attack evidence:
  58. #### Helper functions
  59. We assume the following helper functions:
  60. ```go
  61. // Returns trace of verified light blocks starting from rootHeight and ending with targetHeight.
  62. Trace(lightStore LightStore, rootHeight int64, targetHeight int64) LightBlock[]
  63. // Returns validator set for the given height
  64. GetValidators(bc Blockchain, height int64) Validator[]
  65. // Returns validator set for the given height
  66. GetValidators(bc Blockchain, height int64) Validator[]
  67. // Return validator addresses for the given validators
  68. GetAddresses(vals Validator[]) ValidatorAddress[]
  69. ```
  70. ```go
  71. func DetectLightClientAttacks(primary PeerID,
  72. primary_trace []LightBlock,
  73. witness PeerID) (LightClientAttackEvidence, LightClientAttackEvidence) {
  74. primary_lca_evidence, witness_trace = DetectLightClientAttack(primary_trace, witness)
  75. witness_lca_evidence = nil
  76. if witness_trace != nil {
  77. witness_lca_evidence, _ = DetectLightClientAttack(witness_trace, primary)
  78. }
  79. return primary_lca_evidence, witness_lca_evidence
  80. }
  81. func DetectLightClientAttack(trace []LightBlock, peer PeerID) (LightClientAttackEvidence, []LightBlock) {
  82. lightStore = new LightStore().Update(trace[0], StateTrusted)
  83. for i in 1..len(trace)-1 {
  84. lightStore, result = VerifyToTarget(peer, lightStore, trace[i].Header.Height)
  85. if result == ResultFailure then return (nil, nil)
  86. current = lightStore.Get(trace[i].Header.Height)
  87. // if obtained header is the same as in the trace we continue with a next height
  88. if current.Header == trace[i].Header continue
  89. // we have identified a conflicting header
  90. commonBlock = trace[i-1]
  91. conflictingBlock = trace[i]
  92. return (LightClientAttackEvidence { conflictingBlock, commonBlock.Header.Height },
  93. Trace(lightStore, trace[i-1].Header.Height, trace[i].Header.Height))
  94. }
  95. return (nil, nil)
  96. }
  97. ```
  98. ## Evidence handling
  99. As part of on chain evidence handling, full nodes identifies misbehaving processes and informs
  100. the application, so they can be slashed. Note that only bonded validators should
  101. be reported to the application. There are three types of attacks that can be executed against
  102. Tendermint light client:
  103. - lunatic attack
  104. - equivocation attack and
  105. - amnesia attack.
  106. We now specify the evidence handling logic.
  107. ```go
  108. func detectMisbehavingProcesses(lcAttackEvidence LightClientAttackEvidence, bc Blockchain) []ValidatorAddress {
  109. assume IsValid(lcaEvidence, bc)
  110. // lunatic light client attack
  111. if !isValidBlock(current.Header, conflictingBlock.Header) {
  112. conflictingCommit = lcAttackEvidence.ConflictingBlock.Commit
  113. bondedValidators = GetNextValidators(bc, lcAttackEvidence.CommonHeight)
  114. return getSigners(conflictingCommit) intersection GetAddresses(bondedValidators)
  115. // equivocation light client attack
  116. } else if current.Header.Round == conflictingBlock.Header.Round {
  117. conflictingCommit = lcAttackEvidence.ConflictingBlock.Commit
  118. trustedCommit = bc[conflictingBlock.Header.Height+1].LastCommit
  119. return getSigners(trustedCommit) intersection getSigners(conflictingCommit)
  120. // amnesia light client attack
  121. } else {
  122. HandleAmnesiaAttackEvidence(lcAttackEvidence, bc)
  123. }
  124. }
  125. // Block validity in this context is defined by the trusted header.
  126. func isValidBlock(trusted Header, conflicting Header) boolean {
  127. return trusted.ValidatorsHash == conflicting.ValidatorsHash and
  128. trusted.NextValidatorsHash == conflicting.NextValidatorsHash and
  129. trusted.ConsensusHash == conflicting.ConsensusHash and
  130. trusted.AppHash == conflicting.AppHash and
  131. trusted.LastResultsHash == conflicting.LastResultsHash
  132. }
  133. func getSigners(commit Commit) []ValidatorAddress {
  134. signers = []ValidatorAddress
  135. for (i, commitSig) in commit.Signatures {
  136. if commitSig.BlockIDFlag == BlockIDFlagCommit {
  137. signers.append(commitSig.ValidatorAddress)
  138. }
  139. }
  140. return signers
  141. }
  142. ```
  143. Note that amnesia attack evidence handling involves more complex processing, i.e., cannot be
  144. defined simply on amnesia attack evidence. We explain in the following section a protocol
  145. for handling amnesia attack evidence.
  146. ### Amnesia attack evidence handling
  147. Detecting faulty processes in case of the amnesia attack is more complex and cannot be inferred
  148. purely based on attack evidence data. In this case, in order to detect misbehaving processes we need
  149. access to votes processes sent/received during the conflicting height. Therefore, amnesia handling assumes that
  150. validators persist all votes received and sent during multi-round heights (as amnesia attack
  151. is only possible in heights that executes over multiple rounds, i.e., commit round > 0).
  152. To simplify description of the algorithm we assume existence of the trusted oracle called monitor that will
  153. drive the algorithm and output faulty processes at the end. Monitor can be implemented in a
  154. distributed setting as on-chain module. The algorithm works as follows:
  155. 1) Monitor sends votesets request to validators of the conflicting height. Validators
  156. are expected to send their votesets within predefined timeout.
  157. 2) Upon receiving votesets request, validators send their votesets to a monitor.
  158. 2) Validators which have not sent its votesets within timeout are considered faulty.
  159. 3) The preprocessing of the votesets is done. That means that the received votesets are analyzed
  160. and each vote (valid) sent by process p is added to the voteset of the sender p. This phase ensures that
  161. votes sent by faulty processes observed by at least one correct validator cannot be excluded from the analysis.
  162. 4) Votesets of every validator are analyzed independently to decide whether the validator is correct or faulty.
  163. A faulty validators is the one where at least one of those invalid transitions is found:
  164. - More than one PREVOTE message is sent in a round
  165. - More than one PRECOMMIT message is sent in a round
  166. - PRECOMMIT message is sent without receiving +2/3 of voting-power equivalent
  167. appropriate PREVOTE messages
  168. - PREVOTE message is sent for the value V’ in round r’ and the PRECOMMIT message had
  169. been sent for the value V in round r by the same process (r’ > r) and there are no
  170. +2/3 of voting-power equivalent PREVOTE(vr, V’) messages (vr ≥ 0 and vr > r and vr < r)
  171. as the justification for sending PREVOTE(r’, V’)