diff --git a/Cargo.lock b/Cargo.lock index d3b592d29..34b20d28e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,11 @@ dependencies = [ "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bech32" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bellman" version = "0.1.0" @@ -512,6 +517,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "zcash_client_backend" version = "0.0.0" dependencies = [ + "bech32 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pairing 0.14.2", + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "sapling-crypto 0.0.1", "zcash_primitives 0.0.0", ] @@ -551,6 +560,7 @@ dependencies = [ "checksum aes-soft 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67cc03b0a090a05cb01e96998a01905d7ceedce1bc23b756c0bb7faa0682ccb1" "checksum aesni 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6810b7fb9f2bb4f76f05ac1c170b8dde285b6308955dc3afd89710268c958d9e" "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" +"checksum bech32 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "58946044516aa9dc922182e0d6e9d124a31aafe6b421614654eb27cf90cec09c" "checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f" "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" "checksum blake2-rfc 0.2.18 (git+https://github.com/gtank/blake2-rfc?rev=7a5b5fc99ae483a0043db7547fb79a6fa44b88a9)" = "" diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index d1993baf2..bc57fdb56 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -7,4 +7,10 @@ authors = [ edition = "2018" [dependencies] +bech32 = "0.6" +pairing = { path = "../pairing" } +sapling-crypto = { path = "../sapling-crypto" } zcash_primitives = { path = "../zcash_primitives" } + +[dev-dependencies] +rand = "0.4" diff --git a/zcash_client_backend/src/constants/mainnet.rs b/zcash_client_backend/src/constants/mainnet.rs index 753956e5f..2850c456b 100644 --- a/zcash_client_backend/src/constants/mainnet.rs +++ b/zcash_client_backend/src/constants/mainnet.rs @@ -2,3 +2,11 @@ /// /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md pub const COIN_TYPE: u32 = 133; + +/// The HRP for a Bech32-encoded mainnet [`PaymentAddress`]. +/// +/// Defined in section 5.6.4 of the [Zcash Protocol Specification]. +/// +/// [`PaymentAddress`]: sapling_crypto::primitives::PaymentAddress +/// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf +pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "zs"; diff --git a/zcash_client_backend/src/constants/testnet.rs b/zcash_client_backend/src/constants/testnet.rs index 836340cf0..f4ea3c2a0 100644 --- a/zcash_client_backend/src/constants/testnet.rs +++ b/zcash_client_backend/src/constants/testnet.rs @@ -2,3 +2,11 @@ /// /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md pub const COIN_TYPE: u32 = 1; + +/// The HRP for a Bech32-encoded testnet [`PaymentAddress`]. +/// +/// Defined in section 5.6.4 of the [Zcash Protocol Specification]. +/// +/// [`PaymentAddress`]: sapling_crypto::primitives::PaymentAddress +/// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf +pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "ztestsapling"; diff --git a/zcash_client_backend/src/encoding.rs b/zcash_client_backend/src/encoding.rs new file mode 100644 index 000000000..7150621e0 --- /dev/null +++ b/zcash_client_backend/src/encoding.rs @@ -0,0 +1,173 @@ +//! Encoding and decoding functions for Zcash key and address structs. +//! +//! Human-Readable Prefixes (HRPs) for Bech32 encodings are located in the [`constants`] +//! module. + +use bech32::{convert_bits, Bech32, Error}; +use pairing::bls12_381::Bls12; +use sapling_crypto::{ + jubjub::edwards, + primitives::{Diversifier, PaymentAddress}, +}; +use std::io::{self, Write}; +use zcash_primitives::JUBJUB; + +fn bech32_encode(hrp: &str, write: F) -> String +where + F: Fn(&mut dyn Write) -> io::Result<()>, +{ + let mut data: Vec = vec![]; + write(&mut data).expect("Should be able to write to a Vec"); + + let converted = + convert_bits(&data, 8, 5, true).expect("Should be able to convert Vec to Vec"); + let encoded = Bech32::new_check_data(hrp.into(), converted).expect("hrp is not empty"); + + encoded.to_string() +} + +fn bech32_decode(hrp: &str, s: &str, read: F) -> Result, Error> +where + F: Fn(Vec) -> Option, +{ + let decoded = s.parse::()?; + if decoded.hrp() == hrp { + convert_bits(decoded.data(), 5, 8, false).map(|data| read(data)) + } else { + Ok(None) + } +} + +/// Writes a [`PaymentAddress`] as a Bech32-encoded string. +/// +/// # Examples +/// +/// ``` +/// use pairing::bls12_381::Bls12; +/// use rand::{SeedableRng, XorShiftRng}; +/// use sapling_crypto::{ +/// jubjub::edwards, +/// primitives::{Diversifier, PaymentAddress}, +/// }; +/// use zcash_client_backend::{ +/// constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, +/// encoding::encode_payment_address, +/// }; +/// use zcash_primitives::JUBJUB; +/// +/// let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); +/// +/// let pa = PaymentAddress { +/// diversifier: Diversifier([0u8; 11]), +/// pk_d: edwards::Point::::rand(rng, &JUBJUB).mul_by_cofactor(&JUBJUB), +/// }; +/// +/// assert_eq!( +/// encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &pa), +/// "ztestsapling1qqqqqqqqqqqqqqqqqqxrrfaccydp867g6zg7ne5ht37z38jtfyw0ygmp0ja6hhf07twjq6awtaj", +/// ); +/// ``` +pub fn encode_payment_address(hrp: &str, addr: &PaymentAddress) -> String { + bech32_encode(hrp, |w| { + w.write_all(&addr.diversifier.0)?; + addr.pk_d.write(w) + }) +} + +/// Decodes a [`PaymentAddress`] from a Bech32-encoded string. +/// +/// # Examples +/// +/// ``` +/// use pairing::bls12_381::Bls12; +/// use rand::{SeedableRng, XorShiftRng}; +/// use sapling_crypto::{ +/// jubjub::edwards, +/// primitives::{Diversifier, PaymentAddress}, +/// }; +/// use zcash_client_backend::{ +/// constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, +/// encoding::decode_payment_address, +/// }; +/// use zcash_primitives::JUBJUB; +/// +/// let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); +/// +/// let pa = PaymentAddress { +/// diversifier: Diversifier([0u8; 11]), +/// pk_d: edwards::Point::::rand(rng, &JUBJUB).mul_by_cofactor(&JUBJUB), +/// }; +/// +/// assert_eq!( +/// decode_payment_address( +/// HRP_SAPLING_PAYMENT_ADDRESS, +/// "ztestsapling1qqqqqqqqqqqqqqqqqqxrrfaccydp867g6zg7ne5ht37z38jtfyw0ygmp0ja6hhf07twjq6awtaj", +/// ), +/// Ok(Some(pa)), +/// ); +/// ``` +pub fn decode_payment_address(hrp: &str, s: &str) -> Result>, Error> { + bech32_decode(hrp, s, |data| { + let mut diversifier = Diversifier([0; 11]); + diversifier.0.copy_from_slice(&data[0..11]); + edwards::Point::::read(&data[11..], &JUBJUB) + .ok()? + .as_prime_order(&JUBJUB) + .map(|pk_d| PaymentAddress { pk_d, diversifier }) + }) +} + +#[cfg(test)] +mod tests { + use pairing::bls12_381::Bls12; + use rand::{SeedableRng, XorShiftRng}; + use sapling_crypto::{ + jubjub::edwards, + primitives::{Diversifier, PaymentAddress}, + }; + use zcash_primitives::JUBJUB; + + use super::{decode_payment_address, encode_payment_address}; + use crate::constants; + + #[test] + fn payment_address() { + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let addr = PaymentAddress { + diversifier: Diversifier([0u8; 11]), + pk_d: edwards::Point::::rand(rng, &JUBJUB).mul_by_cofactor(&JUBJUB), + }; + + let encoded_main = + "zs1qqqqqqqqqqqqqqqqqqxrrfaccydp867g6zg7ne5ht37z38jtfyw0ygmp0ja6hhf07twjqj2ug6x"; + let encoded_test = + "ztestsapling1qqqqqqqqqqqqqqqqqqxrrfaccydp867g6zg7ne5ht37z38jtfyw0ygmp0ja6hhf07twjq6awtaj"; + + assert_eq!( + encode_payment_address(constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS, &addr), + encoded_main + ); + assert_eq!( + decode_payment_address( + constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS, + encoded_main + ) + .unwrap(), + Some(addr.clone()) + ); + + assert_eq!( + encode_payment_address(constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, &addr), + encoded_test + ); + assert_eq!( + decode_payment_address( + constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, + encoded_test + ) + .unwrap(), + Some(addr) + ); + } +} diff --git a/zcash_client_backend/src/lib.rs b/zcash_client_backend/src/lib.rs index de76975ba..02de4b405 100644 --- a/zcash_client_backend/src/lib.rs +++ b/zcash_client_backend/src/lib.rs @@ -4,4 +4,5 @@ //! light clients. pub mod constants; +pub mod encoding; pub mod keys;