bvs_library/testing/
account.rs

1use base64::{engine::general_purpose, Engine as _};
2use bech32::{self, ToBase32, Variant};
3use cosmwasm_std::Addr;
4use ripemd::Ripemd160;
5use secp256k1::{ecdsa::Signature, Message, PublicKey, Secp256k1, SecretKey};
6use sha2::{Digest, Sha256};
7
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct Account {
10    secret_key: SecretKey,
11    pub public_key: PublicKey,
12    pub address: Addr,
13}
14
15impl Account {
16    /// Create a new account
17    pub fn new(s: impl Into<String>) -> Self {
18        let s = s.into();
19
20        // Create a new Secp256k1 context
21        let secp = Secp256k1::new();
22
23        // convert string to vec
24        let seed = Sha256::digest(s.as_bytes()).to_vec();
25
26        // Generate a random secret key
27        let secret_key = SecretKey::from_slice(&seed).unwrap();
28
29        // Derive the public key from the secret key
30        let public_key = PublicKey::from_secret_key(&secp, &secret_key);
31
32        // Serialize the public key
33        let public_key_bytes = public_key.serialize();
34
35        // Compute the SHA-256 hash of the public key
36        let sha256_result = Sha256::digest(public_key_bytes);
37
38        // Compute the RIPEMD-160 hash of the SHA-256 hash
39        let ripemd160_result = Ripemd160::digest(sha256_result);
40
41        // Encode the RIPEMD-160 hash as a Bech32 address
42        let address =
43            bech32::encode("cosmwasm", ripemd160_result.to_base32(), Variant::Bech32).unwrap();
44
45        Account {
46            secret_key,
47            public_key,
48            address: Addr::unchecked(address),
49        }
50    }
51
52    /// Sign the message hash with ECDSA
53    ///
54    /// use message_hash to convert string to 32-byte hash or use custom implementation
55    pub fn sign(&self, message_hash: Vec<u8>) -> Signature {
56        // Create a new Secp256k1 signing only context
57        let secp = Secp256k1::signing_only();
58
59        // Convert the message_hash to a 32-byte array
60        let hash_bytes: [u8; 32] = message_hash
61            .as_slice()
62            .try_into()
63            .expect("hash length is not 32 bytes");
64
65        // Sign the hash with the secret key
66        secp.sign_ecdsa(&Message::from_digest(hash_bytes), &self.secret_key)
67    }
68
69    /// Create 32-byte hash of the message
70    ///
71    /// This is a default implementation to create 32-byte hash of the message
72    pub fn message_hash(message: String) -> Vec<u8> {
73        // Create SHA-256 hash of the message and convert it to a 32-byte array
74        let hash = Sha256::digest(message.as_bytes());
75        hash.to_vec()
76    }
77
78    // Return base64 encoding format of public key
79    pub fn public_key_base64(&self) -> String {
80        general_purpose::STANDARD.encode(self.public_key.serialize())
81    }
82}
83
84impl Default for Account {
85    fn default() -> Self {
86        Account::new("test".to_string())
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use cosmwasm_crypto::secp256k1_verify;
94
95    #[test]
96    fn test_new() {
97        let account = Account::new("seed".to_string());
98        assert_eq!(
99            account.public_key.to_string(),
100            "02c8f031561c4758c9551cff47246f2c347189fe684c04da35cf88e813f810e3c2"
101        );
102        assert_eq!(
103            account.address.to_string(),
104            "cosmwasm1efqyslkz34qurfjajpruzwv5v22c65kq5y4r46"
105        );
106    }
107
108    #[test]
109    fn test_sign() {
110        let account = Account::new("seed".to_string());
111        let message = "hello".to_string();
112        let message_hash = Account::message_hash(message.clone());
113        let signature = account.sign(message_hash.clone());
114        assert_eq!(signature.to_string(), "3044022067e20eddd4e86a76c80382e80852ffbccd3131962070ee1ad524a798be5d83cb022000d7a45e0faa08dee805381df8c4a7ead53f8e319c69bd67fa3cc031ef519cbb");
115
116        // verify the signature + public key with secp256k1_verify
117        let verify_res = secp256k1_verify(
118            &message_hash,
119            &signature.serialize_compact(),
120            &account.public_key.serialize(),
121        )
122        .unwrap();
123        assert!(verify_res);
124    }
125}