reclaim_ink/
lib.rs

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}