Coverage Report

Created: 2022-11-10 19:56

/home/runner/work/creditcoin/creditcoin/pallets/creditcoin/src/helpers/external_address.rs
Line
Count
Source
1
use crate::{Blockchain, ExternalAddress};
2
use base58::FromBase58;
3
use core::convert::TryFrom;
4
use frame_support::BoundedVec;
5
use sp_core::ecdsa::Public;
6
use sp_io::hashing::keccak_256;
7
use sp_io::hashing::sha2_256;
8
9
425
pub fn generate_external_address(
10
425
  blockchain: &Blockchain,
11
425
  reference: &ExternalAddress,
12
425
  public_key: Public,
13
425
) -> Option<ExternalAddress> {
14
425
  match blockchain {
15
425
    Blockchain::Evm(_) if EVMAddress::try_extract_address_type(reference).is_some() => {
16
424
      Some(EVMAddress::from_public(&public_key))
17
    },
18
1
    _ => None,
19
  }
20
425
}
21
22
pub trait PublicToAddress {
23
  type AddressType;
24
  fn try_extract_address_type(addr: &ExternalAddress) -> Option<Self::AddressType>;
25
  fn from_public(pkey: &Public) -> ExternalAddress;
26
}
27
28
pub struct EVMAddress;
29
30
impl PublicToAddress for EVMAddress {
31
  type AddressType = ();
32
425
  fn try_extract_address_type(addr: &ExternalAddress) -> Option<Self::AddressType> {
33
425
    if eth_address_is_well_formed(addr) {
34
424
      Some(())
35
    } else {
36
1
      None
37
    }
38
425
  }
39
40
868
  fn from_public(pkey: &Public) -> ExternalAddress {
41
868
    let pkey = libsecp256k1::PublicKey::parse_slice((*pkey).as_ref(), None)
42
868
      .expect("Public can't have invalid input length; qed")
43
868
      .serialize();
44
868
    //pkey uncompressed, 64 bytes
45
868
    let address_bytes = keccak_256(&pkey[1..])[12..].to_vec();
46
868
    BoundedVec::try_from(address_bytes).expect("20 bytes fit within bounds; qed")
47
868
  }
48
}
49
50
423
pub fn address_is_well_formed(blockchain: &Blockchain, address: &ExternalAddress) -> bool {
51
423
  match blockchain {
52
423
    Blockchain::Evm(_) => eth_address_is_well_formed(address),
53
423
  }
54
423
}
55
56
// bitcoin
57
#[cfg_attr(not(test), allow(dead_code))]
58
const BTC_MIN_LENGTH: usize = 25;
59
60
#[cfg_attr(not(test), allow(dead_code))]
61
16
fn btc_address_is_well_formed(address: &[u8]) -> bool {
62
16
  let 
address_str = if let Ok(s15
) = core::str::from_utf8(address) {
63
15
    s
64
  } else {
65
1
    return false;
66
  };
67
68
  // try to decode as bech32
69
15
  if bitcoin_bech32::WitnessProgram::from_address(address_str).is_ok() {
70
3
    return true;
71
12
  }
72
73
  // otherwise fall back to trying base58 check encoding
74
12
  let 
address_decoded = if let Ok(v9
) = address_str.from_base58() {
75
9
    v
76
  } else {
77
3
    return false;
78
  };
79
80
9
  if address_decoded.len() < BTC_MIN_LENGTH {
81
1
    return false;
82
8
  }
83
8
84
8
  let last4 = &address_decoded[address_decoded.len() - 4..];
85
8
  let hash = sha2_256(&sha2_256(&address_decoded[0..address_decoded.len() - 4]));
86
8
  let checksum = &hash[0..4];
87
8
  if last4 != checksum {
88
4
    return false;
89
4
  }
90
4
91
4
  true
92
16
}
93
94
// ether-like
95
96
const ETH_ADDRESS_LENGTH: usize = 20;
97
98
850
fn eth_address_is_well_formed(address: &[u8]) -> bool {
99
850
  address.len() == ETH_ADDRESS_LENGTH
100
850
}
101
#[cfg(test)]
102
mod tests {
103
  use core::convert::{TryFrom, TryInto};
104
  use sp_core::Pair;
105
106
  use super::*;
107
108
1
  #[test]
109
1
  fn eth_address_is_well_formed_works() {
110
1
    // length == 20
111
1
    assert!(eth_address_is_well_formed(
112
1
      hex::decode("5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed").unwrap().as_slice()
113
1
    ));
114
    // length != 20
115
1
    assert!(!eth_address_is_well_formed(&[0u8; ETH_ADDRESS_LENGTH - 1][..]));
116
1
  }
117
118
1
  #[test]
119
1
  fn btc_address_is_well_formed_works() {
120
1
    // p2pkh
121
1
    assert!(btc_address_is_well_formed(b"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"));
122
1
    assert!(btc_address_is_well_formed(b"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"));
123
124
    // bad checksums
125
1
    assert!(!btc_address_is_well_formed(b"1A1zP1eP5QGefi2DMPTfTL5SLmv7DiBEEF"));
126
1
    assert!(!btc_address_is_well_formed(b"1BvBMSEYstWetqTFn5Au4m4GFg7xJaBEEF"));
127
128
    // p2sh
129
1
    assert!(btc_address_is_well_formed(b"3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy"));
130
1
    assert!(btc_address_is_well_formed(b"3GRdnTq18LyNveWa1gQJcgp8qEnzijv5vR"));
131
132
    // bad checksums
133
1
    assert!(!btc_address_is_well_formed(b"3J98t1WpEZ73CNmQviecrnyiWrnqRhBEEF"));
134
1
    assert!(!btc_address_is_well_formed(b"3GRdnTq18LyNveWa1gQJcgp8qEnzijBEEF"));
135
136
    // p2wpkh/bech32
137
1
    assert!(btc_address_is_well_formed(b"bc1qnkyhslv83yyp0q0suxw0uj3lg9drgqq9c0auzc"));
138
1
    assert!(btc_address_is_well_formed(b"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"));
139
1
    assert!(btc_address_is_well_formed(
140
1
      b"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y"
141
1
    ));
142
143
    // bad checksums/invalid
144
1
    assert!(!btc_address_is_well_formed(b"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"));
145
1
    assert!(!btc_address_is_well_formed(
146
1
      b"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"
147
1
    ));
148
1
    assert!(!btc_address_is_well_formed(
149
1
      b"tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut"
150
1
    ));
151
1
  }
152
153
1
  #[test]
154
1
  fn btc_address_invalid_utf8() {
155
1
    assert!(!btc_address_is_well_formed(b"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5\xc3\x28"))
156
1
  }
157
158
1
  #[test]
159
1
  fn btc_address_too_short() {
160
1
    // successfully base58 decodes, but is too short
161
1
    assert!(!btc_address_is_well_formed(b"1A1zi2DMPTfTL5SLmv7DivfNa"))
162
1
  }
163
164
1
  #[test]
165
1
  fn address_is_well_formed_works() {
166
1
    let ethereum = Blockchain::ETHEREUM;
167
1
168
1
    let eth_addr = hex::decode("5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
169
1
      .unwrap()
170
1
      .try_into()
171
1
      .unwrap();
172
1
173
1
    assert!(address_is_well_formed(&ethereum, &eth_addr));
174
1
  }
175
176
1
  #[test]
177
  #[allow(non_snake_case)]
178
1
  fn EVMAddress_roundtrip() {
179
1
    let pair = sp_core::ecdsa::Pair::from_seed_slice(
180
1
      &hex::decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
181
1
        .unwrap(),
182
1
    )
183
1
    .unwrap();
184
1
    let public = pair.public();
185
1
    assert_eq!(
186
1
      public,
187
1
      Public::from_full(
188
1
        &hex::decode("8db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd913ebbe148dd17c56551a52952371071a6c604b3f3abe8f2c8fa742158ea6dd7d4").unwrap()[..],
189
1
      ).unwrap(),
190
1
    );
191
192
1
    let address = ExternalAddress::try_from(
193
1
      hex::decode("09231da7b19A016f9e576d23B16277062F4d46A8").unwrap(),
194
1
    )
195
1
    .unwrap();
196
1
    let address2 = EVMAddress::from_public(&public);
197
1
    assert!(address == address2);
198
1
  }
199
}