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
#![doc = include_str!("../README.md")]
//! # Usage
//! The main interface for the crate is [`CryptoEncode`]. This trait provides methods for
//! generating address and WIFs (serialized private keys). It is implemented for all types that
//! implement `AsRef<[u8]>`.
//!
//! ### Generating Addresses
//! Addresses are generated using [`CryptoEncode::addr`]
//!
//! ```
//! use crypto_addr::{CryptoEncode, AddressOptions, Error};
//! use hex_literal::hex;
//!
//! const PUBLIC_KEY_BYTES: [u8; 33] =
//!     hex!("029d08f65e6aac041328edaeddba9c682356f81b37b052728fd8637c14eddb7ff1");
//!
//! // Generate a Bitcoin P2PKH address
//! assert_eq!(
//!     PUBLIC_KEY_BYTES.addr(&AddressOptions::Bitcoin).as_deref(),
//!     Ok("14cWWeAYZmA9oGgLpjekay9xLwHew3Ni34")
//! );
//!
//! // Generate a Litcoin P2PKH address
//! assert_eq!(
//!     PUBLIC_KEY_BYTES.addr(&AddressOptions::Litecoin).as_deref(),
//!     Ok("LNqTmrUNeRQD45NVzse3rzDiZ9ew7HMYDY")
//! );
//!
//! // Generate a Dogecoin P2PKH address
//! assert_eq!(
//!     PUBLIC_KEY_BYTES.addr(&AddressOptions::Dogecoin).as_deref(),
//!     Ok("D8kc3u7BsB4SLGrwZKeK8jKZE51xKNc3FF")
//! );
//!
//! // Generate a Bitcoin Cash P2PKH address with elided "bitcoincash" prefix
//! assert_eq!(
//!     PUBLIC_KEY_BYTES.addr(&AddressOptions::BitcoinCash(None)).as_deref(),
//!     Ok("qqnelysjjjyrn4eywr04vf4v9nydrqdk6vep2tp2g9")
//! );
//!
//! // Generate a Bitcoin Cash P2PKH address with a custom prefix
//! assert_eq!(
//!     PUBLIC_KEY_BYTES.addr(&AddressOptions::BitcoinCash(Some("foo".to_owned()))).as_deref(),
//!     Ok("foo:qqnelysjjjyrn4eywr04vf4v9nydrqdk6v730zmhs6")
//! );
//!
//! // Errors are detected
//! let partial_pubkey = &PUBLIC_KEY_BYTES[..30];
//! assert_eq!(
//!     partial_pubkey.addr(&AddressOptions::Bitcoin).unwrap_err(),
//!     Error::InvalidLength{expected: 33, received: 30}
//! );
//! ```
//! ### Generating WIFs
//! WIFs are generated using [`CryptoEncode::wif`]
//! ```
//! use crypto_addr::{CryptoEncode, WIFOptions};
//!
//! let sigkey = b"\xeb\x904)e2\x8a\xe1\xe0\x8e\xfd\xeb+x\xbc2\xd0&/\x04-\xe8\xab\xcd]0\xd6\
//!                \x07t\x9c\xb3\xaa";
//! let wif_string = sigkey.wif(WIFOptions::Dogecoin).expect("Could not serialize sigkey data");
//! assert_eq!(wif_string, "QWWXnSvkQ948FxJfGj4hBtkWjTsrHZYjVpMRMBwqogP9NWvJUzFm");
//! ```
//! )

use std::fmt;

type Result<T> = std::result::Result<T, Error>;

mod address;
pub use address::AddressOptions;
mod wif;
pub use wif::WIFOptions;

/// Encode a sequence of bytes as a WIF or an Address
///
/// Provides the main serialization functionality for the crate. Provided methods implement
/// serialization an arbitrary sequence of bytes into either an Address or a WIF. Trait is
/// implemented for all types which expose a sequence of bytes via `AsRef<[u8]>`.
pub trait CryptoEncode: AsRef<[u8]> {
    /// Encode input bytes as an Address.
    ///
    /// How the input bytes are interpreted and formatted is determined by `opts`. See
    /// [`AddressOptions`] for details
    fn addr(&self, opts: &AddressOptions) -> Result<String> {
        opts.serialize(self)
    }
    /// Encode private key as a WIF
    ///
    /// Input bytes are interpreted as a private key. Output contains a String representing the
    /// private key information according to the given `opts`.
    fn wif(&self, opts: WIFOptions) -> Result<String> {
        opts.serialize(self)
    }
}

/// Encode is implemented for all types which expose a sequence of bytes via `AsRef<[u8]>`
impl<T: AsRef<[u8]>> CryptoEncode for T {}

#[derive(Debug, PartialEq, Eq)]
/// Error enum describing something that went wrong.
pub enum Error {
    InvalidLength { received: usize, expected: usize },
    ParseFailure(String),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidLength { received, expected } => write!(
                f,
                "Invalid length detected, expected {}, got {}",
                received, expected
            ),
            Self::ParseFailure(msg) => write!(f, "{}", msg),
        }
    }
}

impl std::error::Error for Error {}

#[cfg(test)]
mod test_vectors;

#[cfg(test)]
mod tests {
    use crate::{AddressOptions, CryptoEncode, WIFOptions};
    #[test]
    fn serialize_addr() {
        use crate::test_vectors::addr::*;
        for tc in BTC_TEST_VECTORS.lines().map(TestCase::from) {
            assert_eq!(
                tc.pubkey.addr(&AddressOptions::Bitcoin).as_deref(),
                Ok(tc.addr)
            )
        }
    }

    #[test]
    fn serialize_wif() {
        use crate::test_vectors::wif::*;
        for tc in BTC_TEST_VECTORS.lines().map(TestCase::from) {
            assert_eq!(tc.prv.wif(WIFOptions::Bitcoin).as_deref(), Ok(tc.wif));
        }
    }
}