nectar_postage/
parallel.rs1use alloy_primitives::Address;
16use alloy_signer::k256::ecdsa::VerifyingKey;
17use alloy_signer::utils::public_key_to_address;
18use rayon::prelude::*;
19
20use crate::{Stamp, StampDigest, StampError};
21use nectar_primitives::SwarmAddress;
22
23#[derive(Debug, Clone)]
27pub struct VerifyResult {
28 pub index: usize,
30 pub result: Result<Address, StampError>,
32}
33
34pub fn verify_stamps_parallel(stamps: &[(&Stamp, &SwarmAddress)]) -> Vec<VerifyResult> {
66 stamps
67 .par_iter()
68 .enumerate()
69 .map(|(index, (stamp, address))| {
70 let result = recover_stamp_signer(stamp, address);
71 VerifyResult { index, result }
72 })
73 .collect()
74}
75
76pub fn verify_stamps_parallel_with_owner(
91 stamps: &[(&Stamp, &SwarmAddress)],
92 expected_owner: Address,
93) -> Vec<VerifyResult> {
94 stamps
95 .par_iter()
96 .enumerate()
97 .map(|(index, (stamp, address))| {
98 let result = verify_stamp_owner(stamp, address, expected_owner);
99 VerifyResult { index, result }
100 })
101 .collect()
102}
103
104pub fn verify_stamps_parallel_with_pubkey(
133 stamps: &[(&Stamp, &SwarmAddress)],
134 owner_pubkey: &VerifyingKey,
135) -> Vec<VerifyResult> {
136 let owner_address = public_key_to_address(owner_pubkey);
137
138 stamps
139 .par_iter()
140 .enumerate()
141 .map(|(index, (stamp, address))| {
142 let result = match stamp.verify_with_pubkey(address, owner_pubkey) {
143 Ok(()) => Ok(owner_address),
144 Err(e) => Err(e),
145 };
146 VerifyResult { index, result }
147 })
148 .collect()
149}
150
151fn recover_stamp_signer(stamp: &Stamp, address: &SwarmAddress) -> Result<Address, StampError> {
156 let digest = StampDigest::new(
157 *address,
158 stamp.batch(),
159 stamp.stamp_index(),
160 stamp.timestamp(),
161 );
162 let prehash = digest.to_prehash();
163
164 stamp
166 .signature()
167 .recover_address_from_msg(prehash.as_slice())
168 .map_err(|_| StampError::InvalidSignature)
169}
170
171fn verify_stamp_owner(
173 stamp: &Stamp,
174 address: &SwarmAddress,
175 expected_owner: Address,
176) -> Result<Address, StampError> {
177 let recovered = recover_stamp_signer(stamp, address)?;
178 if recovered != expected_owner {
179 return Err(StampError::OwnerMismatch {
180 expected: expected_owner,
181 actual: recovered,
182 });
183 }
184 Ok(recovered)
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use alloy_primitives::B256;
191 use alloy_signer::SignerSync;
192 use alloy_signer_local::PrivateKeySigner;
193
194 use crate::{Stamp, StampIndex, current_timestamp};
195
196 fn create_test_stamp(
198 signer: &PrivateKeySigner,
199 chunk_address: &SwarmAddress,
200 batch_id: B256,
201 ) -> Stamp {
202 let index = StampIndex::new(0, 0);
203 let timestamp = current_timestamp();
204 let digest = StampDigest::new(*chunk_address, batch_id, index, timestamp);
205 let prehash = digest.to_prehash();
206
207 let sig = signer.sign_message_sync(prehash.as_slice()).unwrap();
209 Stamp::with_index(batch_id, index, timestamp, sig)
210 }
211
212 #[test]
213 fn test_parallel_verification() {
214 let signer = PrivateKeySigner::random();
215 let expected_owner = signer.address();
216 let batch_id = B256::ZERO;
217
218 let addresses: Vec<_> = (0..50)
220 .map(|_| SwarmAddress::from(B256::random()))
221 .collect();
222 let stamps: Vec<_> = addresses
223 .iter()
224 .map(|addr| create_test_stamp(&signer, addr, batch_id))
225 .collect();
226
227 let verify_input: Vec<_> = stamps.iter().zip(addresses.iter()).collect();
229 let verify_results = verify_stamps_parallel_with_owner(&verify_input, expected_owner);
230
231 assert_eq!(verify_results.len(), 50);
232 for result in &verify_results {
233 assert!(result.result.is_ok());
234 assert_eq!(result.result.as_ref().unwrap(), &expected_owner);
235 }
236 }
237
238 #[test]
239 fn test_verify_wrong_signer() {
240 let signer = PrivateKeySigner::random();
241 let wrong_owner = Address::repeat_byte(0xFF);
242 let batch_id = B256::ZERO;
243
244 let address = SwarmAddress::from(B256::random());
245 let stamp = create_test_stamp(&signer, &address, batch_id);
246
247 let verify_input = [(&stamp, &address)];
249
250 let verify_results = verify_stamps_parallel_with_owner(&verify_input, wrong_owner);
251 assert!(matches!(
252 verify_results[0].result,
253 Err(StampError::OwnerMismatch { .. })
254 ));
255 }
256
257 #[test]
258 fn test_verify_stamps_parallel_basic() {
259 let signer = PrivateKeySigner::random();
260 let expected_owner = signer.address();
261 let batch_id = B256::ZERO;
262
263 let address = SwarmAddress::from(B256::random());
264 let stamp = create_test_stamp(&signer, &address, batch_id);
265
266 let verify_input = [(&stamp, &address)];
267 let results = verify_stamps_parallel(&verify_input);
268
269 assert_eq!(results.len(), 1);
270 assert_eq!(results[0].result.as_ref().unwrap(), &expected_owner);
271 }
272
273 #[test]
274 fn test_verify_stamps_parallel_with_pubkey() {
275 let signer = PrivateKeySigner::random();
276 let expected_owner = signer.address();
277 let batch_id = B256::ZERO;
278
279 let addresses: Vec<_> = (0..50)
281 .map(|_| SwarmAddress::from(B256::random()))
282 .collect();
283 let stamps: Vec<_> = addresses
284 .iter()
285 .map(|addr| create_test_stamp(&signer, addr, batch_id))
286 .collect();
287
288 let pubkey = stamps[0].recover_pubkey(&addresses[0]).unwrap();
290
291 let verify_input: Vec<_> = stamps.iter().zip(addresses.iter()).collect();
293 let verify_results = verify_stamps_parallel_with_pubkey(&verify_input, &pubkey);
294
295 assert_eq!(verify_results.len(), 50);
296 for result in &verify_results {
297 assert!(result.result.is_ok());
298 assert_eq!(result.result.as_ref().unwrap(), &expected_owner);
299 }
300 }
301}