Add CAIP-2 chain ID type
This commit is contained in:
parent
3c8c0c7163
commit
af0563759d
5 changed files with 118 additions and 24 deletions
|
@ -7,6 +7,7 @@ use serde::{
|
|||
};
|
||||
|
||||
use crate::errors::ValidationError;
|
||||
use crate::utils::caip2::ChainId;
|
||||
use crate::utils::currencies::Currency;
|
||||
use super::signatures::recover_address;
|
||||
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
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DidPkh {
|
||||
network_id: String,
|
||||
chain_id: String,
|
||||
chain_id: ChainId,
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
impl DidPkh {
|
||||
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);
|
||||
Self { network_id, chain_id, address }
|
||||
Self { chain_id, address }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,8 @@ impl ToString for DidPkh {
|
|||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
"did:pkh:{}:{}:{}",
|
||||
self.network_id,
|
||||
self.chain_id,
|
||||
self.chain_id.namespace,
|
||||
self.chain_id.reference,
|
||||
self.address,
|
||||
)
|
||||
}
|
||||
|
@ -55,8 +55,10 @@ impl FromStr for DidPkh {
|
|||
let did_pkh_re = Regex::new(DID_PKH_RE).unwrap();
|
||||
let caps = did_pkh_re.captures(value).ok_or(DidParseError)?;
|
||||
let did = Self {
|
||||
network_id: caps["network"].to_string(),
|
||||
chain_id: caps["chain"].to_string(),
|
||||
chain_id: ChainId {
|
||||
namespace: caps["network"].to_string(),
|
||||
reference: caps["chain"].to_string(),
|
||||
},
|
||||
address: caps["address"].to_string(),
|
||||
};
|
||||
Ok(did)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use regex::Regex;
|
||||
use secp256k1::SecretKey;
|
||||
use web3::{
|
||||
signing::Key,
|
||||
types::Address,
|
||||
};
|
||||
|
||||
use crate::utils::caip2::ChainId;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ChainIdError {
|
||||
#[error("invalid chain ID")]
|
||||
|
@ -20,16 +21,13 @@ pub enum ChainIdError {
|
|||
}
|
||||
|
||||
/// 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> {
|
||||
// eip155 namespace: ethereum chain
|
||||
let caip2_re = Regex::new(r"(?P<namespace>\w+):(?P<chain_id>\w+)").unwrap();
|
||||
let caip2_caps = caip2_re.captures(chain_id)
|
||||
.ok_or(ChainIdError::InvalidChainId)?;
|
||||
if &caip2_caps["namespace"] != "eip155" {
|
||||
let chain_id = chain_id.parse::<ChainId>()
|
||||
.map_err(|_| ChainIdError::InvalidChainId)?;
|
||||
if chain_id.namespace != "eip155" {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
57
src/utils/caip2.rs
Normal file
57
src/utils/caip2.rs
Normal 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());
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
Ethereum,
|
||||
}
|
||||
|
@ -11,13 +16,9 @@ impl Currency {
|
|||
}.to_string()
|
||||
}
|
||||
|
||||
/// Network ID and chain ID according to CAIP-2 standard
|
||||
/// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md
|
||||
pub fn caip2(&self) -> (String, String) {
|
||||
let (network_id, chain_id) = match self {
|
||||
Self::Ethereum => ("eip155", "1"),
|
||||
};
|
||||
(network_id.to_string(), chain_id.to_string())
|
||||
/// Returns CAIP-2 chain ID
|
||||
pub fn chain_id(&self) -> ChainId {
|
||||
self.into()
|
||||
}
|
||||
|
||||
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 {
|
||||
format!("${}", currency.code())
|
||||
}
|
||||
|
@ -43,6 +71,14 @@ pub fn get_identity_proof_field_name(proof_type: &str) -> Option<String> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_chain_id_conversion() {
|
||||
let ethereum = Currency::Ethereum;
|
||||
let ethereum_chain_id = ChainId::from(ðereum);
|
||||
let currency = Currency::try_from(ðereum_chain_id).unwrap();
|
||||
assert_eq!(currency, ethereum);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_currency_field_name() {
|
||||
let ethereum = Currency::Ethereum;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod caip2;
|
||||
pub mod crypto;
|
||||
pub mod currencies;
|
||||
pub mod files;
|
||||
|
|
Loading…
Reference in a new issue