Skip to main content

hippo_sdk/
lib.rs

1#[cfg(test)]
2mod tests;
3mod types;
4use std::str::FromStr;
5
6use aes_gcm::{
7    aead::{Aead, AeadCore, OsRng as AesRng},
8    Aes256Gcm, Key, KeyInit, Nonce,
9};
10use secp256k1_zkp::{
11    ecdh, rand::rngs::OsRng as Secp256k1Rng, verify_commitments_sum_to_equal, Generator, Message,
12    PedersenCommitment, PublicKey, Secp256k1, SecretKey,
13};
14use secp256k1_zkp::{ecdsa::Signature, Tweak};
15use secp256k1_zkp::{
16    hashes::{sha256, Hash},
17    Tag,
18};
19use wasm_bindgen::prelude::*;
20
21use types::{AesEncryptedData, Commitment, Did, EncodingType, EncryptedData, KeyPair};
22
23use base64::{engine::general_purpose::STANDARD, Engine as _};
24
25use crate::types::{AesEncryptedDataBytes, EncryptedDataBytes};
26
27#[wasm_bindgen]
28pub fn create_keypair() -> KeyPair {
29    let secp = Secp256k1::new();
30    let (secret_key, public_key) = secp.generate_keypair(&mut Secp256k1Rng);
31    KeyPair::new(
32        public_key.to_string(),
33        secret_key.display_secret().to_string(),
34    )
35}
36
37#[wasm_bindgen]
38pub fn key_to_did(pubkey: String) -> Did {
39    Did::new("did:hp:".to_owned() + &pubkey)
40}
41
42#[wasm_bindgen]
43pub fn did_to_key(did: Did) -> Result<String, JsError> {
44    did.id()
45        .strip_prefix("did:hp:")
46        .map(|s| s.to_string())
47        .ok_or_else(|| JsError::new("Invalid DID format: missing did:hp: prefix"))
48}
49
50#[wasm_bindgen]
51pub fn encrypt(
52    data: String,
53    pubkey: String,
54    encoding_type: EncodingType,
55) -> Result<EncryptedData, JsError> {
56    let secp = Secp256k1::new();
57    // Alice: one-off key pair to caculate shared secret.
58    let (secret_key, public_key) = secp.generate_keypair(&mut Secp256k1Rng);
59    // Bob: param pubkey is the one who's able to decrypt the data.
60    let encrypt_to_pubkey = secp256k1_zkp::PublicKey::from_str(&pubkey)
61        .map_err(|e| JsError::new(&format!("Invalid public key: {}", e)))?;
62    // Only Alice and Bob can know the secret, which is used as a key to encrypt data.
63    let shared_secret = ecdh::SharedSecret::new(&encrypt_to_pubkey, &secret_key).secret_bytes();
64
65    let aes_encrypted_data = encrypt_aes(data, hex::encode(&shared_secret), encoding_type)?;
66
67    Ok(EncryptedData::new(
68        public_key.to_string(),
69        pubkey,
70        aes_encrypted_data.data(),
71        aes_encrypted_data.nonce(),
72    ))
73}
74
75#[wasm_bindgen]
76pub fn decrypt(
77    data: EncryptedData,
78    privkey: String,
79    encoding_type: EncodingType,
80) -> Result<String, JsError> {
81    // Alice: one-off pubkey to calculate shared secret.
82    let encrypt_from_pubkey = secp256k1_zkp::PublicKey::from_str(&data.pubkey_from())
83        .map_err(|e| JsError::new(&format!("Invalid public key in data: {}", e)))?;
84    // Bob: the one who's able to decrypt the data with privkey.
85    let privkey_to_decrypt = secp256k1_zkp::SecretKey::from_str(&privkey)
86        .map_err(|e| JsError::new(&format!("Invalid private key: {}", e)))?;
87    // Only Alice and Bob can know the secret, which is used as a key to encrypt data.
88    let shared_secret =
89        ecdh::SharedSecret::new(&encrypt_from_pubkey, &privkey_to_decrypt).secret_bytes();
90
91    decrypt_aes(
92        AesEncryptedData::new(data.data(), data.nonce()),
93        hex::encode(&shared_secret),
94        encoding_type,
95    )
96}
97
98#[wasm_bindgen]
99pub fn encrypt_aes(
100    data: String,
101    key: String,
102    encoding_type: EncodingType,
103) -> Result<AesEncryptedData, JsError> {
104    // Secret key just fit into AES key
105    let hex_key =
106        hex::decode(key).map_err(|e| JsError::new(&format!("Key is malformed: {}", e)))?;
107    let aes_key = Key::<Aes256Gcm>::from_slice(&hex_key);
108    let cipher = Aes256Gcm::new(&aes_key);
109    // Nonce must be random and non-reusable value
110    let nonce = Aes256Gcm::generate_nonce(&mut AesRng); // 96-bits; unique per message
111    let data_bytes_vec;
112    let data_bytes: &[u8] = match encoding_type {
113        EncodingType::UTF8 => data.as_bytes(),
114        EncodingType::HEX => {
115            data_bytes_vec = hex::decode(data)
116                .map_err(|e| JsError::new(&format!("Wrong hex format data: {}", e)))?;
117            &data_bytes_vec
118        }
119        EncodingType::BASE64 => {
120            data_bytes_vec = STANDARD
121                .decode(data)
122                .map_err(|e| JsError::new(&format!("Wrong base64 format data: {}", e)))?;
123            &data_bytes_vec
124        }
125    };
126    let ciphertext = cipher
127        .encrypt(&nonce, data_bytes)
128        .map_err(|e| JsError::new(&format!("AES encryption failed: {}", e)))?;
129    Ok(AesEncryptedData::new(
130        hex::encode(&ciphertext),
131        hex::encode(&nonce),
132    ))
133}
134#[wasm_bindgen]
135pub fn decrypt_aes(
136    data: AesEncryptedData,
137    key: String,
138    encoding_type: EncodingType,
139) -> Result<String, JsError> {
140    // Secret key just fit into AES key
141    let hex_key =
142        hex::decode(key).map_err(|e| JsError::new(&format!("Key is malformed: {}", e)))?;
143    let aes_key = Key::<Aes256Gcm>::from_slice(&hex_key);
144    let decipher = Aes256Gcm::new(&aes_key);
145    // Nonce hex to bytes
146    let nonce_bytes =
147        hex::decode(&data.nonce()).map_err(|e| JsError::new(&format!("Invalid nonce: {}", e)))?;
148    let nonce = Nonce::from_slice(&nonce_bytes);
149    // Data hex to bytes
150    let data_bytes =
151        hex::decode(&data.data()).map_err(|e| JsError::new(&format!("Invalid data: {}", e)))?;
152    let decrypted_data = decipher
153        .decrypt(&nonce, data_bytes.as_slice())
154        .map_err(|e| JsError::new(&format!("AES decryption failed: {}", e)))?;
155
156    match encoding_type {
157        EncodingType::UTF8 => String::from_utf8(decrypted_data)
158            .map_err(|e| JsError::new(&format!("Wrong utf8 format data: {}", e))),
159        EncodingType::HEX => Ok(hex::encode(decrypted_data)),
160        EncodingType::BASE64 => Ok(STANDARD.encode(decrypted_data)),
161    }
162}
163
164#[wasm_bindgen]
165pub fn sign(data: String, privkey: String) -> Result<String, JsError> {
166    let secp = Secp256k1::new();
167    let digest = sha256::Hash::hash(data.as_bytes());
168    let message = Message::from_digest(digest.to_byte_array());
169    let secret_key = SecretKey::from_str(&privkey)
170        .map_err(|e| JsError::new(&format!("Invalid private key: {}", e)))?;
171
172    Ok(secp.sign_ecdsa(&message, &secret_key).to_string())
173}
174#[wasm_bindgen]
175pub fn verify(data: String, sig: String, pubkey: String) -> Result<bool, JsError> {
176    let secp = Secp256k1::new();
177    let digest = sha256::Hash::hash(data.as_bytes());
178    let message = Message::from_digest(digest.to_byte_array());
179    let signature = Signature::from_str(&sig)
180        .map_err(|e| JsError::new(&format!("Invalid signature: {}", e)))?;
181    let public_key = PublicKey::from_str(&pubkey)
182        .map_err(|e| JsError::new(&format!("Invalid public key: {}", e)))?;
183
184    match secp.verify_ecdsa(&message, &signature, &public_key) {
185        Ok(_) => Ok(true),
186        Err(_) => Ok(false),
187    }
188}
189#[wasm_bindgen]
190pub fn sha256(data: String) -> String {
191    sha256::Hash::hash(data.as_bytes()).to_string()
192}
193#[wasm_bindgen]
194pub fn ecdh(privkey: String, pubkey: String) -> Result<String, JsError> {
195    // Alice: param privkey is the one who's able to decrypt the data.
196    let alice = secp256k1_zkp::SecretKey::from_str(&privkey)
197        .map_err(|e| JsError::new(&format!("Invalid private key: {}", e)))?;
198    // Bob: param pubkey is the one who's able to decrypt the data.
199    let bob = secp256k1_zkp::PublicKey::from_str(&pubkey)
200        .map_err(|e| JsError::new(&format!("Invalid public key: {}", e)))?;
201    // Only Alice and Bob can know the secret, which is used as a key to sign or encrypt data(or any other).
202    let shared_secret = ecdh::SharedSecret::new(&bob, &alice);
203    Ok(shared_secret.display_secret().to_string())
204}
205// Pederson commitment is building block for zkp.
206// commitment is additively homomorphic.
207// blinding_factor must be kept as secret, to prevent the confidentiality of the committed value.
208// Tag is for domain(or purpose) separation.
209#[wasm_bindgen]
210pub fn pedersen_commit(value: u64, tag: String) -> Commitment {
211    let secp = Secp256k1::new();
212    let blinding_factor = Tweak::new(&mut Secp256k1Rng);
213    let tag = Tag::from(sha256::Hash::hash(tag.as_bytes()).to_byte_array());
214    Commitment::new(
215        PedersenCommitment::new(
216            &secp,
217            value,
218            blinding_factor,
219            // Blinded Generator is used in MimbleWimble for
220            // 1. Unlinkability: G' unique to a specific transaction, any commitments using it not linked to commitments from other transactions.
221            // 2. Proof of Ownership: the sum of all blinding factors in a transaction must equal a public key.
222            // Original pedersen: C=v⋅G+r⋅H
223            // Blinded Generator pedersen: C=v⋅G'+r⋅H (where G' = G+g⋅H, g is distinct blinding factor).
224            // Here, not using Blinded Generator as we only care the confidentiality of value.
225            Generator::new_unblinded(&secp, tag),
226        )
227        .to_string(),
228        blinding_factor.to_string(),
229    )
230}
231// Perderson verify by revealing value.
232#[wasm_bindgen]
233pub fn pedersen_reveal(commitment: Commitment, value: u64, tag: String) -> Result<bool, JsError> {
234    let secp = Secp256k1::new();
235    let blinding_factor = Tweak::from_str(&commitment.secret_blinding_factor())
236        .map_err(|e| JsError::new(&format!("Wrong blinding factor: {}", e)))?;
237    let tag = Tag::from(sha256::Hash::hash(tag.as_bytes()).to_byte_array());
238
239    let commitment_obj = PedersenCommitment::from_str(&commitment.commitment())
240        .map_err(|e| JsError::new(&format!("Wrong commitment: {}", e)))?;
241
242    Ok(verify_commitments_sum_to_equal(
243        &secp,
244        &vec![PedersenCommitment::new(
245            &secp,
246            value,
247            blinding_factor,
248            // Blinded Generator is used in MimbleWimble for
249            // 1. Unlinkability: G' unique to a specific transaction, any commitments using it not linked to commitments from other transactions.
250            // 2. Proof of Ownership: the sum of all blinding factors in a transaction must equal a public key
251            // Original pedersen: C=v⋅G+r⋅H
252            // Blinded Generator pedersen: C=v⋅G'+r⋅H (where G' = G+g⋅H, g is distinct blinding factor)
253            // Here, not using Blinded Generator as we only care the confidentiality of value
254            Generator::new_unblinded(&secp, tag),
255        )],
256        &vec![commitment_obj],
257    ))
258}
259
260#[wasm_bindgen]
261pub fn sha256_bytes(data: Vec<u8>) -> Vec<u8> {
262    sha256::Hash::hash(&data).to_byte_array().to_vec()
263}
264
265#[wasm_bindgen]
266pub fn encrypt_bytes(data: Vec<u8>, pubkey: String) -> Result<EncryptedDataBytes, JsError> {
267    let secp = Secp256k1::new();
268    // Alice: one-off key pair to caculate shared secret.
269    let (secret_key, public_key) = secp.generate_keypair(&mut Secp256k1Rng);
270    // Bob: param pubkey is the one who's able to decrypt the data.
271    let encrypt_to_pubkey = secp256k1_zkp::PublicKey::from_str(&pubkey)
272        .map_err(|e| JsError::new(&format!("Invalid public key: {}", e)))?;
273    // Only Alice and Bob can know the secret, which is used as a key to encrypt data.
274    let shared_secret = ecdh::SharedSecret::new(&encrypt_to_pubkey, &secret_key).secret_bytes();
275
276    let aes_encrypted_data = encrypt_aes_bytes(data, hex::encode(&shared_secret))?;
277
278    Ok(EncryptedDataBytes::new(
279        public_key.to_string(),
280        pubkey,
281        aes_encrypted_data.data(),
282        aes_encrypted_data.nonce(),
283    ))
284}
285
286#[wasm_bindgen]
287pub fn decrypt_bytes(data: EncryptedDataBytes, privkey: String) -> Result<Vec<u8>, JsError> {
288    // Alice: one-off pubkey to calculate shared secret.
289    let encrypt_from_pubkey = secp256k1_zkp::PublicKey::from_str(&data.pubkey_from())
290        .map_err(|e| JsError::new(&format!("Invalid public key in data: {}", e)))?;
291    // Bob: the one who's able to decrypt the data with privkey.
292    let privkey_to_decrypt = secp256k1_zkp::SecretKey::from_str(&privkey)
293        .map_err(|e| JsError::new(&format!("Invalid private key: {}", e)))?;
294    // Only Alice and Bob can know the secret, which is used as a key to encrypt data.
295    let shared_secret =
296        ecdh::SharedSecret::new(&encrypt_from_pubkey, &privkey_to_decrypt).secret_bytes();
297
298    decrypt_aes_bytes(
299        AesEncryptedDataBytes::new(data.data(), data.nonce()),
300        hex::encode(&shared_secret),
301    )
302}
303
304#[wasm_bindgen]
305pub fn encrypt_aes_bytes(data: Vec<u8>, key: String) -> Result<AesEncryptedDataBytes, JsError> {
306    // Secret key just fit into AES key
307    let hex_key =
308        hex::decode(key).map_err(|e| JsError::new(&format!("Key is malformed: {}", e)))?;
309    let aes_key = Key::<Aes256Gcm>::from_slice(&hex_key);
310    let cipher = Aes256Gcm::new(&aes_key);
311    // Nonce must be random and non-reusable value
312    let nonce = Aes256Gcm::generate_nonce(&mut AesRng); // 96-bits; unique per message
313
314    let ciphertext = cipher
315        .encrypt(&nonce, &*data)
316        .map_err(|e| JsError::new(&format!("AES encryption failed: {}", e)))?;
317    Ok(AesEncryptedDataBytes::new(ciphertext, nonce.to_vec()))
318}
319#[wasm_bindgen]
320pub fn decrypt_aes_bytes(data: AesEncryptedDataBytes, key: String) -> Result<Vec<u8>, JsError> {
321    // Secret key just fit into AES key
322    let hex_key =
323        hex::decode(key).map_err(|e| JsError::new(&format!("Key is malformed: {}", e)))?;
324    let aes_key = Key::<Aes256Gcm>::from_slice(&hex_key);
325    let decipher = Aes256Gcm::new(&aes_key);
326
327    decipher
328        .decrypt(
329            Nonce::from_slice(data.nonce().as_slice()),
330            data.data().as_slice(),
331        )
332        .map_err(|e| JsError::new(&format!("AES decryption failed: {}", e)))
333}
334
335#[wasm_bindgen(start)]
336pub fn init_panic_hook() -> Result<(), JsValue> {
337    console_error_panic_hook::set_once();
338    Ok(())
339}