diff --git a/rust/src/protocol_types/governance/drep.rs b/rust/src/protocol_types/governance/drep.rs index 7d96e9eb..4143223e 100644 --- a/rust/src/protocol_types/governance/drep.rs +++ b/rust/src/protocol_types/governance/drep.rs @@ -1,3 +1,4 @@ +use core::prelude::v1::Ok; use crate::*; use bech32::ToBase32; @@ -29,6 +30,93 @@ pub enum DRepKind { AlwaysNoConfidence, } +#[wasm_bindgen] +#[derive(Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +pub enum DRepExtendedId { + Key(Ed25519KeyHash), + Script(ScriptHash), +} + +const DREP_CIP129_PREFIX_KEY: u8 = 34; +const DREP_CIP129_PREFIX_SCRIPT: u8 = 35; + +#[wasm_bindgen] +impl DRepExtendedId { + + pub fn from_bytes(data: &Vec) -> Result { + if data.len() != 29 { + return Err(JsError::from_str("Malformed DRep Extended ID (incorrect len)")); + } + let prefix = data.get(0) + .ok_or(JsError::from_str("Malformed DRep (unexpected failure to get bytes prefix)"))?; + match prefix { + &DREP_CIP129_PREFIX_KEY => return Ok( + DRepExtendedId::Key( + Ed25519KeyHash::from_bytes(data[1..].to_vec()) + .map_err(|_| JsError::from_str("Malformed DRep KeyHash"))?, + ) + ), + &DREP_CIP129_PREFIX_SCRIPT => return Ok( + DRepExtendedId::Script( + ScriptHash::from_bytes(data[1..].to_vec()) + .map_err(|_| JsError::from_str("Malformed DRep ScriptHash"))?, + ) + ), + _ => return Err(JsError::from_str("Malformed DRep Extended ID (incorrect prefix byte)")) + } + } + + pub fn from_hex(hex_str: &str) -> Result { + DRepExtendedId::from_bytes( + &hex::decode(hex_str) + .map_err(|e| JsError::from_str(&e.to_string()))? + ) + } + + pub fn from_bech32(bech32_str: &str) -> Result { + let (hrp, u5data) = + bech32::decode(bech32_str).map_err(|e| JsError::from_str(&e.to_string()))?; + if hrp != "drep" { + return Err(JsError::from_str("Malformed DRep Extended ID (incorrect prefix)")); + } + let data: Vec = bech32::FromBase32::from_base32(&u5data) + .map_err(|e: bech32::Error| JsError::from_str(&format!("Malformed DRep base32: {}", &e.to_string())))?; + DRepExtendedId::from_bytes(&data) + } + + pub fn to_hex(&self) -> String { + hex::encode(self.to_bytes()) + } + + pub fn to_bytes(&self) -> Vec { + let (prefix, data) = match self { + DRepExtendedId::Key(keyhash) => (DREP_CIP129_PREFIX_KEY, keyhash.to_bytes()), + DRepExtendedId::Script(scripthash) => (DREP_CIP129_PREFIX_SCRIPT, scripthash.to_bytes()), + }; + let mut res = vec![prefix]; + res.extend(data); + res + } + + pub fn to_bech32(&self) -> Result { + bech32::encode("drep", self.to_bytes().to_base32()).map_err(|e| JsError::from_str(&format! {"{:?}", e})) + } + + pub fn kind(&self) -> DRepKind { + match self { + DRepExtendedId::Key(_) => DRepKind::KeyHash, + DRepExtendedId::Script(_) => DRepKind::ScriptHash, + } + } + + pub fn to_drep(&self) -> DRep { + match self { + DRepExtendedId::Key(key) => DRep(DRepEnum::KeyHash(key.clone())), + DRepExtendedId::Script(script) => DRep(DRepEnum::ScriptHash(script.clone())), + } + } +} + #[derive( Clone, Debug, @@ -95,9 +183,17 @@ impl DRep { } } - pub fn to_bech32(&self) -> Result { + pub fn to_extended_id(&self) -> Option { + match &self.0 { + DRepEnum::KeyHash(x) => Some(DRepExtendedId::Key(x.clone())), + DRepEnum::ScriptHash(x) => Some(DRepExtendedId::Script(x.clone())), + _ => None + } + } + + fn internal_to_bech32(&self, vkh_prefix: String) -> Result { let (hrp, data) = match &self.0 { - DRepEnum::KeyHash(keyhash) => Ok(("drep", keyhash.to_bytes())), + DRepEnum::KeyHash(keyhash) => Ok((vkh_prefix.as_str(), keyhash.to_bytes())), DRepEnum::ScriptHash(scripthash) => Ok(("drep_script", scripthash.to_bytes())), DRepEnum::AlwaysAbstain => { Err(JsError::from_str("Cannot convert AlwaysAbstain to bech32")) @@ -109,23 +205,36 @@ impl DRep { bech32::encode(&hrp, data.to_base32()).map_err(|e| JsError::from_str(&format! {"{:?}", e})) } + pub fn to_bech32(&self) -> Result { + self.internal_to_bech32("drep".to_string()) + } + + pub fn to_bech32_cip129(&self) -> Result { + self.internal_to_bech32("drep_vkh".to_string()) + } + pub fn from_bech32(bech32_str: &str) -> Result { let (hrp, u5data) = bech32::decode(bech32_str).map_err(|e| JsError::from_str(&e.to_string()))?; let data: Vec = bech32::FromBase32::from_base32(&u5data) - .map_err(|_| JsError::from_str("Malformed DRep"))?; + .map_err(|e: bech32::Error| JsError::from_str(&format!("Malformed DRep base32: {}", &e.to_string())))?; let kind = match hrp.as_str() { - "drep" => DRepKind::KeyHash, + "drep" => match data.len() { + 28 => DRepKind::KeyHash, // pre cip129 compatibility + 29 => return DRepExtendedId::from_bech32(bech32_str).and_then(|id| Ok(id.to_drep())), + _ => return Err(JsError::from_str("Malformed DRep (drep1 byte len)")) + }, + "drep_vkh" => DRepKind::KeyHash, "drep_script" => DRepKind::ScriptHash, - _ => return Err(JsError::from_str("Malformed DRep")), + _ => return Err(JsError::from_str("Malformed DRep (bech prefix)")), }; let drep = match kind { DRepKind::KeyHash => DRepEnum::KeyHash( Ed25519KeyHash::from_bytes(data) - .map_err(|_| JsError::from_str("Malformed DRep"))?, + .map_err(|_| JsError::from_str("Malformed DRep KeyHash"))?, ), DRepKind::ScriptHash => DRepEnum::ScriptHash( - ScriptHash::from_bytes(data).map_err(|_| JsError::from_str("Malformed DRep"))?, + ScriptHash::from_bytes(data).map_err(|_| JsError::from_str("Malformed DRep ScriptHash"))?, ), DRepKind::AlwaysAbstain => DRepEnum::AlwaysAbstain, DRepKind::AlwaysNoConfidence => DRepEnum::AlwaysNoConfidence, diff --git a/rust/src/tests/protocol_types/governance/common.rs b/rust/src/tests/protocol_types/governance/common.rs index c88964b1..11c2fbaa 100644 --- a/rust/src/tests/protocol_types/governance/common.rs +++ b/rust/src/tests/protocol_types/governance/common.rs @@ -265,3 +265,56 @@ fn voting_procedures_setters_getters_test() { assert!(governance_action_ids_2.0.contains(&governance_action_id_2)); assert!(governance_action_ids_2.0.contains(&governance_action_id_3)); } + +#[test] +fn drep_bech32_129_parsing_key_test() { + let drep1 = DRep::from_bech32("drep1uz9v5d3vzyp3xwh8y2ceswqxqx4ljecfwd2kwnjysjapzh3avs7").unwrap(); + let drep2 = DRep::from_bech32("drep1ytsg4j3k9sgsxye6uu3trxpcqcq6h7t8p9e42e6wgjzt5yggls86y").unwrap(); + assert_eq!(drep1.kind(), DRepKind::KeyHash); + assert_eq!(drep2.kind(), DRepKind::KeyHash); + assert_eq!(drep1.to_key_hash().unwrap(), drep2.to_key_hash().unwrap()); +} + +#[test] +fn drep_bech32_129_parsing_script_test() { + let drep1 = DRep::from_bech32("drep_script1dja6lg0xdt4tfrd7r2svc3ywh5xqrl6w85axjp0gtdu6xw6h2wn").unwrap(); + let drep2 = DRep::from_bech32("drep1ydkthtapue4w4dydhcd2pnzy367scq0lfc7n56g9apdhngcaf8d6w").unwrap(); + assert_eq!(drep1.kind(), DRepKind::ScriptHash); + assert_eq!(drep2.kind(), DRepKind::ScriptHash); + assert_eq!(drep1.to_script_hash().unwrap(), drep2.to_script_hash().unwrap()); + +} + +#[test] +fn drep_bech32_129_to_bech() { + let drep1 = DRep::from_bech32("drep1uz9v5d3vzyp3xwh8y2ceswqxqx4ljecfwd2kwnjysjapzh3avs7").unwrap(); + let drep2 = DRep::from_bech32("drep_script1dja6lg0xdt4tfrd7r2svc3ywh5xqrl6w85axjp0gtdu6xw6h2wn").unwrap(); + assert_eq!(drep1.to_bech32().unwrap(), "drep1uz9v5d3vzyp3xwh8y2ceswqxqx4ljecfwd2kwnjysjapzh3avs7"); + assert_eq!(drep1.to_bech32_cip129().unwrap(), "drep_vkh1uz9v5d3vzyp3xwh8y2ceswqxqx4ljecfwd2kwnjysjapz3kx3ey"); + assert_eq!(drep2.to_bech32().unwrap(), "drep_script1dja6lg0xdt4tfrd7r2svc3ywh5xqrl6w85axjp0gtdu6xw6h2wn"); + assert_eq!(drep2.to_bech32_cip129().unwrap(), "drep_script1dja6lg0xdt4tfrd7r2svc3ywh5xqrl6w85axjp0gtdu6xw6h2wn"); +} + +#[test] +fn drep_bech32_129_extended_id() { + let drep1 = DRep::from_bech32("drep_vkh1uz9v5d3vzyp3xwh8y2ceswqxqx4ljecfwd2kwnjysjapz3kx3ey").unwrap(); + let drep2 = DRep::from_bech32("drep_script1dja6lg0xdt4tfrd7r2svc3ywh5xqrl6w85axjp0gtdu6xw6h2wn").unwrap(); + assert_eq!(drep1.to_extended_id().unwrap().to_bech32().unwrap(), "drep1ytsg4j3k9sgsxye6uu3trxpcqcq6h7t8p9e42e6wgjzt5yggls86y"); + assert_eq!(drep1.to_extended_id().unwrap().to_hex(), "22e08aca362c1103133ae722b198380601abf967097355674e4484ba11"); + assert_eq!(drep2.to_extended_id().unwrap().to_bech32().unwrap(), "drep1ydkthtapue4w4dydhcd2pnzy367scq0lfc7n56g9apdhngcaf8d6w"); + assert_eq!(drep2.to_extended_id().unwrap().to_hex(), "236cbbafa1e66aeab48dbe1aa0cc448ebd0c01ff4e3d3a6905e85b79a3"); + assert_eq!(DRep::new_always_abstain().to_extended_id(), None); + assert_eq!(DRep::new_always_no_confidence().to_extended_id(), None); +} + +#[test] +fn drep_bech32_129_extended_id_hex_and_bytes() { + let bech1 = "drep1ytsg4j3k9sgsxye6uu3trxpcqcq6h7t8p9e42e6wgjzt5yggls86y"; + let bech2 = "drep1ydkthtapue4w4dydhcd2pnzy367scq0lfc7n56g9apdhngcaf8d6w"; + let drep1 = DRepExtendedId::from_bech32(bech1).unwrap(); + let drep2 = DRepExtendedId::from_bech32(bech2).unwrap(); + assert_eq!(DRepExtendedId::from_bytes(&drep1.to_bytes()).unwrap().to_bech32().unwrap(), bech1); + assert_eq!(DRepExtendedId::from_bytes(&drep2.to_bytes()).unwrap().to_bech32().unwrap(), bech2); + assert_eq!(DRepExtendedId::from_hex(&drep1.to_hex()).unwrap().to_bech32().unwrap(), bech1); + assert_eq!(DRepExtendedId::from_hex(&drep2.to_hex()).unwrap().to_bech32().unwrap(), bech2); +}