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::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)
|
||||||
|
|
|
@ -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
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 {
|
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(ðereum);
|
||||||
|
let currency = Currency::try_from(ðereum_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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue