use chrono::{DateTime, ParseError, SecondsFormat, TimeZone, Utc};
use hex::encode;
use lazy_static::lazy_static;
use regex::Regex;
use secp256k1::Secp256k1;
use serde::{Deserialize, Serialize};
use std::{
fmt::{Debug, Display, LowerHex, UpperHex},
ops::Deref,
};
use thiserror::Error;
use web3::{
signing::{hash_message, keccak256, recover, RecoveryError},
types::{H160, H256},
};
#[derive(Debug, PartialEq, Error)]
pub enum DecodeHexError {
#[error("hexadecimal value must be prefixed with 0x")]
MissingPrefix,
#[error("odd number of digits")]
OddLength,
#[error("invalid string length")]
InvalidLength,
#[error("invalid character {c:?} at position {index}")]
InvalidHexCharacter { c: char, index: usize },
}
impl From<hex::FromHexError> for DecodeHexError {
fn from(err: hex::FromHexError) -> Self {
match err {
hex::FromHexError::OddLength => DecodeHexError::OddLength,
hex::FromHexError::InvalidStringLength => DecodeHexError::InvalidLength,
hex::FromHexError::InvalidHexCharacter { c, index } => {
DecodeHexError::InvalidHexCharacter {
c,
index: index + 2,
}
}
}
}
}
impl From<secp256k1::Error> for DecodeHexError {
fn from(_value: secp256k1::Error) -> Self {
DecodeHexError::InvalidLength
}
}
pub fn decode(value: &str) -> Result<Vec<u8>, DecodeHexError> {
if !value.starts_with("0x") {
return Err(DecodeHexError::MissingPrefix);
}
if value.len() % 2 != 0 {
return Err(DecodeHexError::OddLength);
}
Ok(hex::decode(&value[2..])?)
}
pub fn decode_to_slice(value: &str, bits: &mut [u8]) -> Result<(), DecodeHexError> {
if !value.starts_with("0x") {
return Err(DecodeHexError::MissingPrefix);
}
if value.len() != ((bits.len() * 2) + 2) {
return Err(DecodeHexError::InvalidLength);
}
Ok(hex::decode_to_slice(&value[2..], bits)?)
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Copy)]
#[serde(try_from = "String", into = "String")]
pub struct Address(H160);
impl Deref for Address {
type Target = H160;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<[u8; 20]> for Address {
fn from(value: [u8; 20]) -> Self {
Self(H160(value))
}
}
impl From<H160> for Address {
fn from(value: H160) -> Self {
Self(value)
}
}
impl std::cmp::PartialEq<H160> for Address {
fn eq(&self, other: &H160) -> bool {
self.0 == *other
}
}
impl std::cmp::PartialEq<H160> for &Address {
fn eq(&self, other: &H160) -> bool {
self.0 == *other
}
}
impl From<Address> for String {
fn from(value: Address) -> Self {
value.checksum()
}
}
impl TryFrom<&str> for Address {
type Error = DecodeHexError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let mut bits: [u8; 20] = [0; 20];
match decode_to_slice(value, &mut bits) {
Ok(_) => Ok(Self::from(bits)),
Err(err) => Err(err),
}
}
}
impl TryFrom<String> for Address {
type Error = DecodeHexError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from(value.as_str())
}
}
impl UpperHex for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::UpperHex::fmt(&**self, f)
}
}
impl LowerHex for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::LowerHex::fmt(&**self, f)
}
}
impl Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:#x}")
}
}
impl Address {
pub fn zero() -> Self {
Self::from([0; 20])
}
pub fn checksum(&self) -> String {
let hash = keccak256(format!("{self:x}").as_bytes());
let checksum = self
.as_bytes()
.iter()
.enumerate()
.map(|(i, b)| {
let h1 = (hash[i] & 0b1111_0000) >> 4;
let h2 = hash[i] & 0b0000_1111;
let hex = format!("{b:02x}");
let b1 = hex.get(0..=0).unwrap_or("0");
let b2 = hex.get(1..=1).unwrap_or("0");
format!(
"{}{}",
if h1 >= 8 {
b1.to_uppercase()
} else {
b1.to_lowercase()
},
if h2 >= 8 {
b2.to_uppercase()
} else {
b2.to_lowercase()
},
)
})
.collect::<String>();
format!("0x{checksum}")
}
}
pub const PERSONAL_SIGNATURE_SIZE: usize = 65;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(try_from = "String", into = "String")]
pub struct PersonalSignature([u8; PERSONAL_SIGNATURE_SIZE]);
impl Deref for PersonalSignature {
type Target = [u8; PERSONAL_SIGNATURE_SIZE];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Default for PersonalSignature {
fn default() -> Self {
PersonalSignature([0; PERSONAL_SIGNATURE_SIZE])
}
}
impl From<[u8; PERSONAL_SIGNATURE_SIZE]> for PersonalSignature {
fn from(value: [u8; PERSONAL_SIGNATURE_SIZE]) -> Self {
Self(value)
}
}
impl From<web3::signing::Signature> for PersonalSignature {
fn from(value: web3::signing::Signature) -> Self {
let mut bits = [0u8; PERSONAL_SIGNATURE_SIZE];
bits[..32].copy_from_slice(&value.r.0);
bits[32..64].copy_from_slice(&value.s.0);
bits[64] = (value.v * 0b1111) as u8;
Self(bits)
}
}
impl From<secp256k1::ecdsa::RecoverableSignature> for PersonalSignature {
fn from(value: secp256k1::ecdsa::RecoverableSignature) -> Self {
let mut bits = [0u8; PERSONAL_SIGNATURE_SIZE];
let (recovery_id, signature) = value.serialize_compact();
bits[..64].copy_from_slice(&signature);
bits[64] = 27 + recovery_id.to_i32() as u8;
Self(bits)
}
}
impl TryFrom<&str> for PersonalSignature {
type Error = DecodeHexError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
if value.len() != 132 {
return Err(DecodeHexError::InvalidLength);
}
let mut bits = [0u8; PERSONAL_SIGNATURE_SIZE];
match decode_to_slice(value, &mut bits) {
Ok(_) => Ok(Self::from(bits)),
Err(err) => Err(err),
}
}
}
impl TryFrom<String> for PersonalSignature {
type Error = DecodeHexError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from(value.as_str())
}
}
impl From<PersonalSignature> for String {
fn from(value: PersonalSignature) -> Self {
value.to_string()
}
}
impl From<PersonalSignature> for web3::signing::Signature {
fn from(value: PersonalSignature) -> Self {
let mut r: [u8; 32] = [0; 32];
r.copy_from_slice(&value[..32]);
let mut s: [u8; 32] = [0; 32];
s.copy_from_slice(&value[32..64]);
let v: u64 = value.0[64] as u64;
Self {
v,
r: H256(r),
s: H256(s),
}
}
}
impl From<PersonalSignature> for Vec<u8> {
fn from(value: PersonalSignature) -> Self {
value.to_vec()
}
}
impl Display for PersonalSignature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "0x{}", hex::encode(self.0))
}
}
impl PersonalSignature {
pub fn try_recover_from_message(&self, message: &str) -> Result<Address, RecoveryError> {
let result = recover(
hash_message(message).as_bytes(),
&self.0[..=63],
(self.0[64] as i32) - 27,
);
match result {
Ok(h160) => Ok(Address::from(h160)),
Err(err) => Err(err),
}
}
pub fn is_valid_signature(&self, message: &str, signer: &Address) -> bool {
self.try_recover_from_message(message)
.map(|address| address == *signer)
.unwrap_or(false)
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(try_from = "String", into = "String")]
pub struct EIP1271Signature(Vec<u8>);
impl Deref for EIP1271Signature {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Display for EIP1271Signature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "0x{}", hex::encode(&self.0))
}
}
impl TryFrom<&str> for EIP1271Signature {
type Error = DecodeHexError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let data = decode(value)?;
Ok(Self(data))
}
}
impl TryFrom<String> for EIP1271Signature {
type Error = DecodeHexError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from(value.as_str())
}
}
impl From<EIP1271Signature> for Vec<u8> {
fn from(value: EIP1271Signature) -> Self {
value.to_vec()
}
}
impl From<EIP1271Signature> for String {
fn from(value: EIP1271Signature) -> Self {
format!("0x{}", hex::encode(value.0))
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, PartialOrd, Copy)]
#[serde(try_from = "String", into = "String")]
pub struct Expiration(DateTime<Utc>);
impl Deref for Expiration {
type Target = DateTime<Utc>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: chrono::TimeZone> From<DateTime<T>> for Expiration {
fn from(value: DateTime<T>) -> Self {
Expiration(value.with_timezone(&Utc))
}
}
impl TryFrom<&str> for Expiration {
type Error = ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let expiration = DateTime::parse_from_rfc3339(value)?;
Ok(Self(expiration.with_timezone(&Utc)))
}
}
impl TryFrom<String> for Expiration {
type Error = ParseError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from(value.as_str())
}
}
impl From<Expiration> for String {
fn from(value: Expiration) -> Self {
value.to_string()
}
}
impl Display for Expiration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.to_rfc3339_opts(SecondsFormat::Millis, true))
}
}
impl From<Expiration> for DateTime<Utc> {
fn from(value: Expiration) -> Self {
value.0
}
}
#[derive(PartialEq, Debug, Error)]
pub enum EphemeralPayloadError {
#[error("invalid payload content")]
InvalidPayload,
#[error("missing title line on payload")]
MissingTitle,
#[error("missing address line on payload")]
MissingAddress,
#[error("invalid address: {err} (address: {value})")]
InvalidAddress { err: DecodeHexError, value: String },
#[error("missing expiration line on payload")]
MissingExpiration,
#[error("invalid expiration: {err} (expiration: {value})")]
InvalidExpiration { err: ParseError, value: String },
}
pub type EIP1654Signature = EIP1271Signature;
static DEFAULT_EPHEMERAL_PAYLOAD_TITLE: &str = "Decentraland Login";
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
#[serde(try_from = "String", into = "String")]
pub struct EphemeralPayload {
pub title: String,
pub address: Address,
pub expiration: Expiration,
}
static RE_TITLE_CAPTURE: &str = "title";
static RE_ADDRESS_CAPTURE: &str = "address";
static RE_EXPIRATION_CAPTURE: &str = "expiration";
impl TryFrom<&str> for EphemeralPayload {
type Error = EphemeralPayloadError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
lazy_static! {
static ref EPHEMERAL_PAYLOAD_REGEX: Regex = Regex::new(&format!(
r"^(?P<{}>[^\r\n]*)\r?\nEphemeral address: (?P<{}>[^\r\n]*)\r?\nExpiration: (?P<{}>.*)$",
RE_TITLE_CAPTURE, RE_ADDRESS_CAPTURE, RE_EXPIRATION_CAPTURE
))
.unwrap();
}
let captures = match EPHEMERAL_PAYLOAD_REGEX.captures(value) {
None => return Err(EphemeralPayloadError::InvalidPayload),
Some(captures) => captures,
};
let title = match captures.name(RE_TITLE_CAPTURE) {
None => return Err(EphemeralPayloadError::MissingTitle),
Some(title) => title.as_str().to_string(),
};
let address = match captures.name(RE_ADDRESS_CAPTURE) {
None => return Err(EphemeralPayloadError::MissingAddress),
Some(address) => {
let value = address.as_str();
Address::try_from(value).map_err(|err| EphemeralPayloadError::InvalidAddress {
value: value.to_string(),
err,
})?
}
};
let expiration = match captures.name(RE_EXPIRATION_CAPTURE) {
None => return Err(EphemeralPayloadError::MissingExpiration),
Some(expiration) => {
let value = expiration.as_str();
Expiration::try_from(value).map_err(|err| {
EphemeralPayloadError::InvalidExpiration {
value: value.to_string(),
err,
}
})?
}
};
Ok(Self {
title,
address,
expiration,
})
}
}
impl TryFrom<String> for EphemeralPayload {
type Error = EphemeralPayloadError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from(value.as_str())
}
}
impl Display for EphemeralPayload {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}\nEphemeral address: {}\nExpiration: {}",
self.title,
self.address.checksum(),
self.expiration
)
}
}
impl From<EphemeralPayload> for String {
fn from(payload: EphemeralPayload) -> Self {
format!("{}", payload)
}
}
impl EphemeralPayload {
pub fn new(address: Address, expiration: Expiration) -> Self {
Self::new_with_title(
String::from(DEFAULT_EPHEMERAL_PAYLOAD_TITLE),
address,
expiration,
)
}
pub fn new_with_title(title: String, address: Address, expiration: Expiration) -> Self {
Self {
title,
address,
expiration,
}
}
pub fn is_expired(&self) -> bool {
*self.expiration < Utc::now()
}
pub fn is_expired_at<Z: TimeZone>(&self, time: &DateTime<Z>) -> bool {
*self.expiration < *time
}
}
struct Hash(H256);
impl secp256k1::ThirtyTwoByteHash for Hash {
fn into_32(self) -> [u8; 32] {
self.0 .0
}
}
fn to_public_key(secret: &secp256k1::SecretKey) -> secp256k1::PublicKey {
lazy_static! {
static ref SECP256K1: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
}
secret.public_key(&SECP256K1)
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct EphemeralAccountRepresentation {
pub address: String,
pub public_key: String,
pub private_key: String,
}
impl TryFrom<EphemeralAccountRepresentation> for Account {
type Error = DecodeHexError;
fn try_from(value: EphemeralAccountRepresentation) -> Result<Self, Self::Error> {
Account::try_from(value.private_key)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(
try_from = "EphemeralAccountRepresentation",
into = "EphemeralAccountRepresentation"
)]
pub struct Account(secp256k1::SecretKey);
impl TryFrom<&str> for Account {
type Error = DecodeHexError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let mut bytes = [0u8; 32];
decode_to_slice(value, &mut bytes)?;
let key = secp256k1::SecretKey::from_slice(&bytes)?;
Ok(Self(key))
}
}
impl TryFrom<String> for Account {
type Error = DecodeHexError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from(value.as_str())
}
}
impl From<Account> for String {
fn from(account: Account) -> Self {
format!("0x{}", encode(account.0.secret_bytes()))
}
}
impl From<Account> for EphemeralAccountRepresentation {
fn from(account: Account) -> Self {
let public = to_public_key(&account.0).serialize_uncompressed();
Self {
address: account.address().checksum(),
public_key: format!("0x{}", hex::encode(public)),
private_key: account.into(),
}
}
}
impl Account {
pub fn random() -> Self {
Self::from_rng(&mut rand::thread_rng())
}
pub fn from_rng<R: rand::Rng + ?Sized>(r: &mut R) -> Self {
Self(secp256k1::SecretKey::new(r))
}
}
pub trait Signer {
fn address(&self) -> Address;
fn sign<M: AsRef<[u8]>>(&self, message: M) -> PersonalSignature;
}
impl Signer for Account {
fn address(&self) -> Address {
let public = to_public_key(&self.0).serialize_uncompressed();
let hash = keccak256(&public[1..]);
let mut bytes = [0u8; 20];
bytes.copy_from_slice(&hash[12..]);
Address::from(bytes)
}
fn sign<M: AsRef<[u8]>>(&self, message: M) -> PersonalSignature {
let hash = Hash(hash_message(message.as_ref()));
let message = secp256k1::Message::from(hash);
let secp = Secp256k1::new();
secp.sign_ecdsa_recoverable(&message, &self.0).into()
}
}