crypto_addr/
address.rs

1use std::fmt;
2use std::str::FromStr;
3
4use base58::ToBase58;
5use bitcoin_hashes::hash160::Hash as Hash160;
6use bitcoin_hashes::sha256d::Hash as SHA256d;
7use bitcoin_hashes::Hash;
8use cashaddr::CashEnc;
9#[cfg(feature = "eth")]
10use k256::elliptic_curve::sec1::ToEncodedPoint;
11#[cfg(feature = "eth")]
12use sha3::{Digest, Keccak256};
13
14use super::{Error, Result};
15
16/// Format specification for generating addresses from public keys.
17///
18/// Specifies how a public key should be formatted into an address. Unit-like variants
19/// merely specify the blockchain network for which the address is intended to be used. No further
20/// information is needed for unit-like variants because this crate only supports one way to
21/// format the public key into an address string for that blockchain network.
22///
23/// Non-unit-like variants specify a blockchain network, but also contain additional user options
24/// that further specify how the address should be formatted.
25#[non_exhaustive]
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum AddressFormat {
28    /// Bitcoin mainnet.
29    // Bitcoin is the default value because it has the highest market capitalization at the time of
30    // writing
31    Bitcoin(BitcoinFormat),
32    BitcoinTestnet,
33    #[cfg(feature = "eth")]
34    Ethereum,
35    /// Bitcoin cash mainnet. Inner value is the human-readable prefix to use in the address. If it
36    /// is `Some`, the contained `String` will be used as the prefix to calculate with the cashaddr
37    /// string with and it will be included in the cashaddr. If it is `None`, `"bitcoincash"` will
38    /// be used as the prefix, but it will be omitted from the cashaddr as it is implied.
39    BitcoinCash(Option<String>),
40    Litecoin,
41    LitecoinCash,
42    Dogecoin,
43    Dash,
44    Ripple,
45    Namecoin,
46    Asiacoin,
47    BitcoinGold,
48    Beetlecoin,
49    Blackcoin,
50    Clams,
51    Europecoin,
52    Feathercoin,
53    Fujicoin,
54    Hempcoin,
55    Peercoin,
56    Potcoin,
57    ShadowCash,
58    Stratis,
59    Syscoin,
60    VCash,
61    Verge,
62    Vertcoin,
63    ZCash,
64}
65
66impl Default for AddressFormat {
67    fn default() -> Self {
68        Self::Bitcoin(Default::default())
69    }
70}
71
72/// Bitcoin Address format type
73///
74/// Bitcoin public keys can generate addresses in differeent formats. Bitcoin is special among the
75/// other `AddressFormat` variants in that there is more the one way to format the public key. This
76/// enum is used to specify which of the supported formats to use for Bitcoin addresses.
77#[non_exhaustive]
78#[derive(Debug, Clone, PartialEq, Eq, Default)]
79pub enum BitcoinFormat {
80    #[default]
81    /// Legacy P2PKH format
82    Legacy,
83    /// Pay to script address that embeds a witness pay to public key. This is a segwit address
84    /// type that looks familiar (as p2sh) to legacy clients.
85    P2SHWPKH,
86}
87
88impl FromStr for AddressFormat {
89    type Err = Error;
90
91    /// Parse a string `s` to return an `AddressFormat` instance. `s` must be either the common
92    /// ticker symbol which represents the cryptocurrency or the name of the blockchain network.
93    /// Parsing is case-insensitive: e.g. `"btc"`, `"bitcoin"`, `"BTC"`, and `"Bitcoin"` all parse
94    /// to `Bitcoin`, `"ltc"` and `"litecoin"` parse to `Litecoin`, etc.
95    ///
96    /// In the case of Bitcoin Cash addresses, the optional human-readable prefix can be specified
97    /// after a `:`, e.g. `"bch"` parses to `BitcoinCash(None)` and `"bch:hrpref` parses to
98    /// `BitcoinCash(Some(String::from("hrpref")))`
99    fn from_str(s: &str) -> Result<Self> {
100        if s.to_lowercase().starts_with("bch") {
101            return match s.find(':') {
102                None => Ok(Self::BitcoinCash(None)),
103                Some(position) => Ok(Self::BitcoinCash(Some(s[position + 1..].to_owned()))),
104            };
105        }
106        match s.to_lowercase().as_ref() {
107            "btc" | "bitcoin" => Ok(Self::Bitcoin(BitcoinFormat::Legacy)),
108            "btc:sw" => Ok(Self::Bitcoin(BitcoinFormat::P2SHWPKH)),
109            "btctest" | "bitcointestnet" => Ok(Self::BitcoinTestnet),
110            "ltc" | "litecoin" => Ok(Self::Litecoin),
111            "lcc" | "litecoincash" => Ok(Self::LitecoinCash),
112            "doge" | "dogecoin" => Ok(Self::Dogecoin),
113            "dash" => Ok(Self::Dash),
114            "btg" | "bitcoingold" => Ok(Self::BitcoinGold),
115            "xrp" => Ok(Self::Ripple),
116            "erc" | "europecoin" => Ok(Self::Europecoin),
117            "ppc" | "peercoin" => Ok(Self::Peercoin),
118            "sys" | "syscoin" => Ok(Self::Syscoin),
119            "ftc" | "feathercoin" => Ok(Self::Feathercoin),
120            "pot" | "potcoin" => Ok(Self::Potcoin),
121            "nmc" | "namecoin" => Ok(Self::Namecoin),
122            "fjc" | "fujicoin" => Ok(Self::Fujicoin),
123            "xvg" | "verge" => Ok(Self::Verge),
124            "thc" | "hempcoin" => Ok(Self::Hempcoin),
125            "xvc" | "vcash" => Ok(Self::VCash),
126            "zec" | "zcash" => Ok(Self::ZCash),
127            "vtc" | "vertcoin" => Ok(Self::Vertcoin),
128            "strat" | "stratis" => Ok(Self::Stratis),
129            "clam" | "clams" => Ok(Self::Clams),
130            "blk" | "blackcoin" => Ok(Self::Blackcoin),
131            "sdc" | "shadowcash" => Ok(Self::ShadowCash),
132            "beet" | "beetlecoin" => Ok(Self::Beetlecoin),
133            "ac" | "asiacoin" => Ok(Self::Asiacoin),
134            #[cfg(feature = "eth")]
135            "eth" | "ethereum" => Ok(Self::Ethereum),
136            _ => Err(Error::ParseFailure(format!(
137                "Could not parse {} to AddressFormat",
138                s
139            ))),
140        }
141    }
142}
143
144impl fmt::Display for AddressFormat {
145    /// Write the name of the blockchain network this AddressFormat is associated with
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        match self {
148            Self::Bitcoin(_) => write!(f, "Bitcoin"),
149            Self::BitcoinTestnet => write!(f, "Bitcoin Testnet"),
150            Self::BitcoinCash(_) => write!(f, "Bitcoin Cash"),
151            Self::BitcoinGold => write!(f, "Bitcoin Gold"),
152            Self::Litecoin => write!(f, "Litecoin"),
153            Self::LitecoinCash => write!(f, "Litecoin Cash"),
154            Self::Dogecoin => write!(f, "Dogecoin"),
155            Self::Dash => write!(f, "Dash"),
156            Self::Europecoin => write!(f, "Europecoin"),
157            Self::Ripple => write!(f, "Ripple"),
158            Self::Peercoin => write!(f, "Peercoin"),
159            Self::Syscoin => write!(f, "Syscoin"),
160            Self::Feathercoin => write!(f, "Feathercoin"),
161            Self::Potcoin => write!(f, "Potcoin"),
162            Self::Namecoin => write!(f, "Namecoin"),
163            Self::Fujicoin => write!(f, "Fujicoin"),
164            Self::Verge => write!(f, "Verge"),
165            Self::Hempcoin => write!(f, "Hempcoin"),
166            Self::VCash => write!(f, "VCash"),
167            Self::ZCash => write!(f, "ZCash"),
168            Self::Vertcoin => write!(f, "Vertcoin"),
169            Self::Stratis => write!(f, "Stratis"),
170            Self::Clams => write!(f, "Clams"),
171            Self::Blackcoin => write!(f, "Blackcoin"),
172            Self::ShadowCash => write!(f, "ShadowCash"),
173            Self::Beetlecoin => write!(f, "Beetlecoin"),
174            Self::Asiacoin => write!(f, "Asiacoin"),
175            #[cfg(feature = "eth")]
176            Self::Ethereum => write!(f, "Ethereum"),
177        }
178    }
179}
180
181fn pubkey_hash(pubkey: &[u8], prefix: u8) -> [u8; 25] {
182    let pubkey_hash = Hash160::hash(pubkey);
183    let mut raw_final = [prefix; 25];
184    raw_final[1..21].copy_from_slice(&pubkey_hash);
185    let checksum = SHA256d::hash(&raw_final[..21]);
186    raw_final[21..].copy_from_slice(&checksum[..4]);
187    raw_final
188}
189
190impl AddressFormat {
191    /// get the address's prefix byte if it exists
192    pub fn address_prefix(&self) -> Option<u8> {
193        use AddressFormat::*;
194        match self {
195            Bitcoin(BitcoinFormat::Legacy) => Some(0),
196            Bitcoin(BitcoinFormat::P2SHWPKH) => None,
197            BitcoinTestnet => Some(0x6F),
198            BitcoinCash(_) => Some(0),
199            Litecoin => Some(0x30),
200            LitecoinCash => Some(0x1C),
201            Dogecoin => Some(0x1E),
202            Dash => Some(0x4C),
203            BitcoinGold => Some(0x26),
204            Europecoin => Some(0x21),
205            Ripple => Some(0x00),
206            Peercoin => Some(0x37),
207            Syscoin => Some(0x3F),
208            Feathercoin => Some(0x0E),
209            Potcoin => Some(0x37),
210            Namecoin => Some(0x34),
211            Fujicoin => Some(0x24),
212            Verge => Some(0x1E),
213            Hempcoin => Some(0x28),
214            VCash => Some(0x47),
215            Vertcoin => Some(0x47),
216            Stratis => Some(0x3F),
217            Clams => Some(0x89),
218            Blackcoin => Some(0x19),
219            ShadowCash => Some(0x3F),
220            Beetlecoin => Some(0x1A),
221            Asiacoin => Some(0x17),
222            ZCash => None, // Zcash used a 2-byte prefix so it is a special case
223            #[cfg(feature = "eth")]
224            Ethereum => None,
225        }
226    }
227    /// Get the name of the network associated with these options
228    pub fn network_name(&self) -> String {
229        self.to_string()
230    }
231    /// Get the maximum string length of the address generated for the given address options. This
232    /// is useful for formatting.
233    pub fn length(&self) -> usize {
234        match self {
235            Self::BitcoinCash(prefix) => match prefix {
236                Some(prefix) => 43 + prefix.len(),
237                None => 42,
238            },
239            #[cfg(feature = "eth")]
240            Self::Ethereum => 42,
241            _ => 34,
242        }
243    }
244    fn verify_len(actual: usize, expected: usize) -> Result<()> {
245        if actual != expected {
246            Err(Error::InvalidLength { received: actual, expected})
247        } else {
248            Ok(())
249        }
250    }
251    /// Compute the P2PKH address of a public encoded as SEC 1 compressed public key bytes
252    pub(crate) fn derive(&self, pubkey_bytes: impl AsRef<[u8]>) -> Result<String> {
253        use AddressFormat::*;
254        let bytes = pubkey_bytes.as_ref();
255
256        match self {
257            Ripple => {
258                Self::verify_len(bytes.len(), 33)?;
259                let pkh = pubkey_hash(bytes, self.address_prefix().unwrap());
260                Ok(bs58::encode(pkh)
261                    .with_alphabet(bs58::Alphabet::RIPPLE)
262                    .into_string())
263            }
264            BitcoinCash(prefix) => {
265                let keyhash = Hash160::hash(bytes);
266                let string = match prefix {
267                    Some(prefix) => keyhash.encode_p2pkh(prefix).unwrap(),
268                    None => {
269                        let mut string = keyhash.encode_p2pkh("bitcoincash").unwrap();
270                        let position = string.find(':').unwrap();
271                        string.replace_range(..position + 1, "");
272                        string
273                    }
274                };
275                Ok(string)
276            }
277            ZCash => {
278                let pubkey_hash = Hash160::hash(bytes);
279                let mut raw_final: [u8; 26] = [0; 26];
280                raw_final[..2].copy_from_slice(&[0x1C, 0xB8]);
281                raw_final[2..22].copy_from_slice(&pubkey_hash);
282                let checksum = SHA256d::hash(&raw_final[..22]);
283                raw_final[22..].copy_from_slice(&checksum[..4]);
284                Ok(raw_final.to_base58())
285            }
286            Bitcoin(BitcoinFormat::P2SHWPKH) => {
287                let pubkey_hash = Hash160::hash(bytes);
288                let mut raw_final: [u8; 25] = [0x05; 25];
289                let mut buf: [u8; 22] = [0; 22];
290                buf[..2].copy_from_slice(&[0x00, 0x14]);
291                buf[2..].copy_from_slice(&pubkey_hash);
292                raw_final[1..21].copy_from_slice(&Hash160::hash(&buf));
293                let (l, r) = raw_final.split_at_mut(21);
294                r.copy_from_slice(&SHA256d::hash(l)[..4]);
295                Ok(raw_final.to_base58())
296            }
297            #[cfg(feature = "eth")]
298            Ethereum => {
299                let pubkey =
300                    k256::PublicKey::from_sec1_bytes(bytes).expect("failed parsing eth bytes");
301                let addr = Keccak256::digest(&pubkey.to_encoded_point(false).as_bytes()[1..]);
302                Ok(eip55::checksum(&hex::encode(&addr[12..])))
303            }
304            // Bitcoin style address format. These formats differ from each other only in the
305            // version byte used
306            _ => {
307                Self::verify_len(bytes.len(), 33)?;
308                Ok(pubkey_hash(bytes, self.address_prefix().unwrap()).to_base58())
309            }
310        }
311    }
312}
313
314
315/// Generate an Address string from existing Public Key
316///
317/// This trait provides the main functionality of the crate. The `addr` method implements encoding
318/// a public key as an address.
319///
320/// ### Usage
321/// Addresses are generated from public keys using [`Address::addr`]
322///
323/// ```
324/// use crypto_addr::{Address, AddressFormat as AF, Error};
325/// use hex_literal::hex;
326///
327/// // This is a compressed public key in SEC 1 standard format
328/// const PUBKEY_BYTES: [u8; 33] =
329///     hex!("029d08f65e6aac041328edaeddba9c682356f81b37b052728fd8637c14eddb7ff1");
330///
331/// // Generate a Bitcoin P2PKH address
332///
333/// // Derive a legacy P2PKH Bitcoin Address from the Public Key (this is the Default AddressFormat)
334/// assert_eq!(PUBKEY_BYTES.addr(&AF::default()).as_deref(), Ok("14cWWeAYZmA9oGgLpjekay9xLwHew3Ni34"));
335///
336/// // Derive Addresses from public key bytes in various other formats
337/// assert_eq!(PUBKEY_BYTES.addr(&AF::Litecoin).as_deref(), Ok("LNqTmrUNeRQD45NVzse3rzDiZ9ew7HMYDY"));
338/// assert_eq!(PUBKEY_BYTES.addr(&AF::Dogecoin).as_deref(), Ok("D8kc3u7BsB4SLGrwZKeK8jKZE51xKNc3FF"));
339/// assert_eq!(PUBKEY_BYTES.addr(&AF::Ripple).as_deref(),   Ok("rhcWWewYZmw9oGgLFjek2y9xLAHeAs45sh"));
340/// assert_eq!(PUBKEY_BYTES.addr(&AF::Dash).as_deref(),     Ok("XeJMLtpSXUNjxDGvgcxySVqkBGsM1d5LRg"));
341/// assert_eq!(PUBKEY_BYTES.addr(&AF::ZCash).as_deref(),    Ok("t1MV7WyagY5wkPujEmATsinFsbbUjoMy6b3"));
342/// assert_eq!(PUBKEY_BYTES.addr(&AF::BitcoinGold).as_deref(),   Ok("GMTRvmVVYcmSsjydkgJs1jVrG75W2Zc7dR"));
343///
344/// // Generate a Bitcoin Cash P2PKH address with elided "bitcoincash" prefix
345/// assert_eq!(
346///     PUBKEY_BYTES.addr(&AF::BitcoinCash(None)).as_deref(),
347///     Ok("qqnelysjjjyrn4eywr04vf4v9nydrqdk6vep2tp2g9")
348/// );
349///
350/// // Generate a Bitcoin Cash P2PKH address with a custom prefix
351/// assert_eq!(
352///     PUBKEY_BYTES.addr(&AF::BitcoinCash(Some("foo".to_owned()))).as_deref(),
353///     Ok("foo:qqnelysjjjyrn4eywr04vf4v9nydrqdk6v730zmhs6")
354/// );
355///
356/// // Errors are detected
357/// let partial_pubkey = &PUBKEY_BYTES[..30];
358/// assert_eq!(
359///     partial_pubkey.addr(&Default::default()).unwrap_err(),
360///     Error::InvalidLength{expected: 33, received: 30}
361/// );
362/// ```
363pub trait Address {
364    /// Derive an address string from a public key (`self`). Parameters for the address format
365    /// are given by `opts`. Returns `Err` if public key is not able to derive an address, i.e. if
366    /// it is not the correct length.
367    fn addr(&self, opts: &AddressFormat) -> Result<String>;
368}
369
370/// Array of input bytes is interpreted as a compressed public key encoded with encoding described
371/// in [SEC 1: Elliptic Curve Cryptography (Version 2.0)](http://www.secg.org/sec1-v2.pdf) section
372/// 2.3.3 (page 10).
373impl Address for crate::PubkeyBytes {
374    fn addr(&self, opts: &AddressFormat) -> Result<String> {
375        opts.derive(self)
376    }
377}
378
379/// Slice of input bytes is interpreted as a compressed public key encoded with encoding described
380/// in [SEC 1: Elliptic Curve Cryptography (Version 2.0)](http://www.secg.org/sec1-v2.pdf) section
381/// 2.3.3 (page 10).
382impl Address for [u8] {
383    fn addr(&self, opts: &AddressFormat) -> Result<String> {
384        opts.derive(self)
385    }
386}
387
388
389#[cfg(feature = "secp256k1")]
390impl Address for secp256k1::PublicKey {
391    fn addr(&self, opts: &AddressFormat) -> Result<String> {
392        opts.derive(self.serialize())
393    }
394}
395
396#[cfg(feature = "k256")]
397impl Address for k256::PublicKey {
398    fn addr(&self, opts: &AddressFormat) -> Result<String> {
399        opts.derive(self.to_encoded_point(true).as_bytes())
400    }
401}
402
403#[cfg(feature = "k256")]
404impl Address for k256::ecdsa::VerifyingKey {
405    fn addr(&self, opts: &AddressFormat) -> Result<String> {
406        opts.derive(self.to_encoded_point(true)) }
407}
408
409#[cfg(test)]
410mod tests {
411    use super::{AddressFormat::{self, *}, BitcoinFormat::*, Address};
412    use crate::test_vectors::addr::*;
413    #[cfg(feature = "k256")]
414    use hex_literal::hex;
415
416    #[test]
417    fn parse_network() {
418        assert_eq!("Bitcoin".parse(), Ok(Bitcoin(Legacy)));
419        assert_eq!("BTC".parse(), Ok(Bitcoin(Legacy)));
420        assert_eq!("btc".parse(), Ok(Bitcoin(Legacy)));
421        assert_eq!("litecoin".parse(), Ok(Litecoin));
422        assert_eq!("Litecoin".parse(), Ok(Litecoin));
423        assert_eq!("ltc".parse(), Ok(Litecoin));
424        assert_eq!("LTC".parse(), Ok(Litecoin));
425        assert_eq!("dogecoin".parse(), Ok(Dogecoin));
426        assert_eq!("Dogecoin".parse(), Ok(Dogecoin));
427        assert_eq!("doge".parse(), Ok(Dogecoin));
428        assert_eq!("DOGE".parse(), Ok(Dogecoin));
429        assert_eq!("bch".parse(), Ok(BitcoinCash(None)));
430        assert_eq!(
431            "bch:ligma".parse(),
432            Ok(BitcoinCash(Some("ligma".to_owned())))
433        );
434        #[cfg(feature = "eth")]
435        assert_eq!("eth".parse(), Ok(Ethereum));
436        #[cfg(feature = "eth")]
437        assert_eq!("etheReUm".parse(), Ok(Ethereum));
438    }
439    #[test]
440    fn network_name() {
441        assert_eq!(Bitcoin(Legacy).network_name(), "Bitcoin");
442        assert_eq!(Bitcoin(P2SHWPKH).network_name(), "Bitcoin");
443        assert_eq!(BitcoinCash(None).network_name(), "Bitcoin Cash");
444        assert_eq!(
445            BitcoinCash(Some(String::from("test"))).network_name(),
446            "Bitcoin Cash"
447        );
448        assert_eq!(Litecoin.network_name(), "Litecoin");
449        assert_eq!(Dogecoin.network_name(), "Dogecoin");
450        assert_eq!(Dash.network_name(), "Dash");
451        #[cfg(feature = "eth")]
452        assert_eq!(Ethereum.network_name(), "Ethereum");
453    }
454
455    fn testfun(vectors: &str, opts: &AddressFormat) {
456        for tc in vectors.lines().map(TestCase::from) {
457            assert_eq!(tc.pubkey.addr(opts).as_deref(), Ok(tc.addr));
458        }
459    }
460
461    #[test]
462    fn format_doge() {
463        testfun(DOGE_TEST_VECTORS, &Dogecoin)
464    }
465    #[test]
466    fn format_btc() {
467        testfun(BTC_TEST_VECTORS, &Bitcoin(Legacy))
468    }
469    #[test]
470    fn format_ltc() {
471        testfun(LTC_TEST_VECTORS, &Litecoin)
472    }
473    #[test]
474    fn format_dash() {
475        testfun(DASH_TEST_VECTORS, &Dash)
476    }
477    #[test]
478    fn format_btg() {
479        testfun(BTG_TEST_VECTORS, &BitcoinGold)
480    }
481    #[test]
482    fn format_erc() {
483        testfun(ERC_TEST_VECTORS, &Europecoin)
484    }
485    #[test]
486    fn format_xrp() {
487        testfun(XRP_TEST_VECTORS, &Ripple)
488    }
489    #[test]
490    fn format_ppc() {
491        testfun(PPC_TEST_VECTORS, &Peercoin)
492    }
493    #[test]
494    fn format_bch() {
495        testfun(BCH_TEST_VECTORS, &BitcoinCash(None))
496    }
497    #[test]
498    fn format_sys() {
499        testfun(SYS_TEST_VECTORS, &Syscoin);
500    }
501    #[test]
502    fn format_ftc() {
503        testfun(FTC_TEST_VECTORS, &Feathercoin);
504    }
505    #[test]
506    fn format_pot() {
507        testfun(POT_TEST_VECTORS, &Potcoin);
508    }
509    #[test]
510    fn format_nmc() {
511        testfun(NMC_TEST_VECTORS, &Namecoin);
512    }
513    #[test]
514    fn format_btc_testnet() {
515        testfun(BTCTESTNET_TEST_VECTORS, &BitcoinTestnet);
516    }
517    #[test]
518    fn format_lcc() {
519        testfun(LCC_TEST_VECTORS, &LitecoinCash);
520    }
521    #[test]
522    fn format_fjc() {
523        testfun(FJC_TEST_VECTORS, &Fujicoin);
524    }
525    #[test]
526    fn format_xvg() {
527        testfun(XVG_TEST_VECTORS, &Verge);
528    }
529    #[test]
530    fn format_thc() {
531        testfun(THC_TEST_VECTORS, &Hempcoin);
532    }
533    #[test]
534    fn format_xvc() {
535        testfun(XVC_TEST_VECTORS, &VCash);
536    }
537    #[test]
538    fn format_zec() {
539        testfun(ZEC_TEST_VECTORS, &ZCash);
540    }
541    #[test]
542    fn format_vtc() {
543        testfun(VTC_TEST_VECTORS, &Vertcoin);
544    }
545    #[test]
546    fn format_strat() {
547        testfun(STRAT_TEST_VECTORS, &Stratis);
548    }
549    #[test]
550    fn format_clams() {
551        testfun(CLAMS_TEST_VECTORS, &Clams);
552    }
553    #[test]
554    fn format_blk() {
555        testfun(BLK_TEST_VECTORS, &Blackcoin);
556    }
557    #[test]
558    fn format_btc_swp2sh() {
559        testfun(BTC_SWP2SH_TEST_VECTORS, &Bitcoin(P2SHWPKH));
560    }
561    #[test]
562    fn format_sdc() {
563        testfun(SDC_TEST_VECTORS, &ShadowCash);
564    }
565    #[test]
566    fn format_beet() {
567        testfun(BEET_TEST_VECTORS, &Beetlecoin);
568    }
569    #[test]
570    fn format_ac() {
571        testfun(AC_TEST_VECTORS, &Asiacoin);
572    }
573    #[cfg(feature = "eth")]
574    #[test]
575    fn format_eth() {
576        testfun(ETH_TEST_VECTORS, &Ethereum)
577    }
578
579    #[test]
580    fn format_bch_prefix() {
581        for tc in BCH_PREFIXED_VECTORS.lines().map(TestCase::from) {
582            let prefix = &tc.addr[..5];
583            let addr = BitcoinCash(Some(prefix.to_owned())).derive(tc.pubkey);
584            assert_eq!(addr.as_deref(), Ok(tc.addr))
585        }
586    }
587    #[cfg(feature = "secp256k1")]
588    #[test]
589    fn addr_for_secp_pubkey() {
590        let public_key: secp256k1::PublicKey =
591            "029d08f65e6aac041328edaeddba9c682356f81b37b052728fd8637c14eddb7ff1".parse().unwrap();
592        let addr = public_key.addr(&Default::default());
593        assert_eq!(addr.as_deref(), Ok("14cWWeAYZmA9oGgLpjekay9xLwHew3Ni34"));
594    }
595    #[cfg(feature = "k256")]
596    #[test]
597    fn addr_for_k256_pubkey() {
598        let pubkey_bytes = hex!("029d08f65e6aac041328edaeddba9c682356f81b37b052728fd8637c14eddb7ff1");
599        let pubkey = k256::PublicKey::from_sec1_bytes(&pubkey_bytes).unwrap();
600        assert_eq!(
601            pubkey.addr(&Default::default()).as_deref(),
602            Ok("14cWWeAYZmA9oGgLpjekay9xLwHew3Ni34")
603        )
604    }
605    #[cfg(feature = "k256")]
606    #[test]
607    fn addr_for_k256_verifying() {
608        let pubkey_bytes = hex!("029d08f65e6aac041328edaeddba9c682356f81b37b052728fd8637c14eddb7ff1");
609        let pubkey = k256::ecdsa::VerifyingKey::from_sec1_bytes(&pubkey_bytes).unwrap();
610        assert_eq!(
611            pubkey.addr(&Default::default()).as_deref(),
612            Ok("14cWWeAYZmA9oGgLpjekay9xLwHew3Ni34")
613        )
614    }
615}