Add CAIP-2 chain ID type

This commit is contained in:
silverpill 2022-08-04 12:07:57 +00:00
parent 3c8c0c7163
commit af0563759d
5 changed files with 118 additions and 24 deletions

View file

@ -7,6 +7,7 @@ use serde::{
}; };
use crate::errors::ValidationError; use crate::errors::ValidationError;
use crate::utils::caip2::ChainId;
use crate::utils::currencies::Currency; use crate::utils::currencies::Currency;
use super::signatures::recover_address; use super::signatures::recover_address;
use super::utils::address_to_string; use super::utils::address_to_string;
@ -17,16 +18,15 @@ pub const ETHEREUM_EIP191_PROOF: &str = "ethereum-eip191-00";
// https://github.com/w3c-ccg/did-pkh/blob/main/did-pkh-method-draft.md // https://github.com/w3c-ccg/did-pkh/blob/main/did-pkh-method-draft.md
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct DidPkh { pub struct DidPkh {
network_id: String, chain_id: ChainId,
chain_id: String,
pub address: String, pub address: String,
} }
impl DidPkh { impl DidPkh {
pub fn from_address(currency: &Currency, address: &str) -> Self { pub fn from_address(currency: &Currency, address: &str) -> Self {
let (network_id, chain_id) = currency.caip2(); let chain_id = currency.chain_id();
let address = currency.normalize_address(address); let address = currency.normalize_address(address);
Self { network_id, chain_id, address } Self { chain_id, address }
} }
} }
@ -34,8 +34,8 @@ impl ToString for DidPkh {
fn to_string(&self) -> String { fn to_string(&self) -> String {
format!( format!(
"did:pkh:{}:{}:{}", "did:pkh:{}:{}:{}",
self.network_id, self.chain_id.namespace,
self.chain_id, self.chain_id.reference,
self.address, self.address,
) )
} }
@ -55,8 +55,10 @@ impl FromStr for DidPkh {
let did_pkh_re = Regex::new(DID_PKH_RE).unwrap(); let did_pkh_re = Regex::new(DID_PKH_RE).unwrap();
let caps = did_pkh_re.captures(value).ok_or(DidParseError)?; let caps = did_pkh_re.captures(value).ok_or(DidParseError)?;
let did = Self { let did = Self {
network_id: caps["network"].to_string(), chain_id: ChainId {
chain_id: caps["chain"].to_string(), namespace: caps["network"].to_string(),
reference: caps["chain"].to_string(),
},
address: caps["address"].to_string(), address: caps["address"].to_string(),
}; };
Ok(did) Ok(did)

View file

@ -1,12 +1,13 @@
use std::str::FromStr; use std::str::FromStr;
use regex::Regex;
use secp256k1::SecretKey; use secp256k1::SecretKey;
use web3::{ use web3::{
signing::Key, signing::Key,
types::Address, types::Address,
}; };
use crate::utils::caip2::ChainId;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum ChainIdError { pub enum ChainIdError {
#[error("invalid chain ID")] #[error("invalid chain ID")]
@ -20,16 +21,13 @@ pub enum ChainIdError {
} }
/// Parses CAIP-2 chain ID /// Parses CAIP-2 chain ID
/// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md
pub fn parse_caip2_chain_id(chain_id: &str) -> Result<u32, ChainIdError> { pub fn parse_caip2_chain_id(chain_id: &str) -> Result<u32, ChainIdError> {
// eip155 namespace: ethereum chain let chain_id = chain_id.parse::<ChainId>()
let caip2_re = Regex::new(r"(?P<namespace>\w+):(?P<chain_id>\w+)").unwrap(); .map_err(|_| ChainIdError::InvalidChainId)?;
let caip2_caps = caip2_re.captures(chain_id) if chain_id.namespace != "eip155" {
.ok_or(ChainIdError::InvalidChainId)?;
if &caip2_caps["namespace"] != "eip155" {
return Err(ChainIdError::UnsupportedChain); return Err(ChainIdError::UnsupportedChain);
}; };
let eth_chain_id: u32 = caip2_caps["chain_id"].parse()?; let eth_chain_id: u32 = chain_id.reference.parse()?;
Ok(eth_chain_id) Ok(eth_chain_id)
} }

57
src/utils/caip2.rs Normal file
View file

@ -0,0 +1,57 @@
/// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md
use std::str::FromStr;
use regex::Regex;
const CAIP2_RE: &str = r"(?P<namespace>[-a-z0-9]{3,8}):(?P<reference>[-a-zA-Z0-9]{1,32})";
#[derive(Clone, Debug, PartialEq)]
pub struct ChainId {
pub namespace: String,
pub reference: String,
}
#[derive(thiserror::Error, Debug)]
#[error("Chain ID parse error")]
pub struct ChainIdError;
impl FromStr for ChainId {
type Err = ChainIdError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let caip2_re = Regex::new(CAIP2_RE).unwrap();
let caps = caip2_re.captures(value).ok_or(ChainIdError)?;
let chain_id = Self {
namespace: caps["namespace"].to_string(),
reference: caps["reference"].to_string(),
};
Ok(chain_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_bitcoin_chain_id() {
let value = "bip122:000000000019d6689c085ae165831e93";
let chain_id = value.parse::<ChainId>().unwrap();
assert_eq!(chain_id.namespace, "bip122");
assert_eq!(chain_id.reference, "000000000019d6689c085ae165831e93");
}
#[test]
fn test_parse_ethereum_chain_id() {
let value = "eip155:1";
let chain_id = value.parse::<ChainId>().unwrap();
assert_eq!(chain_id.namespace, "eip155");
assert_eq!(chain_id.reference, "1");
}
#[test]
fn test_parse_invalid_chain_id() {
let value = "eip155/1/abcde";
assert!(value.parse::<ChainId>().is_err());
}
}

View file

@ -1,5 +1,10 @@
use crate::ethereum::identity::ETHEREUM_EIP191_PROOF; use std::convert::TryFrom;
use crate::errors::ConversionError;
use crate::ethereum::identity::ETHEREUM_EIP191_PROOF;
use super::caip2::ChainId;
#[derive(Debug, PartialEq)]
pub enum Currency { pub enum Currency {
Ethereum, Ethereum,
} }
@ -11,13 +16,9 @@ impl Currency {
}.to_string() }.to_string()
} }
/// Network ID and chain ID according to CAIP-2 standard /// Returns CAIP-2 chain ID
/// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md pub fn chain_id(&self) -> ChainId {
pub fn caip2(&self) -> (String, String) { self.into()
let (network_id, chain_id) = match self {
Self::Ethereum => ("eip155", "1"),
};
(network_id.to_string(), chain_id.to_string())
} }
pub fn normalize_address(&self, address: &str) -> String { pub fn normalize_address(&self, address: &str) -> String {
@ -27,6 +28,33 @@ impl Currency {
} }
} }
impl From<&Currency> for ChainId {
fn from(value: &Currency) -> Self {
let (namespace, reference) = match value {
Currency::Ethereum => ("eip155", "1"),
};
Self {
namespace: namespace.to_string(),
reference: reference.to_string(),
}
}
}
impl TryFrom<&ChainId> for Currency {
type Error = ConversionError;
fn try_from(value: &ChainId) -> Result<Self, Self::Error> {
let currency = match value.namespace.as_str() {
"eip155" => match value.reference.as_str() {
"1" => Self::Ethereum,
_ => return Err(ConversionError),
},
_ => return Err(ConversionError),
};
Ok(currency)
}
}
pub fn get_currency_field_name(currency: &Currency) -> String { pub fn get_currency_field_name(currency: &Currency) -> String {
format!("${}", currency.code()) format!("${}", currency.code())
} }
@ -43,6 +71,14 @@ pub fn get_identity_proof_field_name(proof_type: &str) -> Option<String> {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn test_chain_id_conversion() {
let ethereum = Currency::Ethereum;
let ethereum_chain_id = ChainId::from(&ethereum);
let currency = Currency::try_from(&ethereum_chain_id).unwrap();
assert_eq!(currency, ethereum);
}
#[test] #[test]
fn test_get_currency_field_name() { fn test_get_currency_field_name() {
let ethereum = Currency::Ethereum; let ethereum = Currency::Ethereum;

View file

@ -1,3 +1,4 @@
pub mod caip2;
pub mod crypto; pub mod crypto;
pub mod currencies; pub mod currencies;
pub mod files; pub mod files;