1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
use std::fmt;
use std::str::FromStr;

use base58::ToBase58;
use bitcoin_hashes::hash160::Hash as Hash160;
use bitcoin_hashes::sha256d::Hash as SHA256d;
use bitcoin_hashes::Hash;
use cashaddr::CashEnc;
#[cfg(feature = "eth")]
use k256::elliptic_curve::sec1::ToEncodedPoint;
#[cfg(feature = "eth")]
use sha3::{Digest, Keccak256};

use super::{Error, Result};

#[allow(unused_imports)]
use super::Address;

/// Format specification for generating addresses from public keys.
///
/// Specifies how a public key should be formatted into an address. Unit-like variants
/// merely specify the blockchain network for which the address is intended to be used. No further
/// information is needed for unit-like variants because this crate only supports one way to
/// format the public key into an address string for that blockchain network.
///
/// Non-unit-like variants specify a blockchain network, but also contain additional user options
/// that further specify how the address should be formatted.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum AddressFormat {
    /// Bitcoin mainnet.
    // Bitcoin is the default value because it has the highest market capitalization at the time of
    // writing
    #[default]
    Bitcoin,
    /// Bitcoin cash mainnet. Inner value is the human-readable prefix to use in the address. If it
    /// is `Some`, the contained `String` will be used as the prefix to calculate with the cashaddr
    /// string with and it will be included in the cashaddr. If it is `None`, `"bitcoincash"` will
    /// be used as the prefix, but it will be omitted from the cashaddr as it is implied.
    BitcoinCash(Option<String>),
    /// Litecoin mainnet.
    Litecoin,
    /// Dogecoin mainnet.
    Dogecoin,
    /// Dash mainnet.
    Dash,
    /// Bitcoin Gold mainnet
    BitcoinGold,
    /// Europecoin mainnet
    Europecoin,
    /// Ripple mainnet
    Ripple,
    /// Peercoin mainnet
    Peercoin,
    /// Ethereum mainnet
    #[cfg(feature = "eth")]
    Ethereum,
}

impl FromStr for AddressFormat {
    type Err = Error;

    /// Parse a string `s` to return an `AddressFormat` instance. `s` must be either the common
    /// ticker symbol which represents the cryptocurrency or the name of the blockchain network.
    /// Parsing is case-insensitive: e.g. `"btc"`, `"bitcoin"`, `"BTC"`, and `"Bitcoin"` all parse
    /// to `Bitcoin`, `"ltc"` and `"litecoin"` parse to `Litecoin`, etc.
    ///
    /// In the case of Bitcoin Cash addresses, the optional human-readable prefix can be specified
    /// after a `:`, e.g. `"bch"` parses to `BitcoinCash(None)` and `"bch:hrpref` parses to
    /// `BitcoinCash(Some(String::from("hrpref")))`
    fn from_str(s: &str) -> Result<Self> {
        if s.to_lowercase().starts_with("bch") {
            return match s.find(':') {
                None => Ok(Self::BitcoinCash(None)),
                Some(position) => Ok(Self::BitcoinCash(Some(s[position + 1..].to_owned()))),
            };
        }
        match s.to_lowercase().as_ref() {
            "btc" | "bitcoin" => Ok(Self::Bitcoin),
            "ltc" | "litecoin" => Ok(Self::Litecoin),
            "doge" | "dogecoin" => Ok(Self::Dogecoin),
            "dash" => Ok(Self::Dash),
            "btg" | "bitcoingold" => Ok(Self::BitcoinGold),
            "xrp" => Ok(Self::Ripple),
            "erc" | "europecoin" => Ok(Self::Europecoin),
            "ppc" | "peercoin" => Ok(Self::Peercoin),
            #[cfg(feature = "eth")]
            "eth" | "ethereum" => Ok(Self::Ethereum),
            _ => Err(Error::ParseFailure(format!(
                "Could not parse {} to AddressFormat",
                s
            ))),
        }
    }
}

impl fmt::Display for AddressFormat {
    /// Write the name of the blockchain network this AddressFormat is associated with
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Bitcoin => write!(f, "Bitcoin"),
            Self::BitcoinCash(_) => write!(f, "Bitcoin Cash"),
            Self::BitcoinGold => write!(f, "Bitcoin Gold"),
            Self::Litecoin => write!(f, "Litecoin"),
            Self::Dogecoin => write!(f, "Dogecoin"),
            Self::Dash => write!(f, "Dash"),
            Self::Europecoin => write!(f, "Europecoin"),
            Self::Ripple => write!(f, "Ripple"),
            Self::Peercoin => write!(f, "Peercoin"),
            #[cfg(feature = "eth")]
            Self::Ethereum => write!(f, "Ethereum"),
        }
    }
}

fn pubkey_hash(pubkey: &[u8], prefix: u8) -> [u8; 25] {
    let pubkey_hash = Hash160::hash(pubkey);
    let mut raw_final = [prefix; 25];
    raw_final[1..21].copy_from_slice(&pubkey_hash);
    let checksum = SHA256d::hash(&raw_final[..21]);
    raw_final[21..].copy_from_slice(&checksum[..4]);
    raw_final
}

impl AddressFormat {
    /// get the address's prefix byte if it exists
    pub fn address_prefix(&self) -> Option<u8> {
        use AddressFormat::*;
        match self {
            Bitcoin => Some(0),
            BitcoinCash(_) => Some(0),
            Litecoin => Some(0x30),
            Dogecoin => Some(0x1E),
            Dash => Some(0x4C),
            BitcoinGold => Some(0x26),
            Europecoin => Some(0x21),
            Ripple => Some(0x00),
            Peercoin => Some(0x37),
            #[cfg(feature = "eth")]
            Ethereum => None,
        }
    }
    /// Get the name of the network associated with these options
    pub fn network_name(&self) -> String {
        self.to_string()
    }
    /// Get the maximum string length of the address generated for the given address options. This
    /// is useful for formatting.
    pub fn length(&self) -> usize {
        match self {
            Self::BitcoinCash(prefix) => match prefix {
                Some(prefix) => 43 + prefix.len(),
                None => 42,
            },
            #[cfg(feature = "eth")]
            Self::Ethereum => 42,
            _ => 34,
        }
    }
    fn verify_len(actual: usize, expected: usize) -> Result<()> {
        if actual != expected {
            Err(Error::InvalidLength { received: actual, expected})
        } else {
            Ok(())
        }
    }
    /// Compute the P2PKH address of a public encoded as SEC 1 compressed public key bytes
    pub(crate) fn derive(&self, pubkey_bytes: impl AsRef<[u8]>) -> Result<String> {
        use AddressFormat::*;
        let bytes = pubkey_bytes.as_ref();

        match self {
            Bitcoin | Litecoin | Dogecoin | Dash | BitcoinGold | Europecoin | Peercoin => {
                Self::verify_len(bytes.len(), 33)?;
                Ok(pubkey_hash(bytes, self.address_prefix().unwrap()).to_base58())
            }
            Ripple => {
                Self::verify_len(bytes.len(), 33)?;
                let pkh = pubkey_hash(bytes, self.address_prefix().unwrap());
                Ok(bs58::encode(pkh)
                    .with_alphabet(bs58::Alphabet::RIPPLE)
                    .into_string())
            }
            BitcoinCash(prefix) => {
                let keyhash = Hash160::hash(bytes);
                let string = match prefix {
                    Some(prefix) => keyhash.encode_p2pkh(prefix).unwrap(),
                    None => {
                        let mut string = keyhash.encode_p2pkh("bitcoincash").unwrap();
                        let position = string.find(':').unwrap();
                        string.replace_range(..position + 1, "");
                        string
                    }
                };
                Ok(string)
            }
            #[cfg(feature = "eth")]
            Ethereum => {
                let pubkey =
                    k256::PublicKey::from_sec1_bytes(bytes).expect("failed parsing eth bytes");
                let addr = Keccak256::digest(&pubkey.to_encoded_point(false).as_bytes()[1..]);
                Ok(eip55::checksum(&hex::encode(&addr[12..])))
            }
        }
    }
}
#[cfg(test)]
mod tests {
    use super::AddressFormat::{self, *};
    use crate::test_vectors::addr::*;

    #[test]
    fn parse_network() {
        assert_eq!("Bitcoin".parse(), Ok(Bitcoin));
        assert_eq!("BTC".parse(), Ok(Bitcoin));
        assert_eq!("btc".parse(), Ok(Bitcoin));
        assert_eq!("litecoin".parse(), Ok(Litecoin));
        assert_eq!("Litecoin".parse(), Ok(Litecoin));
        assert_eq!("ltc".parse(), Ok(Litecoin));
        assert_eq!("LTC".parse(), Ok(Litecoin));
        assert_eq!("dogecoin".parse(), Ok(Dogecoin));
        assert_eq!("Dogecoin".parse(), Ok(Dogecoin));
        assert_eq!("doge".parse(), Ok(Dogecoin));
        assert_eq!("DOGE".parse(), Ok(Dogecoin));
        assert_eq!("bch".parse(), Ok(BitcoinCash(None)));
        assert_eq!(
            "bch:ligma".parse(),
            Ok(BitcoinCash(Some("ligma".to_owned())))
        );
        #[cfg(feature = "eth")]
        assert_eq!("eth".parse(), Ok(Ethereum));
        #[cfg(feature = "eth")]
        assert_eq!("etheReUm".parse(), Ok(Ethereum));
    }
    #[test]
    fn network_name() {
        assert_eq!(Bitcoin.network_name(), "Bitcoin");
        assert_eq!(BitcoinCash(None).network_name(), "Bitcoin Cash");
        assert_eq!(
            BitcoinCash(Some(String::from("test"))).network_name(),
            "Bitcoin Cash"
        );
        assert_eq!(Litecoin.network_name(), "Litecoin");
        assert_eq!(Dogecoin.network_name(), "Dogecoin");
        assert_eq!(Dash.network_name(), "Dash");
        #[cfg(feature = "eth")]
        assert_eq!(Ethereum.network_name(), "Ethereum");
    }

    fn testfun(vectors: &str, opts: &AddressFormat) {
        for tc in vectors.lines().map(TestCase::from) {
            assert_eq!(opts.derive(tc.pubkey).as_deref(), Ok(tc.addr));
        }
    }

    #[test]
    fn serialize_doge() {
        testfun(DOGE_TEST_VECTORS, &Dogecoin)
    }
    #[test]
    fn serialize_btc() {
        testfun(BTC_TEST_VECTORS, &Bitcoin)
    }
    #[test]
    fn serialize_ltc() {
        testfun(LTC_TEST_VECTORS, &Litecoin)
    }
    #[test]
    fn serialize_dash() {
        testfun(DASH_TEST_VECTORS, &Dash)
    }
    #[test]
    fn serialize_btg() {
        testfun(BTG_TEST_VECTORS, &BitcoinGold)
    }
    #[test]
    fn serialize_erc() {
        testfun(ERC_TEST_VECTORS, &Europecoin)
    }
    #[test]
    fn serialize_xrp() {
        testfun(XRP_TEST_VECTORS, &Ripple)
    }
    #[test]
    fn serialize_ppc() {
        testfun(PPC_TEST_VECTORS, &Peercoin)
    }
    #[test]
    fn serialize_bch() {
        testfun(BCH_TEST_VECTORS, &BitcoinCash(None))
    }
    #[cfg(feature = "eth")]
    #[test]
    fn serialize_eth() {
        testfun(ETH_TEST_VECTORS, &Ethereum)
    }

    #[test]
    fn serialize_bch_prefix() {
        for tc in BCH_PREFIXED_VECTORS.lines().map(TestCase::from) {
            let prefix = &tc.addr[..5];
            let addr = BitcoinCash(Some(prefix.to_owned())).derive(tc.pubkey);
            assert_eq!(addr.as_deref(), Ok(tc.addr))
        }
    }
}