/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(ðereum, ð_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 | | } |