Skip to content

Commit

Permalink
Missing crypto wasm api (#292)
Browse files Browse the repository at this point in the history
* Add missing hashing/crypto WASM bindings

Fixes #289

For script data hashing we do not pass in the witness set encodings like
in the rust bindings. We might wish to expose the CBOR encodings
generally, or for this API we could have something that takes in the
entire witness set. This function is likely to be used more indirectly
via the builders anyway.

* calc_script_data_hash_from_witness()

* cargo fmt --all (cargo fmt left these)
  • Loading branch information
rooooooooob committed Dec 29, 2023
1 parent e6f9ea8 commit cd32fb6
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 33 deletions.
36 changes: 35 additions & 1 deletion chain/rust/src/crypto/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use cml_crypto::{
use crate::{
auxdata::AuxiliaryData,
plutus::{CostModels, Language, PlutusData, Redeemer},
transaction::{cbor_encodings::TransactionWitnessSetEncoding, TransactionBody},
transaction::{
cbor_encodings::TransactionWitnessSetEncoding, TransactionBody, TransactionWitnessSet,
},
};

pub fn hash_auxiliary_data(auxiliary_data: &AuxiliaryData) -> AuxiliaryDataHash {
Expand All @@ -22,6 +24,11 @@ pub fn hash_plutus_data(plutus_data: &PlutusData) -> DatumHash {
DatumHash::from(blake2b256(&plutus_data.to_cbor_bytes()))
}

/// Calculates the hash for script data (no plutus scripts) if it is necessary.
/// Returns None if it was not necessary (no datums/redeemers) to include.
///
/// Most users will not directly need this as when using the builders
/// it will be invoked for you.
pub fn hash_script_data(
redeemers: &[Redeemer],
cost_models: &CostModels,
Expand Down Expand Up @@ -114,6 +121,11 @@ pub enum ScriptDataHashError {
MissingCostModel(Language),
}

/// Calculates the hash for script data (with plutus scripts) if it is necessary.
/// Returns None if it was not necessary (no datums/redeemers) to include.
///
/// Most users will not directly need this as when using the builders
/// it will be invoked for you.
pub fn calc_script_data_hash(
redeemers: &[Redeemer],
datums: &[PlutusData],
Expand Down Expand Up @@ -170,6 +182,28 @@ pub fn calc_script_data_hash(
}
}

/// Calculates the hash for script data from a witness if it is necessary.
/// Returns None if it was not necessary (no datums/redeemers) to include.
///
/// Most users will not directly need this as when using the builders
/// it will be invoked for you.
pub fn calc_script_data_hash_from_witness(
witnesses: &TransactionWitnessSet,
cost_models: &CostModels,
) -> Result<Option<ScriptDataHash>, ScriptDataHashError> {
if let (Some(redeemers), Some(datums)) = (&witnesses.redeemers, &witnesses.plutus_datums) {
calc_script_data_hash(
redeemers,
datums,
cost_models,
witnesses.languages().as_ref(),
witnesses.encodings.as_ref(),
)
} else {
Ok(None)
}
}

/// Each new language uses a different namespace for hashing its script
/// This is because you could have a language where the same bytes have different semantics
/// So this avoids scripts in different languages mapping to the same hash
Expand Down
15 changes: 15 additions & 0 deletions chain/rust/src/transaction/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::BTreeSet;

use crate::{
address::Address,
plutus::Language,
transaction::{DatumOption, ScriptRef, TransactionOutput},
Value,
};
Expand Down Expand Up @@ -191,4 +192,18 @@ impl TransactionWitnessSet {
}
}
}

pub fn languages(&self) -> Vec<Language> {
let mut used_langs = vec![];
if self.plutus_v1_scripts.is_some() {
used_langs.push(Language::PlutusV1);
}
if self.plutus_v2_scripts.is_some() {
used_langs.push(Language::PlutusV2);
}
if self.plutus_v3_scripts.is_some() {
used_langs.push(Language::PlutusV3);
}
used_langs
}
}
99 changes: 99 additions & 0 deletions chain/wasm/src/crypto/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use wasm_bindgen::prelude::{wasm_bindgen, JsError};

use crate::{
auxdata::AuxiliaryData,
plutus::{CostModels, PlutusData},
transaction::{TransactionBody, TransactionWitnessSet},
utils::LanguageList,
PlutusDataList, RedeemerList,
};

use cml_crypto_wasm::{AuxiliaryDataHash, DatumHash, ScriptDataHash, TransactionHash};

#[wasm_bindgen]
pub fn hash_auxiliary_data(auxiliary_data: &AuxiliaryData) -> AuxiliaryDataHash {
cml_chain::crypto::hash::hash_auxiliary_data(auxiliary_data.as_ref()).into()
}

#[wasm_bindgen]
pub fn hash_transaction(tx_body: &TransactionBody) -> TransactionHash {
cml_chain::crypto::hash::hash_transaction(tx_body.as_ref()).into()
}

#[wasm_bindgen]
pub fn hash_plutus_data(plutus_data: &PlutusData) -> DatumHash {
cml_chain::crypto::hash::hash_plutus_data(plutus_data.as_ref()).into()
}

/// Calculates the hash for script data (no plutus scripts) if it is necessary.
/// Returns None if it was not necessary (no datums/redeemers) to include.
///
/// Most users will not directly need this as when using the builders
/// it will be invoked for you.
///
/// Note: This WASM binding does not work with non-standard witness set
/// encodings. If you created the witness set manually this is not an issue
/// but for constructing it from deserializing a transaction/witness then
/// please use calc_script_data_hash_from_witness()
#[wasm_bindgen]
pub fn hash_script_data(
redeemers: &RedeemerList,
cost_models: &CostModels,
datums: Option<PlutusDataList>,
// encoding: Option<TransactionWitnessSetEncoding>,
) -> ScriptDataHash {
cml_chain::crypto::hash::hash_script_data(
redeemers.as_ref(),
cost_models.as_ref(),
datums.as_ref().map(AsRef::as_ref),
None,
)
.into()
}

/// Calculates the hash for script data (with plutus scripts) if it is necessary.
/// Returns None if it was not necessary (no datums/redeemers) to include.
///
/// Most users will not directly need this as when using the builders
/// it will be invoked for you.
///
/// Note: This WASM binding does not work with non-standard witness set
/// encodings. If you created the witness set manually this is not an issue
/// but for constructing it from deserializing a transaction/witness then
/// please use calc_script_data_hash_from_witness()
#[wasm_bindgen]
pub fn calc_script_data_hash(
redeemers: &RedeemerList,
datums: &PlutusDataList,
cost_models: &CostModels,
used_langs: &LanguageList,
// encoding: Option<TransactionWitnessSetEncoding>,
) -> Result<Option<ScriptDataHash>, JsError> {
cml_chain::crypto::hash::calc_script_data_hash(
redeemers.as_ref(),
datums.as_ref(),
cost_models.as_ref(),
used_langs.as_ref(),
None,
)
.map(|sdh| sdh.map(Into::into))
.map_err(Into::into)
}

/// Calculates the hash for script data from a witness if it is necessary.
/// Returns None if it was not necessary (no datums/redeemers) to include.
///
/// Most users will not directly need this as when using the builders
/// it will be invoked for you.
#[wasm_bindgen]
pub fn calc_script_data_hash_from_witness(
witnesses: &TransactionWitnessSet,
cost_models: &CostModels,
) -> Result<Option<ScriptDataHash>, JsError> {
cml_chain::crypto::hash::calc_script_data_hash_from_witness(
witnesses.as_ref(),
cost_models.as_ref(),
)
.map(|sdh| sdh.map(Into::into))
.map_err(Into::into)
}
3 changes: 3 additions & 0 deletions chain/wasm/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pub use cml_crypto_wasm::{
ScriptDataHash, ScriptHash, TransactionHash, VRFKeyHash, VRFVkey,
};

pub mod hash;
pub mod utils;

use wasm_bindgen::prelude::{wasm_bindgen, JsError, JsValue};

use cml_core_wasm::{impl_wasm_cbor_json_api, impl_wasm_conversions};
Expand Down
17 changes: 17 additions & 0 deletions chain/wasm/src/crypto/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use wasm_bindgen::prelude::{wasm_bindgen, JsError};

use crate::{byron::AddressContent, crypto::BootstrapWitness, Vkeywitness};

use cml_crypto_wasm::{PrivateKey, TransactionHash};

#[wasm_bindgen]
impl BootstrapWitness {
pub fn to_address(&self) -> Result<AddressContent, JsError> {
self.0.to_address().map(Into::into).map_err(Into::into)
}
}

#[wasm_bindgen]
pub fn make_vkey_witness(tx_body_hash: &TransactionHash, sk: &PrivateKey) -> Vkeywitness {
cml_chain::crypto::utils::make_vkey_witness(tx_body_hash.as_ref(), sk.as_ref()).into()
}
27 changes: 2 additions & 25 deletions chain/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use ::wasm_bindgen::prelude::{wasm_bindgen, JsError, JsValue};
use cml_core_wasm::metadata::TransactionMetadatumList;
use cml_core_wasm::{impl_wasm_cbor_json_api, impl_wasm_conversions};
use cml_core_wasm::{impl_wasm_cbor_json_api, impl_wasm_conversions, impl_wasm_list};

pub use cml_core_wasm::Int;

Expand Down Expand Up @@ -642,30 +642,7 @@ impl NativeScriptList {
}
}

#[derive(Clone, Debug)]
#[wasm_bindgen]
pub struct PlutusDataList(Vec<cml_chain::plutus::PlutusData>);

impl_wasm_conversions!(Vec<cml_chain::plutus::PlutusData>, PlutusDataList);

#[wasm_bindgen]
impl PlutusDataList {
pub fn new() -> Self {
Self(Vec::new())
}

pub fn len(&self) -> usize {
self.0.len()
}

pub fn get(&self, index: usize) -> PlutusData {
self.0[index].clone().into()
}

pub fn add(&mut self, elem: &PlutusData) {
self.0.push(elem.clone().into());
}
}
impl_wasm_list!(cml_chain::plutus::PlutusData, PlutusData, PlutusDataList);

#[derive(Clone, Debug)]
#[wasm_bindgen]
Expand Down
5 changes: 5 additions & 0 deletions chain/wasm/src/transaction/utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
address::Address,
transaction::{DatumOption, ScriptRef, TransactionOutput},
utils::LanguageList,
Ed25519KeyHashList, NativeScript, Value,
};
use cml_crypto_wasm::{DatumHash, ScriptHash};
Expand Down Expand Up @@ -74,4 +75,8 @@ impl TransactionWitnessSet {
pub fn add_all_witnesses(&mut self, other: &TransactionWitnessSet) {
self.0.add_all_witnesses(other.clone().into());
}

pub fn languages(&self) -> LanguageList {
self.0.languages().into()
}
}
4 changes: 3 additions & 1 deletion chain/wasm/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use super::{Int, Script, ScriptHash};
use cml_chain::plutus::Language;
use wasm_bindgen::prelude::{wasm_bindgen, JsError, JsValue};

use cml_core_wasm::{impl_wasm_cbor_json_api, impl_wasm_conversions};
use cml_core_wasm::{impl_wasm_cbor_json_api, impl_wasm_conversions, impl_wasm_list};

impl_wasm_list!(Language, Language, LanguageList, true, true);

#[wasm_bindgen]
#[derive(Clone, Debug)]
Expand Down
6 changes: 6 additions & 0 deletions core/wasm/src/wasm_wrappers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ macro_rules! impl_wasm_list {
}
}

impl AsRef<[$rust_elem_name]> for $wasm_list_name {
fn as_ref(&self) -> &[$rust_elem_name] {
&self.0
}
}

$crate::impl_wasm_list_add!(
$rust_elem_name,
$wasm_elem_name,
Expand Down
28 changes: 22 additions & 6 deletions specs/conway/transaction.cddl
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ transaction_output = alonzo_format_tx_out / conway_format_tx_out
; Since this data does not exist in contiguous form inside a transaction, it needs
; to be independently constructed by each recipient.
;
; script data format:
; [ redeemers | datums | language views ]
; The bytestring which is hashed is the concatenation of three things:
; redeemers || datums || language views
; The redeemers are exactly the data present in the transaction witness set.
; Similarly for the datums, if present. If no datums are provided, the middle
; field is an empty string.
; field is omitted (i.e. it is the empty/null bytestring).
;
; language views CDDL:
; { * language => script_integrity_data }
Expand All @@ -83,21 +83,37 @@ transaction_output = alonzo_format_tx_out / conway_format_tx_out
; in (byte-wise) lexical order sorts earlier.
;
; For PlutusV1 (language id 0), the language view is the following:
; - the value of costmdls map at key 0 is encoded as an indefinite length
; list and the result is encoded as a bytestring. (our apologies)
; - the value of costmdls map at key 0 (in other words, the script_integrity_data)
; is encoded as an indefinite length list and the result is encoded as a bytestring.
; (our apologies)
; For example, the script_integrity_data corresponding to the all zero costmodel for V1
; would be encoded as (in hex):
; 58a89f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff
; - the language ID tag is also encoded twice. first as a uint then as
; a bytestring. (our apologies)
; Concretely, this means that the language version for V1 is encoded as
; 4100 in hex.
; For PlutusV2 (language id 1), the language view is the following:
; - the value of costmdls map at key 1 is encoded as an definite length list.
; For example, the script_integrity_data corresponding to the all zero costmodel for V2
; would be encoded as (in hex):
; 98af0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
; - the language ID tag is encoded as expected.
; Concretely, this means that the language version for V2 is encoded as
; 01 in hex.
; For PlutusV3 (language id 2), the language view is the following:
; - the value of costmdls map at key 2 is encoded as a definite length list.
;
; Note that each Plutus language represented inside a transaction must have
; a cost model in the costmdls protocol parameter in order to execute,
; regardless of what the script integrity data is.
;
; Finally, note that in the case that a transaction includes datums but does not
; include any redeemers, the script data format becomes (in hex):
; include the redeemers field, the script data format becomes (in hex):
; [ 80 | datums | A0 ]
; corresponding to a CBOR empty list and an empty map.
; Note that a transaction might include the redeemers field and it to the
; empty map, in which case the user supplied encoding of the empty map is used.

; data = #6.24(bytes .cbor plutus_data)

Expand Down

0 comments on commit cd32fb6

Please sign in to comment.