Merge pull request #352 from str4d/zcash_address
New component: zcash_address crate
This commit is contained in:
commit
2f3e4989ba
|
@ -1,6 +1,7 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"components/equihash",
|
||||
"components/zcash_address",
|
||||
"components/zcash_note_encryption",
|
||||
"zcash_client_backend",
|
||||
"zcash_client_sqlite",
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "zcash_address"
|
||||
description = "Zcash address parsing and serialization"
|
||||
version = "0.0.0"
|
||||
authors = [
|
||||
"Jack Grigg <jack@electriccoin.co>",
|
||||
]
|
||||
homepage = "https://github.com/zcash/librustzcash"
|
||||
repository = "https://github.com/zcash/librustzcash"
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bech32 = "0.8"
|
||||
blake2b_simd = "0.5"
|
||||
bs58 = { version = "0.4", features = ["check"] }
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "0.10.1"
|
|
@ -0,0 +1,202 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 Electric Coin Company
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,21 @@
|
|||
# zcash_address
|
||||
|
||||
Zcash address parsing and serialization. This library allows its users to easily
|
||||
recognize and give good error messages for new Zcash address types.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally
|
||||
submitted for inclusion in the work by you, as defined in the Apache-2.0
|
||||
license, shall be dual licensed as above, without any additional terms or
|
||||
conditions.
|
|
@ -0,0 +1,7 @@
|
|||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc e08469bc301313ef868b97a5c37d9a9746d9720c915a9127c89db25c3be778fd # shrinks to ua = Address([Sapling([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), P2pkh([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])])
|
|
@ -0,0 +1,175 @@
|
|||
use std::{error::Error, fmt};
|
||||
|
||||
use crate::{kind::*, AddressKind, Network, ZcashAddress};
|
||||
|
||||
/// An address type is not supported for conversion.
|
||||
#[derive(Debug)]
|
||||
pub struct UnsupportedAddress(&'static str);
|
||||
|
||||
impl fmt::Display for UnsupportedAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Zcash {} addresses are not supported", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for UnsupportedAddress {}
|
||||
|
||||
/// A helper trait for converting a [`ZcashAddress`] into another type.
|
||||
///
|
||||
/// [`ZcashAddress`]: crate::ZcashAddress
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use zcash_address::{FromAddress, Network, UnsupportedAddress, ZcashAddress};
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct MySapling([u8; 43]);
|
||||
///
|
||||
/// // Implement the FromAddress trait, overriding whichever conversion methods match your
|
||||
/// // requirements for the resulting type.
|
||||
/// impl FromAddress for MySapling {
|
||||
/// fn from_sapling(net: Network, data: [u8; 43]) -> Result<Self, UnsupportedAddress> {
|
||||
/// Ok(MySapling(data))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // For a supported address type, the conversion works.
|
||||
/// let addr: ZcashAddress =
|
||||
/// "zs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpq6d8g"
|
||||
/// .parse()
|
||||
/// .unwrap();
|
||||
/// assert!(addr.convert::<MySapling>().is_ok());
|
||||
///
|
||||
/// // For an unsupported address type, we get an error.
|
||||
/// let addr: ZcashAddress = "t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs".parse().unwrap();
|
||||
/// assert_eq!(
|
||||
/// addr.convert::<MySapling>().unwrap_err().to_string(),
|
||||
/// "Zcash transparent P2PKH addresses are not supported",
|
||||
/// );
|
||||
/// ```
|
||||
pub trait FromAddress: Sized {
|
||||
fn from_sprout(net: Network, data: sprout::Data) -> Result<Self, UnsupportedAddress> {
|
||||
let _ = (net, data);
|
||||
Err(UnsupportedAddress("Sprout"))
|
||||
}
|
||||
|
||||
fn from_sapling(net: Network, data: sapling::Data) -> Result<Self, UnsupportedAddress> {
|
||||
let _ = (net, data);
|
||||
Err(UnsupportedAddress("Sapling"))
|
||||
}
|
||||
|
||||
fn from_unified(net: Network, data: unified::Address) -> Result<Self, UnsupportedAddress> {
|
||||
let _ = (net, data);
|
||||
Err(UnsupportedAddress("Unified"))
|
||||
}
|
||||
|
||||
fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Result<Self, UnsupportedAddress> {
|
||||
let _ = (net, data);
|
||||
Err(UnsupportedAddress("transparent P2PKH"))
|
||||
}
|
||||
|
||||
fn from_transparent_p2sh(net: Network, data: p2sh::Data) -> Result<Self, UnsupportedAddress> {
|
||||
let _ = (net, data);
|
||||
Err(UnsupportedAddress("transparent P2SH"))
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper trait for converting another type into a [`ZcashAddress`].
|
||||
///
|
||||
/// This trait is sealed and cannot be implemented for types outside this crate. Its
|
||||
/// purpose is to move these conversion functions out of the main `ZcashAddress` API
|
||||
/// documentation, as they are only required when creating addresses (rather than when
|
||||
/// parsing addresses, which is a more common occurrence).
|
||||
///
|
||||
/// [`ZcashAddress`]: crate::ZcashAddress
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use zcash_address::{ToAddress, Network, ZcashAddress};
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct MySapling([u8; 43]);
|
||||
///
|
||||
/// impl MySapling {
|
||||
/// /// Encodes this Sapling address for the given network.
|
||||
/// fn encode(&self, net: Network) -> ZcashAddress {
|
||||
/// ZcashAddress::from_sapling(net, self.0)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let addr = MySapling([0; 43]);
|
||||
/// let encoded = addr.encode(Network::Main);
|
||||
/// assert_eq!(
|
||||
/// encoded.to_string(),
|
||||
/// "zs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpq6d8g",
|
||||
/// );
|
||||
/// ```
|
||||
pub trait ToAddress: private::Sealed {
|
||||
fn from_sprout(net: Network, data: sprout::Data) -> Self;
|
||||
|
||||
fn from_sapling(net: Network, data: sapling::Data) -> Self;
|
||||
|
||||
fn from_unified(net: Network, data: unified::Address) -> Self;
|
||||
|
||||
fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Self;
|
||||
|
||||
fn from_transparent_p2sh(net: Network, data: p2sh::Data) -> Self;
|
||||
}
|
||||
|
||||
impl ToAddress for ZcashAddress {
|
||||
fn from_sprout(net: Network, data: sprout::Data) -> Self {
|
||||
ZcashAddress {
|
||||
net: if let Network::Regtest = net {
|
||||
Network::Test
|
||||
} else {
|
||||
net
|
||||
},
|
||||
kind: AddressKind::Sprout(data),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_sapling(net: Network, data: sapling::Data) -> Self {
|
||||
ZcashAddress {
|
||||
net,
|
||||
kind: AddressKind::Sapling(data),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_unified(net: Network, data: unified::Address) -> Self {
|
||||
ZcashAddress {
|
||||
net,
|
||||
kind: AddressKind::Unified(data),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Self {
|
||||
ZcashAddress {
|
||||
net: if let Network::Regtest = net {
|
||||
Network::Test
|
||||
} else {
|
||||
net
|
||||
},
|
||||
kind: AddressKind::P2pkh(data),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_transparent_p2sh(net: Network, data: p2sh::Data) -> Self {
|
||||
ZcashAddress {
|
||||
net: if let Network::Regtest = net {
|
||||
Network::Test
|
||||
} else {
|
||||
net
|
||||
},
|
||||
kind: AddressKind::P2sh(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod private {
|
||||
use crate::ZcashAddress;
|
||||
|
||||
pub trait Sealed {}
|
||||
impl Sealed for ZcashAddress {}
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
use std::{convert::TryInto, error::Error, fmt, str::FromStr};
|
||||
|
||||
use bech32::{self, FromBase32, ToBase32, Variant};
|
||||
|
||||
use crate::{kind::*, AddressKind, Network, ZcashAddress};
|
||||
|
||||
/// An error while attempting to parse a string as a Zcash address.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ParseError {
|
||||
/// The string is an invalid encoding.
|
||||
InvalidEncoding,
|
||||
/// The string is not a Zcash address.
|
||||
NotZcash,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ParseError::InvalidEncoding => write!(f, "Invalid encoding"),
|
||||
ParseError::NotZcash => write!(f, "Not a Zcash address"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseError {}
|
||||
|
||||
impl FromStr for ZcashAddress {
|
||||
type Err = ParseError;
|
||||
|
||||
/// Attempts to parse the given string as a Zcash address.
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// Remove leading and trailing whitespace, to handle copy-paste errors.
|
||||
let s = s.trim();
|
||||
|
||||
// Most Zcash addresses use Bech32 or Bech32m, so try those first.
|
||||
match bech32::decode(s) {
|
||||
Ok((hrp, data, Variant::Bech32m)) => {
|
||||
// If we reached this point, the encoding is supposed to be valid Bech32m.
|
||||
let data =
|
||||
Vec::<u8>::from_base32(&data).map_err(|_| ParseError::InvalidEncoding)?;
|
||||
|
||||
let net = match hrp.as_str() {
|
||||
unified::MAINNET => Network::Main,
|
||||
unified::TESTNET => Network::Test,
|
||||
unified::REGTEST => Network::Regtest,
|
||||
// We will not define new Bech32m address encodings.
|
||||
_ => {
|
||||
return Err(ParseError::NotZcash);
|
||||
}
|
||||
};
|
||||
|
||||
return data[..]
|
||||
.try_into()
|
||||
.map(AddressKind::Unified)
|
||||
.map_err(|_| ParseError::InvalidEncoding)
|
||||
.map(|kind| ZcashAddress { net, kind });
|
||||
}
|
||||
Ok((hrp, data, Variant::Bech32)) => {
|
||||
// If we reached this point, the encoding is supposed to be valid Bech32.
|
||||
let data =
|
||||
Vec::<u8>::from_base32(&data).map_err(|_| ParseError::InvalidEncoding)?;
|
||||
|
||||
let net = match hrp.as_str() {
|
||||
sapling::MAINNET => Network::Main,
|
||||
sapling::TESTNET => Network::Test,
|
||||
sapling::REGTEST => Network::Regtest,
|
||||
// We will not define new Bech32 address encodings.
|
||||
_ => {
|
||||
return Err(ParseError::NotZcash);
|
||||
}
|
||||
};
|
||||
|
||||
return data[..]
|
||||
.try_into()
|
||||
.map(AddressKind::Sapling)
|
||||
.map_err(|_| ParseError::InvalidEncoding)
|
||||
.map(|kind| ZcashAddress { net, kind });
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
|
||||
// The rest use Base58Check.
|
||||
if let Ok(decoded) = bs58::decode(s).with_check(None).into_vec() {
|
||||
let net = match decoded[..2].try_into().unwrap() {
|
||||
sprout::MAINNET | p2pkh::MAINNET | p2sh::MAINNET => Network::Main,
|
||||
sprout::TESTNET | p2pkh::TESTNET | p2sh::TESTNET => Network::Test,
|
||||
// We will not define new Base58Check address encodings.
|
||||
_ => return Err(ParseError::NotZcash),
|
||||
};
|
||||
|
||||
return match decoded[..2].try_into().unwrap() {
|
||||
sprout::MAINNET | sprout::TESTNET => {
|
||||
decoded[2..].try_into().map(AddressKind::Sprout)
|
||||
}
|
||||
p2pkh::MAINNET | p2pkh::TESTNET => decoded[2..].try_into().map(AddressKind::P2pkh),
|
||||
p2sh::MAINNET | p2sh::TESTNET => decoded[2..].try_into().map(AddressKind::P2sh),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.map_err(|_| ParseError::InvalidEncoding)
|
||||
.map(|kind| ZcashAddress { kind, net });
|
||||
};
|
||||
|
||||
// If it's not valid Bech32 or Base58Check, it's not a Zcash address.
|
||||
Err(ParseError::NotZcash)
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_bech32m(hrp: &str, data: &[u8]) -> String {
|
||||
bech32::encode(hrp, data.to_base32(), Variant::Bech32m).expect("hrp is invalid")
|
||||
}
|
||||
|
||||
fn encode_bech32(hrp: &str, data: &[u8]) -> String {
|
||||
bech32::encode(hrp, data.to_base32(), Variant::Bech32).expect("hrp is invalid")
|
||||
}
|
||||
|
||||
fn encode_b58(prefix: [u8; 2], data: &[u8]) -> String {
|
||||
let mut bytes = Vec::with_capacity(2 + data.len());
|
||||
bytes.extend_from_slice(&prefix);
|
||||
bytes.extend_from_slice(data);
|
||||
bs58::encode(bytes).with_check().into_string()
|
||||
}
|
||||
|
||||
impl fmt::Display for ZcashAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let encoded = match &self.kind {
|
||||
AddressKind::Sprout(data) => encode_b58(
|
||||
match self.net {
|
||||
Network::Main => sprout::MAINNET,
|
||||
Network::Test | Network::Regtest => sprout::TESTNET,
|
||||
},
|
||||
data,
|
||||
),
|
||||
AddressKind::Sapling(data) => encode_bech32(
|
||||
match self.net {
|
||||
Network::Main => sapling::MAINNET,
|
||||
Network::Test => sapling::TESTNET,
|
||||
Network::Regtest => sapling::REGTEST,
|
||||
},
|
||||
data,
|
||||
),
|
||||
AddressKind::Unified(data) => encode_bech32m(
|
||||
match self.net {
|
||||
Network::Main => unified::MAINNET,
|
||||
Network::Test => unified::TESTNET,
|
||||
Network::Regtest => unified::REGTEST,
|
||||
},
|
||||
&data.to_bytes(),
|
||||
),
|
||||
AddressKind::P2pkh(data) => encode_b58(
|
||||
match self.net {
|
||||
Network::Main => p2pkh::MAINNET,
|
||||
Network::Test | Network::Regtest => p2pkh::TESTNET,
|
||||
},
|
||||
data,
|
||||
),
|
||||
AddressKind::P2sh(data) => encode_b58(
|
||||
match self.net {
|
||||
Network::Main => p2sh::MAINNET,
|
||||
Network::Test | Network::Regtest => p2sh::TESTNET,
|
||||
},
|
||||
data,
|
||||
),
|
||||
};
|
||||
write!(f, "{}", encoded)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::kind::unified;
|
||||
|
||||
fn encoding(encoded: &str, decoded: ZcashAddress) {
|
||||
assert_eq!(decoded.to_string(), encoded);
|
||||
assert_eq!(encoded.parse(), Ok(decoded));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sprout() {
|
||||
encoding(
|
||||
"zc8E5gYid86n4bo2Usdq1cpr7PpfoJGzttwBHEEgGhGkLUg7SPPVFNB2AkRFXZ7usfphup5426dt1buMmY3fkYeRrQGLa8y",
|
||||
ZcashAddress { net: Network::Main, kind: AddressKind::Sprout([0; 64]) },
|
||||
);
|
||||
encoding(
|
||||
"ztJ1EWLKcGwF2S4NA17pAJVdco8Sdkz4AQPxt1cLTEfNuyNswJJc2BbBqYrsRZsp31xbVZwhF7c7a2L9jsF3p3ZwRWpqqyS",
|
||||
ZcashAddress { net: Network::Test, kind: AddressKind::Sprout([0; 64]) },
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sapling() {
|
||||
encoding(
|
||||
"zs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpq6d8g",
|
||||
ZcashAddress {
|
||||
net: Network::Main,
|
||||
kind: AddressKind::Sapling([0; 43]),
|
||||
},
|
||||
);
|
||||
encoding(
|
||||
"ztestsapling1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhgwqu",
|
||||
ZcashAddress {
|
||||
net: Network::Test,
|
||||
kind: AddressKind::Sapling([0; 43]),
|
||||
},
|
||||
);
|
||||
encoding(
|
||||
"zregtestsapling1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqknpr3m",
|
||||
ZcashAddress {
|
||||
net: Network::Regtest,
|
||||
kind: AddressKind::Sapling([0; 43]),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unified() {
|
||||
encoding(
|
||||
"u1cd8yzk5mdn4n9r8c24tp8j8e9ethw3rr7ker5zhew3kycyyxggdzfkcq5f9yf2jv8m5ar8krncsntlfpx3p4azvwrkp8z74t3vu4kqq2",
|
||||
ZcashAddress {
|
||||
net: Network::Main,
|
||||
kind: AddressKind::Unified(unified::Address(vec![unified::Receiver::Sapling([0; 43])])),
|
||||
},
|
||||
);
|
||||
encoding(
|
||||
"utest1cd8yzk5mdn4n9r8c24tp8j8e9ethw3rr7ker5zhew3kycyyxggdzfkcq5f9yf2jv8m5ar8krncsntlfpx3p4azvwrkp8z74t3vptphj8",
|
||||
ZcashAddress {
|
||||
net: Network::Test,
|
||||
kind: AddressKind::Unified(unified::Address(vec![unified::Receiver::Sapling([0; 43])])),
|
||||
},
|
||||
);
|
||||
encoding(
|
||||
"uregtest1cd8yzk5mdn4n9r8c24tp8j8e9ethw3rr7ker5zhew3kycyyxggdzfkcq5f9yf2jv8m5ar8krncsntlfpx3p4azvwrkp8z74t3vsnt5j0",
|
||||
ZcashAddress {
|
||||
net: Network::Regtest,
|
||||
kind: AddressKind::Unified(unified::Address(vec![unified::Receiver::Sapling([0; 43])])),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transparent() {
|
||||
encoding(
|
||||
"t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs",
|
||||
ZcashAddress {
|
||||
net: Network::Main,
|
||||
kind: AddressKind::P2pkh([0; 20]),
|
||||
},
|
||||
);
|
||||
encoding(
|
||||
"tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma",
|
||||
ZcashAddress {
|
||||
net: Network::Test,
|
||||
kind: AddressKind::P2pkh([0; 20]),
|
||||
},
|
||||
);
|
||||
encoding(
|
||||
"t3JZcvsuaXE6ygokL4XUiZSTrQBUoPYFnXJ",
|
||||
ZcashAddress {
|
||||
net: Network::Main,
|
||||
kind: AddressKind::P2sh([0; 20]),
|
||||
},
|
||||
);
|
||||
encoding(
|
||||
"t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
|
||||
ZcashAddress {
|
||||
net: Network::Test,
|
||||
kind: AddressKind::P2sh([0; 20]),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whitespace() {
|
||||
assert_eq!(
|
||||
" t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs".parse(),
|
||||
Ok(ZcashAddress {
|
||||
net: Network::Main,
|
||||
kind: AddressKind::P2pkh([0; 20])
|
||||
}),
|
||||
);
|
||||
assert_eq!(
|
||||
"t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs ".parse(),
|
||||
Ok(ZcashAddress {
|
||||
net: Network::Main,
|
||||
kind: AddressKind::P2pkh([0; 20])
|
||||
}),
|
||||
);
|
||||
assert_eq!(
|
||||
"something t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs".parse::<ZcashAddress>(),
|
||||
Err(ParseError::NotZcash),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
pub mod unified;
|
||||
|
||||
pub(crate) mod sapling;
|
||||
pub(crate) mod sprout;
|
||||
|
||||
pub(crate) mod p2pkh;
|
||||
pub(crate) mod p2sh;
|
|
@ -0,0 +1,7 @@
|
|||
/// The prefix for a Base58Check-encoded mainnet transparent P2PKH address.
|
||||
pub(crate) const MAINNET: [u8; 2] = [0x1c, 0xb8];
|
||||
|
||||
/// The prefix for a Base58Check-encoded testnet transparent P2PKH address.
|
||||
pub(crate) const TESTNET: [u8; 2] = [0x1d, 0x25];
|
||||
|
||||
pub(crate) type Data = [u8; 20];
|
|
@ -0,0 +1,7 @@
|
|||
/// The prefix for a Base58Check-encoded mainnet transparent P2SH address.
|
||||
pub(crate) const MAINNET: [u8; 2] = [0x1c, 0xbd];
|
||||
|
||||
/// The prefix for a Base58Check-encoded testnet transparent P2SH address.
|
||||
pub(crate) const TESTNET: [u8; 2] = [0x1c, 0xba];
|
||||
|
||||
pub(crate) type Data = [u8; 20];
|
|
@ -0,0 +1,22 @@
|
|||
/// The HRP for a Bech32-encoded mainnet Sapling address.
|
||||
///
|
||||
/// Defined in the [Zcash Protocol Specification section 5.6.4][saplingpaymentaddrencoding].
|
||||
///
|
||||
/// [saplingpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding
|
||||
pub(crate) const MAINNET: &str = "zs";
|
||||
|
||||
/// The HRP for a Bech32-encoded testnet Sapling address.
|
||||
///
|
||||
/// Defined in the [Zcash Protocol Specification section 5.6.4][saplingpaymentaddrencoding].
|
||||
///
|
||||
/// [saplingpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding
|
||||
pub(crate) const TESTNET: &str = "ztestsapling";
|
||||
|
||||
/// The HRP for a Bech32-encoded regtest Sapling address.
|
||||
///
|
||||
/// It is defined in [the `zcashd` codebase].
|
||||
///
|
||||
/// [the `zcashd` codebase]: https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L493
|
||||
pub(crate) const REGTEST: &str = "zregtestsapling";
|
||||
|
||||
pub(crate) type Data = [u8; 43];
|
|
@ -0,0 +1,15 @@
|
|||
/// The prefix for a Base58Check-encoded mainnet Sprout address.
|
||||
///
|
||||
/// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding].
|
||||
///
|
||||
/// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding
|
||||
pub(crate) const MAINNET: [u8; 2] = [0x16, 0x9a];
|
||||
|
||||
/// The prefix for a Base58Check-encoded testnet Sprout address.
|
||||
///
|
||||
/// Defined in the [Zcash Protocol Specification section 5.6.3][].
|
||||
///
|
||||
/// []: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding
|
||||
pub(crate) const TESTNET: [u8; 2] = [0x16, 0xb6];
|
||||
|
||||
pub(crate) type Data = [u8; 64];
|
|
@ -0,0 +1,196 @@
|
|||
use std::convert::{TryFrom, TryInto};
|
||||
use std::iter;
|
||||
|
||||
use crate::{kind, ParseError};
|
||||
|
||||
mod f4jumble;
|
||||
|
||||
/// The HRP for a Bech32m-encoded mainnet Unified Address.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
pub(crate) const MAINNET: &str = "u";
|
||||
|
||||
/// The HRP for a Bech32m-encoded testnet Unified Address.
|
||||
///
|
||||
/// Defined in [ZIP 316][zip-0316].
|
||||
///
|
||||
/// [zip-0316]: https://zips.z.cash/zip-0316
|
||||
pub(crate) const TESTNET: &str = "utest";
|
||||
|
||||
/// The HRP for a Bech32m-encoded regtest Unified Address.
|
||||
pub(crate) const REGTEST: &str = "uregtest";
|
||||
|
||||
const PADDING_LEN: usize = 16;
|
||||
|
||||
/// The set of known Receivers for Unified Addresses.
|
||||
///
|
||||
/// This enum is an internal-only type, and is maintained in preference order, so that the
|
||||
/// derived [`PartialOrd`] will sort receivers correctly. From its documentation:
|
||||
///
|
||||
/// > When derived on enums, variants are ordered by their top-to-bottom discriminant
|
||||
/// > order.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub(crate) enum Receiver {
|
||||
Orchard([u8; 43]),
|
||||
Sapling(kind::sapling::Data),
|
||||
P2pkh(kind::p2pkh::Data),
|
||||
P2sh(kind::p2sh::Data),
|
||||
Unknown { typecode: u8, data: Vec<u8> },
|
||||
}
|
||||
|
||||
impl TryFrom<(u8, &[u8])> for Receiver {
|
||||
type Error = ParseError;
|
||||
|
||||
fn try_from((typecode, addr): (u8, &[u8])) -> Result<Self, Self::Error> {
|
||||
match typecode {
|
||||
0x00 => addr.try_into().map(Receiver::P2pkh),
|
||||
0x01 => addr.try_into().map(Receiver::P2sh),
|
||||
0x02 => addr.try_into().map(Receiver::Sapling),
|
||||
0x03 => addr.try_into().map(Receiver::Orchard),
|
||||
_ => Ok(Receiver::Unknown {
|
||||
typecode,
|
||||
data: addr.to_vec(),
|
||||
}),
|
||||
}
|
||||
.map_err(|_| ParseError::InvalidEncoding)
|
||||
}
|
||||
}
|
||||
|
||||
impl Receiver {
|
||||
fn typecode(&self) -> u8 {
|
||||
match self {
|
||||
Receiver::P2pkh(_) => 0x00,
|
||||
Receiver::P2sh(_) => 0x01,
|
||||
Receiver::Sapling(_) => 0x02,
|
||||
Receiver::Orchard(_) => 0x03,
|
||||
Receiver::Unknown { typecode, .. } => *typecode,
|
||||
}
|
||||
}
|
||||
|
||||
fn addr(&self) -> &[u8] {
|
||||
match self {
|
||||
Receiver::P2pkh(data) => data,
|
||||
Receiver::P2sh(data) => data,
|
||||
Receiver::Sapling(data) => data,
|
||||
Receiver::Orchard(data) => data,
|
||||
Receiver::Unknown { data, .. } => data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Unified Address.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Address(pub(crate) Vec<Receiver>);
|
||||
|
||||
impl TryFrom<&[u8]> for Address {
|
||||
type Error = ParseError;
|
||||
|
||||
fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
|
||||
let encoded = f4jumble::f4jumble_inv(buf).ok_or(ParseError::InvalidEncoding)?;
|
||||
|
||||
// Validate and strip trailing zero bytes.
|
||||
let encoded = match encoded.split_at(encoded.len() - PADDING_LEN) {
|
||||
(encoded, tail) if tail == &[0; PADDING_LEN][..] => Ok(encoded),
|
||||
_ => Err(ParseError::InvalidEncoding),
|
||||
}?;
|
||||
|
||||
iter::repeat(())
|
||||
.scan(encoded, |encoded, _| match encoded {
|
||||
// Base case: we've parsed the full encoding.
|
||||
[] => None,
|
||||
// The raw encoding of a Unified Address is a concatenation of:
|
||||
// - typecode: byte
|
||||
// - length: byte
|
||||
// - addr: byte[length]
|
||||
[typecode, length, data @ ..] if data.len() >= *length as usize => {
|
||||
let (addr, rest) = data.split_at(*length as usize);
|
||||
*encoded = rest;
|
||||
Some(Receiver::try_from((*typecode, addr)))
|
||||
}
|
||||
// The encoding is truncated.
|
||||
_ => Some(Err(ParseError::InvalidEncoding)),
|
||||
})
|
||||
.collect::<Result<_, _>>()
|
||||
.map(Address)
|
||||
}
|
||||
}
|
||||
|
||||
impl Address {
|
||||
/// Returns the raw encoding of this Unified Address.
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
let encoded: Vec<_> = self
|
||||
.0
|
||||
.iter()
|
||||
.flat_map(|receiver| {
|
||||
let addr = receiver.addr();
|
||||
// Holds by construction.
|
||||
assert!(addr.len() < 256);
|
||||
|
||||
iter::empty()
|
||||
.chain(Some(receiver.typecode()))
|
||||
.chain(Some(addr.len() as u8))
|
||||
.chain(addr.iter().cloned())
|
||||
})
|
||||
.chain(iter::repeat(0).take(PADDING_LEN))
|
||||
.collect();
|
||||
|
||||
f4jumble::f4jumble(&encoded).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use proptest::{
|
||||
array::{uniform11, uniform20, uniform32},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use super::{Address, Receiver};
|
||||
|
||||
prop_compose! {
|
||||
fn uniform43()(a in uniform11(0u8..), b in uniform32(0u8..)) -> [u8; 43] {
|
||||
let mut c = [0; 43];
|
||||
c[..11].copy_from_slice(&a);
|
||||
c[11..].copy_from_slice(&b);
|
||||
c
|
||||
}
|
||||
}
|
||||
|
||||
fn arb_shielded_receiver() -> BoxedStrategy<Receiver> {
|
||||
prop_oneof![
|
||||
uniform43().prop_map(Receiver::Sapling),
|
||||
uniform43().prop_map(Receiver::Orchard),
|
||||
]
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn arb_transparent_receiver() -> BoxedStrategy<Receiver> {
|
||||
prop_oneof![
|
||||
uniform20(0u8..).prop_map(Receiver::P2pkh),
|
||||
uniform20(0u8..).prop_map(Receiver::P2sh),
|
||||
]
|
||||
.boxed()
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
fn arb_unified_address()(
|
||||
shielded in prop::collection::hash_set(arb_shielded_receiver(), 1..2),
|
||||
transparent in prop::option::of(arb_transparent_receiver()),
|
||||
) -> Address {
|
||||
Address(shielded.into_iter().chain(transparent).collect())
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn ua_roundtrip(ua in arb_unified_address()) {
|
||||
let bytes = ua.to_bytes();
|
||||
let decoded = Address::try_from(&bytes[..]);
|
||||
prop_assert_eq!(decoded, Ok(ua));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
mod convert;
|
||||
mod encoding;
|
||||
mod kind;
|
||||
|
||||
pub use convert::{FromAddress, ToAddress, UnsupportedAddress};
|
||||
pub use encoding::ParseError;
|
||||
pub use kind::unified;
|
||||
|
||||
/// A Zcash address.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ZcashAddress {
|
||||
net: Network,
|
||||
kind: AddressKind,
|
||||
}
|
||||
|
||||
/// The Zcash network for which an address is encoded.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Network {
|
||||
/// Zcash Mainnet.
|
||||
Main,
|
||||
/// Zcash Testnet.
|
||||
Test,
|
||||
/// Private integration / regression testing, used in `zcashd`.
|
||||
///
|
||||
/// For some address types there is no distinction between test and regtest encodings;
|
||||
/// those will always be parsed as `Network::Test`.
|
||||
Regtest,
|
||||
}
|
||||
|
||||
/// Known kinds of Zcash addresses.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
enum AddressKind {
|
||||
Sprout(kind::sprout::Data),
|
||||
Sapling(kind::sapling::Data),
|
||||
Unified(unified::Address),
|
||||
P2pkh(kind::p2pkh::Data),
|
||||
P2sh(kind::p2sh::Data),
|
||||
}
|
||||
|
||||
impl ZcashAddress {
|
||||
/// Attempts to parse the given string as a Zcash address.
|
||||
///
|
||||
/// This simply calls [`s.parse()`], leveraging the [`FromStr` implementation].
|
||||
///
|
||||
/// [`s.parse()`]: std::primitive::str::parse
|
||||
/// [`FromStr` implementation]: ZcashAddress#impl-FromStr
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - If the parser can detect that the string _must_ contain an address encoding used
|
||||
/// by Zcash, [`ParseError::InvalidEncoding`] will be returned if any subsequent
|
||||
/// part of that encoding is invalid.
|
||||
///
|
||||
/// - In all other cases, [`ParseError::NotZcash`] will be returned on failure.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use zcash_address::ZcashAddress;
|
||||
///
|
||||
/// let encoded = "zs1z7rejlpsa98s2rrrfkwmaxu53e4ue0ulcrw0h4x5g8jl04tak0d3mm47vdtahatqrlkngh9sly";
|
||||
/// let addr = ZcashAddress::try_from_encoded(&encoded);
|
||||
/// assert_eq!(encoded.parse(), addr);
|
||||
/// ```
|
||||
pub fn try_from_encoded(s: &str) -> Result<Self, ParseError> {
|
||||
s.parse()
|
||||
}
|
||||
|
||||
/// Converts this address into another type.
|
||||
///
|
||||
/// `convert` can convert into any type that implements the [`FromAddress`] trait.
|
||||
/// This enables `ZcashAddress` to be used as a common parsing and serialization
|
||||
/// interface for Zcash addresses, while delegating operations on those addresses
|
||||
/// (such as constructing transactions) to downstream crates.
|
||||
///
|
||||
/// If you want to get the encoded string for this address, use the [`Display`]
|
||||
/// implementation instead via [`address.to_string()`].
|
||||
///
|
||||
/// [`Display`]: std::fmt::Display
|
||||
/// [`address.to_string()`]: std::string::ToString
|
||||
pub fn convert<T: FromAddress>(self) -> Result<T, UnsupportedAddress> {
|
||||
match self.kind {
|
||||
AddressKind::Sprout(data) => T::from_sprout(self.net, data),
|
||||
AddressKind::Sapling(data) => T::from_sapling(self.net, data),
|
||||
AddressKind::Unified(data) => T::from_unified(self.net, data),
|
||||
AddressKind::P2pkh(data) => T::from_transparent_p2pkh(self.net, data),
|
||||
AddressKind::P2sh(data) => T::from_transparent_p2sh(self.net, data),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
/// Types and algorithms used in support of Zcash Unified Addresses
|
||||
pub mod f4jumble;
|
|
@ -9,7 +9,6 @@
|
|||
// Temporary until we have addressed all Result<T, ()> cases.
|
||||
#![allow(clippy::result_unit_err)]
|
||||
|
||||
pub mod address;
|
||||
pub mod block;
|
||||
pub mod consensus;
|
||||
pub mod constants;
|
||||
|
|
Loading…
Reference in New Issue