mtv_crypto/
account.rs

1use chrono::{DateTime, ParseError, SecondsFormat, TimeZone, Utc};
2use hex::encode;
3use lazy_static::lazy_static;
4use regex::Regex;
5use secp256k1::Secp256k1;
6use serde::{Deserialize, Serialize};
7use std::{
8    fmt::{Debug, Display, LowerHex, UpperHex},
9    ops::Deref,
10};
11
12use thiserror::Error;
13use web3::{
14    signing::{hash_message, keccak256, recover, RecoveryError},
15    types::{H160, H256},
16};
17
18/// An error that can occur when decoding a hexadecimal string
19#[derive(Debug, PartialEq, Error)]
20pub enum DecodeHexError {
21    #[error("hexadecimal value must be prefixed with 0x")]
22    MissingPrefix,
23
24    #[error("odd number of digits")]
25    OddLength,
26
27    #[error("invalid string length")]
28    InvalidLength,
29
30    #[error("invalid character {c:?} at position {index}")]
31    InvalidHexCharacter { c: char, index: usize },
32}
33
34/// Converts an `hex::FromHexError` into a DecodeHexError
35impl From<hex::FromHexError> for DecodeHexError {
36    fn from(err: hex::FromHexError) -> Self {
37        match err {
38            hex::FromHexError::OddLength => DecodeHexError::OddLength,
39            hex::FromHexError::InvalidStringLength => DecodeHexError::InvalidLength,
40            hex::FromHexError::InvalidHexCharacter { c, index } => {
41                DecodeHexError::InvalidHexCharacter {
42                    c,
43                    index: index + 2,
44                }
45            }
46        }
47    }
48}
49
50impl From<secp256k1::Error> for DecodeHexError {
51    fn from(_value: secp256k1::Error) -> Self {
52        DecodeHexError::InvalidLength
53    }
54}
55
56/// Decodes a hex string prefixed with `0x` into raw bytes.
57///
58/// Both, upper and lower case characters are valid in the input string and can
59/// even be mixed (e.g. `0xf9b4ca`, `0xF9B4CA` and `0xf9B4Ca` are all valid strings).
60///
61/// # Example
62///
63/// ```
64/// use mtv_crypto::account::{decode, DecodeHexError};
65///
66/// assert_eq!(
67///     decode("0x48656c6c6f20776f726c6421"),
68///     Ok("Hello world!".to_owned().into_bytes())
69/// );
70///
71/// assert_eq!(decode("123"), Err(DecodeHexError::MissingPrefix));
72/// assert_eq!(decode("0x123"), Err(DecodeHexError::OddLength));
73/// assert_eq!(decode("0xfo"), Err(DecodeHexError::InvalidHexCharacter { c: 'o', index: 3 }));
74/// ```
75pub fn decode(value: &str) -> Result<Vec<u8>, DecodeHexError> {
76    if !value.starts_with("0x") {
77        return Err(DecodeHexError::MissingPrefix);
78    }
79
80    if value.len() % 2 != 0 {
81        return Err(DecodeHexError::OddLength);
82    }
83
84    Ok(hex::decode(&value[2..])?)
85}
86
87/// Decode a hex string prefixed with `0x` into a mutable bytes slice.
88///
89/// Both, upper and lower case characters are valid in the input string and can
90/// even be mixed (e.g. `0xf9b4ca`, `0xF9B4CA` and `0xf9B4Ca` are all valid strings).
91///
92/// # Example
93///
94/// ```
95/// use mtv_crypto::account::{decode_to_slice, DecodeHexError};
96///
97/// let mut bytes = [0u8; 4];
98/// assert_eq!(decode_to_slice("0x6b697769", &mut bytes as &mut [u8]), Ok(()));
99/// assert_eq!(&bytes, b"kiwi");
100/// ```
101pub fn decode_to_slice(value: &str, bits: &mut [u8]) -> Result<(), DecodeHexError> {
102    if !value.starts_with("0x") {
103        return Err(DecodeHexError::MissingPrefix);
104    }
105
106    if value.len() != ((bits.len() * 2) + 2) {
107        return Err(DecodeHexError::InvalidLength);
108    }
109
110    Ok(hex::decode_to_slice(&value[2..], bits)?)
111}
112
113#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Copy)]
114#[serde(try_from = "String", into = "String")]
115pub struct Address(H160);
116
117impl Deref for Address {
118    type Target = H160;
119    fn deref(&self) -> &Self::Target {
120        &self.0
121    }
122}
123
124impl From<[u8; 20]> for Address {
125    /// Converts `[u8; 20]` into an `Address`
126    ///
127    /// ```rust
128    ///   use mtv_crypto::account::Address;
129    ///
130    ///   let address = Address::from([0; 20]);
131    ///   assert_eq!(address.to_string(), "0x0000000000000000000000000000000000000000")
132    /// ```
133    fn from(value: [u8; 20]) -> Self {
134        Self(H160(value))
135    }
136}
137
138impl From<H160> for Address {
139    /// Converts an `H160` into an `Address`
140    ///
141    /// ```rust
142    ///   use web3::types::H160;
143    ///   use mtv_crypto::account::Address;
144    ///
145    ///   let address = Address::from(H160([0; 20]));
146    ///   assert_eq!(address.to_string(), "0x0000000000000000000000000000000000000000")
147    /// ```
148    fn from(value: H160) -> Self {
149        Self(value)
150    }
151}
152
153impl std::cmp::PartialEq<H160> for Address {
154    fn eq(&self, other: &H160) -> bool {
155        self.0 == *other
156    }
157}
158
159impl std::cmp::PartialEq<H160> for &Address {
160    fn eq(&self, other: &H160) -> bool {
161        self.0 == *other
162    }
163}
164
165impl From<Address> for String {
166    /// Formats an `Address` into its `String` representation
167    fn from(value: Address) -> Self {
168        value.checksum()
169    }
170}
171
172impl TryFrom<&str> for Address {
173    type Error = DecodeHexError;
174
175    /// Converts an hexadecimal representation into an Address
176    ///
177    /// ```rust
178    ///   use mtv_crypto::account::Address;
179    ///
180    ///   let lower_address = Address::try_from("0xf15fd08462c3248b2bfe9c39b48af7801fc303db");
181    ///   let upper_address = Address::try_from("0xF15FD08462C3248B2BFE9C39B48AF7801FC303DB");
182    ///   let address_expected: [u8; 20] = [ 241, 95, 208, 132, 98, 195, 36, 139, 43, 254, 156, 57, 180, 138, 247, 128, 31, 195, 3, 219];
183    ///   assert_eq!(lower_address.unwrap(), Address::from(address_expected));
184    ///   assert_eq!(upper_address.unwrap(), Address::from(address_expected));
185    /// ```
186    ///
187    /// It requires the address string to be prefixed with `0x`
188    /// ```rust
189    ///   use mtv_crypto::account::{Address, DecodeHexError};
190    ///
191    ///   let not_prefixed_address = Address::try_from("f15fd08462c3248b2bfe9c39b48af7801fc303db");
192    ///   assert_eq!(not_prefixed_address.is_err(), true);
193    ///   assert_eq!(not_prefixed_address, Err(DecodeHexError::MissingPrefix));
194    /// ```
195    ///
196    /// It requires the address to be `42` characters long
197    /// ```rust
198    ///   use mtv_crypto::account::{Address, DecodeHexError};
199    ///
200    ///   let len_41_address = Address::try_from("0xf15fd08462c3248b2bfe9c39b48af7801fc303d");
201    ///   assert_eq!(len_41_address.is_err(), true);
202    ///   assert_eq!(len_41_address, Err(DecodeHexError::InvalidLength));
203    ///
204    ///   let len_43_address = Address::try_from("0xf15fd08462c3248b2bfe9c39b48af7801fc303dbb");
205    ///   assert_eq!(len_43_address.is_err(), true);
206    ///   assert_eq!(len_43_address, Err(DecodeHexError::InvalidLength));
207    /// ```
208    ///
209    /// It requires all characters to be hexadecimals
210    /// ```rust
211    ///   use mtv_crypto::account::{Address, DecodeHexError};
212    ///
213    ///   let not_hex_address = Address::try_from("0xf15fd08462c3248b2bfe9c39b48af7801fc303dx");
214    ///   assert_eq!(not_hex_address.is_err(), true);
215    ///   assert_eq!(not_hex_address, Err(DecodeHexError::InvalidHexCharacter{ c: 'x', index: 41}));
216    /// ```
217    fn try_from(value: &str) -> Result<Self, Self::Error> {
218        let mut bits: [u8; 20] = [0; 20];
219        match decode_to_slice(value, &mut bits) {
220            Ok(_) => Ok(Self::from(bits)),
221            Err(err) => Err(err),
222        }
223    }
224}
225
226impl TryFrom<String> for Address {
227    type Error = DecodeHexError;
228    fn try_from(value: String) -> Result<Self, Self::Error> {
229        Self::try_from(value.as_str())
230    }
231}
232
233impl UpperHex for Address {
234    /// Formats the `Address` into its hexadecimal uppercase representation
235    ///
236    /// ```rust
237    ///     use mtv_crypto::account::Address;
238    ///     let address = Address::from([255; 20]);
239    ///     let zero = Address::from([0; 20]);
240    ///
241    ///     assert_eq!(format!("{zero:X}"), "0000000000000000000000000000000000000000");
242    ///     assert_eq!(format!("{address:X}"), "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
243    ///
244    ///     assert_eq!(format!("{zero:#X}"), "0X0000000000000000000000000000000000000000");
245    ///     assert_eq!(format!("{address:#X}"), "0XFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
246    /// ```
247    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248        std::fmt::UpperHex::fmt(&**self, f)
249    }
250}
251
252impl LowerHex for Address {
253    /// Formats the `Address` into its hexadecimal lowercase representation
254    ///
255    /// ```rust
256    ///     use mtv_crypto::account::Address;
257    ///     let address = Address::from([255; 20]);
258    ///     let zero = Address::from([0; 20]);
259    ///
260    ///     assert_eq!(format!("{zero:x}"), "0000000000000000000000000000000000000000");
261    ///     assert_eq!(format!("{address:x}"), "ffffffffffffffffffffffffffffffffffffffff");
262    ///
263    ///     assert_eq!(format!("{zero:#x}"), "0x0000000000000000000000000000000000000000");
264    ///     assert_eq!(format!("{address:#x}"), "0xffffffffffffffffffffffffffffffffffffffff");
265    /// ```
266    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267        std::fmt::LowerHex::fmt(&**self, f)
268    }
269}
270
271impl Display for Address {
272    /// Format an Address into it string representation
273    ///
274    /// ```rust
275    ///   use mtv_crypto::account::Address;
276    ///   assert_eq!(Address::from([0; 20]).to_string(), "0x0000000000000000000000000000000000000000");
277    ///   assert_eq!(Address::from([255; 20]).to_string(), "0xffffffffffffffffffffffffffffffffffffffff");
278    /// ```
279    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280        write!(f, "{self:#x}")
281    }
282}
283
284impl Address {
285    /// Creates an instance of the zero address
286    ///
287    /// ```rust
288    ///   use mtv_crypto::account::Address;
289    ///   assert_eq!(Address::zero().to_string(), "0x0000000000000000000000000000000000000000")
290    /// ```
291    pub fn zero() -> Self {
292        Self::from([0; 20])
293    }
294
295    /// Calculate ERC-55 version of the address
296    ///
297    /// ```rust
298    ///     use mtv_crypto::account::Address;
299    ///     assert_eq!(Address::try_from("0x0f5d2fb29fb7d3cfee444a200298f468908cc942").unwrap().checksum(), "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942");
300    ///     assert_eq!(Address::try_from("0x554bb6488ba955377359bed16b84ed0822679cdc").unwrap().checksum(), "0x554BB6488bA955377359bED16b84Ed0822679CDC");
301    ///     assert_eq!(Address::try_from("0x1784ef41af86e97f8d28afe95b573a24aeda966e").unwrap().checksum(), "0x1784Ef41af86e97f8D28aFe95b573a24aEDa966e");
302    ///     assert_eq!(Address::from([255; 20]).checksum(), "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF");
303    ///     assert_eq!(Address::from([0; 20]).checksum(), "0x0000000000000000000000000000000000000000");
304    /// ```
305    pub fn checksum(&self) -> String {
306        let hash = keccak256(format!("{self:x}").as_bytes());
307        let checksum = self
308            .as_bytes()
309            .iter()
310            .enumerate()
311            .map(|(i, b)| {
312                // let h = hash[i];
313                let h1 = (hash[i] & 0b1111_0000) >> 4;
314                let h2 = hash[i] & 0b0000_1111;
315                let hex = format!("{b:02x}");
316                let b1 = hex.get(0..=0).unwrap_or("0");
317                let b2 = hex.get(1..=1).unwrap_or("0");
318                // hex
319                format!(
320                    "{}{}",
321                    if h1 >= 8 {
322                        b1.to_uppercase()
323                    } else {
324                        b1.to_lowercase()
325                    },
326                    if h2 >= 8 {
327                        b2.to_uppercase()
328                    } else {
329                        b2.to_lowercase()
330                    },
331                )
332            })
333            .collect::<String>();
334
335        format!("0x{checksum}")
336    }
337}
338
339pub const PERSONAL_SIGNATURE_SIZE: usize = 65;
340
341#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
342#[serde(try_from = "String", into = "String")]
343pub struct PersonalSignature([u8; PERSONAL_SIGNATURE_SIZE]);
344
345impl Deref for PersonalSignature {
346    type Target = [u8; PERSONAL_SIGNATURE_SIZE];
347    fn deref(&self) -> &Self::Target {
348        &self.0
349    }
350}
351
352impl Default for PersonalSignature {
353    fn default() -> Self {
354        PersonalSignature([0; PERSONAL_SIGNATURE_SIZE])
355    }
356}
357
358impl From<[u8; PERSONAL_SIGNATURE_SIZE]> for PersonalSignature {
359    fn from(value: [u8; PERSONAL_SIGNATURE_SIZE]) -> Self {
360        Self(value)
361    }
362}
363
364impl From<web3::signing::Signature> for PersonalSignature {
365    fn from(value: web3::signing::Signature) -> Self {
366        let mut bits = [0u8; PERSONAL_SIGNATURE_SIZE];
367        bits[..32].copy_from_slice(&value.r.0);
368        bits[32..64].copy_from_slice(&value.s.0);
369        bits[64] = (value.v * 0b1111) as u8;
370        Self(bits)
371    }
372}
373
374impl From<secp256k1::ecdsa::RecoverableSignature> for PersonalSignature {
375    fn from(value: secp256k1::ecdsa::RecoverableSignature) -> Self {
376        let mut bits = [0u8; PERSONAL_SIGNATURE_SIZE];
377        let (recovery_id, signature) = value.serialize_compact();
378        bits[..64].copy_from_slice(&signature);
379        bits[64] = 27 + recovery_id.to_i32() as u8;
380        Self(bits)
381    }
382}
383impl TryFrom<&str> for PersonalSignature {
384    type Error = DecodeHexError;
385
386    fn try_from(value: &str) -> Result<Self, Self::Error> {
387        if value.len() != 132 {
388            return Err(DecodeHexError::InvalidLength);
389        }
390
391        let mut bits = [0u8; PERSONAL_SIGNATURE_SIZE];
392        match decode_to_slice(value, &mut bits) {
393            Ok(_) => Ok(Self::from(bits)),
394            Err(err) => Err(err),
395        }
396    }
397}
398
399impl TryFrom<String> for PersonalSignature {
400    type Error = DecodeHexError;
401
402    fn try_from(value: String) -> Result<Self, Self::Error> {
403        Self::try_from(value.as_str())
404    }
405}
406
407impl From<PersonalSignature> for String {
408    fn from(value: PersonalSignature) -> Self {
409        value.to_string()
410    }
411}
412
413impl From<PersonalSignature> for web3::signing::Signature {
414    fn from(value: PersonalSignature) -> Self {
415        let mut r: [u8; 32] = [0; 32];
416        r.copy_from_slice(&value[..32]);
417
418        let mut s: [u8; 32] = [0; 32];
419        s.copy_from_slice(&value[32..64]);
420
421        let v: u64 = value.0[64] as u64;
422
423        Self {
424            v,
425            r: H256(r),
426            s: H256(s),
427        }
428    }
429}
430
431impl From<PersonalSignature> for Vec<u8> {
432    fn from(value: PersonalSignature) -> Self {
433        value.to_vec()
434    }
435}
436
437impl Display for PersonalSignature {
438    /// Format signature on its hexadecimal representation
439    ///
440    /// ```rust
441    ///   use mtv_crypto::account::PersonalSignature;
442    ///   assert_eq!(PersonalSignature::from([0; 65]).to_string(), "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
443    /// ```
444    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
445        write!(f, "0x{}", hex::encode(self.0))
446    }
447}
448
449impl PersonalSignature {
450    /// Recover the signer of the signature from a giving message
451    ///
452    /// ```rust
453    ///   use mtv_crypto::account::PersonalSignature;
454    ///   let signer = "0x13fe90239bfda363ec33a849b716616958c04f0f";
455    ///   let message = "Memetaverse Login\nEphemeral address: 0x68560651BD91509EB22b90f6F748422A26CA3425\nExpiration: 8020-07-28T11:11:08.989Z";
456    ///   let payload = "0xaf237ccf4690d21c671c03a65aae48d1cbf94afdcfa3ed7569c20eb5769f2e273d4249c19e4a138acf063e5dff2b925e78a6c057fa864737ebaf65d9f1f464741c";
457    ///
458    ///   let sign = PersonalSignature::try_from(payload).unwrap();
459    ///   let address = sign.try_recover_from_message(message).unwrap();
460    ///   assert_eq!(address.to_string(), signer)
461    /// ```
462    pub fn try_recover_from_message(&self, message: &str) -> Result<Address, RecoveryError> {
463        let result = recover(
464            hash_message(message).as_bytes(),
465            &self.0[..=63],
466            (self.0[64] as i32) - 27,
467        );
468
469        match result {
470            Ok(h160) => Ok(Address::from(h160)),
471            Err(err) => Err(err),
472        }
473    }
474
475    pub fn is_valid_signature(&self, message: &str, signer: &Address) -> bool {
476        self.try_recover_from_message(message)
477            .map(|address| address == *signer)
478            .unwrap_or(false)
479    }
480}
481
482/// And
483#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
484#[serde(try_from = "String", into = "String")]
485pub struct EIP1271Signature(Vec<u8>);
486
487impl Deref for EIP1271Signature {
488    type Target = Vec<u8>;
489    fn deref(&self) -> &Self::Target {
490        &self.0
491    }
492}
493
494impl Display for EIP1271Signature {
495    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496        write!(f, "0x{}", hex::encode(&self.0))
497    }
498}
499
500impl TryFrom<&str> for EIP1271Signature {
501    type Error = DecodeHexError;
502
503    fn try_from(value: &str) -> Result<Self, Self::Error> {
504        let data = decode(value)?;
505        Ok(Self(data))
506    }
507}
508
509impl TryFrom<String> for EIP1271Signature {
510    type Error = DecodeHexError;
511
512    fn try_from(value: String) -> Result<Self, Self::Error> {
513        Self::try_from(value.as_str())
514    }
515}
516
517impl From<EIP1271Signature> for Vec<u8> {
518    fn from(value: EIP1271Signature) -> Self {
519        value.to_vec()
520    }
521}
522
523impl From<EIP1271Signature> for String {
524    fn from(value: EIP1271Signature) -> Self {
525        format!("0x{}", hex::encode(value.0))
526    }
527}
528
529#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, PartialOrd, Copy)]
530#[serde(try_from = "String", into = "String")]
531pub struct Expiration(DateTime<Utc>);
532
533impl Deref for Expiration {
534    type Target = DateTime<Utc>;
535    fn deref(&self) -> &Self::Target {
536        &self.0
537    }
538}
539
540impl<T: chrono::TimeZone> From<DateTime<T>> for Expiration {
541    fn from(value: DateTime<T>) -> Self {
542        Expiration(value.with_timezone(&Utc))
543    }
544}
545
546impl TryFrom<&str> for Expiration {
547    type Error = ParseError;
548
549    fn try_from(value: &str) -> Result<Self, Self::Error> {
550        let expiration = DateTime::parse_from_rfc3339(value)?;
551        Ok(Self(expiration.with_timezone(&Utc)))
552    }
553}
554
555impl TryFrom<String> for Expiration {
556    type Error = ParseError;
557
558    fn try_from(value: String) -> Result<Self, Self::Error> {
559        Self::try_from(value.as_str())
560    }
561}
562
563impl From<Expiration> for String {
564    fn from(value: Expiration) -> Self {
565        value.to_string()
566    }
567}
568
569impl Display for Expiration {
570    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
571        write!(f, "{}", self.0.to_rfc3339_opts(SecondsFormat::Millis, true))
572    }
573}
574
575impl From<Expiration> for DateTime<Utc> {
576    fn from(value: Expiration) -> Self {
577        value.0
578    }
579}
580
581#[derive(PartialEq, Debug, Error)]
582pub enum EphemeralPayloadError {
583    #[error("invalid payload content")]
584    InvalidPayload,
585
586    #[error("missing title line on payload")]
587    MissingTitle,
588
589    #[error("missing address line on payload")]
590    MissingAddress,
591
592    #[error("invalid address: {err} (address: {value})")]
593    InvalidAddress { err: DecodeHexError, value: String },
594
595    #[error("missing expiration line on payload")]
596    MissingExpiration,
597
598    #[error("invalid expiration: {err} (expiration: {value})")]
599    InvalidExpiration { err: ParseError, value: String },
600}
601
602/// Alias of EIP1271Signature
603/// See <https://eips.ethereum.org/EIPS/eip-1271>
604/// See <https://github.com/ethereum/EIPs/issues/1654>
605pub type EIP1654Signature = EIP1271Signature;
606
607static DEFAULT_EPHEMERAL_PAYLOAD_TITLE: &str = "Memetaverse Login";
608
609/// An `EphemeralPayload` is a message that delegates the right to sign a message to a specific address until an expiration date.
610///
611/// ```rust
612///     use mtv_crypto::account::{Address, EphemeralPayload, Expiration};
613///
614///     let payload = EphemeralPayload::try_from("Memetaverse Login\nEphemeral address: 0xA69ef8104E05325B01A15bA822Be43eF13a2f5d3\nExpiration: 2023-03-30T15:44:55.787Z").unwrap();
615///     let expiration = Expiration::try_from("2023-03-30T15:44:55.787Z").unwrap();
616///
617///     assert_eq!(payload, EphemeralPayload::new(
618///         Address::try_from("0xA69ef8104E05325B01A15bA822Be43eF13a2f5d3").unwrap(),
619///         expiration,
620///     ))
621/// ```
622#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
623#[serde(try_from = "String", into = "String")]
624pub struct EphemeralPayload {
625    pub title: String,
626    pub address: Address,
627    pub expiration: Expiration,
628}
629
630static RE_TITLE_CAPTURE: &str = "title";
631static RE_ADDRESS_CAPTURE: &str = "address";
632static RE_EXPIRATION_CAPTURE: &str = "expiration";
633
634impl TryFrom<&str> for EphemeralPayload {
635    type Error = EphemeralPayloadError;
636
637    fn try_from(value: &str) -> Result<Self, Self::Error> {
638        lazy_static! {
639            static ref EPHEMERAL_PAYLOAD_REGEX: Regex = Regex::new(&format!(
640                r"^(?P<{}>[^\r\n]*)\r?\nEphemeral address: (?P<{}>[^\r\n]*)\r?\nExpiration: (?P<{}>.*)$",
641                RE_TITLE_CAPTURE, RE_ADDRESS_CAPTURE, RE_EXPIRATION_CAPTURE
642            ))
643            .unwrap();
644        }
645
646        let captures = match EPHEMERAL_PAYLOAD_REGEX.captures(value) {
647            None => return Err(EphemeralPayloadError::InvalidPayload),
648            Some(captures) => captures,
649        };
650
651        let title = match captures.name(RE_TITLE_CAPTURE) {
652            None => return Err(EphemeralPayloadError::MissingTitle),
653            Some(title) => title.as_str().to_string(),
654        };
655
656        let address = match captures.name(RE_ADDRESS_CAPTURE) {
657            None => return Err(EphemeralPayloadError::MissingAddress),
658            Some(address) => {
659                let value = address.as_str();
660                Address::try_from(value).map_err(|err| EphemeralPayloadError::InvalidAddress {
661                    value: value.to_string(),
662                    err,
663                })?
664            }
665        };
666
667        let expiration = match captures.name(RE_EXPIRATION_CAPTURE) {
668            None => return Err(EphemeralPayloadError::MissingExpiration),
669            Some(expiration) => {
670                let value = expiration.as_str();
671                Expiration::try_from(value).map_err(|err| {
672                    EphemeralPayloadError::InvalidExpiration {
673                        value: value.to_string(),
674                        err,
675                    }
676                })?
677            }
678        };
679
680        Ok(Self {
681            title,
682            address,
683            expiration,
684        })
685    }
686}
687
688impl TryFrom<String> for EphemeralPayload {
689    type Error = EphemeralPayloadError;
690
691    fn try_from(value: String) -> Result<Self, Self::Error> {
692        Self::try_from(value.as_str())
693    }
694}
695
696impl Display for EphemeralPayload {
697    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
698        write!(
699            f,
700            "{}\nEphemeral address: {}\nExpiration: {}",
701            self.title,
702            self.address.checksum(),
703            self.expiration
704        )
705    }
706}
707
708impl From<EphemeralPayload> for String {
709    fn from(payload: EphemeralPayload) -> Self {
710        format!("{}", payload)
711    }
712}
713
714impl EphemeralPayload {
715    pub fn new(address: Address, expiration: Expiration) -> Self {
716        Self::new_with_title(
717            String::from(DEFAULT_EPHEMERAL_PAYLOAD_TITLE),
718            address,
719            expiration,
720        )
721    }
722
723    pub fn new_with_title(title: String, address: Address, expiration: Expiration) -> Self {
724        Self {
725            title,
726            address,
727            expiration,
728        }
729    }
730
731    pub fn is_expired(&self) -> bool {
732        *self.expiration < Utc::now()
733    }
734
735    pub fn is_expired_at<Z: TimeZone>(&self, time: &DateTime<Z>) -> bool {
736        *self.expiration < *time
737    }
738}
739
740// Abstraction to implement secp256k1::ThirtyTwoByteHash for H256
741struct Hash(H256);
742
743impl secp256k1::ThirtyTwoByteHash for Hash {
744    fn into_32(self) -> [u8; 32] {
745        self.0 .0
746    }
747}
748
749// Calculate the public key from a secret key
750fn to_public_key(secret: &secp256k1::SecretKey) -> secp256k1::PublicKey {
751    lazy_static! {
752        static ref SECP256K1: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
753    }
754
755    secret.public_key(&SECP256K1)
756}
757
758// Intermediary representation of a private key from an Identity
759#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
760#[serde(rename_all = "camelCase")]
761struct EphemeralAccountRepresentation {
762    pub address: String,
763    pub public_key: String,
764    pub private_key: String,
765}
766
767impl TryFrom<EphemeralAccountRepresentation> for Account {
768    type Error = DecodeHexError;
769
770    fn try_from(value: EphemeralAccountRepresentation) -> Result<Self, Self::Error> {
771        Account::try_from(value.private_key)
772    }
773}
774
775
776/// A Struct that allows us to sign messages and serialize and deserialize it easily
777#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
778#[serde(
779    try_from = "EphemeralAccountRepresentation",
780    into = "EphemeralAccountRepresentation"
781)]
782pub struct Account(secp256k1::SecretKey);
783
784impl TryFrom<&str> for Account {
785    type Error = DecodeHexError;
786
787    /// Creates a new account from a private key in hex format.
788    ///
789    /// ```rust
790    /// use mtv_crypto::account::Account;
791    ///
792    /// ```
793    fn try_from(value: &str) -> Result<Self, Self::Error> {
794        let mut bytes = [0u8; 32];
795        decode_to_slice(value, &mut bytes)?;
796        let key = secp256k1::SecretKey::from_slice(&bytes)?;
797        Ok(Self(key))
798    }
799}
800
801/// Creates an account from a private key in hex format.
802impl TryFrom<String> for Account {
803    type Error = DecodeHexError;
804
805    fn try_from(value: String) -> Result<Self, Self::Error> {
806        Self::try_from(value.as_str())
807    }
808}
809
810/// Converts an account into the hexadecimal representation of its private key.
811impl From<Account> for String {
812    fn from(account: Account) -> Self {
813        format!("0x{}", encode(account.0.secret_bytes()))
814    }
815}
816
817/// Converts an account into a representation that can be serialized.
818impl From<Account> for EphemeralAccountRepresentation {
819    fn from(account: Account) -> Self {
820        let public = to_public_key(&account.0).serialize_uncompressed();
821        Self {
822            address: account.address().checksum(),
823            public_key: format!("0x{}", hex::encode(public)),
824            private_key: account.into(),
825        }
826    }
827}
828
829impl Account {
830
831    /// Creates a new account generating a random private key.
832    pub fn random() -> Self {
833        Self::from_rng(&mut rand::thread_rng())
834    }
835
836    /// Creates a new account using a custom RNG (Random Number Generator) to create the private key.
837    pub fn from_rng<R: rand::Rng + ?Sized>(r: &mut R) -> Self {
838        Self(secp256k1::SecretKey::new(r))
839    }
840}
841
842/// A trait for signing messages with an associated address.
843pub trait Signer {
844
845    /// Return the address of the signer.
846    fn address(&self) -> Address;
847
848    /// Sign a message with the Address's private key.
849    fn sign<M: AsRef<[u8]>>(&self, message: M) -> PersonalSignature;
850}
851
852impl Signer for Account {
853    /// Return the address of the account.
854    ///
855    /// ```rust
856    /// use mtv_crypto::account::{Account, Address, Signer};
857    ///
858    /// let account = Account::try_from("0xbc453a92d9baeb3d10294cbc1d48ef6738f718fd31b4eb8085efe7b311299399").unwrap();
859    /// assert_eq!(account.address(), Address::try_from("0x84452bbFA4ca14B7828e2F3BBd106A2bD495CD34").unwrap());
860    /// ```
861    fn address(&self) -> Address {
862        let public = to_public_key(&self.0).serialize_uncompressed();
863        let hash = keccak256(&public[1..]);
864        let mut bytes = [0u8; 20];
865        bytes.copy_from_slice(&hash[12..]);
866        Address::from(bytes)
867    }
868
869    /// Sign a message with the account.
870    ///
871    /// ```rust
872    /// use mtv_crypto::account::{Account, PersonalSignature, Signer};
873    ///
874    /// let account = Account::try_from("0xbc453a92d9baeb3d10294cbc1d48ef6738f718fd31b4eb8085efe7b311299399").unwrap();
875    /// let message = account.sign("signed message");
876    /// assert_eq!(message, PersonalSignature::try_from("0x013e0b0b75bd8404d70a37d96bb893596814d8f29f517e383d9d1421111f83c32d4ca0d6e399349c7badd54261feaa39895d027880d28d806c01089677400b7c1b").unwrap());
877    /// ```
878    ///
879    fn sign<M: AsRef<[u8]>>(&self, message: M) -> PersonalSignature {
880        let hash = Hash(hash_message(message.as_ref()));
881        let message = secp256k1::Message::from(hash);
882        let secp = Secp256k1::new();
883        secp.sign_ecdsa_recoverable(&message, &self.0).into()
884    }
885}