use crate::{
constants::{
ASSET_IDENTIFIER_LENGTH, ASSET_IDENTIFIER_PERSONALIZATION, GH_FIRST_BLOCK,
VALUE_COMMITMENT_GENERATOR_PERSONALIZATION,
},
sapling::ValueCommitment,
};
use blake2s_simd::Params as Blake2sParams;
use borsh::BorshSchema;
use borsh::{BorshDeserialize, BorshSerialize};
use group::{Group, GroupEncoding, cofactor::CofactorGroup};
use std::{
cmp::Ordering,
fmt::{Display, Formatter},
hash::{Hash, Hasher},
};
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, BorshSerialize, BorshDeserialize, Clone, Copy, Eq, BorshSchema)]
pub struct AssetType {
identifier: [u8; ASSET_IDENTIFIER_LENGTH], #[borsh(skip)]
nonce: Option<u8>,
}
impl AssetType {
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(())?;
}
}
pub fn new_with_nonce(name: &[u8], nonce: u8) -> Option<AssetType> {
use std::slice::from_ref;
assert_eq!(ASSET_IDENTIFIER_PERSONALIZATION.len(), 8);
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 AssetType::hash_to_point(h.as_array()).is_some() {
Some(AssetType {
identifier: *h.as_array(),
nonce: Some(nonce),
})
} else {
None
}
}
fn hash_to_point(identifier: &[u8; ASSET_IDENTIFIER_LENGTH]) -> Option<jubjub::ExtendedPoint> {
assert_eq!(VALUE_COMMITMENT_GENERATOR_PERSONALIZATION.len(), 8);
use ff::PrimeField;
assert_eq!(bls12_381::Scalar::NUM_BITS, 255);
let h = Blake2sParams::new()
.hash_length(32)
.personal(VALUE_COMMITMENT_GENERATOR_PERSONALIZATION)
.to_state()
.update(identifier)
.finalize();
let p = jubjub::ExtendedPoint::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 }
}
pub fn get_identifier(&self) -> &[u8; ASSET_IDENTIFIER_LENGTH] {
&self.identifier
}
pub fn from_identifier(identifier: &[u8; ASSET_IDENTIFIER_LENGTH]) -> Option<AssetType> {
if AssetType::hash_to_point(identifier).is_some() {
Some(AssetType {
identifier: *identifier,
nonce: None,
})
} else {
None }
}
pub fn asset_generator(&self) -> jubjub::ExtendedPoint {
AssetType::hash_to_point(self.get_identifier())
.expect("AssetType internal identifier state inconsistent")
}
pub fn value_commitment_generator(&self) -> jubjub::SubgroupPoint {
CofactorGroup::clear_cofactor(&self.asset_generator())
}
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()
}
pub fn value_commitment(&self, value: u64, randomness: jubjub::Fr) -> ValueCommitment {
ValueCommitment {
asset_generator: self.asset_generator(),
value,
randomness,
}
}
pub fn get_nonce(&self) -> Option<u8> {
self.nonce
}
pub fn read<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
let mut atype = [0; crate::constants::ASSET_IDENTIFIER_LENGTH];
reader.read_exact(&mut atype)?;
AssetType::from_identifier(&atype).ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid asset type")
})
}
}
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> {
Some(self.cmp(other))
}
}
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()
}
}
}