state.rs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. use {
  2. crate::api::ChainId,
  3. anyhow::{ensure, Result},
  4. ethers::types::Address,
  5. sha3::{Digest, Keccak256},
  6. };
  7. /// A hash chain of a specific length. The hash chain has the property that
  8. /// hash(chain.reveal_ith(i)) == chain.reveal_ith(i - 1)
  9. ///
  10. /// The implementation subsamples the elements of the chain such that it uses less memory
  11. /// to keep the chain around.
  12. #[derive(Clone)]
  13. pub struct PebbleHashChain {
  14. hash: Vec<[u8; 32]>,
  15. sample_interval: usize,
  16. length: usize,
  17. }
  18. impl PebbleHashChain {
  19. // Given a secret, we hash it with Keccak256 len times to get the final hash, this is an S/KEY
  20. // like protocol in which revealing the hashes in reverse proves knowledge.
  21. pub fn new(secret: [u8; 32], length: usize, sample_interval: usize) -> Self {
  22. assert!(sample_interval > 0, "Sample interval must be positive");
  23. let mut hash = Vec::<[u8; 32]>::with_capacity(length);
  24. let mut current: [u8; 32] = Keccak256::digest(secret).into();
  25. hash.push(current);
  26. for i in 1..length {
  27. current = Keccak256::digest(current).into();
  28. if i % sample_interval == 0 {
  29. hash.push(current);
  30. }
  31. }
  32. hash.reverse();
  33. Self {
  34. hash,
  35. sample_interval,
  36. length,
  37. }
  38. }
  39. pub fn from_config(
  40. secret: &str,
  41. chain_id: &ChainId,
  42. provider_address: &Address,
  43. contract_address: &Address,
  44. random: &[u8; 32],
  45. chain_length: u64,
  46. sample_interval: u64,
  47. ) -> Result<Self> {
  48. let mut input: Vec<u8> = vec![];
  49. input.extend_from_slice(&hex::decode(secret.trim())?);
  50. input.extend_from_slice(chain_id.as_bytes());
  51. input.extend_from_slice(provider_address.as_bytes());
  52. input.extend_from_slice(contract_address.as_bytes());
  53. input.extend_from_slice(random);
  54. let secret: [u8; 32] = Keccak256::digest(input).into();
  55. Ok(Self::new(
  56. secret,
  57. chain_length.try_into()?,
  58. sample_interval.try_into()?,
  59. ))
  60. }
  61. pub fn reveal_ith(&self, i: usize) -> Result<[u8; 32]> {
  62. ensure!(i < self.len(), "index not in range");
  63. // Note that subsample_interval may not perfectly divide length, in which case the uneven segment is
  64. // actually at the *front* of the list. Thus, it's easier to compute indexes from the end of the list.
  65. let index_from_end_of_subsampled_list = ((self.len() - 1) - i) / self.sample_interval;
  66. let mut i_index = self.len() - 1 - index_from_end_of_subsampled_list * self.sample_interval;
  67. let mut val = self.hash[self.hash.len() - 1 - index_from_end_of_subsampled_list];
  68. while i_index > i {
  69. val = Keccak256::digest(val).into();
  70. i_index -= 1;
  71. }
  72. Ok(val)
  73. }
  74. #[allow(clippy::len_without_is_empty)]
  75. pub fn len(&self) -> usize {
  76. self.length
  77. }
  78. }
  79. /// `HashChainState` tracks the mapping between on-chain sequence numbers to hash chains.
  80. /// This struct is required to handle the case where the provider rotates their commitment,
  81. /// which requires tracking multiple hash chains here.
  82. pub struct HashChainState {
  83. // The sequence number where the hash chain starts. Must be stored in sorted order.
  84. pub offsets: Vec<usize>,
  85. pub hash_chains: Vec<PebbleHashChain>,
  86. }
  87. impl HashChainState {
  88. pub fn from_chain_at_offset(offset: usize, chain: PebbleHashChain) -> HashChainState {
  89. HashChainState {
  90. offsets: vec![offset],
  91. hash_chains: vec![chain],
  92. }
  93. }
  94. pub fn reveal(&self, sequence_number: u64) -> Result<[u8; 32]> {
  95. let sequence_number: usize = sequence_number.try_into()?;
  96. let chain_index = self
  97. .offsets
  98. .partition_point(|x| x <= &sequence_number)
  99. .checked_sub(1)
  100. .ok_or(anyhow::anyhow!(
  101. "Hash chain for the requested sequence number is not available."
  102. ))?;
  103. self.hash_chains[chain_index].reveal_ith(sequence_number - self.offsets[chain_index])
  104. }
  105. }
  106. #[cfg(test)]
  107. mod test {
  108. use {
  109. crate::state::PebbleHashChain,
  110. sha3::{Digest, Keccak256},
  111. };
  112. fn run_hash_chain_test(secret: [u8; 32], length: usize, sample_interval: usize) {
  113. // Calculate the hash chain the naive way as a comparison point to the subsampled implementation.
  114. let mut basic_chain = Vec::<[u8; 32]>::with_capacity(length);
  115. let mut current: [u8; 32] = Keccak256::digest(secret).into();
  116. basic_chain.push(current);
  117. for _ in 1..length {
  118. current = Keccak256::digest(current).into();
  119. basic_chain.push(current);
  120. }
  121. basic_chain.reverse();
  122. let chain = PebbleHashChain::new(secret, length, sample_interval);
  123. let mut last_val = chain.reveal_ith(0).unwrap();
  124. #[allow(clippy::needless_range_loop)]
  125. for i in 1..length {
  126. let cur_val = chain.reveal_ith(i).unwrap();
  127. println!("{}", i);
  128. assert_eq!(basic_chain[i], cur_val);
  129. let expected_last_val: [u8; 32] = Keccak256::digest(cur_val).into();
  130. assert_eq!(expected_last_val, last_val);
  131. last_val = cur_val;
  132. }
  133. }
  134. #[test]
  135. fn test_hash_chain() {
  136. run_hash_chain_test([0u8; 32], 10, 1);
  137. run_hash_chain_test([0u8; 32], 10, 2);
  138. run_hash_chain_test([0u8; 32], 10, 3);
  139. run_hash_chain_test([1u8; 32], 10, 1);
  140. run_hash_chain_test([1u8; 32], 10, 2);
  141. run_hash_chain_test([1u8; 32], 10, 3);
  142. run_hash_chain_test([0u8; 32], 100, 1);
  143. run_hash_chain_test([0u8; 32], 100, 2);
  144. run_hash_chain_test([0u8; 32], 100, 3);
  145. run_hash_chain_test([0u8; 32], 100, 7);
  146. run_hash_chain_test([0u8; 32], 100, 50);
  147. run_hash_chain_test([0u8; 32], 100, 55);
  148. }
  149. }