add amount and asset_type

This commit is contained in:
adust09 2023-07-11 00:18:37 +09:00
parent 0f2f990b63
commit 7b80d7c92e
4 changed files with 612 additions and 1 deletions

View File

@ -25,7 +25,9 @@ rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"]
[dependencies]
aes = "0.8"
bitvec = "1"
borsh = {version = "0.9", features = ["const-generics"]}
blake2b_simd = "1"
blake2s_simd = "1"
ff = "0.13"
fpe = "0.6"
group = { version = "0.13", features = ["wnaf-memuse"] }

406
src/amount.rs Normal file
View File

@ -0,0 +1,406 @@
use std::convert::TryFrom;
use std::iter::Sum;
use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
use memuse::DynamicUsage;
use crate::value as orchard;
///missing documentation for a constant
pub const COIN: i64 = 1_0000_0000;
///missing documentation for a constant
pub const MAX_MONEY: i64 = 21_000_000 * COIN;
#[deprecated(
since = "0.12.0",
note = "To calculate the ZIP 317 fee, use `transaction::fees::zip317::FeeRule`.
For a constant representing the minimum ZIP 317 fee, use `transaction::fees::zip317::MINIMUM_FEE`.
For the constant amount 1000 zatoshis, use `Amount::const_from_i64(1000)`."
)]
///missing documentation for a constant
pub const DEFAULT_FEE: Amount = Amount(1000);
/// A type-safe representation of some quantity of Zcash.
///
/// An Amount can only be constructed from an integer that is within the valid monetary
/// range of `{-MAX_MONEY..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis).
/// However, this range is not preserved as an invariant internally; it is possible to
/// add two valid Amounts together to obtain an invalid Amount. It is the user's
/// responsibility to handle the result of serializing potentially-invalid Amounts. In
/// particular, a [`Transaction`] containing serialized invalid Amounts will be rejected
/// by the network consensus rules.
///
/// [`Transaction`]: crate::transaction::Transaction
/// TODO: should downgrade rust analyzer?
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
pub struct Amount(i64);
memuse::impl_no_dynamic_usage!(Amount);
impl Amount {
/// Returns a zero-valued Amount.
pub const fn zero() -> Self {
Amount(0)
}
/// Creates a constant Amount from an i64.
///
/// Panics: if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`.
pub const fn const_from_i64(amount: i64) -> Self {
assert!(-MAX_MONEY <= amount && amount <= MAX_MONEY); // contains is not const
Amount(amount)
}
/// Creates an Amount from an i64.
///
/// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`.
pub fn from_i64(amount: i64) -> Result<Self, ()> {
if (-MAX_MONEY..=MAX_MONEY).contains(&amount) {
Ok(Amount(amount))
} else {
Err(())
}
}
/// Creates a non-negative Amount from an i64.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_nonnegative_i64(amount: i64) -> Result<Self, ()> {
if (0..=MAX_MONEY).contains(&amount) {
Ok(Amount(amount))
} else {
Err(())
}
}
/// Creates an Amount from a u64.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_u64(amount: u64) -> Result<Self, ()> {
if amount <= MAX_MONEY as u64 {
Ok(Amount(amount as i64))
} else {
Err(())
}
}
/// Reads an Amount from a signed 64-bit little-endian integer.
///
/// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`.
pub fn from_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> {
let amount = i64::from_le_bytes(bytes);
Amount::from_i64(amount)
}
/// Reads a non-negative Amount from a signed 64-bit little-endian integer.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> {
let amount = i64::from_le_bytes(bytes);
Amount::from_nonnegative_i64(amount)
}
/// Reads an Amount from an unsigned 64-bit little-endian integer.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> {
let amount = u64::from_le_bytes(bytes);
Amount::from_u64(amount)
}
/// Returns the Amount encoded as a signed 64-bit little-endian integer.
pub fn to_i64_le_bytes(self) -> [u8; 8] {
self.0.to_le_bytes()
}
/// Returns `true` if `self` is positive and `false` if the Amount is zero or
/// negative.
pub const fn is_positive(self) -> bool {
self.0.is_positive()
}
/// Returns `true` if `self` is negative and `false` if the Amount is zero or
/// positive.
pub const fn is_negative(self) -> bool {
self.0.is_negative()
}
///missing documentation for a constant
pub fn sum<I: IntoIterator<Item = Amount>>(values: I) -> Option<Amount> {
let mut result = Amount::zero();
for value in values {
result = (result + value)?;
}
Some(result)
}
}
impl TryFrom<i64> for Amount {
type Error = ();
fn try_from(value: i64) -> Result<Self, ()> {
Amount::from_i64(value)
}
}
impl From<Amount> for i64 {
fn from(amount: Amount) -> i64 {
amount.0
}
}
impl From<&Amount> for i64 {
fn from(amount: &Amount) -> i64 {
amount.0
}
}
impl From<Amount> for u64 {
fn from(amount: Amount) -> u64 {
amount.0 as u64
}
}
impl Add<Amount> for Amount {
type Output = Option<Amount>;
fn add(self, rhs: Amount) -> Option<Amount> {
Amount::from_i64(self.0 + rhs.0).ok()
}
}
impl Add<Amount> for Option<Amount> {
type Output = Self;
fn add(self, rhs: Amount) -> Option<Amount> {
self.and_then(|lhs| lhs + rhs)
}
}
impl AddAssign<Amount> for Amount {
fn add_assign(&mut self, rhs: Amount) {
*self = (*self + rhs).expect("Addition must produce a valid amount value.")
}
}
impl Sub<Amount> for Amount {
type Output = Option<Amount>;
fn sub(self, rhs: Amount) -> Option<Amount> {
Amount::from_i64(self.0 - rhs.0).ok()
}
}
impl Sub<Amount> for Option<Amount> {
type Output = Self;
fn sub(self, rhs: Amount) -> Option<Amount> {
self.and_then(|lhs| lhs - rhs)
}
}
impl SubAssign<Amount> for Amount {
fn sub_assign(&mut self, rhs: Amount) {
*self = (*self - rhs).expect("Subtraction must produce a valid amount value.")
}
}
impl Sum<Amount> for Option<Amount> {
fn sum<I: Iterator<Item = Amount>>(iter: I) -> Self {
iter.fold(Some(Amount::zero()), |acc, a| acc? + a)
}
}
impl<'a> Sum<&'a Amount> for Option<Amount> {
fn sum<I: Iterator<Item = &'a Amount>>(iter: I) -> Self {
iter.fold(Some(Amount::zero()), |acc, a| acc? + *a)
}
}
impl Neg for Amount {
type Output = Self;
fn neg(self) -> Self {
Amount(-self.0)
}
}
impl Mul<usize> for Amount {
type Output = Option<Amount>;
fn mul(self, rhs: usize) -> Option<Amount> {
let rhs: i64 = rhs.try_into().ok()?;
self.0
.checked_mul(rhs)
.and_then(|i| Amount::try_from(i).ok())
}
}
impl TryFrom<orchard::ValueSum> for Amount {
type Error = ();
fn try_from(v: orchard::ValueSum) -> Result<Amount, Self::Error> {
i64::try_from(v).map_err(|_| ()).and_then(Amount::try_from)
}
}
/// A type-safe representation of some nonnegative amount of Zcash.
///
/// A NonNegativeAmount can only be constructed from an integer that is within the valid monetary
/// range of `{0..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis).
/// TODO: should downgrade rust analyzer?
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
pub struct NonNegativeAmount(Amount);
impl NonNegativeAmount {
/// Creates a NonNegativeAmount from a u64.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_u64(amount: u64) -> Result<Self, ()> {
Amount::from_u64(amount).map(NonNegativeAmount)
}
/// Creates a NonNegativeAmount from an i64.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_nonnegative_i64(amount: i64) -> Result<Self, ()> {
Amount::from_nonnegative_i64(amount).map(NonNegativeAmount)
}
}
impl From<NonNegativeAmount> for Amount {
fn from(n: NonNegativeAmount) -> Self {
n.0
}
}
/// A type for balance violations in amount addition and subtraction
/// (overflow and underflow of allowed ranges)
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum BalanceError {
///missing documentation for a constant
Overflow,
///missing documentation for a constant
Underflow,
}
impl std::fmt::Display for BalanceError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match &self {
BalanceError::Overflow => {
write!(
f,
"Amount addition resulted in a value outside the valid range."
)
}
BalanceError::Underflow => write!(
f,
"Amount subtraction resulted in a value outside the valid range."
),
}
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::prop_compose;
use super::{Amount, MAX_MONEY};
prop_compose! {
pub fn arb_amount()(amt in -MAX_MONEY..MAX_MONEY) -> Amount {
Amount::from_i64(amt).unwrap()
}
}
prop_compose! {
pub fn arb_nonnegative_amount()(amt in 0i64..MAX_MONEY) -> Amount {
Amount::from_i64(amt).unwrap()
}
}
prop_compose! {
pub fn arb_positive_amount()(amt in 1i64..MAX_MONEY) -> Amount {
Amount::from_i64(amt).unwrap()
}
}
}
#[cfg(test)]
mod tests {
use super::{Amount, MAX_MONEY};
#[test]
fn amount_in_range() {
let zero = b"\x00\x00\x00\x00\x00\x00\x00\x00";
assert_eq!(Amount::from_u64_le_bytes(*zero).unwrap(), Amount(0));
assert_eq!(
Amount::from_nonnegative_i64_le_bytes(*zero).unwrap(),
Amount(0)
);
assert_eq!(Amount::from_i64_le_bytes(*zero).unwrap(), Amount(0));
let neg_one = b"\xff\xff\xff\xff\xff\xff\xff\xff";
assert!(Amount::from_u64_le_bytes(*neg_one).is_err());
assert!(Amount::from_nonnegative_i64_le_bytes(*neg_one).is_err());
assert_eq!(Amount::from_i64_le_bytes(*neg_one).unwrap(), Amount(-1));
let max_money = b"\x00\x40\x07\x5a\xf0\x75\x07\x00";
assert_eq!(
Amount::from_u64_le_bytes(*max_money).unwrap(),
Amount(MAX_MONEY)
);
assert_eq!(
Amount::from_nonnegative_i64_le_bytes(*max_money).unwrap(),
Amount(MAX_MONEY)
);
assert_eq!(
Amount::from_i64_le_bytes(*max_money).unwrap(),
Amount(MAX_MONEY)
);
let max_money_p1 = b"\x01\x40\x07\x5a\xf0\x75\x07\x00";
assert!(Amount::from_u64_le_bytes(*max_money_p1).is_err());
assert!(Amount::from_nonnegative_i64_le_bytes(*max_money_p1).is_err());
assert!(Amount::from_i64_le_bytes(*max_money_p1).is_err());
let neg_max_money = b"\x00\xc0\xf8\xa5\x0f\x8a\xf8\xff";
assert!(Amount::from_u64_le_bytes(*neg_max_money).is_err());
assert!(Amount::from_nonnegative_i64_le_bytes(*neg_max_money).is_err());
assert_eq!(
Amount::from_i64_le_bytes(*neg_max_money).unwrap(),
Amount(-MAX_MONEY)
);
let neg_max_money_m1 = b"\xff\xbf\xf8\xa5\x0f\x8a\xf8\xff";
assert!(Amount::from_u64_le_bytes(*neg_max_money_m1).is_err());
assert!(Amount::from_nonnegative_i64_le_bytes(*neg_max_money_m1).is_err());
assert!(Amount::from_i64_le_bytes(*neg_max_money_m1).is_err());
}
#[test]
fn add_overflow() {
let v = Amount(MAX_MONEY);
assert_eq!(v + Amount(1), None)
}
#[test]
#[should_panic]
fn add_assign_panics_on_overflow() {
let mut a = Amount(MAX_MONEY);
a += Amount(1);
}
#[test]
fn sub_underflow() {
let v = Amount(-MAX_MONEY);
assert_eq!(v - Amount(1), None)
}
#[test]
#[should_panic]
fn sub_assign_panics_on_underflow() {
let mut a = Amount(-MAX_MONEY);
a -= Amount(1);
}
}

201
src/asset_type.rs Normal file
View File

@ -0,0 +1,201 @@
use crate::{
constants::{
ASSET_IDENTIFIER_LENGTH, ASSET_IDENTIFIER_PERSONALIZATION, GH_FIRST_BLOCK,
VALUE_COMMITMENT_GENERATOR_PERSONALIZATION,
},
value::ValueCommitment,
};
use blake2s_simd::Params as Blake2sParams;
use borsh::{BorshDeserialize, BorshSerialize};
use group::{cofactor::CofactorGroup, Group, GroupEncoding};
use pasta_curves::pallas;
use std::{
cmp::Ordering,
fmt::{Display, Formatter},
hash::{Hash, Hasher},
};
/// A type representing an asset type
#[derive(Debug, BorshSerialize, BorshDeserialize, Clone, Copy, Eq)]
pub struct AssetType {
identifier: [u8; ASSET_IDENTIFIER_LENGTH], //32 byte asset type preimage
#[borsh_skip]
nonce: Option<u8>,
}
// Abstract type representing an asset
impl AssetType {
/// Create a new AsstType from a unique asset name
/// Not constant-time, uses rejection sampling
pub fn new(name: &[u8]) -> Result<AssetType, ()> {
let mut nonce = 0u8;
loop {
if let Some(asset_type) = AssetType::new_with_nonce(name, nonce) {
return Ok(asset_type);
}
nonce = nonce.checked_add(1).ok_or(())?;
}
}
/// Attempt to create a new AssetType from a unique asset name and fixed nonce
/// Not yet constant-time; assume not-constant-time
pub fn new_with_nonce(name: &[u8], nonce: u8) -> Option<AssetType> {
use std::slice::from_ref;
// Check the personalization is acceptable length
assert_eq!(ASSET_IDENTIFIER_PERSONALIZATION.len(), 8);
// Create a new BLAKE2s state for deriving the asset identifier
//TODO:sinsemillaに置き換える
let h = Blake2sParams::new()
.hash_length(ASSET_IDENTIFIER_LENGTH)
.personal(ASSET_IDENTIFIER_PERSONALIZATION)
.to_state()
.update(GH_FIRST_BLOCK)
.update(name)
.update(from_ref(&nonce))
.finalize();
// If the hash state is a valid asset identifier, use it
if AssetType::hash_to_point(h.as_array()).is_some() {
Some(AssetType {
identifier: *h.as_array(),
nonce: Some(nonce),
})
} else {
None
}
}
//Attempt to hash an identifier to a curve point
// https://crypto.stackexchange.com/questions/102414/where-is-hash-to-curve-used
fn hash_to_point(identifier: &[u8; ASSET_IDENTIFIER_LENGTH]) -> Option<pallas::Point> {
//check the personalization is acceptable length
assert_eq!(ASSET_IDENTIFIER_PERSONALIZATION.len(), 8);
//Check to see that scalar field is 255 bits
use ff::PrimeField;
assert_eq!(pallas::Base::NUM_BITS, 255);
let h = Blake2sParams::new()
.hash_length(32)
.personal(VALUE_COMMITMENT_GENERATOR_PERSONALIZATION)
.to_state()
.update(identifier)
.finalize();
let p = pallas::Point::from_bytes(h.as_array());
if p.is_some().into() {
let p = p.unwrap();
let p_prime = CofactorGroup::clear_cofactor(&p);
if p_prime.is_identity().into() {
None
} else {
Some(p)
}
} else {
None
}
}
/// Return the identifier of this asset type
pub fn get_identifier(&self) -> &[u8; ASSET_IDENTIFIER_LENGTH] {
&self.identifier
}
/// Attempt to construct an asset type from an existing asset identifier
pub fn from_identifier(identifier: &[u8; ASSET_IDENTIFIER_LENGTH]) -> Option<AssetType> {
// Attempt to hash to point
if AssetType::hash_to_point(identifier).is_some() {
Some(AssetType {
identifier: *identifier,
nonce: None,
})
} else {
None
}
}
/// Produces an asset generator without cofactor cleared
pub fn asset_generator(&self) -> pallas::Point {
AssetType::hash_to_point(self.get_identifier())
.expect("AssetType::get_identifier() should never return None")
}
/// Produces a value commitment generator with cofactor cleared
/// todo:返り値をsubgroup
pub fn value_commitment_generator(&self) -> () {
CofactorGroup::clear_cofactor(&self.asset_generator());
}
/// Get the asset identifier as a vector of bools
pub fn identifier_bits(&self) -> Vec<Option<bool>> {
self.get_identifier()
.iter()
.flat_map(|&v| (0..8).map(move |i| Some((v >> i) & 1 == 1)))
.collect()
}
/// Get the asset identifier as a vector of bools
pub fn value_commitment(&self, randomness: pallas::Point) -> ValueCommitment {
ValueCommitment(randomness)
}
/// Get the asset identifier as a vector of bools
pub fn get_nonce(&self) -> Option<u8> {
self.nonce
}
}
impl PartialEq for AssetType {
fn eq(&self, other: &Self) -> bool {
self.get_identifier() == other.get_identifier()
}
}
impl Display for AssetType {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{}", hex::encode(self.get_identifier()))
}
}
impl Hash for AssetType {
fn hash<H: Hasher>(&self, state: &mut H) {
self.get_identifier().hash(state);
}
}
impl PartialOrd for AssetType {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.get_identifier().partial_cmp(other.get_identifier())
}
}
impl Ord for AssetType {
fn cmp(&self, other: &Self) -> Ordering {
self.get_identifier().cmp(other.get_identifier())
}
}
impl std::str::FromStr for AssetType {
type Err = std::io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let vec = hex::decode(s).map_err(|x| Self::Err::new(std::io::ErrorKind::InvalidData, x))?;
Self::from_identifier(
&vec.try_into()
.map_err(|_| Self::Err::from(std::io::ErrorKind::InvalidData))?,
)
.ok_or_else(|| Self::Err::from(std::io::ErrorKind::InvalidData))
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::*;
prop_compose! {
pub fn arb_asset_type()(name in proptest::collection::vec(prop::num::u8::ANY, 0..64)) -> super::AssetType {
super::AssetType::new(&name).unwrap()
}
}
}

View File

@ -13,11 +13,13 @@
// Catch documentation errors caused by code changes.
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(missing_debug_implementations)]
#![deny(missing_docs)]
// #![deny(missing_docs)]
#![deny(unsafe_code)]
mod action;
mod address;
pub mod amount;
pub mod asset_type;
pub mod builder;
pub mod bundle;
pub mod circuit;