diff --git a/src/components/finutils/src/bins/fn.rs b/src/components/finutils/src/bins/fn.rs index d10840272..6efbab495 100644 --- a/src/components/finutils/src/bins/fn.rs +++ b/src/components/finutils/src/bins/fn.rs @@ -454,6 +454,49 @@ fn run() -> Result<()> { ) .expect("randomizer write failed"); } + } else if let Some(m) = matches.subcommand_matches("convert-abar-to-bar") { + let anon_keys = match m.value_of("anon-keys") { + Some(path) => { + let f = + fs::read_to_string(path).c(d!("Failed to read anon-keys file"))?; + let keys = serde_json::from_str::(f.as_str()).c(d!())?; + keys + } + None => return Err(eg!("path for anon-keys file not found")), + }; + let axfr_secret_key = anon_keys.axfr_secret_key; + let randomizer = m.value_of("randomizer"); + let dec_key = anon_keys.dec_key; + let to = m + .value_of("to-xfr-pubkey") + .c(d!()) + .and_then(|pk| wallet::public_key_from_base64(pk).c(d!())) + .or_else(|_| { + m.value_of("to-wallet-address").c(d!()).and_then(|addr| { + wallet::public_key_from_bech32(addr).c(d!("invalid wallet address")) + }) + })?; + let fee_xfr_seckey = match m.value_of("fee-xfr-seckey") { + Some(path) => { + Some(fs::read_to_string(path).c(d!("Failed to read seckey file"))?) + } + None => None, + }; + + if randomizer.is_none() { + println!("{}", m.usage()); + } else { + common::convert_abar2bar( + axfr_secret_key, + randomizer.unwrap(), + dec_key, + &to, + fee_xfr_seckey.as_deref(), + m.is_present("confidential-amount"), + m.is_present("confidential-type"), + ) + .c(d!())?; + } } else if let Some(m) = matches.subcommand_matches("gen-anon-keys") { let mut prng = ChaChaRng::from_entropy(); let keypair = AXfrKeyPair::generate(&mut prng); diff --git a/src/components/finutils/src/bins/fn.yml b/src/components/finutils/src/bins/fn.yml index 5dbd1b96f..549e12f40 100644 --- a/src/components/finutils/src/bins/fn.yml +++ b/src/components/finutils/src/bins/fn.yml @@ -533,6 +533,48 @@ subcommands: takes_value: true value_name: TXO SID required: true + - convert-abar-to-bar: + about: Convert a ABAR to BAR + args: + - anon-keys: + help: Anon keys file path of the sender + short: a + long: anon-keys + takes_value: true + value_name: ANON KEYS PATH + required: true + - randomizer: + help: Randomizer for the input Anon BAR + short: r + long: randomizer + takes_value: true + value_name: RANDOMIZER + required: true + - to-pubkey: + help: base64-formated `XfrPublicKey` of the receiver + short: t + long: to-pubkey + takes_value: true + value_name: PubKey + - to-wallet-address: + help: Xfr public key of the receiver + short: T + long: to-wallet-address + takes_value: true + value_name: XFR WALLET ADDRESS + required: true + - fee-xfr-seckey: + help: Xfr secret key for fee payment + long: fee-xfr-seckey + takes_value: true + value_name: XFR SECRET KEY + required: false + - confidential-amount: + help: mask the amount sent on the transaction log + long: confidential-amount + - confidential-type: + help: mask the asset type sent on the transaction log + long: confidential-type - anon-transfer: about: Perform Anonymous Transfer args: @@ -552,14 +594,12 @@ subcommands: required: true - to-axfr-public-key: help: Axfr public key of the receiver - short: tp long: to-axfr-public-key takes_value: true value_name: PUBLIC KEY required: true - to-enc-key: help: Receiver encryption key for anon transfer - short: te long: to-enc-key takes_value: true value_name: ENCRYPTION KEY @@ -597,14 +637,12 @@ subcommands: required: true - to-axfr-public-key-file: help: Axfr public keys of the receivers - short: tp long: to-axfr-public-key-file takes_value: true value_name: PUBLIC KEY required: true - to-enc-key-file: help: Receivers encryption keys for anon transfer - short: te long: to-enc-key-file takes_value: true value_name: ENCRYPTION KEY diff --git a/src/components/finutils/src/common/mod.rs b/src/components/finutils/src/common/mod.rs index e2f371c3c..be518aa93 100644 --- a/src/components/finutils/src/common/mod.rs +++ b/src/components/finutils/src/common/mod.rs @@ -809,6 +809,50 @@ pub fn convert_bar2abar( Ok(r) } +/// Convert an ABAR to a BLind Asset Record +pub fn convert_abar2bar( + axfr_secret_key: String, + r: &str, + dec_key: String, + to: &XfrPublicKey, + fee_xfr_seckey: Option<&str>, + confidential_am: bool, + confidential_ty: bool, +) -> Result<()> { + let from = wallet::anon_secret_key_from_base64(axfr_secret_key.as_str()) + .c(d!("invalid 'from-axfr-secret-key'"))?; + let from_secret_key = + wallet::x_secret_key_from_base64(dec_key.as_str()).c(d!("invalid dec_key"))?; + let fee_secret_key = restore_keypair_from_str_with_default(fee_xfr_seckey)?; + + let r = wallet::randomizer_from_base58(r).c(d!())?; + let randomized_from_pub_key = from.pub_key().randomize(&r); + let axtxo_abar = utils::get_owned_abars(&randomized_from_pub_key).c(d!())?; + let owner_memo = utils::get_abar_memo(&axtxo_abar[0].0).c(d!())?.unwrap(); + let mt_leaf_info = utils::get_abar_proof(&axtxo_abar[0].0).c(d!())?.unwrap(); + + let oabar_in = OpenAnonBlindAssetRecordBuilder::from_abar( + &axtxo_abar[0].1, + owner_memo, + &from, + &from_secret_key, + ) + .unwrap() + .mt_leaf_info(mt_leaf_info) + .build() + .unwrap(); + + let art = match (confidential_am, confidential_ty) { + (true, true) => AssetRecordType::ConfidentialAmount_ConfidentialAssetType, + (true, false) => AssetRecordType::ConfidentialAmount_NonConfidentialAssetType, + (false, true) => AssetRecordType::NonConfidentialAmount_ConfidentialAssetType, + _ => AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType, + }; + + utils::generate_abar2bar_op(&oabar_in, &from, to, art, &fee_secret_key).c(d!())?; + Ok(()) +} + /// Generate OABAR and add anonymous transfer operation pub fn gen_oabar_add_op( axfr_secret_key: String, @@ -830,8 +874,8 @@ pub fn gen_oabar_add_op( wallet::x_public_key_from_base64(to_enc_key).c(d!("invalid to_enc_key"))?; let r = wallet::randomizer_from_base58(r).c(d!())?; - let diversified_from_pub_key = from.pub_key().randomize(&r); - let axtxo_abar = utils::get_owned_abars(&diversified_from_pub_key).c(d!())?; + let randomized_from_pub_key = from.pub_key().randomize(&r); + let axtxo_abar = utils::get_owned_abars(&randomized_from_pub_key).c(d!())?; let owner_memo = utils::get_abar_memo(&axtxo_abar[0].0).c(d!())?.unwrap(); let mt_leaf_info = utils::get_abar_proof(&axtxo_abar[0].0).c(d!())?.unwrap(); diff --git a/src/components/finutils/src/common/utils.rs b/src/components/finutils/src/common/utils.rs index 9cea7f90b..b4ca0197f 100644 --- a/src/components/finutils/src/common/utils.rs +++ b/src/components/finutils/src/common/utils.rs @@ -22,14 +22,14 @@ use { serde::{self, Deserialize, Serialize}, std::collections::HashMap, tendermint::{PrivateKey, PublicKey}, - zei::anon_xfr::structs::AnonBlindAssetRecord, - zei::anon_xfr::{keys::AXfrPubKey, structs::MTLeafInfo}, - zei::xfr::asset_record::AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType, - zei::xfr::structs::OpenAssetRecord, + zei::anon_xfr::{ + keys::{AXfrKeyPair, AXfrPubKey}, + structs::{AnonBlindAssetRecord, MTLeafInfo, OpenAnonBlindAssetRecord}, + }, zei::xfr::{ asset_record::{open_blind_asset_record, AssetRecordType}, sig::{XfrKeyPair, XfrPublicKey}, - structs::{AssetRecordTemplate, OwnerMemo}, + structs::{AssetRecordTemplate, OpenAssetRecord, OwnerMemo}, }, zeialgebra::jubjub::JubjubScalar, }; @@ -545,6 +545,7 @@ pub(crate) fn get_owned_abars( get_serv_addr().c(d!())?, wallet::anon_public_key_to_base64(addr) ); + println!("URL: {:?}", url); attohttpc::get(&url) .send() @@ -727,17 +728,34 @@ pub fn generate_bar2abar_op( ) .c(d!())?; - if input_record.get_record_type() != NonConfidentialAmount_NonConfidentialAssetType - || input_record.asset_type != ASSET_TYPE_FRA - { - let feeop = gen_fee_bar_to_abar(auth_key_pair, txo_sid).c(d!())?; - builder.add_operation(feeop); - } + let feeop = gen_fee_bar_to_abar(auth_key_pair, txo_sid).c(d!())?; + builder.add_operation(feeop); send_tx(&builder.take_transaction()).c(d!())?; Ok(r) } +#[inline(always)] +#[allow(missing_docs)] +pub fn generate_abar2bar_op( + oabar_in: &OpenAnonBlindAssetRecord, + from: &AXfrKeyPair, + to: &XfrPublicKey, + art: AssetRecordType, + fee_keypair: &XfrKeyPair, +) -> Result<()> { + let mut builder: TransactionBuilder = new_tx_builder().c(d!())?; + builder + .add_operation_abar_to_bar(oabar_in, from, to, art) + .c(d!())?; + + let feeop = gen_fee_op(fee_keypair).c(d!())?; + builder.add_operation(feeop); + + send_tx(&builder.take_transaction()).c(d!())?; + Ok(()) +} + #[inline(always)] #[allow(missing_docs)] pub fn get_oar(owner_kp: &XfrKeyPair, txo_sid: TxoSID) -> Result { diff --git a/src/components/finutils/src/txn_builder/mod.rs b/src/components/finutils/src/txn_builder/mod.rs index 343c292ec..404a5e9f3 100644 --- a/src/components/finutils/src/txn_builder/mod.rs +++ b/src/components/finutils/src/txn_builder/mod.rs @@ -7,6 +7,7 @@ mod amount; +use ledger::data_model::AbarToBarOps; use { credentials::CredUserSecretKey, crypto::basics::hybrid_encryption::XPublicKey, @@ -49,6 +50,7 @@ use { tendermint::PrivateKey, zei::{ anon_xfr::{ + abar_to_bar::gen_abar_to_bar_note, bar_to_abar::gen_bar_to_abar_body, config::FEE_CALCULATING_FUNC, gen_anon_xfr_body, @@ -526,6 +528,35 @@ impl TransactionBuilder { Ok((self, r)) } + /// Create a new operation to convert from Anonymous record to Blind Asset Record + #[allow(dead_code)] + pub fn add_operation_abar_to_bar( + &mut self, + input: &OpenAnonBlindAssetRecord, + input_keypair: &AXfrKeyPair, + bar_pub_key: &XfrPublicKey, + asset_record_type: AssetRecordType, + ) -> Result<&mut Self> { + let mut prng = ChaChaRng::from_entropy(); + let user_params = UserParams::abar_to_bar_params(41); + + let note = gen_abar_to_bar_note( + &mut prng, + &user_params, + &input, + &input_keypair, + bar_pub_key, + asset_record_type, + ) + .c(d!())?; + + let abar_to_bar = AbarToBarOps::new(¬e, self.no_replay_token).c(d!())?; + + let op = Operation::AbarToBar(Box::from(abar_to_bar)); + self.txn.add_operation(op); + Ok(self) + } + /// Add an operation to transfer assets held in Anonymous Blind Asset Record. #[allow(dead_code)] pub fn add_operation_anon_transfer( diff --git a/src/components/wasm/src/wasm.rs b/src/components/wasm/src/wasm.rs index e8fe984ee..a766d1712 100644 --- a/src/components/wasm/src/wasm.rs +++ b/src/components/wasm/src/wasm.rs @@ -486,6 +486,61 @@ impl TransactionBuilder { Ok(self) } + /// Adds an operation to transaction builder which converts an abar to a bar. + /// + /// @param {AnonBlindAssetRecord} input - the ABAR to be converted + /// @param {OwnerMemo} owner_memo - the corresponding owner_memo of the ABAR to be converted + /// @param {MTLeafInfo} mt_leaf_info - the Merkle Proof of the ABAR + /// @param {AXfrKeyPair} from_keypair - the owners Anon Key pair + /// @param {XSecretKey} from_dec_key - the owners decryption key + /// @param {XfrPublic} recipient - the BAR owner public key + /// @param {bool} conf_amount - whether the BAR amount should be confidential + /// @param {bool} conf_type - whether the BAR asset type should be confidential + pub fn add_operation_abar_to_bar( + mut self, + input: AnonBlindAssetRecord, + owner_memo: OwnerMemo, + mt_leaf_info: MTLeafInfo, + from_keypair: AXfrKeyPair, + from_dec_key: XSecretKey, + recipient: XfrPublicKey, + conf_amount: bool, + conf_type: bool, + ) -> Result { + let oabar = OpenAnonBlindAssetRecordBuilder::from_abar( + &input, + owner_memo.memo, + &from_keypair, + &from_dec_key, + ) + .c(d!()) + .map_err(|e| JsValue::from_str(&format!("Could not add operation: {}", e)))? + .mt_leaf_info(mt_leaf_info.get_zei_mt_leaf_info().clone()) + .build() + .c(d!()) + .map_err(|e| JsValue::from_str(&format!("Could not add operation: {}", e)))?; + + let art = match (conf_amount, conf_type) { + (true, true) => AssetRecordType::ConfidentialAmount_ConfidentialAssetType, + (true, false) => { + AssetRecordType::ConfidentialAmount_NonConfidentialAssetType + } + (false, true) => { + AssetRecordType::NonConfidentialAmount_ConfidentialAssetType + } + _ => AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType, + }; + + self.get_builder_mut() + .add_operation_abar_to_bar(&oabar, &from_keypair, &recipient, art) + .c(d!()) + .map_err(|e| { + JsValue::from_str(&format!("Could not add operation: {}", e)) + })?; + + Ok(self) + } + /// Returns a list of randomizer base58 strings as json pub fn get_randomizers(&self) -> JsValue { let r = RandomizerStringArray { diff --git a/src/ledger/src/data_model/effects.rs b/src/ledger/src/data_model/effects.rs index 82a2834d2..91aa00cb3 100644 --- a/src/ledger/src/data_model/effects.rs +++ b/src/ledger/src/data_model/effects.rs @@ -1,3 +1,5 @@ +use crate::data_model::AbarToBarOps; +use zei::anon_xfr::abar_to_bar::AbarToBarBody; use { crate::{ data_model::{ @@ -97,6 +99,8 @@ pub struct TxnEffect { pub update_stakers: Vec, /// Newly create Anon Blind Asset Records pub bar_conv_abars: Vec, + /// Nullifiers of conv abars + pub abar_conv_inputs: Vec, /// New anon transfer bodies pub axfr_bodies: Vec, } @@ -202,6 +206,10 @@ impl TxnEffect { check_nonce!(i); te.add_bar_to_abar(i).c(d!())?; } + Operation::AbarToBar(i) => { + check_nonce!(i); + te.add_abar_to_bar(i).c(d!())?; + } Operation::TransferAnonAsset(i) => { check_nonce!(i); te.add_anon_transfer(i).c(d!())?; @@ -579,6 +587,32 @@ impl TxnEffect { Ok(()) } + /// A bar to abar note is valid iff + /// 1. the signature is correct, + /// 2. the ZKP can be verified, + /// 3. the input atxos are unspent. (checked in finish block) + /// + fn add_abar_to_bar(&mut self, abar_to_bar: &AbarToBarOps) -> Result<()> { + let body = abar_to_bar.note.body.clone(); + + //verify singature + let msg: Vec = bincode::serialize(&body).c(d!("Serialization error!"))?; + body.input + .1 + .verify(msg.as_slice(), &abar_to_bar.note.signature) + .c(d!("AbarToBar signature verification failed"))?; + + // collect body to verify ZKP later + self.abar_conv_inputs.push(body.clone()); + self.txos.push(Some(TxOutput { + id: None, + record: body.output, + lien: None, + })); + + Ok(()) + } + fn add_anon_transfer(&mut self, anon_transfer: &AnonTransferOps) -> Result<()> { // verify nullifiers not double spent within txn @@ -689,6 +723,10 @@ impl BlockEffect { current_txn_abars.push(abar); } + for inputs in txn_effect.abar_conv_inputs { + self.new_nullifiers.push(inputs.input.0); + } + for axfr_body in txn_effect.axfr_bodies { for (n, _) in axfr_body.inputs { self.new_nullifiers.push(n); @@ -718,6 +756,11 @@ impl BlockEffect { } } } + for inputs in txn_effect.abar_conv_inputs.iter() { + if self.new_nullifiers.contains(&inputs.input.0) { + return Err(eg!()); + } + } // Check that no AssetType is affected by both the block so far and // this transaction diff --git a/src/ledger/src/data_model/mod.rs b/src/ledger/src/data_model/mod.rs index 007a196c2..33d70d9d2 100644 --- a/src/ledger/src/data_model/mod.rs +++ b/src/ledger/src/data_model/mod.rs @@ -10,6 +10,7 @@ mod effects; mod test; pub use effects::{BlockEffect, TxnEffect}; +use zei::anon_xfr::abar_to_bar::AbarToBarNote; use { crate::converter::ConvertAccount, @@ -1304,6 +1305,39 @@ impl BarToAbarOps { } } +/// Operation for converting a Blind Asset Record to a Anonymous record +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct AbarToBarOps { + /// the note which contains the inp/op and ZKP + pub note: AbarToBarNote, + nonce: NoReplayToken, +} + +impl AbarToBarOps { + /// Generates a new BarToAbarOps object + pub fn new( + abar_to_bar_note: &AbarToBarNote, + nonce: NoReplayToken, + ) -> Result { + Ok(AbarToBarOps { + note: abar_to_bar_note.clone(), + nonce, + }) + } + + #[inline(always)] + /// Sets the nonce for the operation + pub fn set_nonce(&mut self, nonce: NoReplayToken) { + self.nonce = nonce; + } + + #[inline(always)] + /// Fetches the nonce of the operation + pub fn get_nonce(&self) -> NoReplayToken { + self.nonce + } +} + /// A struct to hold the transfer ops #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct AnonTransferOps { @@ -1360,6 +1394,8 @@ pub enum Operation { ConvertAccount(ConvertAccount), /// Anonymous conversion operation BarToAbar(Box), + /// De-anonymize ABAR operation + AbarToBar(Box), /// Anonymous transfer operation TransferAnonAsset(Box), } @@ -1390,6 +1426,7 @@ fn set_no_replay_token(op: &mut Operation, no_replay_token: NoReplayToken) { Operation::UpdateMemo(i) => i.body.no_replay_token = no_replay_token, Operation::ConvertAccount(i) => i.set_nonce(no_replay_token), Operation::BarToAbar(i) => i.set_nonce(no_replay_token), + Operation::AbarToBar(i) => i.set_nonce(no_replay_token), Operation::TransferAnonAsset(i) => i.set_nonce(no_replay_token), _ => {} } @@ -1738,6 +1775,7 @@ impl Transaction { self.check_fee() && !self.is_coinbase_tx() } + #[allow(clippy::if_same_then_else)] /// A simple fee checker /// /// The check logic is as follows: diff --git a/src/ledger/src/store/api_cache.rs b/src/ledger/src/store/api_cache.rs index 8afd4beab..964ecb400 100644 --- a/src/ledger/src/store/api_cache.rs +++ b/src/ledger/src/store/api_cache.rs @@ -270,6 +270,11 @@ where key: i.note.body.input.public_key, }); } + Operation::AbarToBar(i) => { + related_addresses.insert(XfrAddress { + key: i.note.body.output.public_key, + }); + } Operation::TransferAnonAsset(_) => { // Anon } diff --git a/src/ledger/src/store/mod.rs b/src/ledger/src/store/mod.rs index b2ce74e8b..51a10dedc 100644 --- a/src/ledger/src/store/mod.rs +++ b/src/ledger/src/store/mod.rs @@ -8,7 +8,6 @@ mod test; pub mod utils; pub use fbnc; -use zei_accumulators::merkle_tree::TREE_DEPTH; use { crate::{ @@ -55,6 +54,7 @@ use { store::{ImmutablePrefixedStore, PrefixedStore}, }, zei::{ + abar_to_bar::verify_abar_to_bar_body, anon_xfr::{ hash_abar, keys::AXfrPubKey, @@ -70,7 +70,7 @@ use { }, }, zei_accumulators::merkle_tree::{ - ImmutablePersistentMerkleTree, PersistentMerkleTree, Proof, TreePath + ImmutablePersistentMerkleTree, PersistentMerkleTree, Proof, TreePath, TREE_DEPTH }, zeialgebra::{bls12_381::BLSScalar, groups::Scalar}, }; @@ -1310,6 +1310,12 @@ impl LedgerStatus { .collect() } + #[inline(always)] + #[allow(missing_docs)] + pub fn get_abar(&self, uid: ATxoSID) -> Option { + self.ax_utxos.get(&uid) + } + #[inline(always)] #[allow(missing_docs)] fn get_utxo(&self, id: TxoSID) -> Option { @@ -1678,6 +1684,19 @@ impl LedgerStatus { .c(d!())?; } + for abar_conv in &txn_effect.abar_conv_inputs { + let user_params = UserParams::abar_to_bar_params(41); + let node_params = NodeParams::from(user_params); + let abar_version: usize = abar_conv.proof.get_merkle_root_version(); + + verify_abar_to_bar_body( + &node_params, + abar_conv, + &self.get_versioned_abar_hash(abar_version as usize).unwrap(), + ) + .c(d!())?; + } + Ok(()) } diff --git a/tools/triple_masking/abar_to_bar_convert.sh b/tools/triple_masking/abar_to_bar_convert.sh new file mode 100755 index 000000000..af215efe1 --- /dev/null +++ b/tools/triple_masking/abar_to_bar_convert.sh @@ -0,0 +1,20 @@ +set -e +./tools/triple_masking/bar_to_abar_convert.sh + + +sleep 10 +echo "BAR Balance:" +target/release/fn wallet --show + + +randomiser=$(tail -n 1 randomizers) +echo "\n\n Owned Abars after Bar to Abar conversion" +sleep 20 #Do not remove/decrease +target/release/fn owned-abars -p zQa8j0mGYUXM6JxjWN_pqfOi1lvEyenJYq35OIJNN08= -r $randomiser + +target/release/fn convert-abar-to-bar --anon-keys ./anon-keys-temp.keys -r $randomiser --to-wallet-address fra1ck6mu4fgmh7n3g0y5jm0zjrq6hwgckut9q2tf5fpwhrdgkhgdp9qhla5t5 + +sleep 10 + +echo "BAR Balance: after AbarToBar" +target/release/fn wallet --show \ No newline at end of file diff --git a/tools/triple_masking/bar_to_abar_convert.sh b/tools/triple_masking/bar_to_abar_convert.sh index bfacb85e8..992a26f70 100755 --- a/tools/triple_masking/bar_to_abar_convert.sh +++ b/tools/triple_masking/bar_to_abar_convert.sh @@ -33,6 +33,11 @@ echo " \"dec_key\": \"4GNC0J_qOXV2kww5BC5bOCyrTEfCodX5BoFaj06uN1s=\" }" > anon-keys-temp.keys +target/release/fn setup -O mnemonic-temp.keys -S http://0.0.0.0 + +echo "BAR Balance:" +target/release/fn wallet --show + set -e echo "\n\n\n Bar To Abar Conversion" echo "==============================================================================="