Skip to content

Commit

Permalink
change drep bech32 encoder to respect cip129
Browse files Browse the repository at this point in the history
  • Loading branch information
lisicky committed Nov 29, 2024
1 parent a2a19db commit 699e0c6
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 19 deletions.
145 changes: 145 additions & 0 deletions rust/src/protocol_types/governance/cip129_decoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use crate::{CredType, Credential, Ed25519KeyHash, GovernanceActionId, JsError, ScriptHash, TransactionHash};
use bech32::{ToBase32, FromBase32};
use std::convert::TryFrom;

#[derive(Debug, Clone, Copy)]
pub(crate) enum GovIdType {
CCHot = 0x0,
CCCold = 0x1,
DRep = 0x2,
}

impl TryFrom<u8> for GovIdType {
type Error = &'static str;

fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x0 => Ok(GovIdType::CCHot),
0x1 => Ok(GovIdType::CCCold),
0x2 => Ok(GovIdType::DRep),
_ => Err("Invalid KeyType"),
}
}
}

#[derive(Debug, Clone, Copy)]
enum CredentialType {
KeyHash = 0x2,
ScriptHash = 0x3,
}

impl TryFrom<u8> for CredentialType {
type Error = &'static str;

fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x2 => Ok(CredentialType::KeyHash),
0x3 => Ok(CredentialType::ScriptHash),
_ => Err("Invalid CredentialType"),
}
}
}

#[derive(Debug, Clone)]
pub(crate) enum GovernanceIdentifier {
GovCredential {
gov_id_type: GovIdType,
credential: Credential
},
GovAction(GovernanceActionId),
}

impl GovernanceIdentifier {
pub(crate) fn encode(&self) -> Vec<u8> {
match self {
GovernanceIdentifier::GovCredential {
gov_id_type,
credential
} => {
let (cred_type, cred_bytes) = match &credential.0 {
CredType::Key(key_hash) => (CredentialType::KeyHash, key_hash.to_bytes()),
CredType::Script(script_hash) => (CredentialType::ScriptHash, script_hash.to_bytes()),
};
let header = ((*gov_id_type as u8) << 4) | (cred_type as u8);
let mut bytes = vec![header];
bytes.extend_from_slice(&cred_bytes);
bytes
}
GovernanceIdentifier::GovAction(gov_action_id) => {
let mut bytes = gov_action_id.transaction_id.to_bytes();
let index = gov_action_id.index().to_be_bytes();
bytes.extend_from_slice(&index);
bytes
}
}
}

pub(crate) fn decode(prefix: &str, bytes: &[u8]) -> Result<Self, JsError> {
match prefix {
"drep" | "cc_hot" | "cc_cold" => {
if bytes.len() < 1 {
return Err(JsError::from_str("Invalid data length"));
}
let header = bytes[0];
let gov_id_type = GovIdType::try_from(header >> 4).
map_err(|_| JsError::from_str("Invalid GovIdType"))?;
let credential_type = CredentialType::try_from(header & 0x0F)
.map_err(|_| JsError::from_str("Invalid CredentialType"))?;
let credential_bytes = bytes[1..].to_vec();
let credential = match credential_type {
CredentialType::KeyHash => {
Credential::from_keyhash(&Ed25519KeyHash::from_bytes(credential_bytes)
.map_err(|_| JsError::from_str("Invalid key hash"))?)
}
CredentialType::ScriptHash => {
Credential::from_scripthash(&ScriptHash::from_bytes(credential_bytes)
.map_err(|_| JsError::from_str("Invalid script hash"))?)
}
};
Ok(GovernanceIdentifier::GovCredential {
gov_id_type,
credential,
})
}
"gov_action" => {
if bytes.len() < 33 {
return Err(JsError::from_str("Invalid data length"));
}
let tx_id = bytes[0..32].to_vec();
let index_bytes = &bytes[32..];
let index = match index_bytes.len() {
1 => u16::from(index_bytes[0]),
2 => u16::from_be_bytes([index_bytes[0], index_bytes[1]]),
_ => return Err(JsError::from_str("Invalid index length")),
};
let tx_hash = TransactionHash::from_bytes(tx_id)
.map_err(|_| JsError::from_str("Invalid transaction hash"))?;
let governance_action_id = GovernanceActionId::new(&tx_hash, index.into());
Ok(GovernanceIdentifier::GovAction(governance_action_id))
}
_ => Err(JsError::from_str("Unknown prefix")),
}
}

pub(crate) fn to_bech32(&self) -> Result<String, JsError> {
let (prefix, data) = match self {
GovernanceIdentifier::GovCredential { gov_id_type, .. } => {
let prefix = match gov_id_type {
GovIdType::CCHot => "cc_hot",
GovIdType::CCCold => "cc_cold",
GovIdType::DRep => "drep",
};
(prefix, self.encode())
}
GovernanceIdentifier::GovAction { .. } => ("gov_action", self.encode()),
};
let bech32_data = data.to_base32();
bech32::encode(prefix, bech32_data).map_err(|e| JsError::from_str(&e.to_string()))
}

pub(crate) fn from_bech32(s: &str) -> Result<Self, JsError> {
let (prefix, data) = bech32::decode(s).map_err(|e| JsError::from_str(&e.to_string()))?;
let bytes = Vec::<u8>::from_base32(&data).map_err(|e| JsError::from_str(&e.to_string()))?;
GovernanceIdentifier::decode(&prefix, &bytes)
}
}
89 changes: 74 additions & 15 deletions rust/src/protocol_types/governance/drep.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::*;
use std::convert::TryFrom;
use bech32::ToBase32;
use crate::*;
use crate::protocol_types::governance::cip129_decoder::{GovIdType, GovernanceIdentifier};

#[derive(
Clone,
Expand Down Expand Up @@ -95,27 +97,45 @@ impl DRep {
}
}

pub fn to_bech32(&self) -> Result<String, JsError> {
let (hrp, data) = match &self.0 {
DRepEnum::KeyHash(keyhash) => Ok(("drep", keyhash.to_bytes())),
DRepEnum::ScriptHash(scripthash) => Ok(("drep_script", scripthash.to_bytes())),
DRepEnum::AlwaysAbstain => {
Err(JsError::from_str("Cannot convert AlwaysAbstain to bech32"))
}
DRepEnum::AlwaysNoConfidence => Err(JsError::from_str(
"Cannot convert AlwaysNoConfidence to bech32",
)),
}?;
bech32::encode(&hrp, data.to_base32()).map_err(|e| JsError::from_str(&format! {"{:?}", e}))
pub fn to_bech32(&self, cip_129_format: bool) -> Result<String, JsError> {
if cip_129_format {
let gov_identifier: GovernanceIdentifier = self.try_into()?;
gov_identifier.to_bech32().map_err(|e| JsError::from_str(&e.to_string()))
} else {
let (hrp, data) = match &self.0 {
DRepEnum::KeyHash(keyhash) => Ok(("drep_vkh", keyhash.to_bytes())),
DRepEnum::ScriptHash(scripthash) => Ok(("drep_script", scripthash.to_bytes())),
DRepEnum::AlwaysAbstain => {
Err(JsError::from_str("Cannot convert AlwaysAbstain to bech32"))
}
DRepEnum::AlwaysNoConfidence => Err(JsError::from_str(
"Cannot convert AlwaysNoConfidence to bech32",
)),
}?;
bech32::encode(&hrp, data.to_base32()).map_err(|e| JsError::from_str(&format! {"{:?}", e}))
}
}

pub fn from_bech32(bech32_str: &str) -> Result<DRep, JsError> {
let (hrp, u5data) =
bech32::decode(bech32_str).map_err(|e| JsError::from_str(&e.to_string()))?;
let data: Vec<u8> = bech32::FromBase32::from_base32(&u5data)
.map_err(|_| JsError::from_str("Malformed DRep"))?;
let kind = match hrp.as_str() {
.map_err(|e: bech32::Error| JsError::from_str(&format!("Malformed DRep base32: {}", &e.to_string())))?;
let prefix = hrp.as_str();
match prefix {
"drep" => match data.len() {
28 => Self::from_bech32_internal(prefix, data),
29 => GovernanceIdentifier::from_bech32(bech32_str)?.try_into(),
_ => Err(JsError::from_str("Malformed DRep (drep1 byte len)"))
},
_ => Self::from_bech32_internal(prefix, data),
}
}

fn from_bech32_internal(prefix: &str, data: Vec<u8>) -> Result<DRep, JsError> {
let kind = match prefix {
"drep" => DRepKind::KeyHash,
"drep_vkh" => DRepKind::KeyHash,
"drep_script" => DRepKind::ScriptHash,
_ => return Err(JsError::from_str("Malformed DRep")),
};
Expand All @@ -133,3 +153,42 @@ impl DRep {
Ok(DRep(drep))
}
}

impl TryFrom<&DRep> for Credential {
type Error = JsError;

fn try_from(drep: &DRep) -> Result<Self, Self::Error> {
match &drep.0 {
DRepEnum::KeyHash(keyhash) => Ok(Credential(CredType::Key(keyhash.clone()))),
DRepEnum::ScriptHash(scripthash) => Ok(Credential(CredType::Script(scripthash.clone()))),
DRepEnum::AlwaysAbstain => Err(JsError::from_str("Cannot convert AlwaysAbstain to Credential")),
DRepEnum::AlwaysNoConfidence => Err(JsError::from_str("Cannot convert AlwaysNoConfidence to Credential")),
}
}
}

impl TryFrom<&DRep> for GovernanceIdentifier {
type Error = JsError;

fn try_from(drep: &DRep) -> Result<Self, Self::Error> {
let credential = drep.try_into()?;
Ok(GovernanceIdentifier::GovCredential {
gov_id_type: GovIdType::DRep,
credential,
})
}
}

impl TryFrom<GovernanceIdentifier> for DRep {
type Error = JsError;

fn try_from(gov_id: GovernanceIdentifier) -> Result<Self, Self::Error> {
match gov_id {
GovernanceIdentifier::GovCredential {
gov_id_type: GovIdType::DRep,
credential,
} => Ok(DRep::new_from_credential(&credential)),
_ => Err(JsError::from_str("Cannot convert GovernanceActionId to DRep")),
}
}
}
2 changes: 2 additions & 0 deletions rust/src/protocol_types/governance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ mod governance_action_ids;
pub use governance_action_ids::*;

mod proposals;
mod cip129_decoder;

pub use proposals::*;
41 changes: 41 additions & 0 deletions rust/src/tests/protocol_types/governance/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,44 @@ 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("drep_vkh1uz9v5d3vzyp3xwh8y2ceswqxqx4ljecfwd2kwnjysjapz3kx3ey").unwrap();
let drep3 = DRep::from_bech32("drep1ytsg4j3k9sgsxye6uu3trxpcqcq6h7t8p9e42e6wgjzt5yggls86y").unwrap();
assert_eq!(drep1.kind(), DRepKind::KeyHash);
assert_eq!(drep2.kind(), DRepKind::KeyHash);
assert_eq!(drep3.kind(), DRepKind::KeyHash);
assert_eq!(drep1.to_key_hash().unwrap(), drep2.to_key_hash().unwrap());
assert_eq!(drep1.to_key_hash().unwrap(), drep3.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_to_bech() {
let drep1 = DRep::from_bech32("drep1uz9v5d3vzyp3xwh8y2ceswqxqx4ljecfwd2kwnjysjapzh3avs7").unwrap();
let drep2 = DRep::from_bech32("drep_vkh1uz9v5d3vzyp3xwh8y2ceswqxqx4ljecfwd2kwnjysjapz3kx3ey").unwrap();
let drep3 = DRep::from_bech32("drep_script1dja6lg0xdt4tfrd7r2svc3ywh5xqrl6w85axjp0gtdu6xw6h2wn").unwrap();
assert_eq!(drep1.to_bech32(false).unwrap(), "drep_vkh1uz9v5d3vzyp3xwh8y2ceswqxqx4ljecfwd2kwnjysjapz3kx3ey");
assert_eq!(drep2.to_bech32(false).unwrap(), "drep_vkh1uz9v5d3vzyp3xwh8y2ceswqxqx4ljecfwd2kwnjysjapz3kx3ey");
assert_eq!(drep3.to_bech32(false).unwrap(), "drep_script1dja6lg0xdt4tfrd7r2svc3ywh5xqrl6w85axjp0gtdu6xw6h2wn");
}

#[test]
fn drep_bech32_to_bech_cip_129() {
let drep1 = DRep::from_bech32("drep1uz9v5d3vzyp3xwh8y2ceswqxqx4ljecfwd2kwnjysjapzh3avs7").unwrap();
let drep2 = DRep::from_bech32("drep_script1dja6lg0xdt4tfrd7r2svc3ywh5xqrl6w85axjp0gtdu6xw6h2wn").unwrap();
assert_eq!(drep1.to_bech32(true).unwrap(), "drep1ytsg4j3k9sgsxye6uu3trxpcqcq6h7t8p9e42e6wgjzt5yggls86y");
assert_eq!(drep2.to_bech32(true).unwrap(), "drep1ydkthtapue4w4dydhcd2pnzy367scq0lfc7n56g9apdhngcaf8d6w");
}
8 changes: 4 additions & 4 deletions rust/src/tests/serialization/governance/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,30 +76,30 @@ fn drep_always_no_confidence_ser_round_trip() {
#[test]
fn drep_to_from_bech32_keshhash() {
let drep = DRep::new_key_hash(&fake_key_hash(1));
let bech32 = drep.to_bech32().unwrap();
let bech32 = drep.to_bech32(false).unwrap();
let drep_deser = DRep::from_bech32(&bech32).unwrap();
assert_eq!(drep, drep_deser);
}

#[test]
fn drep_to_from_bech32_script_hash() {
let drep = DRep::new_script_hash(&fake_script_hash(1));
let bech32 = drep.to_bech32().unwrap();
let bech32 = drep.to_bech32(false).unwrap();
let drep_deser = DRep::from_bech32(&bech32).unwrap();
assert_eq!(drep, drep_deser);
}

#[test]
fn drep_to_from_bech32_always_abstain() {
let drep = DRep::new_always_abstain();
let bech32 = drep.to_bech32();
let bech32 = drep.to_bech32(false);
assert!(bech32.is_err());
}

#[test]
fn drep_to_from_bech32_always_no_confidence() {
let drep = DRep::new_always_no_confidence();
let bech32 = drep.to_bech32();
let bech32 = drep.to_bech32(false);
assert!(bech32.is_err());
}

Expand Down

0 comments on commit 699e0c6

Please sign in to comment.