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.

133 lines
5.2 KiB

  1. /*
  2. Package light allows you to securely validate headers
  3. without a full node.
  4. This library pulls together all the crypto and algorithms,
  5. so given a relatively recent (< unbonding period) known
  6. validator set, one can get indisputable proof that data is in
  7. the chain (current state) or detect if the node is lying to
  8. the client.
  9. Tendermint RPC exposes a lot of info, but a malicious node
  10. could return any data it wants to queries, or even to block
  11. headers, even making up fake signatures from non-existent
  12. validators to justify it. This is a lot of logic to get
  13. right, to be contained in a small, easy to use library,
  14. that does this for you, so you can just build nice UI.
  15. We design for clients who have no strong trust relationship
  16. with any tendermint node, just the validator set as a whole.
  17. Beyond building nice mobile or desktop applications, the
  18. cosmos hub is another important example of a client,
  19. that needs undeniable proof without syncing the full chain,
  20. in order to efficiently implement IBC.
  21. Commits
  22. There are two main data structures that we pass around - Commit
  23. and FullCommit. Both of them mirror what information is
  24. exposed in tendermint rpc.
  25. Commit is a block header along with enough validator signatures
  26. to prove its validity (> 2/3 of the voting power). A FullCommit
  27. is a Commit along with the full validator set. When the
  28. validator set doesn't change, the Commit is enough, but since
  29. the block header only has a hash, we need the FullCommit to
  30. follow any changes to the validator set.
  31. Certifiers
  32. A Certifier validates a new Commit given the currently known
  33. state. There are three different types of Certifiers exposed,
  34. each one building on the last one, with additional complexity.
  35. Static - given the validator set upon initialization. Verifies
  36. all signatures against that set and if the validator set
  37. changes, it will reject all headers.
  38. Dynamic - This wraps Static and has the same Certify
  39. method. However, it adds an Update method, which can be called
  40. with a FullCommit when the validator set changes. If it can
  41. prove this is a valid transition, it will update the validator
  42. set.
  43. Inquiring - this wraps Dynamic and implements an auto-update
  44. strategy on top of the Dynamic update. If a call to
  45. Certify fails as the validator set has changed, then it
  46. attempts to find a FullCommit and Update to that header.
  47. To get these FullCommits, it makes use of a Provider.
  48. Providers
  49. A Provider allows us to store and retrieve the FullCommits,
  50. to provide memory to the Inquiring Certifier.
  51. NewMemStoreProvider - in-memory cache.
  52. files.NewProvider - disk backed storage.
  53. client.NewHTTPProvider - query tendermint rpc.
  54. NewCacheProvider - combine multiple providers.
  55. The suggested use for local light clients is
  56. client.NewHTTPProvider for getting new data (Source),
  57. and NewCacheProvider(NewMemStoreProvider(),
  58. files.NewProvider()) to store confirmed headers (Trusted)
  59. How We Track Validators
  60. Unless you want to blindly trust the node you talk with, you
  61. need to trace every response back to a hash in a block header
  62. and validate the commit signatures of that block header match
  63. the proper validator set. If there is a contant validator
  64. set, you store it locally upon initialization of the client,
  65. and check against that every time.
  66. Once there is a dynamic validator set, the issue of
  67. verifying a block becomes a bit more tricky. There is
  68. background information in a
  69. github issue (https://github.com/tendermint/tendermint/issues/377).
  70. In short, if there is a block at height H with a known
  71. (trusted) validator set V, and another block at height H'
  72. (H' > H) with validator set V' != V, then we want a way to
  73. safely update it.
  74. First, get the new (unconfirmed) validator set V' and
  75. verify H' is internally consistent and properly signed by
  76. this V'. Assuming it is a valid block, we check that at
  77. least 2/3 of the validators in V also signed it, meaning
  78. it would also be valid under our old assumptions.
  79. That should be enough, but we can also check that the
  80. V counts for at least 2/3 of the total votes in H'
  81. for extra safety (we can have a discussion if this is
  82. strictly required). If we can verify all this,
  83. then we can accept H' and V' as valid and use that to
  84. validate all blocks X > H'.
  85. If we cannot update directly from H -> H' because there was
  86. too much change to the validator set, then we can look for
  87. some Hm (H < Hm < H') with a validator set Vm. Then we try
  88. to update H -> Hm and Hm -> H' in two separate steps.
  89. If one of these steps doesn't work, then we continue
  90. bisecting, until we eventually have to externally
  91. validate the valdiator set changes at every block.
  92. Since we never trust any server in this protocol, only the
  93. signatures themselves, it doesn't matter if the seed comes
  94. from a (possibly malicious) node or a (possibly malicious) user.
  95. We can accept it or reject it based only on our trusted
  96. validator set and cryptographic proofs. This makes it
  97. extremely important to verify that you have the proper
  98. validator set when initializing the client, as that is the
  99. root of all trust.
  100. Or course, this assumes that the known block is within the
  101. unbonding period to avoid the "nothing at stake" problem.
  102. If you haven't seen the state in a few months, you will need
  103. to manually verify the new validator set hash using off-chain
  104. means (the same as getting the initial hash).
  105. */
  106. package light