use std::io;
use std::str::FromStr;
use crate::encode::{self, Encodable, Decodable};
use crate::hashes::{self, hash_newtype, sha256, sha256d, Hash};
use crate::fast_merkle_root::fast_merkle_root;
use secp256k1_zkp::Tag;
use crate::transaction::OutPoint;
const ZERO32: [u8; 32] = [
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,
];
const ONE32: [u8; 32] = [
1, 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,
];
const TWO32: [u8; 32] = [
2, 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,
];
hash_newtype!(
#[hash_newtype(backward)]
pub struct ContractHash(sha256::Hash);
);
impl ContractHash {
#[cfg(feature = "json-contract")]
pub fn from_json_contract(json: &str) -> Result<ContractHash, ::serde_json::Error> {
let ordered: ::std::collections::BTreeMap<String, ::serde_json::Value> =
::serde_json::from_str(json)?;
let mut engine = ContractHash::engine();
::serde_json::to_writer(&mut engine, &ordered).expect("engines don't error");
Ok(ContractHash::from_engine(engine))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Default, PartialOrd, Ord, Hash)]
pub struct AssetId(sha256::Midstate);
impl AssetId {
pub const LIQUID_BTC: AssetId = AssetId(sha256::Midstate([
0x6d, 0x52, 0x1c, 0x38, 0xec, 0x1e, 0xa1, 0x57,
0x34, 0xae, 0x22, 0xb7, 0xc4, 0x60, 0x64, 0x41,
0x28, 0x29, 0xc0, 0xd0, 0x57, 0x9f, 0x0a, 0x71,
0x3d, 0x1c, 0x04, 0xed, 0xe9, 0x79, 0x02, 0x6f,
]));
pub const fn from_inner(midstate: sha256::Midstate) -> AssetId {
AssetId(midstate)
}
pub fn into_inner(self) -> sha256::Midstate {
self.0
}
pub fn from_slice(sl: &[u8]) -> Result<AssetId, hashes::FromSliceError> {
sha256::Midstate::from_slice(sl).map(AssetId)
}
pub fn generate_asset_entropy(
prevout: OutPoint,
contract_hash: ContractHash,
) -> sha256::Midstate {
let prevout_hash = {
let mut enc = sha256d::Hash::engine();
prevout.consensus_encode(&mut enc).unwrap();
sha256d::Hash::from_engine(enc)
};
fast_merkle_root(&[prevout_hash.to_byte_array(), contract_hash.to_byte_array()])
}
pub fn from_entropy(entropy: sha256::Midstate) -> AssetId {
AssetId(fast_merkle_root(&[entropy.to_byte_array(), ZERO32]))
}
pub fn new_issuance(prevout: OutPoint, contract_hash: ContractHash) -> Self {
let entropy = AssetId::generate_asset_entropy(prevout, contract_hash);
AssetId::from_entropy(entropy)
}
pub fn new_reissuance_token(prevout: OutPoint, contract_hash: ContractHash, confidential: bool) -> Self {
let entropy = AssetId::generate_asset_entropy(prevout, contract_hash);
AssetId::reissuance_token_from_entropy(entropy, confidential)
}
pub fn reissuance_token_from_entropy(entropy: sha256::Midstate, confidential: bool) -> AssetId {
let second = match confidential {
false => ONE32,
true => TWO32,
};
AssetId(fast_merkle_root(&[entropy.to_byte_array(), second]))
}
pub fn into_tag(self) -> Tag {
self.0.to_byte_array().into()
}
}
impl ::std::fmt::Display for AssetId {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::std::fmt::Display::fmt(&self.0, f)
}
}
impl ::std::fmt::Debug for AssetId {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::std::fmt::Display::fmt(&self, f)
}
}
impl ::std::fmt::LowerHex for AssetId {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::std::fmt::LowerHex::fmt(&self.0, f)
}
}
impl FromStr for AssetId {
type Err = crate::hashes::hex::HexToArrayError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
sha256::Midstate::from_str(s).map(AssetId)
}
}
impl Encodable for AssetId {
fn consensus_encode<W: io::Write>(&self, e: W) -> Result<usize, encode::Error> {
self.0.consensus_encode(e)
}
}
impl Decodable for AssetId {
fn consensus_decode<D: io::Read>(d: D) -> Result<Self, encode::Error> {
Ok(Self::from_inner(sha256::Midstate::consensus_decode(d)?))
}
}
#[cfg(feature = "serde")]
impl ::serde::Serialize for AssetId {
fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
use crate::hex::ToHex;
if s.is_human_readable() {
s.serialize_str(&self.to_hex())
} else {
s.serialize_bytes(&self.0[..])
}
}
}
#[cfg(feature = "serde")]
impl<'de> ::serde::Deserialize<'de> for AssetId {
fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<AssetId, D::Error> {
if d.is_human_readable() {
struct HexVisitor;
impl ::serde::de::Visitor<'_> for HexVisitor {
type Value = AssetId;
fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
formatter.write_str("an ASCII hex string")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
if let Ok(hex) = ::std::str::from_utf8(v) {
AssetId::from_str(hex).map_err(E::custom)
} else {
Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self))
}
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
AssetId::from_str(v).map_err(E::custom)
}
}
d.deserialize_str(HexVisitor)
} else {
struct BytesVisitor;
impl ::serde::de::Visitor<'_> for BytesVisitor {
type Value = AssetId;
fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
formatter.write_str("a bytestring")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
use core::convert::TryFrom;
match <[u8; 32]>::try_from(v) {
Ok(ret) => Ok(AssetId(sha256::Midstate::from_byte_array(ret))),
Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))),
}
}
}
d.deserialize_bytes(BytesVisitor)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::str::FromStr;
use crate::hashes::sha256;
#[test]
fn example_elements_core() {
let prevout_str = "05a047c98e82a848dee94efcf32462b065198bebf2404d201ba2e06db30b28f4:0";
let entropy_hex = "746f447f691323502cad2ef646f932613d37a83aeaa2133185b316648df4b70a";
let asset_id_hex = "dcd60818d863b5c026c40b2bc3ba6fdaf5018bcc8606c18adf7db4da0bcd8533";
let token_id_hex = "c1adb114f4f87d33bf9ce90dd4f9ca523dd414d6cd010a7917903e2009689530";
let contract_hash = ContractHash::from_byte_array(ZERO32);
let prevout = OutPoint::from_str(prevout_str).unwrap();
let entropy = sha256::Midstate::from_str(entropy_hex).unwrap();
assert_eq!(AssetId::generate_asset_entropy(prevout, contract_hash), entropy);
let asset_id = AssetId::from_str(asset_id_hex).unwrap();
assert_eq!(AssetId::from_entropy(entropy), asset_id);
let token_id = AssetId::from_str(token_id_hex).unwrap();
assert_eq!(AssetId::reissuance_token_from_entropy(entropy, false), token_id);
let prevout_str = "c76664aa4be760056dcc39b59637eeea8f3c3c3b2aeefb9f23a7b99945a2931e:1";
let entropy_hex = "bc67a13736341d8ad19e558433483a38cae48a44a5a8b5598ca0b01b5f9f9f41";
let asset_id_hex = "2ec6c1a06e895b06fffb8dc36084255f890467fb906565b0c048d4c807b4a129";
let token_id_hex = "d09d205ff7c626ca98c91fed24787ff747fec62194ed1b7e6ef6cc775a1a1fdc";
let contract_hash = ContractHash::from_byte_array(ZERO32);
let prevout = OutPoint::from_str(prevout_str).unwrap();
let entropy = sha256::Midstate::from_str(entropy_hex).unwrap();
assert_eq!(AssetId::generate_asset_entropy(prevout, contract_hash), entropy);
let asset_id = AssetId::from_str(asset_id_hex).unwrap();
assert_eq!(AssetId::from_entropy(entropy), asset_id);
let token_id = AssetId::from_str(token_id_hex).unwrap();
assert_eq!(AssetId::reissuance_token_from_entropy(entropy, true), token_id);
let prevout_str = "ee45365ddb62e8822182fbdd132fb156b4991e0b7411cff4aab576fd964f2edb:0"; let contract_hash_hex = "e06e6d4933e76afd7b9cc6a013e0855aa60bbe6d2fca1c27ec6951ff5f1a20c9"; let entropy_hex = "1922da340705eef526640b49d28b08928630d1ad52db0f945f3c389267e292c9"; let asset_id_hex = "8eebf6109bca0331fe559f0cbd1ef846a2bbb6812f3ae3d8b0b610170cc21a4e"; let token_id_hex = "eb02cbc591c9ede071625c129f0a1fab386202cb27a894a45be0d564e961d6bc";
let contract_hash = ContractHash::from_str(contract_hash_hex).unwrap();
let prevout = OutPoint::from_str(prevout_str).unwrap();
let entropy = sha256::Midstate::from_str(entropy_hex).unwrap();
assert_eq!(AssetId::generate_asset_entropy(prevout, contract_hash), entropy);
let asset_id = AssetId::from_str(asset_id_hex).unwrap();
assert_eq!(AssetId::from_entropy(entropy), asset_id);
let token_id = AssetId::from_str(token_id_hex).unwrap();
assert_eq!(AssetId::reissuance_token_from_entropy(entropy, false), token_id);
let prevout_str = "8903ee739b52859877fbfedc58194c2d59d0f5a4ea3c2774dc3cba3031cec757:0";
let entropy_hex = "b9789de8589dc1b664e4f2bda4d04af9d4d2180394a8c47b1f889acfb5e0acc4";
let asset_id_hex = "bdab916e8cda17781bcdb84505452e44d0ab2f080e9e5dd7765ffd5ce0c07cd9";
let token_id_hex = "f144868169dfc7afc024c4d8f55607ac8dfe925e67688650a9cdc54c3cfa5b1c";
let contract_hash = ContractHash::from_byte_array(ZERO32);
let prevout = OutPoint::from_str(prevout_str).unwrap();
let entropy = sha256::Midstate::from_str(entropy_hex).unwrap();
assert_eq!(AssetId::generate_asset_entropy(prevout, contract_hash), entropy);
let asset_id = AssetId::from_str(asset_id_hex).unwrap();
assert_eq!(AssetId::from_entropy(entropy), asset_id);
let token_id = AssetId::from_str(token_id_hex).unwrap();
assert_eq!(AssetId::reissuance_token_from_entropy(entropy, true), token_id);
}
#[cfg(feature = "json-contract")]
#[test]
fn test_json_contract() {
let tether = ContractHash::from_str("3c7f0a53c2ff5b99590620d7f6604a7a3a7bfbaaa6aa61f7bfc7833ca03cde82").unwrap();
let correct = r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"ticker":"USDt","version":0}"#;
let expected = ContractHash::hash(correct.as_bytes());
assert_eq!(tether, expected);
assert_eq!(expected, ContractHash::from_json_contract(correct).unwrap());
let invalid_json = r#"{"entity":{"domain":"tether.to"},"issuer_pubkey:"#;
assert!(ContractHash::from_json_contract(invalid_json).is_err());
let unordered = r#"{"precision":8,"ticker":"USDt","entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","version":0}"#;
assert_eq!(expected, ContractHash::from_json_contract(unordered).unwrap());
let unordered = r#"{"precision":8,"name":"Tether USD","ticker":"USDt","entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","version":0}"#;
assert_eq!(expected, ContractHash::from_json_contract(unordered).unwrap());
let spaces = r#"{"precision":8, "name" : "Tether USD", "ticker":"USDt", "entity":{"domain":"tether.to" }, "issuer_pubkey" :"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","version":0} "#;
assert_eq!(expected, ContractHash::from_json_contract(spaces).unwrap());
let nested_correct = r#"{"entity":{"author":"Tether Inc","copyright":2020,"domain":"tether.to","hq":"Mars"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"ticker":"USDt","version":0}"#;
let nested_expected = ContractHash::hash(nested_correct.as_bytes());
assert_eq!(nested_expected, ContractHash::from_json_contract(nested_correct).unwrap());
let nested_unordered = r#"{"ticker":"USDt","entity":{"domain":"tether.to","hq":"Mars","author":"Tether Inc","copyright":2020},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"version":0}"#;
assert_eq!(nested_expected, ContractHash::from_json_contract(nested_unordered).unwrap());
}
#[test]
fn liquid() {
assert_eq!(
AssetId::LIQUID_BTC.to_string(),
"6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d",
);
}
}