Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CIP-129 DRep #706

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rust/src/legacy_address/crc32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ impl Crc32 {
/// beware that the order in which you update the Crc32
/// matter.
#[inline]
pub fn update<'a, I>(&'a mut self, bytes: I) -> &mut Self
pub fn update<'a, I>(&'a mut self, bytes: I) -> &'a mut Self
where
I: IntoIterator<Item = &'a u8>,
{
Expand Down
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()
} 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
Loading