1use crate::common::{Address as RewardsAddress, Amount};
10use crate::merkle_batch_payment::{CANDIDATES_PER_POOL, CandidateNode, PoolCommitment, PoolHash};
11use serde::{Deserialize, Serialize};
12use std::collections::HashSet;
13use thiserror::Error;
14use tiny_keccak::{Hasher, Sha3};
15use xor_name::XorName;
16
17use super::merkle_tree::MerkleBranch;
18use super::merkle_tree::MidpointProof;
19
20#[derive(Debug, Error, Clone, PartialEq, Eq)]
22pub enum MerklePaymentVerificationError {
23 #[error("Invalid signature for node with address {address}")]
24 InvalidNodeSignature { address: RewardsAddress },
25 #[error("Timestamp mismatch for node {address}: expected {expected}, got {got}")]
26 TimestampMismatch {
27 address: RewardsAddress,
28 expected: u64,
29 got: u64,
30 },
31 #[error("Data type mismatch for node {address}: expected {expected}, got {got}")]
32 DataTypeMismatch {
33 address: RewardsAddress,
34 expected: u32,
35 got: u32,
36 },
37 #[error("Commitment does not match pool")]
38 CommitmentDoesNotMatchPool,
39 #[error("Paid node index {index} out of bounds (pool size: {pool_size})")]
40 PaidNodeIndexOutOfBounds { index: usize, pool_size: usize },
41 #[error("Paid address mismatch at index {index}: expected {expected}, got {got}")]
42 PaidAddressMismatch {
43 index: usize,
44 expected: RewardsAddress,
45 got: RewardsAddress,
46 },
47 #[error("Winner pool hash not found in on-chain commitments")]
48 WinnerPoolNotInCommitments,
49 #[error(
50 "Price mismatch at index {index}: on_chain={on_chain_price}, expected={expected_price}"
51 )]
52 PriceMismatch {
53 index: usize,
54 on_chain_price: String,
55 expected_price: String,
56 },
57}
58
59#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
65pub struct MerklePaymentCandidateNode {
66 pub pub_key: Vec<u8>,
68
69 pub price: Amount,
71
72 pub reward_address: RewardsAddress,
74
75 pub merkle_payment_timestamp: u64,
77
78 pub signature: Vec<u8>,
80
81 #[serde(default)]
86 pub committed_key_count: u32,
87
88 #[serde(default)]
91 pub commitment_pin: Option<[u8; 32]>,
92}
93
94impl MerklePaymentCandidateNode {
95 pub fn bytes_to_sign(
106 price: &Amount,
107 reward_address: &RewardsAddress,
108 timestamp: u64,
109 committed_key_count: u32,
110 commitment_pin: &Option<[u8; 32]>,
111 ) -> Vec<u8> {
112 let mut bytes = Vec::new();
113 bytes.extend_from_slice(&price.to_le_bytes::<32>());
114 bytes.extend_from_slice(reward_address.as_slice());
115 bytes.extend_from_slice(×tamp.to_le_bytes());
116 bytes.extend_from_slice(&committed_key_count.to_le_bytes());
117 match commitment_pin {
118 Some(pin) => {
119 bytes.push(1u8);
120 bytes.extend_from_slice(pin);
121 }
122 None => bytes.push(0u8),
123 }
124 bytes
125 }
126
127 pub(crate) fn to_bytes(&self) -> Vec<u8> {
133 let mut bytes = Vec::new();
134 bytes.extend_from_slice(&self.pub_key);
135 bytes.extend_from_slice(&self.price.to_le_bytes::<32>());
136 bytes.extend_from_slice(self.reward_address.as_slice());
137 bytes.extend_from_slice(&self.merkle_payment_timestamp.to_le_bytes());
138 bytes.extend_from_slice(&self.committed_key_count.to_le_bytes());
139 match &self.commitment_pin {
140 Some(pin) => {
141 bytes.push(1u8);
142 bytes.extend_from_slice(pin);
143 }
144 None => bytes.push(0u8),
145 }
146 bytes.extend_from_slice(&self.signature);
147 bytes
148 }
149}
150
151#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
153pub struct MerklePaymentCandidatePool {
154 pub midpoint_proof: MidpointProof,
156
157 pub candidate_nodes: [MerklePaymentCandidateNode; CANDIDATES_PER_POOL],
159}
160
161pub(crate) fn sha3_256(input: &[u8]) -> [u8; 32] {
163 let mut sha3 = Sha3::v256();
164 let mut output = [0u8; 32];
165 sha3.update(input);
166 sha3.finalize(&mut output);
167 output
168}
169
170impl MerklePaymentCandidatePool {
171 pub fn hash(&self) -> PoolHash {
173 let mut bytes = Vec::new();
174 bytes.extend_from_slice(&self.midpoint_proof.hash());
175 bytes.extend_from_slice(&(self.candidate_nodes.len() as u32).to_le_bytes());
176 for node in &self.candidate_nodes {
177 bytes.extend_from_slice(&node.to_bytes());
178 }
179 sha3_256(&bytes)
180 }
181
182 pub fn to_commitment(&self) -> PoolCommitment {
184 let candidates: [CandidateNode; CANDIDATES_PER_POOL] =
185 self.candidate_nodes.clone().map(|node| CandidateNode {
186 rewards_address: node.reward_address,
187 price: node.price,
188 });
189
190 PoolCommitment {
191 pool_hash: self.hash(),
192 candidates,
193 }
194 }
195
196 pub fn verify_prices(
198 &self,
199 on_chain_commitments: &[PoolCommitment],
200 winner_pool_hash: &PoolHash,
201 ) -> Result<(), MerklePaymentVerificationError> {
202 let on_chain_winner = on_chain_commitments
203 .iter()
204 .find(|pc| pc.pool_hash == *winner_pool_hash)
205 .ok_or(MerklePaymentVerificationError::WinnerPoolNotInCommitments)?;
206
207 for (i, (on_chain_candidate, signed_node)) in on_chain_winner
208 .candidates
209 .iter()
210 .zip(self.candidate_nodes.iter())
211 .enumerate()
212 {
213 if on_chain_candidate.price != signed_node.price {
214 return Err(MerklePaymentVerificationError::PriceMismatch {
215 index: i,
216 on_chain_price: on_chain_candidate.price.to_string(),
217 expected_price: signed_node.price.to_string(),
218 });
219 }
220 }
221
222 Ok(())
223 }
224
225 pub fn candidate_nodes_addresses(&self) -> HashSet<RewardsAddress> {
227 self.candidate_nodes
228 .iter()
229 .map(|node| node.reward_address)
230 .collect()
231 }
232}
233
234#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
238pub struct MerklePaymentProof {
239 pub address: XorName,
241
242 pub data_proof: MerkleBranch,
244
245 pub winner_pool: MerklePaymentCandidatePool,
247
248 #[serde(default)]
256 pub commitment_sidecars: Vec<Vec<u8>>,
257}
258
259impl MerklePaymentProof {
260 pub fn new(
262 address: XorName,
263 data_proof: MerkleBranch,
264 winner_pool: MerklePaymentCandidatePool,
265 ) -> Self {
266 Self {
267 address,
268 data_proof,
269 winner_pool,
270 commitment_sidecars: Vec::new(),
271 }
272 }
273
274 pub fn winner_pool_hash(&self) -> PoolHash {
276 self.winner_pool.hash()
277 }
278}