1#![cfg_attr(not(feature = "std"), no_std, no_main)]
2
3pub use reclaim::ReclaimRef;
4
5#[ink::contract]
6pub mod reclaim {
7 use ecdsa::RecoveryId;
8 use ink::prelude::string::String;
9 use ink::prelude::string::ToString;
10 use ink::prelude::vec::Vec;
11 use ink::prelude::{format, vec};
12 use ink::storage::Mapping;
13 use k256::ecdsa::{Signature, VerifyingKey};
14 use keccak_hash::keccak256;
15 use sha2::{Digest, Sha256};
16
17 #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode, Clone)]
18 #[cfg_attr(
19 feature = "std",
20 derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout)
21 )]
22 pub struct Witness {
23 pub address: String,
24 pub host: [u8; 32],
25 }
26
27 impl Witness {
28 pub fn get_addresses(witness: Vec<Witness>) -> Vec<String> {
29 let mut vec_addresses = vec![];
30 for wit in witness {
31 vec_addresses.push(wit.address);
32 }
33 vec_addresses
34 }
35 }
36
37 #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode, Clone)]
38 #[cfg_attr(
39 feature = "std",
40 derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout)
41 )]
42 pub struct Epoch {
43 pub id: u128,
44 pub timestamp_start: u64,
45 pub timestamp_end: u64,
46 pub minimum_witness_for_claim_creation: u128,
47 pub witness: Vec<Witness>,
48 }
49
50 fn generate_random_seed(bytes: Vec<u8>, offset: usize) -> u32 {
51 let hash_slice = &bytes[offset..offset + 4];
52 let mut seed = 0u32;
53 for (i, &byte) in hash_slice.iter().enumerate() {
54 seed |= u32::from(byte) << (i * 8);
55 }
56
57 seed
58 }
59
60 #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
61 #[cfg_attr(
62 feature = "std",
63 derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout)
64 )]
65 pub struct ClaimInfo {
66 pub provider: String,
67 pub parameters: String,
68 pub context: String,
69 }
70
71 impl ClaimInfo {
72 pub fn hash(&self) -> Vec<u8> {
73 let mut hasher = Sha256::new();
74 let hash_str = format!(
75 "{}\n{}\n{}",
76 &self.provider, &self.parameters, &self.context
77 );
78 hasher.update(hash_str.as_bytes());
79 hasher.finalize().to_vec()
80 }
81 }
82
83 #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
84 #[cfg_attr(
85 feature = "std",
86 derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout)
87 )]
88 pub struct CompleteClaimData {
89 pub identifier: Vec<u8>,
90 pub owner: String,
91 pub epoch: u128,
92 pub timestamp_s: u64,
93 }
94
95 impl CompleteClaimData {
96 pub fn serialise(&self) -> Vec<u8> {
97 let hash_str = format!(
98 "{}\n{}\n{}\n{}",
99 hex::encode(&self.identifier),
100 self.owner,
101 &self.timestamp_s.to_string(),
102 &self.epoch.to_string()
103 );
104 hash_str.as_bytes().to_vec()
105 }
106 }
107
108 #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
109 #[cfg_attr(
110 feature = "std",
111 derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout)
112 )]
113 pub struct SignedClaim {
114 pub claim: CompleteClaimData,
115 pub bytes: Vec<(String, u8)>,
116 }
117
118 impl SignedClaim {
119 pub fn recover_signers_of_signed_claim(self) -> Vec<String> {
120 let mut expected = vec![];
121 let mut hasher = Sha256::new();
122 let serialised_claim = self.claim.serialise();
123 hasher.update(serialised_claim);
124 let mut result = hasher.finalize().to_vec();
125 keccak256(&mut result);
126 for (signature, recid_8) in self.bytes {
127 let arr = Self::recover_raw_signature(signature);
128 let slice_arr = arr.as_slice();
129 let sig = Signature::try_from(slice_arr).unwrap();
130 let recid = RecoveryId::try_from(recid_8).unwrap();
131 let recovered_key =
132 VerifyingKey::recover_from_prehash(&result, &sig, recid).unwrap();
133
134 let str_recovered_key = format!("{:?}", recovered_key);
135 expected.push(str_recovered_key);
136 }
137
138 expected
139 }
140
141 pub fn fetch_witness_for_claim(
142 epoch: Epoch,
143 identifier: Vec<u8>,
144 claim_timestamp: u128,
145 ) -> Vec<Witness> {
146 let mut selected_witness = vec![];
147 let hash_str = format!(
148 "{}\n{}\n{}\n{}",
149 hex::encode(identifier),
150 epoch.minimum_witness_for_claim_creation,
151 claim_timestamp,
152 epoch.id
153 );
154 let result = hash_str.as_bytes().to_vec();
155 let mut hasher = Sha256::new();
156 hasher.update(result);
157 let hash_result = hasher.finalize().to_vec();
158 let witenesses_left_list = epoch.witness;
159 let mut byte_offset = 0;
160 let witness_left = witenesses_left_list.len();
161 for _i in 0..epoch.minimum_witness_for_claim_creation {
162 let random_seed = generate_random_seed(hash_result.clone(), byte_offset) as usize;
163 let witness_index = random_seed % witness_left;
164 let witness = witenesses_left_list.get(witness_index);
165 if let Some(data) = witness {
166 selected_witness.push(data.clone())
167 };
168 byte_offset = (byte_offset + 4) % hash_result.len();
169 }
170
171 selected_witness
172 }
173
174 pub fn recover_raw_signature(signature: String) -> [u8; 64]{
175 let ss = signature.as_str();
176 let sss = &ss[28..156].to_lowercase();
177 let sss_str = sss.as_str();
178 let mut arr = [0_u8; 64];
179 for i in 0..64 {
180 let ss = &sss_str[(2 * i)..(2 * i + 2)];
181 let z = u8::from_str_radix(ss, 16).unwrap();
182 arr[i] = z;
183 }
184 arr
185 }
186 }
187
188 #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
189 #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
190 pub enum ReclaimError {
191 OnlyOwner,
192 AlreadyInitialized,
193 HashMismatch,
194 LengthMismatch,
195 SignatureMismatch,
196 }
197
198 #[ink(event)]
199 pub struct EpochAdded {
200 epoch_id: u128,
201 }
202
203 #[ink(event)]
204 pub struct ProofVerified {
205 epoch_id: u128,
206 }
207
208 #[ink(storage)]
209 pub struct Reclaim {
210 pub owner: AccountId,
211 pub current_epoch: u128,
212 pub epochs: Mapping<u128, Epoch>,
213 }
214
215 impl Default for Reclaim {
216 fn default() -> Self {
217 Self::new()
218 }
219 }
220
221 impl Reclaim {
222 #[ink(constructor)]
223 pub fn new() -> Self {
224 let owner = Self::env().caller();
225 let current_epoch = 0_u128;
226 let epochs = Mapping::new();
227 Self {
228 owner,
229 current_epoch,
230 epochs,
231 }
232 }
233
234 #[ink(message)]
235 pub fn add_epoch(
236 &mut self,
237 witness: Vec<Witness>,
238 minimum_witness: u128,
239 ) -> Result<(), ReclaimError> {
240 let caller = Self::env().caller();
241 if self.owner != caller {
242 return Err(ReclaimError::OnlyOwner);
243 }
244 let new_epoch_id = self.current_epoch + 1_u128;
245 let now = ink::env::block_timestamp::<ink::env::DefaultEnvironment>();
246 let epoch = Epoch {
247 id: new_epoch_id,
248 witness,
249 timestamp_start: now,
250 timestamp_end: now + 10000_u64,
251 minimum_witness_for_claim_creation: minimum_witness,
252 };
253 self.epochs.insert(new_epoch_id, &epoch);
254 self.current_epoch = new_epoch_id;
255 Self::env().emit_event(EpochAdded {
256 epoch_id: new_epoch_id,
257 });
258 Ok(())
259 }
260
261 #[ink(message)]
262 pub fn verify_proof(
263 &mut self,
264 claim_info: ClaimInfo,
265 signed_claim: SignedClaim,
266 ) -> Result<(), ReclaimError> {
267 let epoch_count = self.current_epoch;
268 let current_epoch = self.epochs.get(epoch_count).unwrap();
269 let hashed = claim_info.hash();
270 if signed_claim.claim.identifier != hashed {
271 return Err(ReclaimError::HashMismatch);
272 }
273 let expected_witness = crate::reclaim::SignedClaim::fetch_witness_for_claim(
274 current_epoch.clone(),
275 signed_claim.claim.identifier.clone(),
276 signed_claim.claim.timestamp_s.into(),
277 );
278 let expected_witness_addresses = Witness::get_addresses(expected_witness);
279
280 let signed_witness = signed_claim.recover_signers_of_signed_claim();
281
282 if expected_witness_addresses.len() != signed_witness.len() {
283 return Err(ReclaimError::LengthMismatch);
284 }
285 for signed in signed_witness {
286 if !expected_witness_addresses.contains(&signed) {
287 return Err(ReclaimError::SignatureMismatch);
288 }
289 }
290 Self::env().emit_event(ProofVerified {
291 epoch_id: current_epoch.id,
292 });
293 Ok(())
294 }
295
296 #[ink(message)]
297 pub fn get_owner(&self) -> AccountId {
298 self.owner
299 }
300
301 #[ink(message)]
302 pub fn get_current_epoch(&self) -> u128 {
303 self.current_epoch
304 }
305 }
306
307 #[cfg(test)]
308 mod tests {
309 use ink::env::test::{default_accounts, DefaultAccounts};
310 use k256::ecdsa::SigningKey;
311 use rand_core::OsRng;
312
313 use super::*;
314
315 fn get_default_test_accounts() -> DefaultAccounts<ink::env::DefaultEnvironment> {
316 default_accounts::<ink::env::DefaultEnvironment>()
317 }
318
319 fn set_caller(caller: AccountId) {
320 ink::env::test::set_caller::<ink::env::DefaultEnvironment>(caller);
321 }
322
323 #[ink::test]
324 fn init() {
325 let accounts = get_default_test_accounts();
326 let alice = accounts.alice;
327 set_caller(alice);
328 let reclaim = Reclaim::new();
329 assert_eq!(reclaim.get_owner(), alice);
330 assert_eq!(reclaim.get_current_epoch(), 0_u128);
331 }
332
333 #[ink::test]
334 fn should_add_epochs() {
335 let mut reclaim = Reclaim::new();
336 let signing_key = SigningKey::random(&mut OsRng);
337 let verifying_key = VerifyingKey::from(&signing_key);
338 let str_verifying_key = format!("{:?}", verifying_key);
339 let w1 = Witness {
340 address: str_verifying_key,
341 host: [1_u8; 32],
342 };
343 let mut witnesses_vec = Vec::<Witness>::new();
344 witnesses_vec.push(w1);
345 let minimum_witness = 1;
346 assert_eq!(reclaim.add_epoch(witnesses_vec, minimum_witness), Ok(()));
347 }
348
349 #[ink::test]
350 fn should_approve_valid_proofs() {
351 let mut reclaim = Reclaim::new();
352 let signing_key = SigningKey::random(&mut OsRng);
353 let verifying_key = VerifyingKey::from(&signing_key);
354 let str_verifying_key = format!("{:?}", verifying_key);
355 let w1 = Witness {
356 address: str_verifying_key.clone(),
357 host: [1_u8; 32],
358 };
359 let mut witnesses_vec = Vec::<Witness>::new();
360 witnesses_vec.push(w1);
361 let minimum_witness = 1;
362 assert_eq!(reclaim.add_epoch(witnesses_vec, minimum_witness), Ok(()));
363 let claim_info = ClaimInfo {
364 provider: "provider".to_string(),
365 parameters: "{}".to_string(),
366 context: "context".to_string(),
367 };
368 let hashed = claim_info.hash();
369 let now = ink::env::block_timestamp::<ink::env::DefaultEnvironment>();
370 let complete_claim_data = CompleteClaimData {
371 identifier: hashed,
372 owner: str_verifying_key,
373 epoch: 1_u128,
374 timestamp_s: now,
375 };
376 let mut hasher = Sha256::new();
377 let serialised_claim = complete_claim_data.serialise();
378 hasher.update(serialised_claim);
379 let mut result = hasher.finalize().to_vec();
380 keccak256(&mut result);
381 let mut sigs = Vec::new();
382 let (signature, recid) = signing_key.sign_prehash_recoverable(&result).unwrap();
383 let str_signature = format!("{:?}", signature);
384
385 let recid_8: u8 = recid.try_into().unwrap();
386 sigs.push((str_signature, recid_8));
387
388 let signed_claim = SignedClaim {
389 claim: complete_claim_data,
390 bytes: sigs,
391 };
392 assert_eq!(reclaim.verify_proof(claim_info, signed_claim), Ok(()));
393 }
394
395 #[ink::test]
396 fn should_not_approve_invalid_proofs() {
397 let mut reclaim = Reclaim::new();
398 let signing_key = SigningKey::random(&mut OsRng);
399 let faulty_signing_key = SigningKey::random(&mut OsRng);
400 let verifying_key = VerifyingKey::from(&signing_key);
401 let str_verifying_key = format!("{:?}", verifying_key);
402 let w1 = Witness {
403 address: str_verifying_key.clone(),
404 host: [1_u8; 32],
405 };
406 let mut witnesses_vec = Vec::<Witness>::new();
407 witnesses_vec.push(w1);
408 let minimum_witness = 1;
409 assert_eq!(reclaim.add_epoch(witnesses_vec, minimum_witness), Ok(()));
410 let claim_info = ClaimInfo {
411 provider: "provider".to_string(),
412 parameters: "{}".to_string(),
413 context: "context".to_string(),
414 };
415 let hashed = claim_info.hash();
416 let now = ink::env::block_timestamp::<ink::env::DefaultEnvironment>();
417 let complete_claim_data = CompleteClaimData {
418 identifier: hashed,
419 owner: str_verifying_key,
420 epoch: 1_u128,
421 timestamp_s: now,
422 };
423 let mut hasher = Sha256::new();
424 let serialised_claim = complete_claim_data.serialise();
425 hasher.update(serialised_claim);
426 let mut result = hasher.finalize().to_vec();
427 keccak256(&mut result);
428 let mut sigs = Vec::new();
429 let (signature, recid) = faulty_signing_key
430 .sign_prehash_recoverable(&result)
431 .unwrap();
432 let str_signature = format!("{:?}", signature);
433
434 let recid_8: u8 = recid.try_into().unwrap();
435 sigs.push((str_signature, recid_8));
436
437 let signed_claim = SignedClaim {
438 claim: complete_claim_data,
439 bytes: sigs,
440 };
441 assert_eq!(
442 reclaim.verify_proof(claim_info, signed_claim),
443 Err(ReclaimError::SignatureMismatch)
444 );
445 }
446 }
447}