hermit_toolkit_viewing_key/
lib.rs1extern crate core;
2
3use subtle::ConstantTimeEq;
4
5use cosmwasm_std::{Env, HumanAddr, ReadonlyStorage, StdError, StdResult, Storage};
6use cosmwasm_storage::{PrefixedStorage, ReadonlyPrefixedStorage};
7
8use hermit_toolkit_crypto::{sha_256, Prng, SHA256_HASH_SIZE};
9
10pub const VIEWING_KEY_SIZE: usize = SHA256_HASH_SIZE;
11pub const VIEWING_KEY_PREFIX: &str = "api_key_";
12const SEED_KEY: &[u8] = b"::seed";
13
14pub struct ViewingKey;
19
20impl ViewingKeyStore for ViewingKey {
21 const STORAGE_KEY: &'static [u8] = b"viewing_keys";
22}
23
24pub trait ViewingKeyStore {
29 const STORAGE_KEY: &'static [u8];
30
31 fn set_seed<S: Storage>(storage: &mut S, seed: &[u8]) {
33 let mut seed_key = Vec::new();
34 seed_key.extend_from_slice(Self::STORAGE_KEY);
35 seed_key.extend_from_slice(SEED_KEY);
36
37 storage.set(&seed_key, seed)
38 }
39
40 fn create<S: Storage>(
44 storage: &mut S,
45 env: &Env,
46 account: &HumanAddr,
47 entropy: &[u8],
48 ) -> String {
49 let mut seed_key = Vec::with_capacity(Self::STORAGE_KEY.len() + SEED_KEY.len());
50 seed_key.extend_from_slice(Self::STORAGE_KEY);
51 seed_key.extend_from_slice(SEED_KEY);
52 let seed = storage.get(&seed_key).unwrap_or_default();
53
54 let (viewing_key, next_seed) = new_viewing_key(env, &seed, entropy);
55 let mut balance_store = PrefixedStorage::new(Self::STORAGE_KEY, storage);
56 let hashed_key = sha_256(viewing_key.as_bytes());
57 balance_store.set(account.as_str().as_bytes(), &hashed_key);
58
59 storage.set(&seed_key, &next_seed);
60
61 viewing_key
62 }
63
64 fn set<S: Storage>(storage: &mut S, account: &HumanAddr, viewing_key: &str) {
66 let mut balance_store = PrefixedStorage::new(Self::STORAGE_KEY, storage);
67 balance_store.set(
68 account.as_str().as_bytes(),
69 &sha_256(viewing_key.as_bytes()),
70 );
71 }
72
73 fn check<S: ReadonlyStorage>(
75 storage: &S,
76 account: &HumanAddr,
77 viewing_key: &str,
78 ) -> StdResult<()> {
79 let balance_store = ReadonlyPrefixedStorage::new(Self::STORAGE_KEY, storage);
80 let expected_hash = balance_store.get(account.as_str().as_bytes());
81 let expected_hash = match &expected_hash {
82 Some(hash) => hash.as_slice(),
83 None => &[0u8; VIEWING_KEY_SIZE],
84 };
85 let key_hash = sha_256(viewing_key.as_bytes());
86 if ct_slice_compare(&key_hash, expected_hash) {
87 Ok(())
88 } else {
89 Err(StdError::unauthorized())
90 }
91 }
92}
93
94fn new_viewing_key(env: &Env, seed: &[u8], entropy: &[u8]) -> (String, [u8; 32]) {
95 let entropy_len = 16 + env.message.sender.len() + entropy.len();
97 let mut rng_entropy = Vec::with_capacity(entropy_len);
98 rng_entropy.extend_from_slice(&env.block.height.to_be_bytes());
99 rng_entropy.extend_from_slice(&env.block.time.to_be_bytes());
100 rng_entropy.extend_from_slice(env.message.sender.0.as_bytes());
101 rng_entropy.extend_from_slice(entropy);
102
103 let mut rng = Prng::new(seed, &rng_entropy);
104
105 let rand_slice = rng.rand_bytes();
106
107 let key = sha_256(&rand_slice);
108
109 let viewing_key = VIEWING_KEY_PREFIX.to_string() + &base64::encode(&key);
110 (viewing_key, rand_slice)
111}
112
113fn ct_slice_compare(s1: &[u8], s2: &[u8]) -> bool {
114 bool::from(s1.ct_eq(s2))
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 use cosmwasm_std::testing::{mock_dependencies, mock_env};
122
123 #[test]
124 fn test_viewing_keys() {
125 let account = HumanAddr("user-1".to_string());
126
127 let mut deps = mock_dependencies(20, &[]);
128 let env = mock_env(account.as_str(), &[]);
129
130 let result = ViewingKey::check(&deps.storage, &account, "fake key");
132 assert_eq!(result, Err(StdError::unauthorized()));
133
134 ViewingKey::set_seed(&mut deps.storage, b"seed");
135 let viewing_key = ViewingKey::create(&mut deps.storage, &env, &account, b"entropy");
136
137 let result = ViewingKey::check(&deps.storage, &account, &viewing_key);
138 assert_eq!(result, Ok(()));
139
140 let viewing_key_2 = ViewingKey::create(&mut deps.storage, &env, &account, b"entropy");
142 assert_ne!(viewing_key, viewing_key_2);
143
144 let result = ViewingKey::check(&deps.storage, &account, "fake key");
146 assert_eq!(result, Err(StdError::unauthorized()));
147
148 let viewing_key = "custom key";
149
150 ViewingKey::set(&mut deps.storage, &account, viewing_key);
151
152 let result = ViewingKey::check(&deps.storage, &account, viewing_key);
153 assert_eq!(result, Ok(()));
154
155 let result = ViewingKey::check(&deps.storage, &account, "fake key");
157 assert_eq!(result, Err(StdError::unauthorized()));
158 }
159}