Skip to content

Commit 15b533d

Browse files
committed
Merge #51: Add support for hashing JSON asset contract
94ba7f6 Add support for hashing JSON asset contract (Steven Roose) Pull request description: ACKs for top commit: jonasnick: ACK 94ba7f6 Tree-SHA512: d382bad073dde900f572ee764bbda004da96f1a66c35b2ceed0e83b8a43e1bb87aef35996357ac3a97e51cb56f2c7b27e00b55239adcfa903cb38a43772add92
2 parents dde6ba5 + 94ba7f6 commit 15b533d

File tree

3 files changed

+57
-1
lines changed

3 files changed

+57
-1
lines changed

Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ repository = "https://github.com/ElementsProject/rust-elements/"
99
documentation = "https://docs.rs/elements/"
1010

1111
[features]
12+
default = [ "json-contract" ]
13+
14+
json-contract = [ "serde_json" ]
1215
"serde-feature" = [
1316
"bitcoin/use-serde",
1417
"serde"
@@ -22,6 +25,9 @@ bitcoin = "0.23"
2225
# to avoid requiring two version of bitcoin_hashes.
2326
bitcoin_hashes = "0.7.6"
2427

28+
# Used for ContractHash::from_json_contract.
29+
serde_json = { version = "<=1.0.44", optional = true }
30+
2531
[dependencies.serde]
2632
version = "1.0"
2733
optional = true

src/issuance.rs

+50
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,26 @@ const TWO32: [u8; 32] = [
3939

4040
hash_newtype!(ContractHash, sha256::Hash, 32, doc="The hash of an asset contract.", true);
4141

42+
impl ContractHash {
43+
/// Calculate the contract hash of a JSON contract object.
44+
///
45+
/// This method does not perform any validation of the contents of the contract.
46+
/// After basic JSON syntax validation, the object is formatted in a standard way to calculate
47+
/// the hash.
48+
#[cfg(feature = "json-contract")]
49+
pub fn from_json_contract(json: &str) -> Result<ContractHash, ::serde_json::Error> {
50+
// Parsing the JSON into a BTreeMap will recursively order object keys
51+
// lexicographically. This order is respected when we later serialize
52+
// it again.
53+
let ordered: ::std::collections::BTreeMap<String, ::serde_json::Value> =
54+
::serde_json::from_str(json)?;
55+
56+
let mut engine = ContractHash::engine();
57+
::serde_json::to_writer(&mut engine, &ordered).expect("engines don't error");
58+
Ok(ContractHash::from_engine(engine))
59+
}
60+
}
61+
4262
/// An issued asset ID.
4363
#[derive(Copy, Clone, PartialEq, Eq, Default, PartialOrd, Ord, Hash)]
4464
pub struct AssetId(sha256::Midstate);
@@ -242,4 +262,34 @@ mod test {
242262
let token_id = AssetId::from_hex(token_id_hex).unwrap();
243263
assert_eq!(AssetId::reissuance_token_from_entropy(entropy, false), token_id);
244264
}
265+
266+
#[cfg(feature = "json-contract")]
267+
#[test]
268+
fn test_json_contract() {
269+
let tether = ContractHash::from_hex("3c7f0a53c2ff5b99590620d7f6604a7a3a7bfbaaa6aa61f7bfc7833ca03cde82").unwrap();
270+
271+
let correct = r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"ticker":"USDt","version":0}"#;
272+
let expected = ContractHash::hash(correct.as_bytes());
273+
assert_eq!(tether, expected);
274+
assert_eq!(expected, ContractHash::from_json_contract(&correct).unwrap());
275+
276+
let invalid_json = r#"{"entity":{"domain":"tether.to"},"issuer_pubkey:"#;
277+
assert!(ContractHash::from_json_contract(&invalid_json).is_err());
278+
279+
let unordered = r#"{"precision":8,"ticker":"USDt","entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","version":0}"#;
280+
assert_eq!(expected, ContractHash::from_json_contract(&unordered).unwrap());
281+
282+
let unordered = r#"{"precision":8,"name":"Tether USD","ticker":"USDt","entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","version":0}"#;
283+
assert_eq!(expected, ContractHash::from_json_contract(&unordered).unwrap());
284+
285+
let spaces = r#"{"precision":8, "name" : "Tether USD", "ticker":"USDt", "entity":{"domain":"tether.to" }, "issuer_pubkey" :"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","version":0} "#;
286+
assert_eq!(expected, ContractHash::from_json_contract(&spaces).unwrap());
287+
288+
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}"#;
289+
let nested_expected = ContractHash::hash(nested_correct.as_bytes());
290+
assert_eq!(nested_expected, ContractHash::from_json_contract(&nested_correct).unwrap());
291+
292+
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}"#;
293+
assert_eq!(nested_expected, ContractHash::from_json_contract(&nested_unordered).unwrap());
294+
}
245295
}

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub extern crate bitcoin_hashes;
3131
#[cfg(feature = "serde")] extern crate serde;
3232

3333
#[cfg(test)] extern crate rand;
34-
#[cfg(test)] extern crate serde_json;
34+
#[cfg(any(test, feature = "serde_json"))] extern crate serde_json;
3535

3636
#[macro_use] mod internal_macros;
3737
pub mod address;

0 commit comments

Comments
 (0)