From 36185064d0152dbc4b932164182917ee9513c5f9 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Sun, 28 Nov 2021 14:13:00 +0300 Subject: [PATCH 1/7] Reworked mint helper function to accept minting script instead of just the hash, and added storing these scripts in the resulting witness set --- rust/src/tx_builder.rs | 352 +++++++++++++++++++++++++++-------------- 1 file changed, 229 insertions(+), 123 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 0a7d99eb..42c48697 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -1,7 +1,7 @@ use super::*; use super::fees; use super::utils; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; // comes from witsVKeyNeeded in the Ledger spec fn witness_keys_for_cert(cert_enum: &Certificate, keys: &mut BTreeSet) { @@ -46,9 +46,9 @@ fn fake_private_key() -> Bip32PrivateKey { ).unwrap() } -fn fake_key_hash() -> Ed25519KeyHash { +fn fake_key_hash(x: u8) -> Ed25519KeyHash { Ed25519KeyHash::from_bytes( - vec![142, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100] + vec![x, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100] ).unwrap() } @@ -57,8 +57,6 @@ fn fake_key_hash() -> Ed25519KeyHash { // for use in calculating the size of the final Transaction fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Result { let fake_key_root = fake_private_key(); - let fake_key_hash = fake_key_hash(); - // recall: this includes keys for input, certs and withdrawals let vkeys = match tx_builder.input_types.vkeys.len() { 0 => None, @@ -96,16 +94,13 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul Some(result) }, }; - let full_script_keys = match &tx_builder.mint { + let full_script_keys = match &tx_builder.mint_scripts { None => script_keys, - Some(mint) => { + Some(witness_scripts) => { let mut ns = script_keys .map(|sk| { sk.clone() }) .unwrap_or(NativeScripts::new()); - let spk = ScriptPubkey::new(&fake_key_hash); - mint.keys().0.iter().for_each(|_p| { - ns.add(&NativeScript::new_script_pubkey(&spk)); - }); + witness_scripts.0.iter().for_each(|s| { ns.add(s); }); Some(ns) } }; @@ -127,6 +122,20 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul } fn min_fee(tx_builder: &TransactionBuilder) -> Result { + if let Some(mint) = tx_builder.mint.as_ref() { + if let Some(witness_scripts) = tx_builder.mint_scripts.as_ref() { + let witness_hashes: HashSet = witness_scripts.0.iter().map(|script| { + script.hash(ScriptHashNamespace::NativeScript) + }).collect(); + for mint_hash in mint.keys().0.iter() { + if !witness_hashes.contains(mint_hash) { + return Err(JsError::from_str(&format!("No witness script is found for mint policy '{:?}'! Impossible to estimate fee", hex::encode(mint_hash.to_bytes())))); + } + } + } else { + return Err(JsError::from_str("Impossible to estimate fee if mint is present in the builder, but witness scripts are not provided!")); + } + } let full_tx = fake_full_tx(tx_builder, tx_builder.build()?)?; fees::min_fee(&full_tx, &tx_builder.config.fee_algo) } @@ -260,6 +269,7 @@ pub struct TransactionBuilder { validity_start_interval: Option, input_types: MockWitnessSet, mint: Option, + mint_scripts: Option, inputs_auto_added: bool, } @@ -638,29 +648,66 @@ impl TransactionBuilder { /// Set explicit Mint object to this builder /// it will replace any previously existing mint + /// NOTE! If you use `set_mint` manually - you must use `set_mint_scripts` + /// to provide matching policy scripts or min-fee calculation will be rejected! pub fn set_mint(&mut self, mint: &Mint) { self.mint = Some(mint.clone()); } - /// Add a mint entry to this builder using a PolicyID and MintAssets object - /// It will be securely added to existing or new Mint in this builder - /// It will replace any existing mint assets with the same PolicyID - pub fn set_mint_asset(&mut self, policy_id: &PolicyID, mint_assets: &MintAssets) { + /// Returns a copy of the current mint state in the builder + pub fn get_mint(&self) -> Option { + self.mint.clone() + } + + /// Set explicit witness set to this builder + /// It will replace any previously existing witnesses + /// NOTE! Use carefully! If you are using `set_mint` - then you must be using + /// this setter as well to be able to calculate fee automatically! + pub fn set_mint_scripts(&mut self, mint_scripts: &NativeScripts) { + self.mint_scripts = Some(mint_scripts.clone()); + } + + /// Returns a copy of the current mint witness scripts in the builder + pub fn get_mint_scripts(&self) -> Option { + self.mint_scripts.clone() + } + + fn _set_mint_asset(&mut self, policy_id: &PolicyID, policy_script: &NativeScript, mint_assets: &MintAssets) { let mut mint = self.mint.as_ref().cloned().unwrap_or(Mint::new()); - mint.insert(policy_id, mint_assets); + let is_new_policy = mint.insert(&policy_id, mint_assets).is_none(); self.set_mint(&mint); + if is_new_policy { + // If policy has not been encountered before - insert the script into witnesses + let mut witness_scripts = self.mint_scripts.as_ref().cloned() + .unwrap_or(NativeScripts::new()); + witness_scripts.add(&policy_script.clone()); + self.set_mint_scripts(&witness_scripts); + } } - /// Add a mint entry to this builder using a PolicyID, AssetName, and Int object for amount + /// Add a mint entry to this builder using a PolicyID and MintAssets object /// It will be securely added to existing or new Mint in this builder - /// It will replace any previous existing amount same PolicyID and AssetName - pub fn add_mint_asset(&mut self, policy_id: &PolicyID, asset_name: &AssetName, amount: Int) { + /// It will replace any existing mint assets with the same PolicyID + pub fn set_mint_asset(&mut self, policy_script: &NativeScript, mint_assets: &MintAssets) { + let policy_id: PolicyID = policy_script.hash(ScriptHashNamespace::NativeScript); + self._set_mint_asset(&policy_id, policy_script, mint_assets); + } + + fn _add_mint_asset(&mut self, policy_id: &PolicyID, policy_script: &NativeScript, asset_name: &AssetName, amount: Int) { let mut asset = self.mint.as_ref() - .map(|m| { m.get(policy_id).as_ref().cloned() }) + .map(|m| { m.get(&policy_id).as_ref().cloned() }) .unwrap_or(None) .unwrap_or(MintAssets::new()); asset.insert(asset_name, amount); - self.set_mint_asset(policy_id, &asset); + self._set_mint_asset(&policy_id, policy_script, &asset); + } + + /// Add a mint entry to this builder using a PolicyID, AssetName, and Int object for amount + /// It will be securely added to existing or new Mint in this builder + /// It will replace any previous existing amount same PolicyID and AssetName + pub fn add_mint_asset(&mut self, policy_script: &NativeScript, asset_name: &AssetName, amount: Int) { + let policy_id: PolicyID = policy_script.hash(ScriptHashNamespace::NativeScript); + self._add_mint_asset(&policy_id, policy_script, asset_name, amount); } /// Add a mint entry together with an output to this builder @@ -669,7 +716,7 @@ impl TransactionBuilder { /// A new output will be added with the specified Address, the Coin value, and the minted asset pub fn add_mint_asset_and_output( &mut self, - policy_id: &PolicyID, + policy_script: &NativeScript, asset_name: &AssetName, amount: Int, address: &Address, @@ -678,9 +725,10 @@ impl TransactionBuilder { if !amount.is_positive() { return Err(JsError::from_str("Output value must be positive!")); } - self.add_mint_asset(policy_id, asset_name, amount.clone()); + let policy_id: PolicyID = policy_script.hash(ScriptHashNamespace::NativeScript); + self._add_mint_asset(&policy_id, policy_script, asset_name, amount.clone()); let multiasset = Mint::new_from_entry( - policy_id, + &policy_id, &MintAssets::new_from_entry(asset_name, amount.clone()) ).as_positive_multiasset(); self.add_output_coin_and_asset(address, output_coin, &multiasset) @@ -693,7 +741,7 @@ impl TransactionBuilder { /// The output will be set to contain the minimum required amount of Coin pub fn add_mint_asset_and_output_min_required_coin( &mut self, - policy_id: &PolicyID, + policy_script: &NativeScript, asset_name: &AssetName, amount: Int, address: &Address, @@ -701,9 +749,10 @@ impl TransactionBuilder { if !amount.is_positive() { return Err(JsError::from_str("Output value must be positive!")); } - self.add_mint_asset(policy_id, asset_name, amount.clone()); + let policy_id: PolicyID = policy_script.hash(ScriptHashNamespace::NativeScript); + self._add_mint_asset(&policy_id, policy_script, asset_name, amount.clone()); let multiasset = Mint::new_from_entry( - policy_id, + &policy_id, &MintAssets::new_from_entry(asset_name, amount.clone()) ).as_positive_multiasset(); self.add_output_asset_and_min_required_coin(address, &multiasset) @@ -726,6 +775,7 @@ impl TransactionBuilder { }, validity_start_interval: None, mint: None, + mint_scripts: None, inputs_auto_added: false, } } @@ -1049,7 +1099,7 @@ impl TransactionBuilder { /// Returns object the body of the new transaction /// Auxiliary data itself is not included - /// You can use `get_auxiliary_date` or `build_tx` + /// You can use `get_auxiliary_data` or `build_tx` pub fn build(&self) -> Result { let (body, full_tx_size) = self.build_and_size()?; if full_tx_size > self.config.max_tx_size as usize { @@ -1063,13 +1113,21 @@ impl TransactionBuilder { } } + fn get_witness_set(&self) -> TransactionWitnessSet { + let mut wit = TransactionWitnessSet::new(); + if let Some(scripts) = self.mint_scripts.as_ref() { + wit.set_native_scripts(scripts); + } + wit + } + /// Returns full Transaction object with the body and the auxiliary data - /// NOTE: witness_set is set to just empty set + /// NOTE: witness_set will contain all mint_scripts if any been added or set /// NOTE: is_valid set to true pub fn build_tx(&self) -> Result { Ok(Transaction { body: self.build()?, - witness_set: TransactionWitnessSet::new(), + witness_set: self.get_witness_set(), is_valid: true, auxiliary_data: self.auxiliary_data.clone(), }) @@ -1656,12 +1714,12 @@ mod tests { ) .to_address(); - let policy_id = PolicyID::from([0u8; 28]); + let (min_script, policy_id) = mint_script_and_policy(0); let name = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); let amount = to_bignum(1234); // Adding mint of the asset - which should work as an input - tx_builder.add_mint_asset(&policy_id, &name, Int::new(&amount)); + tx_builder.add_mint_asset(&min_script, &name, Int::new(&amount)); let mut ass = Assets::new(); ass.insert(&name, &amount); @@ -1739,14 +1797,14 @@ mod tests { ) .to_address(); - let policy_id = PolicyID::from([0u8; 28]); + let (min_script, policy_id) = mint_script_and_policy(0); let name = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); let amount_minted = to_bignum(1000); let amount_sent = to_bignum(500); // Adding mint of the asset - which should work as an input - tx_builder.add_mint_asset(&policy_id, &name, Int::new(&amount_minted)); + tx_builder.add_mint_asset(&min_script, &name, Int::new(&amount_minted)); let mut ass = Assets::new(); ass.insert(&name, &amount_sent); @@ -3135,72 +3193,104 @@ mod tests { assert_eq!(result_asset.get(&create_asset_name()).unwrap(), Int::new_i32(1234)); } + fn mint_script_and_policy(x: u8) -> (NativeScript, PolicyID) { + let mint_script = NativeScript::new_script_pubkey( + &ScriptPubkey::new(&fake_key_hash(x)) + ); + let policy_id = mint_script.hash(ScriptHashNamespace::NativeScript); + (mint_script, policy_id) + } + #[test] fn set_mint_asset_with_empty_mint() { let mut tx_builder = create_default_tx_builder(); - let policy_id = PolicyID::from([0u8; 28]); - tx_builder.set_mint_asset(&policy_id, &create_mint_asset()); + let (mint_script, policy_id) = mint_script_and_policy(0); + tx_builder.set_mint_asset(&mint_script, &create_mint_asset()); assert!(tx_builder.mint.is_some()); + assert!(tx_builder.mint_scripts.is_some()); let mint = tx_builder.mint.unwrap(); + let mint_scripts = tx_builder.mint_scripts.unwrap(); assert_eq!(mint.len(), 1); assert_mint_asset(&mint, &policy_id); + + assert_eq!(mint_scripts.len(), 1); + assert_eq!(mint_scripts.get(0), mint_script); } #[test] fn set_mint_asset_with_existing_mint() { let mut tx_builder = create_default_tx_builder(); - let policy_id1 = PolicyID::from([0u8; 28]); + let (_, policy_id1) = mint_script_and_policy(0); + let (mint_script2, policy_id2) = mint_script_and_policy(1); + tx_builder.set_mint(&create_mint_with_one_asset(&policy_id1)); - let policy_id2 = PolicyID::from([1u8; 28]); - tx_builder.set_mint_asset(&policy_id2, &create_mint_asset()); + tx_builder.set_mint_asset(&mint_script2, &create_mint_asset()); assert!(tx_builder.mint.is_some()); + assert!(tx_builder.mint_scripts.is_some()); let mint = tx_builder.mint.unwrap(); + let mint_scripts = tx_builder.mint_scripts.unwrap(); assert_eq!(mint.len(), 2); assert_mint_asset(&mint, &policy_id1); assert_mint_asset(&mint, &policy_id2); + + // Only second script is present in the scripts + assert_eq!(mint_scripts.len(), 1); + assert_eq!(mint_scripts.get(0), mint_script2); } #[test] fn add_mint_asset_with_empty_mint() { let mut tx_builder = create_default_tx_builder(); - let policy_id = PolicyID::from([0u8; 28]); - tx_builder.add_mint_asset(&policy_id, &create_asset_name(), Int::new_i32(1234)); + let (mint_script, policy_id) = mint_script_and_policy(0); + + tx_builder.add_mint_asset(&mint_script, &create_asset_name(), Int::new_i32(1234)); assert!(tx_builder.mint.is_some()); + assert!(tx_builder.mint_scripts.is_some()); let mint = tx_builder.mint.unwrap(); + let mint_scripts = tx_builder.mint_scripts.unwrap(); assert_eq!(mint.len(), 1); assert_mint_asset(&mint, &policy_id); + + assert_eq!(mint_scripts.len(), 1); + assert_eq!(mint_scripts.get(0), mint_script); } #[test] fn add_mint_asset_with_existing_mint() { let mut tx_builder = create_default_tx_builder(); - let policy_id1 = PolicyID::from([0u8; 28]); - tx_builder.set_mint(&create_mint_with_one_asset(&policy_id1)); + let (_, policy_id1) = mint_script_and_policy(0); + let (mint_script2, policy_id2) = mint_script_and_policy(1); - let policy_id2 = PolicyID::from([1u8; 28]); - tx_builder.add_mint_asset(&policy_id2, &create_asset_name(), Int::new_i32(1234)); + tx_builder.set_mint(&create_mint_with_one_asset(&policy_id1)); + tx_builder.add_mint_asset(&mint_script2, &create_asset_name(), Int::new_i32(1234)); assert!(tx_builder.mint.is_some()); + assert!(tx_builder.mint_scripts.is_some()); let mint = tx_builder.mint.unwrap(); + let mint_scripts = tx_builder.mint_scripts.unwrap(); assert_eq!(mint.len(), 2); assert_mint_asset(&mint, &policy_id1); assert_mint_asset(&mint, &policy_id2); + + // Only second script is present in the scripts + assert_eq!(mint_scripts.len(), 1); + assert_eq!(mint_scripts.get(0), mint_script2); } #[test] @@ -3280,8 +3370,9 @@ mod tests { fn add_mint_asset_and_output() { let mut tx_builder = create_default_tx_builder(); - let policy_id0 = PolicyID::from([0u8; 28]); - let policy_id1 = PolicyID::from([1u8; 28]); + let (mint_script0, policy_id0) = mint_script_and_policy(0); + let (mint_script1, policy_id1) = mint_script_and_policy(1); + let name = create_asset_name(); let amount = Int::new_i32(1234); @@ -3289,10 +3380,10 @@ mod tests { let coin = to_bignum(100); // Add unrelated mint first to check it is NOT added to output later - tx_builder.add_mint_asset(&policy_id0, &name, amount.clone()); + tx_builder.add_mint_asset(&mint_script0, &name, amount.clone()); tx_builder.add_mint_asset_and_output( - &policy_id1, + &mint_script1, &name, amount.clone(), &address, @@ -3300,14 +3391,20 @@ mod tests { ).unwrap(); assert!(tx_builder.mint.is_some()); + assert!(tx_builder.mint_scripts.is_some()); let mint = tx_builder.mint.as_ref().unwrap(); + let mint_scripts = tx_builder.mint_scripts.as_ref().unwrap(); // Mint contains two entries assert_eq!(mint.len(), 2); assert_mint_asset(mint, &policy_id0); assert_mint_asset(mint, &policy_id1); + assert_eq!(mint_scripts.len(), 2); + assert_eq!(mint_scripts.get(0), mint_script0); + assert_eq!(mint_scripts.get(1), mint_script1); + // One new output is created assert_eq!(tx_builder.outputs.len(), 1); let out = tx_builder.outputs.get(0); @@ -3331,32 +3428,40 @@ mod tests { fn add_mint_asset_and_min_required_coin() { let mut tx_builder = create_reallistic_tx_builder(); - let policy_id0 = PolicyID::from([0u8; 28]); - let policy_id1 = PolicyID::from([1u8; 28]); + let (mint_script0, policy_id0) = mint_script_and_policy(0); + let (mint_script1, policy_id1) = mint_script_and_policy(1); + let name = create_asset_name(); let amount = Int::new_i32(1234); let address = byron_address(); // Add unrelated mint first to check it is NOT added to output later - tx_builder.add_mint_asset(&policy_id0, &name, amount.clone()); + tx_builder.add_mint_asset(&mint_script0, &name, amount.clone()); tx_builder.add_mint_asset_and_output_min_required_coin( - &policy_id1, + &mint_script1, &name, amount.clone(), &address, ).unwrap(); assert!(tx_builder.mint.is_some()); + assert!(tx_builder.mint_scripts.is_some()); let mint = tx_builder.mint.as_ref().unwrap(); + let mint_scripts = tx_builder.mint_scripts.as_ref().unwrap(); // Mint contains two entries assert_eq!(mint.len(), 2); assert_mint_asset(mint, &policy_id0); assert_mint_asset(mint, &policy_id1); + + assert_eq!(mint_scripts.len(), 2); + assert_eq!(mint_scripts.get(0), mint_script0); + assert_eq!(mint_scripts.get(1), mint_script1); + // One new output is created assert_eq!(tx_builder.outputs.len(), 1); let out = tx_builder.outputs.get(0); @@ -3377,66 +3482,66 @@ mod tests { } - #[test] - fn add_mint_includes_witnesses_into_fee_estimation() { - let mut tx_builder = create_reallistic_tx_builder(); - - let original_tx_fee = tx_builder.min_fee().unwrap(); - assert_eq!(original_tx_fee, to_bignum(156217)); - - let policy_id1 = PolicyID::from([0u8; 28]); - let policy_id2 = PolicyID::from([1u8; 28]); - let policy_id3 = PolicyID::from([2u8; 28]); - let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); - let name2 = AssetName::new(vec![1u8, 1, 2, 3]).unwrap(); - let name3 = AssetName::new(vec![2u8, 1, 2, 3]).unwrap(); - let name4 = AssetName::new(vec![3u8, 1, 2, 3]).unwrap(); - let amount = Int::new_i32(1234); - - let mut mint = Mint::new(); - mint.insert( - &policy_id1, - &MintAssets::new_from_entry(&name1, amount.clone()), - ); - mint.insert( - &policy_id2, - &MintAssets::new_from_entry(&name2, amount.clone()), - ); - // Third policy with two asset names - let mut mass = MintAssets::new_from_entry(&name3, amount.clone()); - mass.insert(&name4, amount.clone()); - mint.insert(&policy_id3, &mass); - - let mint_len = mint.to_bytes().len(); - let fee_coefficient = tx_builder.config.fee_algo.coefficient(); - - let raw_mint_fee = fee_coefficient - .checked_mul(&to_bignum(mint_len as u64)) - .unwrap(); - - assert_eq!(raw_mint_fee, to_bignum(5544)); - - tx_builder.set_mint(&mint); - - let new_tx_fee = tx_builder.min_fee().unwrap(); - - let fee_diff_from_adding_mint = - new_tx_fee.checked_sub(&original_tx_fee) - .unwrap(); - - let witness_fee_increase = - fee_diff_from_adding_mint.checked_sub(&raw_mint_fee) - .unwrap(); - - assert_eq!(witness_fee_increase, to_bignum(4356)); - - let fee_increase_bytes = from_bignum(&witness_fee_increase) - .checked_div(from_bignum(&fee_coefficient)) - .unwrap(); - - // Three policy IDs of 32 bytes each + 3 byte overhead - assert_eq!(fee_increase_bytes, 99); - } + // #[test] + // fn add_mint_includes_witnesses_into_fee_estimation() { + // let mut tx_builder = create_reallistic_tx_builder(); + // + // let original_tx_fee = tx_builder.min_fee().unwrap(); + // assert_eq!(original_tx_fee, to_bignum(156217)); + // + // let policy_id1 = PolicyID::from([0u8; 28]); + // let policy_id2 = PolicyID::from([1u8; 28]); + // let policy_id3 = PolicyID::from([2u8; 28]); + // let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); + // let name2 = AssetName::new(vec![1u8, 1, 2, 3]).unwrap(); + // let name3 = AssetName::new(vec![2u8, 1, 2, 3]).unwrap(); + // let name4 = AssetName::new(vec![3u8, 1, 2, 3]).unwrap(); + // let amount = Int::new_i32(1234); + // + // let mut mint = Mint::new(); + // mint.insert( + // &policy_id1, + // &MintAssets::new_from_entry(&name1, amount.clone()), + // ); + // mint.insert( + // &policy_id2, + // &MintAssets::new_from_entry(&name2, amount.clone()), + // ); + // // Third policy with two asset names + // let mut mass = MintAssets::new_from_entry(&name3, amount.clone()); + // mass.insert(&name4, amount.clone()); + // mint.insert(&policy_id3, &mass); + // + // let mint_len = mint.to_bytes().len(); + // let fee_coefficient = tx_builder.config.fee_algo.coefficient(); + // + // let raw_mint_fee = fee_coefficient + // .checked_mul(&to_bignum(mint_len as u64)) + // .unwrap(); + // + // assert_eq!(raw_mint_fee, to_bignum(5544)); + // + // tx_builder.set_mint(&mint); + // + // let new_tx_fee = tx_builder.min_fee().unwrap(); + // + // let fee_diff_from_adding_mint = + // new_tx_fee.checked_sub(&original_tx_fee) + // .unwrap(); + // + // let witness_fee_increase = + // fee_diff_from_adding_mint.checked_sub(&raw_mint_fee) + // .unwrap(); + // + // assert_eq!(witness_fee_increase, to_bignum(4356)); + // + // let fee_increase_bytes = from_bignum(&witness_fee_increase) + // .checked_div(from_bignum(&fee_coefficient)) + // .unwrap(); + // + // // Three policy IDs of 32 bytes each + 3 byte overhead + // assert_eq!(fee_increase_bytes, 99); + // } #[test] fn total_input_with_mint_and_burn() { @@ -3449,8 +3554,9 @@ mod tests { .derive(0) .to_public(); - let policy_id1 = &PolicyID::from([0u8; 28]); - let policy_id2 = &PolicyID::from([1u8; 28]); + let (mint_script1, policy_id1) = mint_script_and_policy(0); + let (mint_script2, policy_id2) = mint_script_and_policy(1); + let name = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); let ma_input1 = 100; @@ -3461,12 +3567,12 @@ mod tests { .iter() .map(|input| { let mut multiasset = MultiAsset::new(); - multiasset.insert(policy_id1, &{ + multiasset.insert(&policy_id1, &{ let mut assets = Assets::new(); assets.insert(&name, &to_bignum(*input)); assets }); - multiasset.insert(policy_id2, &{ + multiasset.insert(&policy_id2, &{ let mut assets = Assets::new(); assets.insert(&name, &to_bignum(*input)); assets @@ -3493,18 +3599,18 @@ mod tests { assert_eq!(total_input_before_mint.coin, to_bignum(300)); let ma1 = total_input_before_mint.multiasset.unwrap(); - assert_eq!(ma1.get(policy_id1).unwrap().get(&name).unwrap(), to_bignum(360)); - assert_eq!(ma1.get(policy_id2).unwrap().get(&name).unwrap(), to_bignum(360)); + assert_eq!(ma1.get(&policy_id1).unwrap().get(&name).unwrap(), to_bignum(360)); + assert_eq!(ma1.get(&policy_id2).unwrap().get(&name).unwrap(), to_bignum(360)); - tx_builder.add_mint_asset(policy_id1, &name, Int::new_i32(40)); - tx_builder.add_mint_asset(policy_id2, &name, Int::new_i32(-40)); + tx_builder.add_mint_asset(&mint_script1, &name, Int::new_i32(40)); + tx_builder.add_mint_asset(&mint_script2, &name, Int::new_i32(-40)); let total_input_after_mint = tx_builder.get_total_input().unwrap(); assert_eq!(total_input_after_mint.coin, to_bignum(300)); let ma2 = total_input_after_mint.multiasset.unwrap(); - assert_eq!(ma2.get(policy_id1).unwrap().get(&name).unwrap(), to_bignum(400)); - assert_eq!(ma2.get(policy_id2).unwrap().get(&name).unwrap(), to_bignum(320)); + assert_eq!(ma2.get(&policy_id1).unwrap().get(&name).unwrap(), to_bignum(400)); + assert_eq!(ma2.get(&policy_id2).unwrap().get(&name).unwrap(), to_bignum(320)); } } From edbab5f1780cddd2ce9ce809dd9873cba728893d Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Sun, 28 Nov 2021 14:26:34 +0300 Subject: [PATCH 2/7] Fixed test for fee estimation --- rust/src/tx_builder.rs | 112 +++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 60 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 42c48697..3bcb2c75 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -3481,67 +3481,59 @@ mod tests { assert_eq!(asset.get(&name).unwrap(), to_bignum(1234)); } + #[test] + fn add_mint_includes_witnesses_into_fee_estimation() { + let mut tx_builder = create_reallistic_tx_builder(); + + let original_tx_fee = tx_builder.min_fee().unwrap(); + assert_eq!(original_tx_fee, to_bignum(156217)); + + let (mint_script1, policy_id1) = mint_script_and_policy(0); + let (mint_script2, policy_id2) = mint_script_and_policy(1); + let (mint_script3, policy_id3) = mint_script_and_policy(2); + + let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); + let name2 = AssetName::new(vec![1u8, 1, 2, 3]).unwrap(); + let name3 = AssetName::new(vec![2u8, 1, 2, 3]).unwrap(); + let name4 = AssetName::new(vec![3u8, 1, 2, 3]).unwrap(); + let amount = Int::new_i32(1234); + + tx_builder.add_mint_asset(&mint_script1, &name1, amount.clone()); + tx_builder.add_mint_asset(&mint_script2, &name2, amount.clone()); + tx_builder.add_mint_asset(&mint_script3, &name3, amount.clone()); + tx_builder.add_mint_asset(&mint_script3, &name4, amount.clone()); + + let mint = tx_builder.get_mint().unwrap(); + + let mint_len = mint.to_bytes().len(); + + let fee_coefficient = tx_builder.config.fee_algo.coefficient(); - // #[test] - // fn add_mint_includes_witnesses_into_fee_estimation() { - // let mut tx_builder = create_reallistic_tx_builder(); - // - // let original_tx_fee = tx_builder.min_fee().unwrap(); - // assert_eq!(original_tx_fee, to_bignum(156217)); - // - // let policy_id1 = PolicyID::from([0u8; 28]); - // let policy_id2 = PolicyID::from([1u8; 28]); - // let policy_id3 = PolicyID::from([2u8; 28]); - // let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); - // let name2 = AssetName::new(vec![1u8, 1, 2, 3]).unwrap(); - // let name3 = AssetName::new(vec![2u8, 1, 2, 3]).unwrap(); - // let name4 = AssetName::new(vec![3u8, 1, 2, 3]).unwrap(); - // let amount = Int::new_i32(1234); - // - // let mut mint = Mint::new(); - // mint.insert( - // &policy_id1, - // &MintAssets::new_from_entry(&name1, amount.clone()), - // ); - // mint.insert( - // &policy_id2, - // &MintAssets::new_from_entry(&name2, amount.clone()), - // ); - // // Third policy with two asset names - // let mut mass = MintAssets::new_from_entry(&name3, amount.clone()); - // mass.insert(&name4, amount.clone()); - // mint.insert(&policy_id3, &mass); - // - // let mint_len = mint.to_bytes().len(); - // let fee_coefficient = tx_builder.config.fee_algo.coefficient(); - // - // let raw_mint_fee = fee_coefficient - // .checked_mul(&to_bignum(mint_len as u64)) - // .unwrap(); - // - // assert_eq!(raw_mint_fee, to_bignum(5544)); - // - // tx_builder.set_mint(&mint); - // - // let new_tx_fee = tx_builder.min_fee().unwrap(); - // - // let fee_diff_from_adding_mint = - // new_tx_fee.checked_sub(&original_tx_fee) - // .unwrap(); - // - // let witness_fee_increase = - // fee_diff_from_adding_mint.checked_sub(&raw_mint_fee) - // .unwrap(); - // - // assert_eq!(witness_fee_increase, to_bignum(4356)); - // - // let fee_increase_bytes = from_bignum(&witness_fee_increase) - // .checked_div(from_bignum(&fee_coefficient)) - // .unwrap(); - // - // // Three policy IDs of 32 bytes each + 3 byte overhead - // assert_eq!(fee_increase_bytes, 99); - // } + let raw_mint_fee = fee_coefficient + .checked_mul(&to_bignum(mint_len as u64)) + .unwrap(); + + assert_eq!(raw_mint_fee, to_bignum(5544)); + + let new_tx_fee = tx_builder.min_fee().unwrap(); + + let fee_diff_from_adding_mint = + new_tx_fee.checked_sub(&original_tx_fee) + .unwrap(); + + let witness_fee_increase = + fee_diff_from_adding_mint.checked_sub(&raw_mint_fee) + .unwrap(); + + assert_eq!(witness_fee_increase, to_bignum(4356)); + + let fee_increase_bytes = from_bignum(&witness_fee_increase) + .checked_div(from_bignum(&fee_coefficient)) + .unwrap(); + + // Three pubkey scripts of 32 bytes each + 3 byte overhead + assert_eq!(fee_increase_bytes, 99); + } #[test] fn total_input_with_mint_and_burn() { From b6de84d85572c3604e73e3f409658c5f9f18d50a Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Mon, 29 Nov 2021 11:49:02 +0300 Subject: [PATCH 3/7] Added test that fee-estimation fails when mint scripts are not provided --- rust/src/tx_builder.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 3bcb2c75..a640da95 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -3535,6 +3535,47 @@ mod tests { assert_eq!(fee_increase_bytes, 99); } + #[test] + fn fee_estimation_fails_on_missing_mint_scripts() { + let mut tx_builder = create_reallistic_tx_builder(); + + // No error estimating fee without mint + assert!(tx_builder.min_fee().is_ok()); + + let (mint_script1, policy_id1) = mint_script_and_policy(0); + let (mint_script2, policy_id2) = mint_script_and_policy(1); + + let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); + let amount = Int::new_i32(1234); + + let mut mint = Mint::new(); + mint.insert( + &policy_id1, + &MintAssets::new_from_entry(&name1, amount.clone()), + ); + + tx_builder.set_mint(&mint); + + // Mint exists but no witness scripts at all present + let est1 = tx_builder.min_fee(); + assert!(est1.is_err()); + assert!(est1.err().unwrap().to_string().contains("witness scripts are not provided")); + + tx_builder.add_mint_asset(&mint_script2, &name1, amount.clone()); + + // Now two different policies are minted but only one witness script is present + let est2 = tx_builder.min_fee(); + assert!(est2.is_err()); + assert!(est2.err().unwrap().to_string().contains(&format!("{:?}", hex::encode(policy_id1.to_bytes())))); + + let mut scripts = tx_builder.get_mint_scripts().unwrap(); + scripts.add(&mint_script1); + tx_builder.set_mint_scripts(&scripts); + + let est3 = tx_builder.min_fee(); + assert!(est3.is_ok()) + } + #[test] fn total_input_with_mint_and_burn() { let mut tx_builder = create_tx_builder_with_fee(&create_linear_fee(0, 1)); From 276334a4ca0dcf45766d8ebced8c6bc06c2d6bcd Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Mon, 29 Nov 2021 14:52:21 +0300 Subject: [PATCH 4/7] Fixed fee-estimation to add vkey witnesses for each minting policy and updated a test for it --- rust/src/tx_builder.rs | 101 +++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 28 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index a640da95..fc155ea4 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -57,17 +57,18 @@ fn fake_key_hash(x: u8) -> Ed25519KeyHash { // for use in calculating the size of the final Transaction fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Result { let fake_key_root = fake_private_key(); + let raw_key = fake_key_root.to_raw_key(); + let fake_vkey_witness = Vkeywitness::new( + &Vkey::new(&raw_key.to_public()), + &raw_key.sign([1u8; 100].as_ref()) + ); // recall: this includes keys for input, certs and withdrawals let vkeys = match tx_builder.input_types.vkeys.len() { 0 => None, x => { let mut result = Vkeywitnesses::new(); - let raw_key = fake_key_root.to_raw_key(); for _i in 0..x { - result.add(&Vkeywitness::new( - &Vkey::new(&raw_key.to_public()), - &raw_key.sign([1u8; 100].as_ref()) - )); + result.add(&fake_vkey_witness.clone()); } Some(result) }, @@ -94,18 +95,24 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul Some(result) }, }; - let full_script_keys = match &tx_builder.mint_scripts { - None => script_keys, + let (full_vkeys, full_script_keys) = match &tx_builder.mint_scripts { + None => (vkeys, script_keys), Some(witness_scripts) => { + let mut vw = vkeys + .map(|x| { x.clone() }) + .unwrap_or(Vkeywitnesses::new()); let mut ns = script_keys - .map(|sk| { sk.clone() }) + .map(|x| { x.clone() }) .unwrap_or(NativeScripts::new()); - witness_scripts.0.iter().for_each(|s| { ns.add(s); }); - Some(ns) + witness_scripts.0.iter().for_each(|s| { + vw.add(&fake_vkey_witness.clone()); + ns.add(s); + }); + (Some(vw), Some(ns)) } }; let witness_set = TransactionWitnessSet { - vkeys: vkeys, + vkeys: full_vkeys, native_scripts: full_script_keys, bootstraps: bootstrap_keys, // TODO: plutus support? @@ -3193,12 +3200,18 @@ mod tests { assert_eq!(result_asset.get(&create_asset_name()).unwrap(), Int::new_i32(1234)); } - fn mint_script_and_policy(x: u8) -> (NativeScript, PolicyID) { + fn mint_script_and_policy_and_hash(x: u8) -> (NativeScript, PolicyID, Ed25519KeyHash) { + let hash = fake_key_hash(x); let mint_script = NativeScript::new_script_pubkey( - &ScriptPubkey::new(&fake_key_hash(x)) + &ScriptPubkey::new(&hash) ); let policy_id = mint_script.hash(ScriptHashNamespace::NativeScript); - (mint_script, policy_id) + (mint_script, policy_id, hash) + } + + fn mint_script_and_policy(x: u8) -> (NativeScript, PolicyID) { + let (m, p, _) = mint_script_and_policy_and_hash(x); + (m, p) } #[test] @@ -3483,14 +3496,14 @@ mod tests { #[test] fn add_mint_includes_witnesses_into_fee_estimation() { + let mut tx_builder = create_reallistic_tx_builder(); - let original_tx_fee = tx_builder.min_fee().unwrap(); - assert_eq!(original_tx_fee, to_bignum(156217)); + let hash0 = fake_key_hash(0); - let (mint_script1, policy_id1) = mint_script_and_policy(0); - let (mint_script2, policy_id2) = mint_script_and_policy(1); - let (mint_script3, policy_id3) = mint_script_and_policy(2); + let (mint_script1, policy_id1, hash1) = mint_script_and_policy_and_hash(1); + let (mint_script2, policy_id2, hash2) = mint_script_and_policy_and_hash(2); + let (mint_script3, policy_id3, hash3) = mint_script_and_policy_and_hash(3); let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); let name2 = AssetName::new(vec![1u8, 1, 2, 3]).unwrap(); @@ -3498,41 +3511,73 @@ mod tests { let name4 = AssetName::new(vec![3u8, 1, 2, 3]).unwrap(); let amount = Int::new_i32(1234); + // One input from unrelated address + tx_builder.add_key_input( + &hash0, + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(10_000_000)), + ); + + // One input from same address as mint + tx_builder.add_key_input( + &hash1, + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(10_000_000)), + ); + + // Original tx fee now assumes two VKey signatures for two inputs + let original_tx_fee = tx_builder.min_fee().unwrap(); + assert_eq!(original_tx_fee, to_bignum(168361)); + + // Add minting four assets from three different policies tx_builder.add_mint_asset(&mint_script1, &name1, amount.clone()); tx_builder.add_mint_asset(&mint_script2, &name2, amount.clone()); tx_builder.add_mint_asset(&mint_script3, &name3, amount.clone()); tx_builder.add_mint_asset(&mint_script3, &name4, amount.clone()); let mint = tx_builder.get_mint().unwrap(); - let mint_len = mint.to_bytes().len(); + let mint_scripts = tx_builder.get_witness_set(); + let mint_scripts_len = mint_scripts.to_bytes().len() + - TransactionWitnessSet::new().to_bytes().len(); + let fee_coefficient = tx_builder.config.fee_algo.coefficient(); let raw_mint_fee = fee_coefficient .checked_mul(&to_bignum(mint_len as u64)) .unwrap(); + let raw_mint_script_fee = fee_coefficient + .checked_mul(&to_bignum(mint_scripts_len as u64)) + .unwrap(); + assert_eq!(raw_mint_fee, to_bignum(5544)); + assert_eq!(raw_mint_script_fee, to_bignum(4312)); let new_tx_fee = tx_builder.min_fee().unwrap(); - let fee_diff_from_adding_mint = - new_tx_fee.checked_sub(&original_tx_fee) + let fee_diff_from_adding_mint = new_tx_fee + .checked_sub(&original_tx_fee) .unwrap(); - let witness_fee_increase = - fee_diff_from_adding_mint.checked_sub(&raw_mint_fee) - .unwrap(); + let witness_fee_increase = fee_diff_from_adding_mint + .checked_sub(&raw_mint_fee).unwrap() + .checked_sub(&raw_mint_script_fee).unwrap(); - assert_eq!(witness_fee_increase, to_bignum(4356)); + assert_eq!(witness_fee_increase, to_bignum(13376)); let fee_increase_bytes = from_bignum(&witness_fee_increase) .checked_div(from_bignum(&fee_coefficient)) .unwrap(); - // Three pubkey scripts of 32 bytes each + 3 byte overhead - assert_eq!(fee_increase_bytes, 99); + // Three vkey witnesses 96 bytes each (32 byte pubkey + 64 byte signature) + // Plus 16 bytes overhead for CBOR wrappers + // This is happening because we have three different minting policies + // and we are assuming each one needs a separate signature even tho + // they might refer to the same address with another policy or one of the inputs + // + assert_eq!(fee_increase_bytes, 304); } #[test] From 91bbbb81df306564a576f9528fe672666af7aeaa Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 30 Nov 2021 11:57:46 +0300 Subject: [PATCH 5/7] Flowgen update. Warnings fixed. Version bump to beta7 --- package-lock.json | 2 +- package.json | 2 +- rust/Cargo.lock | 2 +- rust/Cargo.toml | 2 +- rust/pkg/cardano_serialization_lib.js.flow | 43 +++++++++++++++++----- rust/src/tx_builder.rs | 20 +++++----- 6 files changed, 47 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index c130067d..d25ec6bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "9.1.2", + "version": "10.0.0-beta.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 16b5cd5f..823a0e86 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "9.1.2", + "version": "10.0.0-beta.7", "description": "(De)serialization functions for the Cardano blockchain along with related utility functions", "scripts": { "rust:build-nodejs": "(rimraf ./rust/pkg && cd rust; wasm-pack build --target=nodejs; wasm-pack pack) && npm run js:flowgen", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 17de1a4b..520ae10d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -50,7 +50,7 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cardano-serialization-lib" -version = "9.1.2" +version = "10.0.0-beta.7" dependencies = [ "bech32", "cbor_event", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 9db068ba..89aee6f7 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cardano-serialization-lib" -version = "9.1.2" +version = "10.0.0-beta.7" edition = "2018" authors = ["EMURGO"] license = "MIT" diff --git a/rust/pkg/cardano_serialization_lib.js.flow b/rust/pkg/cardano_serialization_lib.js.flow index 2a66c8ec..b7652eab 100644 --- a/rust/pkg/cardano_serialization_lib.js.flow +++ b/rust/pkg/cardano_serialization_lib.js.flow @@ -5179,29 +5179,52 @@ declare export class TransactionBuilder { /** * Set explicit Mint object to this builder * it will replace any previously existing mint + * NOTE! If you use `set_mint` manually - you must use `set_mint_scripts` + * to provide matching policy scripts or min-fee calculation will be rejected! * @param {Mint} mint */ set_mint(mint: Mint): void; + /** + * Returns a copy of the current mint state in the builder + * @returns {Mint | void} + */ + get_mint(): Mint | void; + + /** + * Set explicit witness set to this builder + * It will replace any previously existing witnesses + * NOTE! Use carefully! If you are using `set_mint` - then you must be using + * this setter as well to be able to calculate fee automatically! + * @param {NativeScripts} mint_scripts + */ + set_mint_scripts(mint_scripts: NativeScripts): void; + + /** + * Returns a copy of the current mint witness scripts in the builder + * @returns {NativeScripts | void} + */ + get_mint_scripts(): NativeScripts | void; + /** * Add a mint entry to this builder using a PolicyID and MintAssets object * It will be securely added to existing or new Mint in this builder * It will replace any existing mint assets with the same PolicyID - * @param {ScriptHash} policy_id + * @param {NativeScript} policy_script * @param {MintAssets} mint_assets */ - set_mint_asset(policy_id: ScriptHash, mint_assets: MintAssets): void; + set_mint_asset(policy_script: NativeScript, mint_assets: MintAssets): void; /** * Add a mint entry to this builder using a PolicyID, AssetName, and Int object for amount * It will be securely added to existing or new Mint in this builder * It will replace any previous existing amount same PolicyID and AssetName - * @param {ScriptHash} policy_id + * @param {NativeScript} policy_script * @param {AssetName} asset_name * @param {Int} amount */ add_mint_asset( - policy_id: ScriptHash, + policy_script: NativeScript, asset_name: AssetName, amount: Int ): void; @@ -5211,14 +5234,14 @@ declare export class TransactionBuilder { * Using a PolicyID, AssetName, Int for amount, Address, and Coin (BigNum) objects * The asset will be securely added to existing or new Mint in this builder * A new output will be added with the specified Address, the Coin value, and the minted asset - * @param {ScriptHash} policy_id + * @param {NativeScript} policy_script * @param {AssetName} asset_name * @param {Int} amount * @param {Address} address * @param {BigNum} output_coin */ add_mint_asset_and_output( - policy_id: ScriptHash, + policy_script: NativeScript, asset_name: AssetName, amount: Int, address: Address, @@ -5231,13 +5254,13 @@ declare export class TransactionBuilder { * The asset will be securely added to existing or new Mint in this builder * A new output will be added with the specified Address and the minted asset * The output will be set to contain the minimum required amount of Coin - * @param {ScriptHash} policy_id + * @param {NativeScript} policy_script * @param {AssetName} asset_name * @param {Int} amount * @param {Address} address */ add_mint_asset_and_output_min_required_coin( - policy_id: ScriptHash, + policy_script: NativeScript, asset_name: AssetName, amount: Int, address: Address @@ -5300,14 +5323,14 @@ declare export class TransactionBuilder { /** * Returns object the body of the new transaction * Auxiliary data itself is not included - * You can use `get_auxiliary_date` or `build_tx` + * You can use `get_auxiliary_data` or `build_tx` * @returns {TransactionBody} */ build(): TransactionBody; /** * Returns full Transaction object with the body and the auxiliary data - * NOTE: witness_set is set to just empty set + * NOTE: witness_set will contain all mint_scripts if any been added or set * NOTE: is_valid set to true * @returns {Transaction} */ diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index fc155ea4..986071cf 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -46,12 +46,6 @@ fn fake_private_key() -> Bip32PrivateKey { ).unwrap() } -fn fake_key_hash(x: u8) -> Ed25519KeyHash { - Ed25519KeyHash::from_bytes( - vec![x, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100] - ).unwrap() -} - // tx_body must be the result of building from tx_builder // constructs the rest of the Transaction using fake witness data of the correct length // for use in calculating the size of the final Transaction @@ -1170,6 +1164,12 @@ mod tests { Bip32PrivateKey::from_bip39_entropy(&entropy, &[]) } + fn fake_key_hash(x: u8) -> Ed25519KeyHash { + Ed25519KeyHash::from_bytes( + vec![x, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100] + ).unwrap() + } + fn harden(index: u32) -> u32 { index | 0x80_00_00_00 } @@ -3501,9 +3501,9 @@ mod tests { let hash0 = fake_key_hash(0); - let (mint_script1, policy_id1, hash1) = mint_script_and_policy_and_hash(1); - let (mint_script2, policy_id2, hash2) = mint_script_and_policy_and_hash(2); - let (mint_script3, policy_id3, hash3) = mint_script_and_policy_and_hash(3); + let (mint_script1, _, hash1) = mint_script_and_policy_and_hash(1); + let (mint_script2, _, _) = mint_script_and_policy_and_hash(2); + let (mint_script3, _, _) = mint_script_and_policy_and_hash(3); let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); let name2 = AssetName::new(vec![1u8, 1, 2, 3]).unwrap(); @@ -3588,7 +3588,7 @@ mod tests { assert!(tx_builder.min_fee().is_ok()); let (mint_script1, policy_id1) = mint_script_and_policy(0); - let (mint_script2, policy_id2) = mint_script_and_policy(1); + let (mint_script2, _) = mint_script_and_policy(1); let name1 = AssetName::new(vec![0u8, 1, 2, 3]).unwrap(); let amount = Int::new_i32(1234); From 94460c2b8510883561b2539a428333fea9d64c29 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 1 Dec 2021 23:29:49 +0300 Subject: [PATCH 6/7] Fixed unique vkey signatures for minting. Version bump to beta8 --- package-lock.json | 2 +- package.json | 2 +- rust/Cargo.lock | 2 +- rust/Cargo.toml | 2 +- rust/src/lib.rs | 103 +++++++++++++++++++++++++++++++++++++++++ rust/src/tx_builder.rs | 48 ++++++++++--------- 6 files changed, 134 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index d25ec6bd..11d70945 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.0.0-beta.7", + "version": "10.0.0-beta.8", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 823a0e86..4c1dd14a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.0.0-beta.7", + "version": "10.0.0-beta.8", "description": "(De)serialization functions for the Cardano blockchain along with related utility functions", "scripts": { "rust:build-nodejs": "(rimraf ./rust/pkg && cd rust; wasm-pack build --target=nodejs; wasm-pack pack) && npm run js:flowgen", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 520ae10d..ab208d1f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -50,7 +50,7 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cardano-serialization-lib" -version = "10.0.0-beta.7" +version = "10.0.0-beta.8" dependencies = [ "bech32", "cbor_event", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 89aee6f7..eaed2c5e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cardano-serialization-lib" -version = "10.0.0-beta.7" +version = "10.0.0-beta.8" edition = "2018" authors = ["EMURGO"] license = "MIT" diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 600d91a4..248f355a 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -58,6 +58,7 @@ use plutus::*; use metadata::*; use utils::*; use std::cmp::Ordering; +use std::collections::BTreeSet; type DeltaCoin = Int; @@ -2752,6 +2753,35 @@ impl NetworkId { } } +pub fn get_all_pubkeys_from_script(script: &NativeScript) -> BTreeSet { + match &script.0 { + NativeScriptEnum::ScriptPubkey(spk) => { + let mut set = BTreeSet::new(); + set.insert(spk.addr_keyhash()); + set + }, + NativeScriptEnum::ScriptAll(all) => { + get_all_pubkeys_from_scripts(&all.native_scripts) + }, + NativeScriptEnum::ScriptAny(any) => { + get_all_pubkeys_from_scripts(&any.native_scripts) + }, + NativeScriptEnum::ScriptNOfK(ofk) => { + get_all_pubkeys_from_scripts(&ofk.native_scripts) + }, + _ => BTreeSet::new(), + } +} + +pub fn get_all_pubkeys_from_scripts(scripts: &NativeScripts) -> BTreeSet { + scripts.0.iter().fold(BTreeSet::new(), |mut set, s| { + get_all_pubkeys_from_script(s).iter().for_each(|pk| { + set.insert(pk.clone()); + }); + set + }) +} + #[cfg(test)] mod tests { use super::*; @@ -2933,4 +2963,77 @@ mod tests { assert_eq!(p_ass.get(&name1).unwrap(), amount1); assert_eq!(n_ass.get(&name1).unwrap(), amount1); } + + fn keyhash(x: u8) -> Ed25519KeyHash { + Ed25519KeyHash::from_bytes(vec![x, 180, 186, 93, 223, 42, 243, 7, 81, 98, 86, 125, 97, 69, 110, 52, 130, 243, 244, 98, 246, 13, 33, 212, 128, 168, 136, 40]).unwrap() + } + + fn pkscript(pk: &Ed25519KeyHash) -> NativeScript { + NativeScript::new_script_pubkey(&ScriptPubkey::new(pk)) + } + + fn scripts_vec(scripts: Vec<&NativeScript>) -> NativeScripts { + NativeScripts(scripts.iter().map(|s| { (*s).clone() }).collect()) + } + + #[test] + fn native_scripts_get_pubkeys() { + let keyhash1 = keyhash(1); + let keyhash2 = keyhash(2); + let keyhash3 = keyhash(3); + + let pks1 = get_all_pubkeys_from_script(&pkscript(&keyhash1)); + assert_eq!(pks1.len(), 1); + assert!(pks1.contains(&keyhash1)); + + let pks2 = get_all_pubkeys_from_script( + &NativeScript::new_timelock_start( + &TimelockStart::new(123), + ), + ); + assert_eq!(pks2.len(), 0); + + let pks3 = get_all_pubkeys_from_script( + &NativeScript::new_script_all( + &ScriptAll::new(&scripts_vec(vec![ + &pkscript(&keyhash1), + &pkscript(&keyhash2), + ])) + ), + ); + assert_eq!(pks3.len(), 2); + assert!(pks3.contains(&keyhash1)); + assert!(pks3.contains(&keyhash2)); + + let pks4 = get_all_pubkeys_from_script( + &NativeScript::new_script_any( + &ScriptAny::new(&scripts_vec(vec![ + &NativeScript::new_script_n_of_k(&ScriptNOfK::new( + 1, + &scripts_vec(vec![ + &NativeScript::new_timelock_start(&TimelockStart::new(132)), + &pkscript(&keyhash3), + ]), + )), + &NativeScript::new_script_all(&ScriptAll::new( + &scripts_vec(vec![ + &NativeScript::new_timelock_expiry(&TimelockExpiry::new(132)), + &pkscript(&keyhash1), + ]), + )), + &NativeScript::new_script_any(&ScriptAny::new( + &scripts_vec(vec![ + &pkscript(&keyhash1), + &pkscript(&keyhash2), + &pkscript(&keyhash3), + ]), + )), + ])) + ), + ); + assert_eq!(pks4.len(), 3); + assert!(pks4.contains(&keyhash1)); + assert!(pks4.contains(&keyhash2)); + assert!(pks4.contains(&keyhash3)); + } } diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 986071cf..df27149e 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -46,20 +46,31 @@ fn fake_private_key() -> Bip32PrivateKey { ).unwrap() } +fn count_needed_vkeys(tx_builder: &TransactionBuilder) -> usize { + let input_hashes = &tx_builder.input_types.vkeys; + match &tx_builder.mint_scripts { + None => input_hashes.len(), + Some(scripts) => { + // Union all input keys with minting keys + input_hashes.union(&get_all_pubkeys_from_scripts(scripts)).count() + } + } +} + // tx_body must be the result of building from tx_builder // constructs the rest of the Transaction using fake witness data of the correct length // for use in calculating the size of the final Transaction fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Result { let fake_key_root = fake_private_key(); - let raw_key = fake_key_root.to_raw_key(); - let fake_vkey_witness = Vkeywitness::new( - &Vkey::new(&raw_key.to_public()), - &raw_key.sign([1u8; 100].as_ref()) - ); // recall: this includes keys for input, certs and withdrawals - let vkeys = match tx_builder.input_types.vkeys.len() { + let vkeys = match count_needed_vkeys(tx_builder) { 0 => None, x => { + let raw_key = fake_key_root.to_raw_key(); + let fake_vkey_witness = Vkeywitness::new( + &Vkey::new(&raw_key.to_public()), + &raw_key.sign([1u8; 100].as_ref()) + ); let mut result = Vkeywitnesses::new(); for _i in 0..x { result.add(&fake_vkey_witness.clone()); @@ -89,24 +100,20 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul Some(result) }, }; - let (full_vkeys, full_script_keys) = match &tx_builder.mint_scripts { - None => (vkeys, script_keys), + let full_script_keys = match &tx_builder.mint_scripts { + None => script_keys, Some(witness_scripts) => { - let mut vw = vkeys - .map(|x| { x.clone() }) - .unwrap_or(Vkeywitnesses::new()); let mut ns = script_keys .map(|x| { x.clone() }) .unwrap_or(NativeScripts::new()); witness_scripts.0.iter().for_each(|s| { - vw.add(&fake_vkey_witness.clone()); ns.add(s); }); - (Some(vw), Some(ns)) + Some(ns) } }; let witness_set = TransactionWitnessSet { - vkeys: full_vkeys, + vkeys, native_scripts: full_script_keys, bootstraps: bootstrap_keys, // TODO: plutus support? @@ -3565,19 +3572,18 @@ mod tests { .checked_sub(&raw_mint_fee).unwrap() .checked_sub(&raw_mint_script_fee).unwrap(); - assert_eq!(witness_fee_increase, to_bignum(13376)); + assert_eq!(witness_fee_increase, to_bignum(8932)); let fee_increase_bytes = from_bignum(&witness_fee_increase) .checked_div(from_bignum(&fee_coefficient)) .unwrap(); - // Three vkey witnesses 96 bytes each (32 byte pubkey + 64 byte signature) - // Plus 16 bytes overhead for CBOR wrappers + // Two vkey witnesses 96 bytes each (32 byte pubkey + 64 byte signature) + // Plus 11 bytes overhead for CBOR wrappers // This is happening because we have three different minting policies - // and we are assuming each one needs a separate signature even tho - // they might refer to the same address with another policy or one of the inputs - // - assert_eq!(fee_increase_bytes, 304); + // but the same key-hash from one of them is already also used in inputs + // so no suplicate witness signature is require for that one + assert_eq!(fee_increase_bytes, 203); } #[test] From ed86ae83150442336be78edc669d658f6dc82310 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 7 Dec 2021 01:23:39 +0300 Subject: [PATCH 7/7] Started changing API for mint-scripts --- rust/src/tx_builder.rs | 67 ++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index df27149e..9285f99d 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -129,20 +129,32 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul }) } -fn min_fee(tx_builder: &TransactionBuilder) -> Result { - if let Some(mint) = tx_builder.mint.as_ref() { - if let Some(witness_scripts) = tx_builder.mint_scripts.as_ref() { - let witness_hashes: HashSet = witness_scripts.0.iter().map(|script| { - script.hash(ScriptHashNamespace::NativeScript) - }).collect(); - for mint_hash in mint.keys().0.iter() { - if !witness_hashes.contains(mint_hash) { - return Err(JsError::from_str(&format!("No witness script is found for mint policy '{:?}'! Impossible to estimate fee", hex::encode(mint_hash.to_bytes())))); - } +fn assert_required_mint_scripts(mint: &Mint, maybe_mint_scripts: Option<&NativeScripts>) -> Result<(), JsError> { + if let Some(mint_scripts) = maybe_mint_scripts { + let witness_hashes: HashSet = witness_scripts.0.iter().map(|script| { + script.hash(ScriptHashNamespace::NativeScript) + }).collect(); + for mint_hash in mint.keys().0.iter() { + if !witness_hashes.contains(mint_hash) { + return Err(JsError::from_str( + &format!( + "No witness script is found for mint policy '{:?}'! Script is required!", + hex::encode(mint_hash.to_bytes()), + )) + ); } - } else { - return Err(JsError::from_str("Impossible to estimate fee if mint is present in the builder, but witness scripts are not provided!")); } + } else { + return Err(JsError::from_str( + "Mint is present in the builder, but witness scripts are not provided!", + )); + } + Ok(()) +} + +fn min_fee(tx_builder: &TransactionBuilder) -> Result { + if let Some(mint) = tx_builder.mint.as_ref() { + assert_required_mint_scripts(mint, tx_builder.mint_scripts.as_ref())?; } let full_tx = fake_full_tx(tx_builder, tx_builder.build()?)?; fees::min_fee(&full_tx, &tx_builder.config.fee_algo) @@ -654,12 +666,14 @@ impl TransactionBuilder { Ok(()) } - /// Set explicit Mint object to this builder - /// it will replace any previously existing mint - /// NOTE! If you use `set_mint` manually - you must use `set_mint_scripts` - /// to provide matching policy scripts or min-fee calculation will be rejected! - pub fn set_mint(&mut self, mint: &Mint) { + /// Set explicit Mint object and the required witnesses to this builder + /// it will replace any previously existing mint and mint scripts + /// NOTE! Error will be returned in case a mint policy does not have a matching script + pub fn set_mint(&mut self, mint: &Mint, mint_scripts: &NativeScripts) -> Result<(), JsError> { + assert_required_mint_scripts(mint, Some(mint_scripts))?; self.mint = Some(mint.clone()); + self.mint_scripts = Some(mint_scripts.clone()); + Ok(()) } /// Returns a copy of the current mint state in the builder @@ -667,14 +681,6 @@ impl TransactionBuilder { self.mint.clone() } - /// Set explicit witness set to this builder - /// It will replace any previously existing witnesses - /// NOTE! Use carefully! If you are using `set_mint` - then you must be using - /// this setter as well to be able to calculate fee automatically! - pub fn set_mint_scripts(&mut self, mint_scripts: &NativeScripts) { - self.mint_scripts = Some(mint_scripts.clone()); - } - /// Returns a copy of the current mint witness scripts in the builder pub fn get_mint_scripts(&self) -> Option { self.mint_scripts.clone() @@ -683,7 +689,16 @@ impl TransactionBuilder { fn _set_mint_asset(&mut self, policy_id: &PolicyID, policy_script: &NativeScript, mint_assets: &MintAssets) { let mut mint = self.mint.as_ref().cloned().unwrap_or(Mint::new()); let is_new_policy = mint.insert(&policy_id, mint_assets).is_none(); - self.set_mint(&mint); + let mint_scripts = { + let mut witness_scripts = self.mint_scripts.as_ref().cloned() + .unwrap_or(NativeScripts::new()); + if is_new_policy { + // If policy has not been encountered before - insert the script into witnesses + witness_scripts.add(&policy_script.clone()); + } + witness_scripts + }; + self.set_mint(&mint, &mint_scripts); if is_new_policy { // If policy has not been encountered before - insert the script into witnesses let mut witness_scripts = self.mint_scripts.as_ref().cloned()