zip32/src/lib.rs

379 lines
11 KiB
Rust

//! Common types for implementing [ZIP 32] for hierarchical deterministic key management.
//!
//! [ZIP 32]: https://zips.z.cash/zip-0032
#![no_std]
#![deny(missing_docs)]
#![deny(unsafe_code)]
#![deny(rustdoc::broken_intra_doc_links)]
#[cfg(feature = "std")]
extern crate std;
use core::mem;
use memuse::{self, DynamicUsage};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
pub mod fingerprint;
/// A type-safe wrapper for account identifiers.
///
/// Accounts are 31-bit unsigned integers, and are always treated as hardened in
/// derivation paths.
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AccountId(u32);
memuse::impl_no_dynamic_usage!(AccountId);
impl TryFrom<u32> for AccountId {
type Error = TryFromIntError;
fn try_from(id: u32) -> Result<Self, Self::Error> {
// Account IDs are always hardened in derivation paths, so they are effectively at
// most 31 bits.
if id < (1 << 31) {
Ok(Self(id))
} else {
Err(TryFromIntError(()))
}
}
}
impl From<AccountId> for u32 {
fn from(id: AccountId) -> Self {
id.0
}
}
impl From<AccountId> for ChildIndex {
fn from(id: AccountId) -> Self {
// Account IDs are always hardened in derivation paths.
ChildIndex::hardened(id.0)
}
}
impl ConditionallySelectable for AccountId {
fn conditional_select(a0: &Self, a1: &Self, c: Choice) -> Self {
AccountId(u32::conditional_select(&a0.0, &a1.0, c))
}
}
impl AccountId {
/// The ID for account zero (the first account).
pub const ZERO: Self = Self(0);
/// Returns the next account ID in sequence, or `None` on overflow.
pub fn next(&self) -> Option<Self> {
Self::try_from(self.0 + 1).ok()
}
}
/// The error type returned when a checked integral type conversion fails.
#[derive(Clone, Copy, Debug)]
pub struct TryFromIntError(());
impl core::fmt::Display for TryFromIntError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "out of range integral type conversion attempted")
}
}
#[cfg(feature = "std")]
impl std::error::Error for TryFromIntError {}
// ZIP 32 structures
/// A child index for a derived key.
///
/// Only hardened derivation is supported.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ChildIndex(u32);
impl ConstantTimeEq for ChildIndex {
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
impl ChildIndex {
/// Parses the given ZIP 32 child index.
///
/// Returns `None` if the hardened bit is not set.
pub fn from_index(i: u32) -> Option<Self> {
if i >= (1 << 31) {
Some(ChildIndex(i))
} else {
None
}
}
/// Constructs a hardened `ChildIndex` from the given value.
///
/// # Panics
///
/// Panics if `value >= (1 << 31)`.
pub const fn hardened(value: u32) -> Self {
assert!(value < (1 << 31));
Self(value + (1 << 31))
}
/// Returns the index as a 32-bit integer, including the hardened bit.
pub fn index(&self) -> u32 {
self.0
}
}
/// A value that is needed, in addition to a spending key, in order to derive descendant
/// keys and addresses of that key.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ChainCode([u8; 32]);
impl ConstantTimeEq for ChainCode {
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
impl ChainCode {
/// Constructs a `ChainCode` from the given array.
pub fn new(c: [u8; 32]) -> Self {
Self(c)
}
/// Returns the byte representation of the chain code, as required for
/// [ZIP 32](https://zips.z.cash/zip-0032) encoding.
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
/// The index for a particular diversifier.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DiversifierIndex([u8; 11]);
impl Default for DiversifierIndex {
fn default() -> Self {
DiversifierIndex::new()
}
}
macro_rules! di_from {
($n:ident) => {
impl From<$n> for DiversifierIndex {
fn from(j: $n) -> Self {
let mut j_bytes = [0; 11];
j_bytes[..mem::size_of::<$n>()].copy_from_slice(&j.to_le_bytes());
DiversifierIndex(j_bytes)
}
}
};
}
di_from!(u32);
di_from!(u64);
di_from!(usize);
impl From<[u8; 11]> for DiversifierIndex {
fn from(j_bytes: [u8; 11]) -> Self {
DiversifierIndex(j_bytes)
}
}
impl TryFrom<u128> for DiversifierIndex {
type Error = TryFromIntError;
fn try_from(value: u128) -> Result<Self, Self::Error> {
if (value >> 88) == 0 {
Ok(Self(value.to_le_bytes()[..11].try_into().unwrap()))
} else {
Err(TryFromIntError(()))
}
}
}
macro_rules! di_try_into {
($n:ident) => {
impl TryFrom<DiversifierIndex> for $n {
type Error = core::num::TryFromIntError;
fn try_from(di: DiversifierIndex) -> Result<Self, Self::Error> {
u128::from(di).try_into()
}
}
};
}
di_try_into!(u32);
di_try_into!(u64);
di_try_into!(usize);
impl From<DiversifierIndex> for u128 {
fn from(di: DiversifierIndex) -> Self {
let mut u128_bytes = [0u8; 16];
u128_bytes[0..11].copy_from_slice(&di.0[..]);
u128::from_le_bytes(u128_bytes)
}
}
impl DiversifierIndex {
/// Constructs the zero index.
pub fn new() -> Self {
DiversifierIndex([0; 11])
}
/// Returns the raw bytes of the diversifier index.
pub fn as_bytes(&self) -> &[u8; 11] {
&self.0
}
/// Increments this index, failing on overflow.
pub fn increment(&mut self) -> Result<(), DiversifierIndexOverflowError> {
for k in 0..11 {
self.0[k] = self.0[k].wrapping_add(1);
if self.0[k] != 0 {
// No overflow
return Ok(());
}
}
// Overflow
Err(DiversifierIndexOverflowError)
}
}
/// The error type returned when a [`DiversifierIndex`] increment fails.
#[derive(Clone, Copy, Debug)]
pub struct DiversifierIndexOverflowError;
impl core::fmt::Display for DiversifierIndexOverflowError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "DiversifierIndex increment overflowed")
}
}
#[cfg(feature = "std")]
impl std::error::Error for DiversifierIndexOverflowError {}
/// The scope of a viewing key or address.
///
/// A "scope" narrows the visibility or usage to a level below "full".
///
/// Consistent usage of `Scope` enables the user to provide consistent views over a wallet
/// to other people. For example, a user can give an external incoming viewing key to a
/// merchant terminal, enabling it to only detect "real" transactions from customers and
/// not internal transactions from the wallet.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Scope {
/// A scope used for wallet-external operations, namely deriving addresses to give to
/// other users in order to receive funds.
External,
/// A scope used for wallet-internal operations, such as creating change notes,
/// auto-shielding, and note management.
Internal,
}
memuse::impl_no_dynamic_usage!(Scope);
#[cfg(test)]
mod tests {
use super::{AccountId, DiversifierIndex};
use assert_matches::assert_matches;
#[test]
fn account_id_next() {
let zero = AccountId::ZERO;
assert_eq!(zero.next(), AccountId::try_from(1).ok());
let max_id = AccountId::try_from((1 << 31) - 1).unwrap();
assert_eq!(max_id.next(), None);
}
#[test]
fn diversifier_index_to_u32() {
let two = DiversifierIndex([
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
assert_eq!(u32::try_from(two), Ok(2));
assert_eq!(DiversifierIndex::from(2_u32), two);
let max_u32 = DiversifierIndex([
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
assert_eq!(u32::try_from(max_u32), Ok(u32::MAX));
assert_eq!(DiversifierIndex::from(u32::MAX), max_u32);
let too_big = DiversifierIndex([
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
assert_matches!(u32::try_from(too_big), Err(_));
}
#[test]
fn diversifier_index_to_u64() {
let two = DiversifierIndex([
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
assert_eq!(u64::try_from(two), Ok(2));
assert_eq!(DiversifierIndex::from(2_u64), two);
let max_u64 = DiversifierIndex([
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
]);
assert_eq!(u64::try_from(max_u64), Ok(u64::MAX));
assert_eq!(DiversifierIndex::from(u64::MAX), max_u64);
let too_big = DiversifierIndex([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
]);
assert_matches!(u64::try_from(too_big), Err(_));
}
#[test]
fn diversifier_index_to_u128() {
let two = DiversifierIndex([
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
assert_eq!(u128::from(two), 2);
assert_eq!(DiversifierIndex::try_from(2_u128).unwrap(), two);
let max_di = DiversifierIndex([
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
]);
assert_eq!(u128::try_from(max_di), Ok(0x00ff_ffff_ffff_ffff_ffff_ffff));
assert_eq!(
DiversifierIndex::try_from(0x00ff_ffff_ffff_ffff_ffff_ffff_u128).unwrap(),
max_di,
);
assert!(DiversifierIndex::try_from(0x0100_0000_0000_0000_0000_0000_u128).is_err());
}
#[test]
fn diversifier_index_increment() {
let mut di = DiversifierIndex::new();
assert_eq!(di, 0_u32.into());
assert_matches!(di.increment(), Ok(_));
assert_eq!(di, 1_u32.into());
assert_matches!(di.increment(), Ok(_));
assert_eq!(di, 2_u32.into());
let mut di = DiversifierIndex::from(0xff_u32);
assert_eq!(di, 0x00ff_u32.into());
assert_matches!(di.increment(), Ok(_));
assert_eq!(di, 0x0100_u32.into());
assert_matches!(di.increment(), Ok(_));
assert_eq!(di, 0x0101_u32.into());
let mut di = DiversifierIndex::try_from(0x00ff_ffff_ffff_ffff_ffff_fffe_u128).unwrap();
assert_eq!(u128::from(di), 0x00ff_ffff_ffff_ffff_ffff_fffe_u128);
assert_matches!(di.increment(), Ok(_));
assert_eq!(u128::from(di), 0x00ff_ffff_ffff_ffff_ffff_ffff_u128);
assert_matches!(di.increment(), Err(_));
}
}