ripple_address_codec/
lib.rs

1//! Encodes/decodes base58 encoded XRP Ledger identifiers
2//!
3//! Functions for encoding and decoding XRP Ledger addresses and seeds.
4//!
5//! # Examples
6//!
7//! See [Functions][crate#functions] section
8
9#![deny(
10    warnings,
11    clippy::all,
12    missing_debug_implementations,
13    missing_copy_implementations,
14    missing_docs,
15    missing_crate_level_docs,
16    missing_doc_code_examples,
17    non_ascii_idents,
18    unreachable_pub
19)]
20#![doc(test(attr(deny(warnings))))]
21#![doc(html_root_url = "https://docs.rs/ripple-address-codec/0.1.1")]
22
23use std::{convert::TryInto, result};
24
25use base_x;
26use ring::digest::{digest, SHA256};
27
28mod error;
29
30pub use self::error::{Error, Error::DecodeError};
31pub use self::Algorithm::{Ed25519, Secp256k1};
32
33const ALPHABET: &str = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz";
34const CHECKSUM_LENGTH: usize = 4;
35const ENTROPY_LEN: usize = 16;
36
37/// Seed entropy array
38///
39/// The entropy must be exactly 16 bytes (128 bits).
40pub type Entropy = [u8; ENTROPY_LEN];
41
42/// Result with decoding error
43pub type Result<T> = result::Result<T, Error>;
44
45/// The elliptic curve digital signature algorithm
46/// with which the seed is intended to be used
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub enum Algorithm {
49    /// Elliptic Curve Digital Signature Algorithm (ECDSA): secp256k1
50    Secp256k1,
51    /// Edwards-curve Digital Signature Algorithm (EdDSA): Ed25519
52    Ed25519,
53}
54
55impl Default for Algorithm {
56    fn default() -> Self {
57        Self::Secp256k1
58    }
59}
60
61/// Encode the given entropy as an XRP Ledger seed (secret)
62///
63/// The entropy must be exactly 16 bytes (128 bits). The encoding
64/// includes which elliptic curve digital signature algorithm the
65/// seed is intended to be used with. The seed is used to produce
66/// the private key.
67///
68/// # Examples
69///
70/// ```
71/// use ripple_address_codec::{encode_seed, Secp256k1, Ed25519};
72///
73/// // In the real world you **must** generate random entropy
74/// let naive_entropy = [0; 16];
75///
76/// assert_eq!(encode_seed(&naive_entropy, &Secp256k1), "sp6JS7f14BuwFY8Mw6bTtLKWauoUs");
77/// assert_eq!(encode_seed(&naive_entropy, &Ed25519), "sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE");
78/// ```
79pub fn encode_seed(entropy: &Entropy, algorithm: &Algorithm) -> String {
80    let prefix = match algorithm {
81        Secp256k1 => SeedSecP256K1.prefix(),
82        Ed25519 => SeedEd25519.prefix(),
83    };
84
85    encode_bytes_with_prefix(prefix, entropy)
86}
87
88/// Decode a seed into a tuple with seed's entropy bytes and algorithm
89///
90/// # Examples
91///
92/// ```
93/// use ripple_address_codec::{decode_seed, Secp256k1, Ed25519};
94///
95/// assert_eq!(decode_seed("sp6JS7f14BuwFY8Mw6bTtLKWauoUs"), Ok(([0; 16], &Secp256k1)));
96/// assert_eq!(decode_seed("sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE"), Ok(([0; 16], &Ed25519)));
97/// ```
98///
99/// # Errors
100///
101/// Returns [`DecodeError`] if seed is invalid.
102pub fn decode_seed(seed: &str) -> Result<(Entropy, &'static Algorithm)> {
103    decode_seed_secp256k1(seed).or(decode_seed_ed25519(seed))
104}
105
106/// Encode bytes as a classic address (starting with r...)
107///
108/// # Examples
109///
110/// ```
111/// use ripple_address_codec::encode_account_id;
112///
113/// assert_eq!(encode_account_id(&[0; 20]), "rrrrrrrrrrrrrrrrrrrrrhoLvTp");
114/// ```
115pub fn encode_account_id(bytes: &[u8; Address::PAYLOAD_LEN]) -> String {
116    encode_bytes_with_prefix(Address.prefix(), bytes)
117}
118
119/// Decode a classic address (starting with r...) to its raw bytes
120///
121/// # Examples
122///
123/// ```
124/// use ripple_address_codec::decode_account_id;
125///
126/// assert_eq!(decode_account_id("rrrrrrrrrrrrrrrrrrrrrhoLvTp"), Ok([0; 20]));
127/// ```
128///
129/// # Errors
130///
131/// Returns [`DecodeError`] if account id string is invalid.
132pub fn decode_account_id(account_id: &str) -> Result<[u8; Address::PAYLOAD_LEN]> {
133    let decoded_bytes = decode_with_xrp_alphabet(account_id)?;
134
135    let payload = get_payload(decoded_bytes, Address)?;
136
137    Ok(payload.try_into().unwrap())
138}
139
140trait Settings {
141    const PAYLOAD_LEN: usize;
142    const PREFIX: &'static [u8] = &[];
143
144    fn prefix(&self) -> &'static [u8] {
145        Self::PREFIX
146    }
147
148    fn prefix_len(&self) -> usize {
149        Self::PREFIX.len()
150    }
151
152    fn payload_len(&self) -> usize {
153        Self::PAYLOAD_LEN
154    }
155}
156
157struct Address;
158
159impl Settings for Address {
160    const PREFIX: &'static [u8] = &[0x00];
161    const PAYLOAD_LEN: usize = 20;
162}
163
164struct SeedSecP256K1;
165
166impl SeedSecP256K1 {
167    const ALG: Algorithm = Secp256k1;
168}
169
170impl Settings for SeedSecP256K1 {
171    const PREFIX: &'static [u8] = &[0x21];
172    const PAYLOAD_LEN: usize = ENTROPY_LEN;
173}
174
175struct SeedEd25519;
176
177impl SeedEd25519 {
178    const ALG: Algorithm = Ed25519;
179}
180
181impl Settings for SeedEd25519 {
182    const PREFIX: &'static [u8] = &[0x01, 0xE1, 0x4B];
183    const PAYLOAD_LEN: usize = ENTROPY_LEN;
184}
185
186fn decode_seed_secp256k1(s: &str) -> Result<(Entropy, &'static Algorithm)> {
187    let decoded_bytes = decode_with_xrp_alphabet(s)?;
188
189    let payload = get_payload(decoded_bytes, SeedSecP256K1)?;
190
191    Ok((payload.try_into().unwrap(), &SeedSecP256K1::ALG))
192}
193
194fn decode_seed_ed25519(s: &str) -> Result<(Entropy, &'static Algorithm)> {
195    let decoded_bytes = decode_with_xrp_alphabet(s)?;
196
197    let payload = get_payload(decoded_bytes, SeedEd25519)?;
198
199    Ok((payload.try_into().unwrap(), &SeedEd25519::ALG))
200}
201
202fn encode_bytes_with_prefix(prefix: &[u8], bytes: &[u8]) -> String {
203    encode_bytes(&[prefix, bytes].concat())
204}
205
206fn encode_bytes(bytes: &[u8]) -> String {
207    let checked_bytes = [bytes, &calc_checksum(bytes)].concat();
208    base_x::encode(ALPHABET, &checked_bytes)
209}
210
211fn decode_with_xrp_alphabet(s: &str) -> Result<Vec<u8>> {
212    Ok(base_x::decode(ALPHABET, s)?)
213}
214
215fn get_payload(bytes: Vec<u8>, settings: impl Settings) -> Result<Vec<u8>> {
216    verify_payload_len(&bytes, settings.prefix_len(), settings.payload_len())?;
217    verify_prefix(settings.prefix(), &bytes)?;
218    let checked_bytes = get_checked_bytes(bytes)?;
219
220    Ok(checked_bytes[settings.prefix_len()..].try_into().unwrap())
221}
222
223fn verify_prefix(prefix: &[u8], bytes: &[u8]) -> Result<()> {
224    if bytes.starts_with(prefix) {
225        return Ok(());
226    }
227
228    Err(DecodeError)
229}
230
231fn verify_payload_len(bytes: &[u8], prefix_len: usize, expected_len: usize) -> Result<()> {
232    if bytes[prefix_len..bytes.len() - CHECKSUM_LENGTH].len() == expected_len {
233        return Ok(());
234    }
235
236    Err(DecodeError)
237}
238
239fn get_checked_bytes(mut bytes_with_checksum: Vec<u8>) -> Result<Vec<u8>> {
240    verify_checksum_lenght(&bytes_with_checksum)?;
241
242    //Split bytes with checksum to checked bytes and checksum
243    let checksum = bytes_with_checksum.split_off(bytes_with_checksum.len() - CHECKSUM_LENGTH);
244    let bytes = bytes_with_checksum;
245
246    verify_checksum(&bytes, &checksum)?;
247
248    Ok(bytes)
249}
250
251fn verify_checksum(input: &[u8], checksum: &[u8]) -> Result<()> {
252    if calc_checksum(input) == checksum {
253        Ok(())
254    } else {
255        Err(DecodeError)
256    }
257}
258
259fn verify_checksum_lenght(bytes: &[u8]) -> Result<()> {
260    let len = bytes.len();
261
262    if len < CHECKSUM_LENGTH + 1 {
263        return Err(DecodeError);
264    }
265
266    Ok(())
267}
268
269fn calc_checksum(bytes: &[u8]) -> [u8; CHECKSUM_LENGTH] {
270    sha256_digest(&sha256_digest(bytes))[..CHECKSUM_LENGTH]
271        .try_into()
272        .unwrap()
273}
274
275fn sha256_digest(data: &[u8]) -> Vec<u8> {
276    digest(&SHA256, data).as_ref().to_vec()
277}