Skip to content

Commit 183571e

Browse files
committed
Attach issued asset id to issuance inputs
Unimplemented for re-issuance transactions. Depends on an umerged PR to bitcoin_hashes (ttps://github.com/rust-bitcoin/bitcoin_hashes/pull/39)
1 parent 0a1b778 commit 183571e

File tree

6 files changed

+300
-11
lines changed

6 files changed

+300
-11
lines changed

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ features = ["serde"]
5959
version = "0.3"
6060
features = ["serde"]
6161

62+
[patch.crates-io]
63+
bitcoin_hashes = { git = "https://github.com/stevenroose/bitcoin_hashes", rev = "2dab3d6cb77ede5201eae067c93020f55cc1b9a4" }
64+
6265
[dependencies.elements]
6366
optional = true
6467
git = "https://github.com/elementsproject/rust-elements"

src/rest.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ impl TxInValue {
215215
is_pegin: txin.is_pegin,
216216
#[cfg(feature = "liquid")]
217217
issuance: if txin.has_issuance() {
218-
Some(IssuanceValue::from(&txin.asset_issuance))
218+
Some(IssuanceValue::from(txin))
219219
} else {
220220
None
221221
},

src/util/elements.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
use bitcoin::blockdata::script::Instruction::PushBytes;
22
use bitcoin::consensus::encode::serialize;
33
use bitcoin::Script;
4+
use bitcoin_hashes::{hex::ToHex, sha256, Hash};
45
use elements::confidential::Value;
5-
use elements::{AssetIssuance, Proof};
6+
use elements::{Proof, TxIn};
67

78
use hex;
89

910
use crate::chain::Network;
10-
use crate::util::{get_script_asm, script_to_address};
11+
use crate::errors::*;
12+
use crate::util::{get_script_asm, script_to_address, AssetId};
1113

1214
#[derive(Serialize, Deserialize)]
1315
pub struct BlockProofValue {
@@ -30,6 +32,7 @@ impl From<&Proof> for BlockProofValue {
3032

3133
#[derive(Serialize, Deserialize, Clone)]
3234
pub struct IssuanceValue {
35+
pub asset_id: Option<String>,
3336
pub is_reissuance: bool,
3437
pub asset_blinding_nonce: Option<String>,
3538
pub asset_entropy: Option<String>,
@@ -39,12 +42,22 @@ pub struct IssuanceValue {
3942
pub tokenamountcommitment: Option<String>,
4043
}
4144

42-
impl From<&AssetIssuance> for IssuanceValue {
43-
fn from(issuance: &AssetIssuance) -> Self {
45+
impl From<&TxIn> for IssuanceValue {
46+
fn from(txin: &TxIn) -> Self {
4447
let zero = [0u8; 32];
48+
49+
let issuance = txin.asset_issuance;
4550
let is_reissuance = issuance.asset_blinding_nonce != zero;
51+
let asset_id = if is_reissuance {
52+
None // TODO
53+
} else {
54+
get_issuance_assetid(txin).ok().map(|s| s.to_hex())
55+
};
56+
57+
debug!("issuance: {:?}", issuance);
4658

4759
IssuanceValue {
60+
asset_id,
4861
is_reissuance,
4962
asset_blinding_nonce: if is_reissuance {
5063
Some(hex::encode(issuance.asset_blinding_nonce))
@@ -76,6 +89,18 @@ impl From<&AssetIssuance> for IssuanceValue {
7689
}
7790
}
7891

92+
fn get_issuance_assetid(txin: &TxIn) -> Result<AssetId> {
93+
if !txin.has_issuance {
94+
bail!("input has no issuance");
95+
}
96+
97+
let contract_hash = sha256::Hash::from_slice(&txin.asset_issuance.asset_entropy)
98+
.chain_err(|| "invalid entropy")?;
99+
let entropy = AssetId::generate_asset_entropy(txin.previous_output.clone(), contract_hash);
100+
101+
Ok(AssetId::from_entropy(entropy))
102+
}
103+
79104
#[derive(Serialize, Deserialize, Clone)]
80105
pub struct PegOutRequest {
81106
pub genesis_hash: String,

src/util/elements_assetid.rs

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
// Copeid from https://github.com/ElementsProject/rust-elements/pull/19 pending merge
2+
3+
use bitcoin::consensus::Encodable;
4+
use bitcoin_hashes::{hex, sha256, sha256d, Error, Hash, HashEngine};
5+
use elements::OutPoint;
6+
7+
/// The zero hash.
8+
const ZERO32: [u8; 32] = [
9+
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,
10+
];
11+
/// The one hash.
12+
const ONE32: [u8; 32] = [
13+
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,
14+
];
15+
/// The two hash.
16+
const TWO32: [u8; 32] = [
17+
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,
18+
];
19+
20+
/// An issued asset ID.
21+
#[derive(Copy, Clone, PartialEq, Eq, Default, PartialOrd, Ord, Hash)]
22+
pub struct AssetId(sha256::Midstate);
23+
24+
impl AssetId {
25+
/// Create an [AssetId] from its inner type.
26+
pub fn from_inner(midstate: sha256::Midstate) -> AssetId {
27+
AssetId(midstate)
28+
}
29+
30+
/// Convert the [AssetId] into its inner type.
31+
pub fn into_inner(self) -> sha256::Midstate {
32+
self.0
33+
}
34+
35+
/// Generate the asset entropy from the issuance prevout and the contract hash.
36+
pub fn generate_asset_entropy(
37+
prevout: OutPoint,
38+
contract_hash: sha256::Hash,
39+
) -> sha256::Midstate {
40+
// E : entropy
41+
// I : prevout
42+
// C : contract
43+
// E = H( H(I) || H(C) )
44+
fast_merkle_root(&[
45+
outpoint_hash(&prevout).into_inner(),
46+
contract_hash.into_inner(),
47+
])
48+
}
49+
50+
/// Calculate the asset ID from the asset entropy.
51+
pub fn from_entropy(entropy: sha256::Midstate) -> AssetId {
52+
// H_a : asset tag
53+
// E : entropy
54+
// H_a = H( E || 0 )
55+
AssetId(fast_merkle_root(&[entropy.into_inner(), ZERO32]))
56+
}
57+
58+
/// Calculate the reissuance token asset ID from the asset entropy.
59+
pub fn reissuance_token_from_entropy(entropy: sha256::Midstate, confidential: bool) -> AssetId {
60+
// H_a : asset reissuance tag
61+
// E : entropy
62+
// if not fConfidential:
63+
// H_a = H( E || 1 )
64+
// else
65+
// H_a = H( E || 2 )
66+
let second = match confidential {
67+
false => ONE32,
68+
true => TWO32,
69+
};
70+
AssetId(fast_merkle_root(&[entropy.into_inner(), second]))
71+
}
72+
}
73+
74+
fn outpoint_hash(out: &OutPoint) -> sha256d::Hash {
75+
let mut enc = sha256d::Hash::engine();
76+
out.consensus_encode(&mut enc).unwrap();
77+
sha256d::Hash::from_engine(enc)
78+
}
79+
80+
impl hex::FromHex for AssetId {
81+
fn from_byte_iter<I>(iter: I) -> Result<Self, Error>
82+
where
83+
I: Iterator<Item = Result<u8, Error>> + ExactSizeIterator + DoubleEndedIterator,
84+
{
85+
sha256::Midstate::from_byte_iter(iter).map(AssetId)
86+
}
87+
}
88+
89+
impl ::std::fmt::Display for AssetId {
90+
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
91+
::std::fmt::Display::fmt(&self.0, f)
92+
}
93+
}
94+
95+
impl ::std::fmt::Debug for AssetId {
96+
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
97+
::std::fmt::Display::fmt(&self, f)
98+
}
99+
}
100+
101+
impl ::std::fmt::LowerHex for AssetId {
102+
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
103+
::std::fmt::LowerHex::fmt(&self.0, f)
104+
}
105+
}
106+
107+
#[cfg(feature = "serde")]
108+
impl ::serde::Serialize for AssetId {
109+
fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
110+
use bitcoin_hashes::hex::ToHex;
111+
if s.is_human_readable() {
112+
s.serialize_str(&self.to_hex())
113+
} else {
114+
s.serialize_bytes(&self.0[..])
115+
}
116+
}
117+
}
118+
119+
#[cfg(feature = "serde")]
120+
impl<'de> ::serde::Deserialize<'de> for AssetId {
121+
fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<AssetId, D::Error> {
122+
use bitcoin_hashes::hex::FromHex;
123+
use bitcoin_hashes::sha256;
124+
125+
if d.is_human_readable() {
126+
struct HexVisitor;
127+
128+
impl<'de> ::serde::de::Visitor<'de> for HexVisitor {
129+
type Value = AssetId;
130+
131+
fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
132+
formatter.write_str("an ASCII hex string")
133+
}
134+
135+
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
136+
where
137+
E: ::serde::de::Error,
138+
{
139+
if let Ok(hex) = ::std::str::from_utf8(v) {
140+
AssetId::from_hex(hex).map_err(E::custom)
141+
} else {
142+
return Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self));
143+
}
144+
}
145+
146+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
147+
where
148+
E: ::serde::de::Error,
149+
{
150+
AssetId::from_hex(v).map_err(E::custom)
151+
}
152+
}
153+
154+
d.deserialize_str(HexVisitor)
155+
} else {
156+
struct BytesVisitor;
157+
158+
impl<'de> ::serde::de::Visitor<'de> for BytesVisitor {
159+
type Value = AssetId;
160+
161+
fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
162+
formatter.write_str("a bytestring")
163+
}
164+
165+
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
166+
where
167+
E: ::serde::de::Error,
168+
{
169+
if v.len() != 32 {
170+
Err(E::invalid_length(v.len(), &stringify!($len)))
171+
} else {
172+
let mut ret = [0; 32];
173+
ret.copy_from_slice(v);
174+
Ok(AssetId(sha256::Midstate::from_inner(ret)))
175+
}
176+
}
177+
}
178+
179+
d.deserialize_bytes(BytesVisitor)
180+
}
181+
}
182+
}
183+
184+
/// Calculate a single sha256 midstate hash of the given left and right leaves.
185+
#[inline]
186+
fn sha256midstate(left: &[u8], right: &[u8]) -> sha256::Midstate {
187+
let mut engine = sha256::Hash::engine();
188+
engine.input(left);
189+
engine.input(right);
190+
engine.midstate()
191+
}
192+
193+
/// Compute the Merkle root of the give hashes using mid-state only.
194+
/// The inputs must be byte slices of length 32.
195+
/// Note that the merkle root calculated with this method is not the same as the
196+
/// one computed by a normal SHA256(d) merkle root.
197+
pub fn fast_merkle_root(leaves: &[[u8; 32]]) -> sha256::Midstate {
198+
let mut result_hash = Default::default();
199+
// Implementation based on ComputeFastMerkleRoot method in Elements Core.
200+
if leaves.is_empty() {
201+
return result_hash;
202+
}
203+
204+
// inner is an array of eagerly computed subtree hashes, indexed by tree
205+
// level (0 being the leaves).
206+
// For example, when count is 25 (11001 in binary), inner[4] is the hash of
207+
// the first 16 leaves, inner[3] of the next 8 leaves, and inner[0] equal to
208+
// the last leaf. The other inner entries are undefined.
209+
//
210+
// First process all leaves into 'inner' values.
211+
let mut inner: [sha256::Midstate; 32] = Default::default();
212+
let mut count: u32 = 0;
213+
while (count as usize) < leaves.len() {
214+
let mut temp_hash = sha256::Midstate::from_inner(leaves[count as usize]);
215+
count += 1;
216+
// For each of the lower bits in count that are 0, do 1 step. Each
217+
// corresponds to an inner value that existed before processing the
218+
// current leaf, and each needs a hash to combine it.
219+
let mut level = 0;
220+
while count & (1u32 << level) == 0 {
221+
temp_hash = sha256midstate(&inner[level][..], &temp_hash[..]);
222+
level += 1;
223+
}
224+
// Store the resulting hash at inner position level.
225+
inner[level] = temp_hash;
226+
}
227+
228+
// Do a final 'sweep' over the rightmost branch of the tree to process
229+
// odd levels, and reduce everything to a single top value.
230+
// Level is the level (counted from the bottom) up to which we've sweeped.
231+
//
232+
// As long as bit number level in count is zero, skip it. It means there
233+
// is nothing left at this level.
234+
let mut level = 0;
235+
while count & (1u32 << level) == 0 {
236+
level += 1;
237+
}
238+
result_hash = inner[level];
239+
240+
while count != (1u32 << level) {
241+
// If we reach this point, hash is an inner value that is not the top.
242+
// We combine it with itself (Bitcoin's special rule for odd levels in
243+
// the tree) to produce a higher level one.
244+
245+
// Increment count to the value it would have if two entries at this
246+
// level had existed and propagate the result upwards accordingly.
247+
count += 1 << level;
248+
level += 1;
249+
while count & (1u32 << level) == 0 {
250+
result_hash = sha256midstate(&inner[level][..], &result_hash[..]);
251+
level += 1;
252+
}
253+
}
254+
// Return result.
255+
result_hash
256+
}

0 commit comments

Comments
 (0)