Jelajahi Sumber

sdk/keypair: Add Address type

x 8 bulan lalu
induk
melakukan
6e2296a89b
1 mengubah file dengan 90 tambahan dan 0 penghapusan
  1. 90 0
      src/sdk/src/crypto/keypair.rs

+ 90 - 0
src/sdk/src/crypto/keypair.rs

@@ -35,6 +35,25 @@ use rand_core::{CryptoRng, RngCore};
 use super::{constants::NullifierK, util::fp_mod_fv};
 use crate::error::ContractError;
 
+#[repr(u8)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum Network {
+    Mainnet = 0x01,
+    Testnet = 0x04,
+}
+
+impl TryFrom<u8> for Network {
+    type Error = ContractError;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0x01 => Ok(Self::Mainnet),
+            0x04 => Ok(Self::Testnet),
+            _ => Err(ContractError::IoError("Invalid Network".to_string())),
+        }
+    }
+}
+
 /// Keypair structure holding a `SecretKey` and its respective `PublicKey`
 #[derive(Copy, Clone, PartialEq, Eq, Debug, SerialEncodable, SerialDecodable)]
 pub struct Keypair {
@@ -210,3 +229,74 @@ impl core::fmt::Display for PublicKey {
         write!(f, "{disp}")
     }
 }
+
+pub struct Address {
+    prefix: Network,
+    spending_key: PublicKey,
+    viewing_key: PublicKey,
+}
+
+impl core::fmt::Display for Address {
+    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+        let mut payload = Vec::with_capacity(69);
+        payload.push(self.prefix as u8);
+        payload.extend_from_slice(&self.spending_key.to_bytes());
+        payload.extend_from_slice(&self.viewing_key.to_bytes());
+
+        let checksum = blake3::hash(&payload);
+        payload.extend_from_slice(&checksum.as_bytes()[..4]);
+
+        write!(f, "{}", bs58::encode(payload).into_string())
+    }
+}
+
+impl FromStr for Address {
+    type Err = ContractError;
+
+    fn from_str(enc: &str) -> Result<Self, Self::Err> {
+        let decoded = bs58::decode(enc).into_vec()?;
+        if decoded.len() != 69 {
+            return Err(Self::Err::IoError("Invalid address length".to_string()))
+        }
+
+        let r_network = Network::try_from(decoded[0])?;
+        let r_spending_key = PublicKey::from_bytes(decoded[1..33].try_into().unwrap())?;
+        let r_viewing_key = PublicKey::from_bytes(decoded[33..65].try_into().unwrap())?;
+        let r_checksum = &decoded[65..];
+
+        let checksum = blake3::hash(&decoded[..65]);
+        if r_checksum != &checksum.as_bytes()[..4] {
+            return Err(Self::Err::IoError("Invalid address checksum".to_string()))
+        }
+
+        Ok(Self { prefix: r_network, spending_key: r_spending_key, viewing_key: r_viewing_key })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use crate::crypto::poseidon_hash;
+    use rand::rngs::OsRng;
+
+    #[test]
+    fn test_address_encoding() {
+        let spending_keypair = Keypair::random(&mut OsRng);
+        let viewing_secret = SecretKey::from(poseidon_hash([spending_keypair.secret.inner()]));
+        let viewing_keypair = Keypair::new(viewing_secret);
+
+        let address = Address {
+            prefix: Network::Mainnet,
+            spending_key: spending_keypair.public,
+            viewing_key: viewing_keypair.public,
+        };
+
+        let addr_enc = address.to_string();
+        let addr_dec = Address::from_str(&addr_enc).unwrap();
+
+        assert_eq!(address.prefix, addr_dec.prefix);
+        assert_eq!(address.spending_key, addr_dec.spending_key);
+        assert_eq!(address.viewing_key, addr_dec.viewing_key);
+    }
+}