From 1e800ceecc021661621691480b806234da334072 Mon Sep 17 00:00:00 2001 From: Harshad Patil Date: Tue, 22 Aug 2023 09:35:21 -0700 Subject: [PATCH] Add triple masking (#1004) * triple_masking * ledger add noah * components add noah * fix lint * update keypair * fix lint * remove is_address_fra * fix xfrboxy * signature.verify * update enable_triple_masking_height to enable_ed25519_triple_masking_height * fix OwnerMemo * fix test * update noah version * fix anon transfer batch --------- Co-authored-by: shaorongqiang triple_masking dependencies to develop (#926) * dependencies to develop * update wasm-bindgen * remove noah * strike build_record_and_get_blinds --------- Co-authored-by: shaorongqiang Co-authored-by: weikengchen Renew #935 for merging develop with main. (#937) * fix * Upgrade noah to v0.4.6 * update --------- Co-authored-by: weikeng Co-authored-by: Sun Co-authored-by: weikengchen Add noah init wasm (#941) Fix abar_to_ar params (#950) Let the Anemoi precompile use the platform-lib-noah library (#963) fix build (#977) Co-authored-by: Weikeng Chen Improve Noah secp support (#981) * Improve Noah secp support * update deps * update platform-lib-utils for testing * public key to bytes in data_model * fix * fix * Avoid lint bug * fix EVM XfrPublicKey deserialization * edit the dependency * fix error handling --------- Co-authored-by: weikengchen EIP 1962 is undecided (#984) Use BN254 Anemoi for asset ID mapping (#985) * Improve Noah secp support * update deps * update platform-lib-utils for testing * public key to bytes in data_model * fix * fix * Avoid lint bug * fix EVM XfrPublicKey deserialization * edit the dependency * fix error handling * update the asset code * fix --------- Co-authored-by: Harshad Patil add validator_whitelist v1 (#972) * fix timeout and checkpoint * add validator_whitelist v1 * add validator_whitelist add validator_whitelist_v2 (#976) * add validator_whitelist_v2 * update timeout_commit 10s * update validator_whitelist * bug fix * fix build validator whitelist v3 (#980) downgrade ctrlc (#973) update timeout_propose 3s (#974) update timeout_commit 10s (#975) Fix Fn_Check Bug (#970) android add execption (#979) validator whitelist v3 (#980) pr Adding checkpoint condition to AnemoiJive381 upgrade only use deprecated AnemoiJive381 for old precompiled contract query server API to get derived asset code fix build remo0ve unneeded sign in tx build fix Lint fmt fix build CLI test file test disable eth address transfer from EVM fix build enable eth-address in CLI fix lint update lib-noah branch update demo tests for triple masking fix CLI eth-address removed unrelated code changes remove some unrelated code review ledger package fix wallet_mobile build remove unneccesary refactoring fix some refactoring synced changes from platform-lib-noah removed testing scripts fmt & lint fix import statements remove unneeded refactoring fix conflicts * removed some non Triple masking related code * rename checkpoint config var * fix derived asset code API * trigger tests * trigger * Update src/components/abciapp/src/abci/server/callback/mod.rs * Update src/components/contracts/modules/evm/precompile/src/lib.rs * Update lib.rs * Update Cargo.toml * Apply suggestions from code review * minor fix in fn.yml * genkey * stake append * staker update * unstake and claim * delegate, undelegate * transfer and transfer_batch * wallet/create, wallet/show * asset * other edits on fn * fmt * undo bash script change * Update src/components/abciapp/src/api/query_server/query_api/ledger_api.rs * Update src/components/abciapp/src/api/query_server/query_api/server.rs * Update src/components/abciapp/src/api/query_server/query_api/mod.rs * Apply suggestions from code review * Update src/components/finutils/src/bins/fn.rs * additional consistency test for the asset code is not necessary * change apphash calculation --------- Co-authored-by: Weikeng Chen --- Cargo.toml | 30 +- src/components/abciapp/src/abci/mod.rs | 1 - .../abciapp/src/abci/server/callback/mod.rs | 119 +- .../abciapp/src/abci/server/callback/utils.rs | 62 +- .../abciapp/src/abci/staking/test.rs | 16 +- .../api/query_server/query_api/ledger_api.rs | 30 +- .../src/api/query_server/query_api/mod.rs | 151 +- .../src/api/query_server/query_api/server.rs | 85 +- src/components/config/src/abci/mod.rs | 12 +- src/components/contracts/baseapp/Cargo.toml | 6 +- src/components/contracts/baseapp/src/lib.rs | 6 + .../contracts/baseapp/src/staking.rs | 3 +- .../contracts/modules/account/Cargo.toml | 2 +- .../modules/evm/precompile/Cargo.toml | 1 + .../modules/evm/precompile/basic/Cargo.toml | 3 - .../evm/precompile/eth-pairings/src/lib.rs | 17 +- .../evm/precompile/eth-pairings/src/tests.rs | 29 + .../modules/evm/precompile/utils/Cargo.toml | 2 +- .../modules/evm/precompile/utils/src/data.rs | 5 + .../contracts/modules/evm/src/lib.rs | 10 +- .../contracts/modules/evm/src/precompile.rs | 5 +- .../contracts/primitives/core/src/module.rs | 11 +- .../contracts/primitives/types/Cargo.toml | 1 + .../primitives/types/src/actions/account.rs | 5 +- .../contracts/primitives/types/src/crypto.rs | 8 +- src/components/contracts/rpc/Cargo.toml | 6 +- src/components/finutils/Cargo.toml | 13 +- src/components/finutils/src/bins/fn.rs | 424 ++++- src/components/finutils/src/bins/fn.yml | 450 ++++- .../finutils/src/bins/key_generator.rs | 2 +- src/components/finutils/src/bins/stt/stt.rs | 18 +- .../finutils/src/common/ddev/init.rs | 33 +- src/components/finutils/src/common/evm.rs | 26 +- src/components/finutils/src/common/mod.rs | 800 ++++++++- src/components/finutils/src/common/utils.rs | 315 +++- .../finutils/src/txn_builder/mod.rs | 1526 ++++++++++++++++- src/components/wallet_mobile/Cargo.toml | 5 +- .../wallet_mobile/src/android/constructor.rs | 5 +- .../wallet_mobile/src/android/evm.rs | 9 +- .../wallet_mobile/src/android/mod.rs | 35 +- .../wallet_mobile/src/android/transfer.rs | 48 +- .../wallet_mobile/src/android/tx_builder.rs | 80 +- .../wallet_mobile/src/ios/tx_builder.rs | 5 +- .../wallet_mobile/src/rust/crypto.rs | 23 +- .../wallet_mobile/src/rust/data_model.rs | 19 +- .../wallet_mobile/src/rust/transaction.rs | 21 +- .../wallet_mobile/src/rust/types.rs | 30 +- src/components/wallet_mobile/src/rust/util.rs | 2 +- src/components/wallet_mobile/src/wasm/mod.rs | 18 +- src/components/wasm/Cargo.toml | 10 +- src/components/wasm/src/wasm.rs | 875 +++++++++- src/components/wasm/src/wasm_data_model.rs | 181 +- src/ledger/Cargo.toml | 16 +- src/ledger/src/data_model/__trash__.rs | 3 +- src/ledger/src/data_model/effects.rs | 219 ++- src/ledger/src/data_model/mod.rs | 512 +++++- src/ledger/src/data_model/test.rs | 3 +- src/ledger/src/lib.rs | 2 +- src/ledger/src/staking/mod.rs | 15 +- src/ledger/src/staking/ops/delegation.rs | 2 +- src/ledger/src/staking/ops/mint_fra.rs | 2 +- src/ledger/src/store/api_cache.rs | 163 +- src/ledger/src/store/helpers.rs | 8 +- src/ledger/src/store/mod.rs | 636 ++++++- src/ledger/src/store/test.rs | 137 +- src/ledger/src/store/utils.rs | 1 + tools/devnet/snapshot.sh | 1 + 67 files changed, 6576 insertions(+), 743 deletions(-) create mode 100644 src/components/contracts/modules/evm/precompile/eth-pairings/src/tests.rs mode change 100644 => 100755 src/ledger/src/data_model/test.rs mode change 100644 => 100755 src/ledger/src/store/test.rs diff --git a/Cargo.toml b/Cargo.toml index 58870478f..1978086b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,22 +36,34 @@ members = [ ] [profile.dev] -incremental = false +opt-level = 3 +lto = "thin" +incremental = true +debug-assertions = true +debug = true +panic = 'abort' overflow-checks = true [profile.release] +opt-level = 3 +lto = "thin" incremental = false overflow-checks = true +panic = 'abort' [profile.bench] +opt-level = 3 +debug = false +rpath = false +lto = "thin" codegen-units = 1 +incremental = true +debug-assertions = false overflow-checks = false -[profile.dev.package.curve25519-dalek] -opt-level = 1 -overflow-checks = false - -[patch.crates-io] -ed25519-dalek = { git = "https://github.com/FindoraNetwork/ed25519-dalek", rev = "ad461f" } -curve25519-dalek = { git = "https://github.com/FindoraNetwork/curve25519-dalek", rev = "a2df65" } -x25519-dalek = { git = "https://github.com/FindoraNetwork/x25519-dalek", rev = "53bb1a" } +[profile.test] +opt-level = 2 +lto = "off" +incremental = true +debug-assertions = true +debug = true \ No newline at end of file diff --git a/src/components/abciapp/src/abci/mod.rs b/src/components/abciapp/src/abci/mod.rs index 507078bd1..10ff8c609 100644 --- a/src/components/abciapp/src/abci/mod.rs +++ b/src/components/abciapp/src/abci/mod.rs @@ -94,7 +94,6 @@ pub fn run() -> Result<()> { "http://{}:{}", config.tendermint_host, config.tendermint_port ); - // keep them running in the background, // avoid being dropped by the jsonrpc crate. mem::forget(fc_rpc::start_web3_service( diff --git a/src/components/abciapp/src/abci/server/callback/mod.rs b/src/components/abciapp/src/abci/server/callback/mod.rs index 081c1d023..34a6e63a0 100644 --- a/src/components/abciapp/src/abci/server/callback/mod.rs +++ b/src/components/abciapp/src/abci/server/callback/mod.rs @@ -2,13 +2,6 @@ //! # Impl function of tendermint ABCI //! -use globutils::wallet; -use ledger::{ - data_model::ASSET_TYPE_FRA, - staking::{FF_ADDR_EXTRA_120_0000, FF_ADDR_LIST}, -}; -use zei::noah_api::xfr::asset_record::AssetRecordType; - mod utils; use { @@ -32,11 +25,15 @@ use { STATE_UPDATE_LIST, TXS, WEB3_SERVICE_START_HEIGHT, }, fp_storage::hash::{Sha256, StorageHasher}, + globutils::wallet, lazy_static::lazy_static, ledger::{ converter::is_convert_account, - data_model::Operation, - staking::{evm::EVM_STAKING, KEEP_HIST, VALIDATOR_UPDATE_BLOCK_ITV}, + data_model::{Operation, Transaction, ASSET_TYPE_FRA}, + staking::{ + evm::EVM_STAKING, FF_ADDR_EXTRA_120_0000, FF_ADDR_LIST, KEEP_HIST, + VALIDATOR_UPDATE_BLOCK_ITV, + }, store::{ api_cache, fbnc::{new_mapx, Mapx}, @@ -56,6 +53,7 @@ use { }, }, tracing::{error, info}, + zei::noah_api::xfr::asset_record::AssetRecordType, }; pub(crate) static TENDERMINT_BLOCK_HEIGHT: AtomicI64 = AtomicI64::new(0); @@ -100,9 +98,15 @@ pub fn info(s: &mut ABCISubmissionServer, req: &RequestInfo) -> ResponseInfo { && h < CFG.checkpoint.enable_frc20_height { resp.set_last_block_app_hash(la_hash); - } else { + } else if h < CFG.checkpoint.enable_triple_masking_height { let cs_hash = s.account_base_app.write().info(req).last_block_app_hash; resp.set_last_block_app_hash(app_hash("info", h, la_hash, cs_hash)); + } else { + let cs_hash = s.account_base_app.write().info(req).last_block_app_hash; + let tm_hash = state.get_anon_state_commitment().0; + resp.set_last_block_app_hash(app_hash_v2( + "info", h, la_hash, cs_hash, tm_hash, + )); } } @@ -140,6 +144,18 @@ pub fn check_tx(s: &mut ABCISubmissionServer, req: &RequestCheckTx) -> ResponseC TxCatalog::FindoraTx => { if matches!(req.field_type, CheckTxType::New) { if let Ok(tx) = convert_tx(req.get_tx()) { + for op in tx.body.operations.iter() { + if let Operation::TransferAnonAsset(op) = op { + let mut inputs = op.note.body.inputs.clone(); + inputs.sort(); + inputs.dedup(); + if inputs.len() != op.note.body.inputs.len() { + resp.log = "anon transfer input error".to_owned(); + resp.code = 1; + return resp; + } + } + } if td_height > CFG.checkpoint.check_signatures_num { for op in tx.body.operations.iter() { if let Operation::TransferAsset(op) = op { @@ -171,6 +187,11 @@ pub fn check_tx(s: &mut ABCISubmissionServer, req: &RequestCheckTx) -> ResponseC } else if TX_HISTORY.read().contains_key(&tx.hash_tm_rawbytes()) { resp.log = "Historical transaction".to_owned(); resp.code = 1; + } else if is_tm_transaction(&tx) + && td_height < CFG.checkpoint.enable_triple_masking_height + { + resp.code = 1; + resp.log = "Triple Masking is disabled".to_owned(); } } else { resp.log = "Invalid format".to_owned(); @@ -272,6 +293,18 @@ pub fn deliver_tx( match tx_catalog { TxCatalog::FindoraTx => { if let Ok(tx) = convert_tx(req.get_tx()) { + for op in tx.body.operations.iter() { + if let Operation::TransferAnonAsset(op) = op { + let mut inputs = op.note.body.inputs.clone(); + inputs.sort(); + inputs.dedup(); + if inputs.len() != op.note.body.inputs.len() { + resp.log = "anon Transfer input error".to_owned(); + resp.code = 1; + return resp; + } + } + } if td_height > CFG.checkpoint.check_signatures_num { for op in tx.body.operations.iter() { if let Operation::TransferAsset(op) = op { @@ -336,7 +369,7 @@ pub fn deliver_tx( if let Err(err) = s.account_base_app.write().deliver_findora_tx(&tx, &hash.0) { - info!(target: "abciapp", "deliver convert account tx failed: {err:?}"); + error!(target: "abciapp", "deliver convert account tx failed: {err:?}"); resp.code = 1; resp.log = @@ -375,6 +408,16 @@ pub fn deliver_tx( .db .write() .discard_session(); + } else if is_tm_transaction(&tx) + && td_height < CFG.checkpoint.enable_triple_masking_height + { + info!(target: "abciapp", + "Triple Masking transaction(FindoraTx) detected at early height {}: {:?}", + td_height, tx + ); + resp.code = 2; + resp.log = "Triple Masking is disabled".to_owned(); + return resp; } else if CFG.checkpoint.utxo_checktx_height < td_height { match tx.check_tx() { Ok(_) => { @@ -597,8 +640,11 @@ pub fn commit(s: &mut ABCISubmissionServer, req: &RequestCommit) -> ResponseComm && td_height < CFG.checkpoint.enable_frc20_height { r.set_data(la_hash); - } else { + } else if td_height < CFG.checkpoint.enable_triple_masking_height { r.set_data(app_hash("commit", td_height, la_hash, cs_hash)); + } else { + let tm_hash = state.get_anon_state_commitment().0; + r.set_data(app_hash_v2("commit", td_height, la_hash, cs_hash, tm_hash)); } IN_SAFE_ITV.store(false, Ordering::Release); @@ -728,3 +774,52 @@ fn app_hash( la_hash } } + +/// Combines ledger state hash and EVM chain state hash +/// and print app hashes for debugging +fn app_hash_v2( + when: &str, + height: i64, + mut la_hash: Vec, + cs_hash: Vec, + tm_hash: Vec, +) -> Vec { + info!(target: "abciapp", + "app_hash_{}: {}_{}_{}, height: {}", + when, + hex::encode(la_hash.clone()), + hex::encode(cs_hash.clone()), + hex::encode(tm_hash.clone()), + height + ); + + if !cs_hash.is_empty() { + la_hash.extend_from_slice(&cs_hash); + + if !tm_hash.is_empty() { + la_hash.extend_from_slice(&tm_hash); + } + + Sha256::hash(la_hash.as_slice()).to_vec() + } else if !tm_hash.is_empty() { + la_hash.extend([0u8; 32]); + la_hash.extend_from_slice(&tm_hash); + + Sha256::hash(la_hash.as_slice()).to_vec() + } else { + la_hash + } +} + +fn is_tm_transaction(tx: &Transaction) -> bool { + tx.body + .operations + .iter() + .try_for_each(|op| match op { + Operation::BarToAbar(_a) => None, + Operation::AbarToBar(_a) => None, + Operation::TransferAnonAsset(_a) => None, + _ => Some(()), + }) + .is_none() +} diff --git a/src/components/abciapp/src/abci/server/callback/utils.rs b/src/components/abciapp/src/abci/server/callback/utils.rs index 767fe014e..7f3e11946 100644 --- a/src/components/abciapp/src/abci/server/callback/utils.rs +++ b/src/components/abciapp/src/abci/server/callback/utils.rs @@ -39,8 +39,13 @@ pub fn gen_tendermint_attr(tx: &Transaction) -> RepeatedField { res.push(ev); let (from, to) = gen_tendermint_attr_addr(tx); + let (nullifiers, commitments) = gen_tendermint_attr_anon(tx); - if !from.is_empty() || !to.is_empty() { + if !from.is_empty() + || !to.is_empty() + || !nullifiers.is_empty() + || !commitments.is_empty() + { let mut ev = Event::new(); ev.set_field_type("addr".to_owned()); @@ -76,6 +81,8 @@ pub fn gen_tendermint_attr(tx: &Transaction) -> RepeatedField { index_addr!(from, "addr.from"); index_addr!(to, "addr.to"); + index_addr!(nullifiers, "nullifier.used"); + index_addr!(commitments, "commitment.created"); } RepeatedField::from_vec(res) @@ -126,6 +133,59 @@ fn gen_tendermint_attr_addr(tx: &Transaction) -> (Vec, Vec) { Operation::UpdateMemo(d) => { append_attr!(d); } + Operation::BarToAbar(d) => { + let mut attr = TagAttr::default(); + attr.addr = globutils::wallet::public_key_to_bech32( + &d.input_record().public_key, + ); + base.0.push(attr); + } + Operation::AbarToBar(d) => { + let mut attr = TagAttr::default(); + attr.addr = globutils::wallet::public_key_to_bech32( + &d.note.get_public_key(), + ); + base.1.push(attr); + } + _ => {} + } + + base + }) +} + +fn gen_tendermint_attr_anon(tx: &Transaction) -> (Vec, Vec) { + tx.body + .operations + .iter() + .fold((vec![], vec![]), |mut base, op| { + match op { + Operation::BarToAbar(d) => { + let mut attr = TagAttr::default(); + attr.addr = globutils::wallet::commitment_to_base58( + &d.output_record().commitment, + ); + base.1.push(attr); + } + Operation::AbarToBar(d) => { + let mut attr = TagAttr::default(); + attr.addr = + globutils::wallet::nullifier_to_base58(&d.note.get_input()); + base.0.push(attr); + } + Operation::TransferAnonAsset(d) => { + for ix in &d.note.body.inputs { + let mut attr = TagAttr::default(); + attr.addr = globutils::wallet::nullifier_to_base58(ix); + base.0.push(attr); + } + for ox in &d.note.body.outputs { + let mut attr = TagAttr::default(); + attr.addr = + globutils::wallet::commitment_to_base58(&ox.commitment); + base.1.push(attr); + } + } _ => {} } diff --git a/src/components/abciapp/src/abci/staking/test.rs b/src/components/abciapp/src/abci/staking/test.rs index d51880391..6dd104146 100644 --- a/src/components/abciapp/src/abci/staking/test.rs +++ b/src/components/abciapp/src/abci/staking/test.rs @@ -19,7 +19,7 @@ use { asset_record::{open_blind_asset_record, AssetRecordType}, structs::{AssetRecordTemplate, XfrAmount}, }, - {XfrKeyPair, XfrPublicKey}, + XfrKeyPair, XfrPublicKey, }, }; @@ -126,12 +126,12 @@ fn gen_transfer_tx( &owner_memo.map(|o| o.into_noah()), &owner_kp.into_noah(), ) - .c(d!()) - .and_then(|ob| { - trans_builder - .add_input(TxoRef::Absolute(sid), ob, None, None, i_am) - .c(d!()) - })?; + .c(d!()) + .and_then(|ob| { + trans_builder + .add_input(TxoRef::Absolute(sid), ob, None, None, i_am) + .c(d!()) + })?; alt!(0 == am, break); } @@ -166,5 +166,5 @@ fn gen_transfer_tx( .c(d!())?; tx_builder.add_operation(op); - Ok(tx_builder.take_transaction()) + tx_builder.build_and_take_transaction() } diff --git a/src/components/abciapp/src/api/query_server/query_api/ledger_api.rs b/src/components/abciapp/src/api/query_server/query_api/ledger_api.rs index 9c45577a4..bc6e38cfb 100644 --- a/src/components/abciapp/src/api/query_server/query_api/ledger_api.rs +++ b/src/components/abciapp/src/api/query_server/query_api/ledger_api.rs @@ -10,11 +10,12 @@ use { DelegationInfo, DelegatorInfo, DelegatorList, NetworkRoute, Validator, ValidatorDetail, ValidatorList, }, - globutils::HashOf, + globutils::{wallet, HashOf}, ledger::{ data_model::{ - AssetType, AssetTypeCode, AssetTypePrefix, AuthenticatedUtxo, StateCommitmentData, - TxnSID, TxoSID, UnAuthenticatedUtxo, Utxo, + ABARData, ATxoSID, AssetType, AssetTypeCode, AssetTypePrefix, + AuthenticatedUtxo, StateCommitmentData, TxnSID, TxoSID, UnAuthenticatedUtxo, + Utxo, }, staking::{ DelegationRwdDetail, DelegationState, Staking, TendermintAddr, @@ -222,8 +223,7 @@ pub async fn query_global_state( data: web::Data>>, ) -> web::Json<(HashOf>, u64, &'static str)> { let qs = data.read(); - let ledger = &qs.ledger_cloned; - let (hash, seq_id) = ledger.get_state_commitment(); + let (hash, seq_id) = qs.get_state_commitment_from_api_cache(); web::Json((hash, seq_id, "v4UVgkIBpj0eNYI1B1QhTTduJHCIHH126HcdesCxRdLkVGDKrVUPgwmNLCDafTVgC5e4oDhAGjPNt1VhUr6ZCQ==")) } @@ -712,6 +712,24 @@ pub async fn query_owned_utxos( .map(|pk| web::Json(pnk!(ledger.get_owned_utxos(&pk)))) } +// query utxos according to `commitment` +pub(super) async fn query_owned_abar( + data: web::Data>>, + com: web::Path, +) -> actix_web::Result>> { + let qs = data.read(); + let ledger = &qs.ledger_cloned; + globutils::wallet::commitment_from_base58(com.as_str()) + .c(d!()) + .map_err(|e| error::ErrorBadRequest(e.generate_log(None))) + .map(|com| { + web::Json(ledger.get_owned_abar(&com).map(|a| { + let c = wallet::commitment_to_base58(&com); + (a, ABARData { commitment: c }) + })) + }) +} + #[allow(missing_docs)] pub enum ApiRoutes { UtxoSid, @@ -725,6 +743,7 @@ pub enum ApiRoutes { TxnSidLight, GlobalStateVersion, OwnedUtxos, + OwnedAbars, ValidatorList, DelegationInfo, DelegatorList, @@ -749,6 +768,7 @@ impl NetworkRoute for ApiRoutes { ApiRoutes::DelegationInfo => "delegation_info", ApiRoutes::DelegatorList => "delegator_list", ApiRoutes::ValidatorDetail => "validator_detail", + ApiRoutes::OwnedAbars => "owned_abars", }; "/".to_owned() + endpoint } diff --git a/src/components/abciapp/src/api/query_server/query_api/mod.rs b/src/components/abciapp/src/api/query_server/query_api/mod.rs index 325a078d1..05aad71c7 100644 --- a/src/components/abciapp/src/api/query_server/query_api/mod.rs +++ b/src/components/abciapp/src/api/query_server/query_api/mod.rs @@ -16,8 +16,8 @@ use { globutils::wallet, ledger::{ data_model::{ - b64dec, AssetTypeCode, DefineAsset, IssuerPublicKey, Transaction, TxOutput, - TxnIDHash, TxnSID, TxoSID, XfrAddress, BLACK_HOLE_PUBKEY, + b64dec, ATxoSID, AssetTypeCode, DefineAsset, IssuerPublicKey, Transaction, + TxOutput, TxnIDHash, TxnSID, TxoSID, XfrAddress, BLACK_HOLE_PUBKEY, }, staking::{ ops::mint_fra::MintEntry, FF_PK_EXTRA_120_0000, FRA, FRA_TOTAL_AMOUNT, @@ -29,11 +29,15 @@ use { serde::{Deserialize, Serialize}, server::QueryServer, std::{ - collections::{BTreeMap, HashSet}, + collections::{BTreeMap, HashMap, HashSet}, sync::Arc, }, tracing::info, - zei::{noah_algebra::serialization::NoahFromToBytes, OwnerMemo, XfrPublicKey}, + zei::{ + noah_algebra::serialization::NoahFromToBytes, + noah_api::anon_xfr::structs::{AxfrOwnerMemo, Commitment, MTLeafInfo}, + OwnerMemo, XfrPublicKey, + }, }; /// Returns the git commit hash and commit date of this build @@ -91,6 +95,44 @@ pub async fn get_owner_memo_batch( Ok(web::Json(resp)) } +/// Returns the owner memo required to decrypt the asset record stored at given index, if it exists. +#[allow(clippy::unnecessary_wraps)] +async fn get_abar_memo( + data: web::Data>>, + info: web::Path, +) -> actix_web::Result>, actix_web::error::Error> { + let server = data.read(); + Ok(web::Json(server.get_abar_memo(ATxoSID(*info)))) +} + +/// Returns the owner memos required to decrypt the asset record stored at between start and end, +/// include start and end, limit 100. +async fn get_abar_memos( + data: web::Data>>, + query: web::Query>, +) -> actix_web::Result>, actix_web::error::Error> { + match (query.get("start"), query.get("end")) { + (Some(start), Some(end)) => { + if end < start || end - start > 100 { + // return limit 100 error. + return Err(actix_web::error::ErrorBadRequest("Limit 100")); + } + let server = data.read(); + Ok(web::Json(server.get_abar_memos(*start, *end))) + } + _ => Err(actix_web::error::ErrorBadRequest("Missing start and end")), + } +} + +/// Return the abar commitment by sid. +async fn get_abar_commitment( + data: web::Data>>, + info: web::Path, +) -> actix_web::Result>, actix_web::error::Error> { + let server = data.read(); + Ok(web::Json(server.get_abar_commitment(ATxoSID(*info)))) +} + /// Returns an array of the utxo sids currently spendable by a given address pub async fn get_owned_utxos( data: web::Data>>, @@ -112,6 +154,53 @@ pub async fn get_owned_utxos( Ok(web::Json(utxos)) } +/// Returns the ATxo Sid currently spendable by a given commitment +async fn get_owned_abar( + data: web::Data>>, + com: web::Path, +) -> actix_web::Result>> { + let qs = data.read(); + let ledger = &qs.ledger_cloned; + //let read = qs.state.as_ref().unwrap().read(); + globutils::wallet::commitment_from_base58(com.as_str()) + .c(d!()) + .map_err(|e| error::ErrorBadRequest(e.generate_log(None))) + .map(|com| web::Json(ledger.get_owned_abar(&com))) +} + +/// Returns the Merkle proof for anonymous transactions +async fn get_abar_proof( + data: web::Data>>, + info: web::Path, +) -> actix_web::Result>, actix_web::error::Error> { + let server = data.read(); + Ok(web::Json(server.get_abar_proof(ATxoSID(*info)))) +} + +/// Checks if a nullifier hash is present in nullifier set +async fn check_nullifier_hash( + data: web::Data>>, + info: web::Path, +) -> actix_web::Result>, actix_web::error::Error> { + let server = data.read(); + Ok(web::Json(server.check_nullifier_hash((*info).clone()))) +} + +async fn get_max_atxo_sid( + data: web::Data>>, +) -> actix_web::Result>, actix_web::error::Error> { + let server = data.read(); + Ok(web::Json(server.max_atxo_sid())) +} + +async fn get_max_atxo_sid_at_height( + data: web::Data>>, + info: web::Path, +) -> actix_web::Result>, actix_web::error::Error> { + let server = data.read(); + Ok(web::Json(server.max_atxo_sid_at_height(*info))) +} + /// Define interface type #[allow(missing_docs)] pub enum QueryServerRoutes { @@ -119,6 +208,14 @@ pub enum QueryServerRoutes { GetOwnerMemo, GetOwnerMemoBatch, GetOwnedUtxos, + GetOwnedAbars, + GetAbarCommitment, + GetAbarMemo, + GetAbarMemos, + GetAbarProof, + CheckNullifierHash, + GetMaxATxoSid, + GetMaxATxoSidAtHeight, GetCreatedAssets, GetIssuedRecords, GetIssuedRecordsByCode, @@ -137,8 +234,16 @@ impl NetworkRoute for QueryServerRoutes { QueryServerRoutes::GetRelatedTxns => "get_related_txns", QueryServerRoutes::GetRelatedXfrs => "get_related_xfrs", QueryServerRoutes::GetOwnedUtxos => "get_owned_utxos", + QueryServerRoutes::GetOwnedAbars => "get_owned_abar", QueryServerRoutes::GetOwnerMemo => "get_owner_memo", QueryServerRoutes::GetOwnerMemoBatch => "get_owner_memo_batch", + QueryServerRoutes::GetAbarCommitment => "get_abar_commitment", + QueryServerRoutes::GetAbarMemo => "get_abar_memo", + QueryServerRoutes::GetAbarMemos => "get_abar_memos", + QueryServerRoutes::GetAbarProof => "get_abar_proof", + QueryServerRoutes::CheckNullifierHash => "check_nullifier_hash", + QueryServerRoutes::GetMaxATxoSid => "get_max_atxo_sid", + QueryServerRoutes::GetMaxATxoSidAtHeight => "get_max_atxo_sid_at_height", QueryServerRoutes::GetCreatedAssets => "get_created_assets", QueryServerRoutes::GetIssuedRecords => "get_issued_records", QueryServerRoutes::GetIssuedRecordsByCode => "get_issued_records_by_code", @@ -519,6 +624,10 @@ impl QueryApi { &QueryServerRoutes::GetOwnedUtxos.with_arg_template("address"), web::get().to(get_owned_utxos), ) + .route( + &QueryServerRoutes::GetOwnedAbars.with_arg_template("commitment"), + web::get().to(get_owned_abar), + ) .route( &QueryServerRoutes::GetOwnerMemo.with_arg_template("txo_sid"), web::get().to(get_owner_memo), @@ -528,6 +637,36 @@ impl QueryApi { .with_arg_template("txo_sid_list"), web::get().to(get_owner_memo_batch), ) + .route( + &QueryServerRoutes::GetAbarCommitment.with_arg_template("atxo_sid"), + web::get().to(get_abar_commitment), + ) + .route( + &QueryServerRoutes::GetAbarMemo.with_arg_template("atxo_sid"), + web::get().to(get_abar_memo), + ) + .route( + &QueryServerRoutes::GetAbarMemos.route(), + web::get().to(get_abar_memos), + ) + .route( + &QueryServerRoutes::GetAbarProof.with_arg_template("atxo_sid"), + web::get().to(get_abar_proof), + ) + .route( + &QueryServerRoutes::CheckNullifierHash + .with_arg_template("null_hash"), + web::get().to(check_nullifier_hash), + ) + .route( + &QueryServerRoutes::GetMaxATxoSid.route(), + web::get().to(get_max_atxo_sid), + ) + .route( + &QueryServerRoutes::GetMaxATxoSidAtHeight + .with_arg_template("height"), + web::get().to(get_max_atxo_sid_at_height), + ) .route( &QueryServerRoutes::GetRelatedTxns.with_arg_template("address"), web::get().to(get_related_txns), @@ -617,6 +756,10 @@ impl QueryApi { &ApiRoutes::OwnedUtxos.with_arg_template("owner"), web::get().to(query_owned_utxos), ) + .route( + &ApiRoutes::OwnedAbars.with_arg_template("owner"), + web::get().to(query_owned_abar), + ) .route( &ApiRoutes::ValidatorList.route(), web::get().to(query_validators), diff --git a/src/components/abciapp/src/api/query_server/query_api/server.rs b/src/components/abciapp/src/api/query_server/query_api/server.rs index a1198a4d8..bca977019 100644 --- a/src/components/abciapp/src/api/query_server/query_api/server.rs +++ b/src/components/abciapp/src/api/query_server/query_api/server.rs @@ -3,11 +3,12 @@ //! use { + globutils::HashOf, lazy_static::lazy_static, ledger::{ data_model::{ - AssetTypeCode, DefineAsset, IssuerPublicKey, Transaction, TxOutput, - TxnIDHash, TxnSID, TxoSID, XfrAddress, + ATxoSID, AssetTypeCode, DefineAsset, IssuerPublicKey, StateCommitmentData, + Transaction, TxOutput, TxnIDHash, TxnSID, TxoSID, XfrAddress, }, staking::{ops::mint_fra::MintEntry, BlockHeight}, store::LedgerState, @@ -15,7 +16,10 @@ use { parking_lot::{Condvar, Mutex, RwLock}, ruc::*, std::{collections::HashSet, sync::Arc}, - zei::OwnerMemo, + zei::{ + noah_api::anon_xfr::structs::{AxfrOwnerMemo, Commitment, MTLeafInfo}, + OwnerMemo, + }, }; lazy_static! { @@ -300,6 +304,81 @@ impl QueryServer { .get(&txo_sid) } + /// Returns the abar owner memo required to decrypt the asset record stored at given index, if it exists. + #[inline(always)] + pub fn get_abar_memo(&self, atxo_sid: ATxoSID) -> Option { + self.ledger_cloned + .api_cache + .as_ref() + .and_then(|api| api.abar_memos.get(&atxo_sid)) + } + + /// Returns the owner memos required to decrypt the asset record stored at between start and end, + /// include start and end, limit 100. + #[inline(always)] + pub fn get_abar_memos(&self, start: u64, end: u64) -> Vec<(u64, AxfrOwnerMemo)> { + let mut memos = vec![]; + let cache = self.ledger_cloned.api_cache.as_ref().unwrap(); + for i in start..=end { + if let Some(memo) = cache.abar_memos.get(&ATxoSID(i)) { + memos.push((i, memo)); + } + } + memos + } + + #[inline(always)] + #[allow(missing_docs)] + pub fn get_state_commitment_from_api_cache( + &self, + ) -> (HashOf>, u64) { + let block_count = self.ledger_cloned.get_block_commit_count(); + let commitment = self + .ledger_cloned + .api_cache + .as_ref() + .unwrap() + .state_commitment_version + .clone() + .unwrap_or_else(|| HashOf::new(&None)); + (commitment, block_count) + } + + /// Returns the abar commitment by given index, if it exists. + pub fn get_abar_commitment(&self, atxo_sid: ATxoSID) -> Option { + self.ledger_cloned.get_abar(&atxo_sid) + } + + /// Returns the Merkle proof from the given ATxoSID + #[inline(always)] + pub fn get_abar_proof(&self, atxo_sid: ATxoSID) -> Option { + self.ledger_cloned.get_abar_proof(atxo_sid).ok() + } + + /// Returns a bool value from the given hash + #[inline(always)] + pub fn check_nullifier_hash(&self, null_hash: String) -> Option { + self.ledger_cloned.check_nullifier_hash(null_hash).ok() + } + + /// Returns an int value for the max ATxoSid + #[inline(always)] + pub fn max_atxo_sid(&self) -> Option { + self.ledger_cloned + .api_cache + .as_ref() + .and_then(|api| api.abar_memos.len().checked_sub(1)) + } + + /// Returns an int value for the max ATxoSid at a given block height + #[inline(always)] + pub fn max_atxo_sid_at_height(&self, height: BlockHeight) -> Option { + self.ledger_cloned + .api_cache + .as_ref() + .and_then(|api| api.height_to_max_atxo.get(&height).unwrap_or(None)) + } + /// retrieve block reward rate at specified block height #[inline(always)] pub fn query_block_rewards_rate(&self, height: &BlockHeight) -> Option<[u128; 2]> { diff --git a/src/components/config/src/abci/mod.rs b/src/components/config/src/abci/mod.rs index 9d92ac6ae..e06eb0468 100644 --- a/src/components/config/src/abci/mod.rs +++ b/src/components/config/src/abci/mod.rs @@ -92,6 +92,8 @@ pub struct CheckPointConfig { pub fix_exec_code: i64, + pub enable_triple_masking_height: i64, + #[serde(default = "def_check_signatures_num")] pub check_signatures_num: i64, @@ -171,14 +173,14 @@ fn def_utxo_asset_prefix_height() -> u64 { DEFAULT_CHECKPOINT_CONFIG.utxo_asset_prefix_height } -fn def_prismxx_inital_height() -> i64 { - DEFAULT_CHECKPOINT_CONFIG.prismxx_inital_height -} - fn def_utxo_asset_prefix_height_2nd_update() -> u64 { DEFAULT_CHECKPOINT_CONFIG.utxo_asset_prefix_height_2nd_update } +fn def_prismxx_inital_height() -> i64 { + DEFAULT_CHECKPOINT_CONFIG.prismxx_inital_height +} + fn def_prism_bridge_address() -> String { DEFAULT_CHECKPOINT_CONFIG.prism_bridge_address.clone() } @@ -259,6 +261,7 @@ lazy_static! { evm_substate_v2_height: 0, disable_delegate_frc20: 0, fix_exec_code: 0, + enable_triple_masking_height: 0, check_signatures_num: 0, fix_deliver_tx_revert_nonce_height: 0, utxo_asset_prefix_height: 0, @@ -307,6 +310,7 @@ lazy_static! { evm_substate_v2_height: 3351349, disable_delegate_frc20: 3401450, fix_exec_code: 3401450, + enable_triple_masking_height: 5000_0000, check_signatures_num: 4004430, fix_deliver_tx_revert_nonce_height: 4004430, utxo_asset_prefix_height: 4004430, diff --git a/src/components/contracts/baseapp/Cargo.toml b/src/components/contracts/baseapp/Cargo.toml index 32483d3ba..996605b1a 100644 --- a/src/components/contracts/baseapp/Cargo.toml +++ b/src/components/contracts/baseapp/Cargo.toml @@ -48,8 +48,12 @@ evm-precompile = {path = "../modules/evm/precompile"} evm-precompile-basic = {path = "../modules/evm/precompile/basic"} evm-precompile-frc20 = {path = "../modules/evm/precompile/frc20"} evm-precompile-modexp = {path = "../modules/evm/precompile/modexp"} +evm-precompile-blake2 = {path = "../modules/evm/precompile/blake2"} +evm-precompile-bn128 = {path = "../modules/evm/precompile/bn128"} +evm-precompile-anemoi = {path = "../modules/evm/precompile/anemoi"} + [features] abci_mock = [] benchmark = ["module-evm/benchmark","module-ethereum/benchmark"] -debug_env = [] \ No newline at end of file +debug_env = [] diff --git a/src/components/contracts/baseapp/src/lib.rs b/src/components/contracts/baseapp/src/lib.rs index 049c4ec9c..8caa73d15 100644 --- a/src/components/contracts/baseapp/src/lib.rs +++ b/src/components/contracts/baseapp/src/lib.rs @@ -164,7 +164,13 @@ impl module_evm::Config for BaseApp { evm_precompile_basic::Ripemd160, evm_precompile_basic::Identity, evm_precompile_modexp::Modexp, + evm_precompile_bn128::Bn128Add, + evm_precompile_bn128::Bn128Mul, + evm_precompile_bn128::Bn128Pairing, + evm_precompile_blake2::Blake2F, evm_precompile_frc20::FRC20, + evm_precompile_anemoi::Anemoi254, + evm_precompile_anemoi::Anemoi381, ); type PrecompilesType = FindoraPrecompiles; type PrecompilesValue = PrecompilesValue; diff --git a/src/components/contracts/baseapp/src/staking.rs b/src/components/contracts/baseapp/src/staking.rs index 74cfc7e97..b588ab99f 100644 --- a/src/components/contracts/baseapp/src/staking.rs +++ b/src/components/contracts/baseapp/src/staking.rs @@ -15,8 +15,7 @@ use module_evm::{ use ruc::{d, Result, RucResult}; use sha3::{Digest, Keccak256}; use std::{collections::BTreeMap, str::FromStr}; -use zei::noah_algebra::prelude::NoahFromToBytes; -use zei::XfrPublicKey; +use zei::{noah_algebra::prelude::NoahFromToBytes, XfrPublicKey}; impl EVMStaking for BaseApp { fn import_validators( diff --git a/src/components/contracts/modules/account/Cargo.toml b/src/components/contracts/modules/account/Cargo.toml index 41ab844e4..c12d7b5aa 100644 --- a/src/components/contracts/modules/account/Cargo.toml +++ b/src/components/contracts/modules/account/Cargo.toml @@ -24,9 +24,9 @@ fp-traits = { path = "../../primitives/traits" } fp-types = { path = "../../primitives/types" } enterprise-web3 = { path = "../../primitives/enterprise-web3" } config = { path = "../../../config"} +zei = { package="platform-lib-noah", git = "https://github.com/FindoraNetwork/platform-lib-noah", branch = "develop" } [dev-dependencies] rand_chacha = "0.3" parking_lot = "0.12" -zei = { package="platform-lib-noah", git = "https://github.com/FindoraNetwork/platform-lib-noah", branch = "develop" } fin_db = { git = "https://github.com/FindoraNetwork/storage.git", tag = "v1.1.6" } diff --git a/src/components/contracts/modules/evm/precompile/Cargo.toml b/src/components/contracts/modules/evm/precompile/Cargo.toml index 61af3152e..943af994c 100644 --- a/src/components/contracts/modules/evm/precompile/Cargo.toml +++ b/src/components/contracts/modules/evm/precompile/Cargo.toml @@ -20,3 +20,4 @@ evm-precompile-bn128 = {path = "./bn128"} fp-core = {path = "../../../primitives/core"} module-evm = {path = "../../../modules/evm"} parking_lot = "0.12" +evm-precompile-eth-pairings = { path = "./eth-pairings" } diff --git a/src/components/contracts/modules/evm/precompile/basic/Cargo.toml b/src/components/contracts/modules/evm/precompile/basic/Cargo.toml index e8b9ad551..5aa4d2c9d 100644 --- a/src/components/contracts/modules/evm/precompile/basic/Cargo.toml +++ b/src/components/contracts/modules/evm/precompile/basic/Cargo.toml @@ -18,8 +18,5 @@ evm = { version = "0.35.0", default-features = false, features = ["with-serde"] module-evm = { path = "../../../../modules/evm"} ripemd = "0.1" -# primitives -#fp-core = { path = "../../../../primitives/core" } -#fp-evm = { path = "../../../../primitives/evm" } fp-types = { path = "../../../../primitives/types" } fp-utils = { path = "../../../../primitives/utils" } \ No newline at end of file diff --git a/src/components/contracts/modules/evm/precompile/eth-pairings/src/lib.rs b/src/components/contracts/modules/evm/precompile/eth-pairings/src/lib.rs index 66f28d8d3..470fec3c3 100644 --- a/src/components/contracts/modules/evm/precompile/eth-pairings/src/lib.rs +++ b/src/components/contracts/modules/evm/precompile/eth-pairings/src/lib.rs @@ -3,11 +3,7 @@ mod tests; use eth_pairings::public_interface::{perform_operation, ApiError, OperationType}; use evm::executor::stack::{PrecompileFailure, PrecompileOutput}; -use evm::{ - // Context, - ExitError, - ExitSucceed, -}; +use evm::{Context, ExitError, ExitSucceed}; use evm_precompile_utils::{EvmDataReader, EvmDataWriter, EvmResult, Gasometer}; use module_evm::precompile::{FinState, Precompile, PrecompileId, PrecompileResult}; use tracing::debug; @@ -29,12 +25,11 @@ pub enum Call { impl Precompile for EthPairing { fn execute( - handle: &mut impl PrecompileHandle, + input: &[u8], + target_gas: Option, + _context: &Context, _state: &FinState, ) -> PrecompileResult { - let input = handle.input(); - let target_gas = handle.gas_limit(); - let mut input = EvmDataReader::new(input); let selector = match input.read_selector::() { Ok(v) => v, @@ -91,11 +86,11 @@ impl EthPairing { Ok(PrecompileOutput { exit_status: ExitSucceed::Returned, - // cost: gasometer.used_gas(), + cost: gasometer.used_gas(), output: EvmDataWriter::new() .write_raw_bytes(result.as_slice()) .build(), - // logs: vec![], + logs: vec![], }) } } diff --git a/src/components/contracts/modules/evm/precompile/eth-pairings/src/tests.rs b/src/components/contracts/modules/evm/precompile/eth-pairings/src/tests.rs new file mode 100644 index 000000000..2ff06a41d --- /dev/null +++ b/src/components/contracts/modules/evm/precompile/eth-pairings/src/tests.rs @@ -0,0 +1,29 @@ +use crate::*; +use ethereum_types::H160; +use fp_mocks::*; + +// Test from eth-pairings (eip1962) repository https://github.com/FindoraNetwork/eip1962 +#[test] +fn test_bls12_pairing() { + use hex; + let hex_string = "07202912811758d871b77a9c3635c28570dc020576f9fc2719d8d0494439162b2b89000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011603e65409b693c8a08aeb3478d10aa3732a6672ba06d12912811758d871b77a9c3635c28570dc020576f9fc2719d8d0494439162b2b84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010206059efde42f3701020127ccb2831f0011c1d547c491b9dffbd4cfdb87598a4b370f5873ea62094a2f201faa6cb7f6fcca66de3f308a25776dac3f61edb792948fbe53e4d18a3d8aefbe011a7e9f75f3fdc77b83a97f7acd58326a0545f8aa37b69bfb32c52dc195763e8c17176e0ad4ee94d9c720e922d42688127c4b812cd7c2f8cf6126acd4c3d7568121e48b3fefe66c279f2ec71f0d6f8156a3343d1cfa54b808d747cd02419278290ad2d7d03f5de1e7b3c97732f53dbe1dfd42e51f9571f7fee3d9c1785d5a1ed6010b4f7f211a0a5f4425728e2df580196d3e3b85ef148ed769acd23e9be6e8440726cb40655787f48eaf46154cb740e2a58db5b96fa02d83fb9d0f94320da1471e0104ece4c46ac4f05a7c28ecda84292f15999747bb77c530c65448f1f837a47dd70e972c4065d0b39d40b5d550a55901516afa7f02b395963d1535fcba1705e31a117cb4beab1dc582198c4ab0c02e96a22f7bd10dde3bbbdbc9182a9596cb0ed32121616b692e8036437efb4c3816f018f11e643c6e0a049da431986a3a722b06"; + let data = hex::decode(hex_string).unwrap(); + + let output = EthPairing::execute( + &EvmDataWriter::new() + .write_selector(Call::ExecuteOperation) + .write_raw_bytes(&data) + .build(), + None, + &evm::Context { + address: H160::from_low_u64_be(2001), + caller: ALICE_ECDSA.address, + apparent_value: From::from(0), + }, + &BASE_APP.lock().unwrap().deliver_state, + ); + + assert!(output.is_ok()); + assert_eq!(output.as_ref().unwrap().cost, 164986); + assert_eq!(output.unwrap().output, vec![0x1]); +} diff --git a/src/components/contracts/modules/evm/precompile/utils/Cargo.toml b/src/components/contracts/modules/evm/precompile/utils/Cargo.toml index d525e8599..5a2091980 100644 --- a/src/components/contracts/modules/evm/precompile/utils/Cargo.toml +++ b/src/components/contracts/modules/evm/precompile/utils/Cargo.toml @@ -12,4 +12,4 @@ evm = { version = "0.35.0", default-features = false, features = ["with-serde"] tracing = "0.1" num_enum = { version = "0.5.3", default-features = false } precompile-utils-macro = { path = "macro" } -sha3 = { version = "0.8", default-features = false } +sha3 = { version = "0.10", default-features = false } diff --git a/src/components/contracts/modules/evm/precompile/utils/src/data.rs b/src/components/contracts/modules/evm/precompile/utils/src/data.rs index 99684f5f6..0958189bc 100644 --- a/src/components/contracts/modules/evm/precompile/utils/src/data.rs +++ b/src/components/contracts/modules/evm/precompile/utils/src/data.rs @@ -115,6 +115,11 @@ impl<'a> EvmDataReader<'a> { Ok(start..end) } + + /// Get slice from cursor to the end of buffer + pub fn get_slice(&mut self) -> &[u8] { + &self.input[self.cursor..] + } } /// Help build an EVM input/output data. diff --git a/src/components/contracts/modules/evm/src/lib.rs b/src/components/contracts/modules/evm/src/lib.rs index 9ded144a5..20ddb498b 100644 --- a/src/components/contracts/modules/evm/src/lib.rs +++ b/src/components/contracts/modules/evm/src/lib.rs @@ -16,8 +16,7 @@ use ethabi::Token; use ethereum::{ Log, ReceiptV0 as Receipt, TransactionAction, TransactionSignature, TransactionV0, }; -use ethereum_types::U256; -use ethereum_types::{Bloom, BloomInput, H160, H256}; +use ethereum_types::{Bloom, BloomInput, H160, H256, U256}; use evm::executor::stack::PrecompileSet as EvmPrecompileSet; use fp_core::{ context::Context, @@ -33,10 +32,10 @@ use fp_traits::{ account::AccountAsset, evm::{AddressMapping, BlockHashMapping, DecimalsMapping, FeeCalculator}, }; -use fp_types::crypto::HA256; + use fp_types::{ actions::evm::Action, - crypto::{Address, HA160}, + crypto::{Address, HA160, HA256}, }; use ledger::staking::evm::EVM_STAKING_MINTS; use ledger::staking::FRA_PRE_ISSUE_AMOUNT; @@ -50,8 +49,7 @@ use std::marker::PhantomData; use std::str::FromStr; use system_contracts::{SystemContracts, SYSTEM_ADDR}; use utils::parse_evm_staking_coinbase_mint_event; -use zei::noah_algebra::serialization::NoahFromToBytes; -use zei::XfrPublicKey; +use zei::{noah_algebra::serialization::NoahFromToBytes, XfrPublicKey}; use crate::utils::parse_evm_staking_mint_event; diff --git a/src/components/contracts/modules/evm/src/precompile.rs b/src/components/contracts/modules/evm/src/precompile.rs index ac28fe117..5eb302871 100644 --- a/src/components/contracts/modules/evm/src/precompile.rs +++ b/src/components/contracts/modules/evm/src/precompile.rs @@ -2,10 +2,7 @@ use crate::runtime::stack::FindoraStackState; use ethereum_types::H160; use evm::{ executor::stack::{PrecompileFailure, PrecompileOutput}, - Context, - // Context, - ExitError, - ExitSucceed, + Context, ExitError, ExitSucceed, }; use impl_trait_for_tuples::impl_for_tuples; diff --git a/src/components/contracts/primitives/core/src/module.rs b/src/components/contracts/primitives/core/src/module.rs index 7a7e843a4..cf0c284c7 100644 --- a/src/components/contracts/primitives/core/src/module.rs +++ b/src/components/contracts/primitives/core/src/module.rs @@ -1,6 +1,6 @@ use crate::context::Context; use abci::*; -use primitive_types::U256; +use fp_types::U256; use ruc::Result; /// AppModuleBasic is the standard form for basic non-dependant elements of an application module. @@ -45,4 +45,13 @@ pub trait AppModule: AppModuleBasic { ) -> (ResponseEndBlock, U256) { Default::default() } + + fn commit( + &mut self, + _ctx: &mut Context, + _height: U256, + _root_hash: &[u8], + ) -> Result<()> { + Ok(()) + } } diff --git a/src/components/contracts/primitives/types/Cargo.toml b/src/components/contracts/primitives/types/Cargo.toml index 23795d7ff..497e24d50 100644 --- a/src/components/contracts/primitives/types/Cargo.toml +++ b/src/components/contracts/primitives/types/Cargo.toml @@ -20,6 +20,7 @@ primitive-types = { version = "0.11.1", default-features = false, features = ["r ruc = "1.0" serde = { version = "1.0.124", features = ["derive"] } serde_json = "1.0" +serde-big-array = "0.4" sha3 = "0.10" zei = { package="platform-lib-noah", git = "https://github.com/FindoraNetwork/platform-lib-noah", branch = "develop" } fixed-hash = "0.8.0" diff --git a/src/components/contracts/primitives/types/src/actions/account.rs b/src/components/contracts/primitives/types/src/actions/account.rs index dc608aa2a..4814f4b04 100644 --- a/src/components/contracts/primitives/types/src/actions/account.rs +++ b/src/components/contracts/primitives/types/src/actions/account.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; -use zei::XfrPublicKey; -use zei::noah_api::xfr::structs::AssetType; +use zei::{ + noah_api::xfr::structs::AssetType, XfrPublicKey +}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Action { diff --git a/src/components/contracts/primitives/types/src/crypto.rs b/src/components/contracts/primitives/types/src/crypto.rs index f29de6cb8..6345fc11e 100644 --- a/src/components/contracts/primitives/types/src/crypto.rs +++ b/src/components/contracts/primitives/types/src/crypto.rs @@ -11,11 +11,10 @@ use { serde::{Deserialize, Serialize}, sha3::{Digest, Keccak256}, std::ops::{Deref, DerefMut}, - zei::noah_algebra::serialization::NoahFromToBytes, - zei::{XfrPublicKey, XfrSignature}, + zei::{noah_algebra::serialization::NoahFromToBytes, XfrPublicKey, XfrSignature}, }; -/// An opaque 32-byte cryptographic identifier. +/// An opaque 34-byte cryptographic identifier. #[derive( Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash, Serialize, Deserialize, Debug, )] @@ -261,9 +260,6 @@ impl Verify for MultiSignature { } } Self::Ecdsa(ref sig) => { - // let mut msg_hashed = [0u8; 32]; - // msg_hashed.copy_from_slice(msg); - let msg_hashed = keccak_256(msg); match secp256k1_ecdsa_recover(sig.as_ref(), &msg_hashed) { Ok(pubkey) => { diff --git a/src/components/contracts/rpc/Cargo.toml b/src/components/contracts/rpc/Cargo.toml index 10400b9c5..a7eef232f 100644 --- a/src/components/contracts/rpc/Cargo.toml +++ b/src/components/contracts/rpc/Cargo.toml @@ -26,7 +26,7 @@ jsonrpc-derive = { git = "https://github.com/FindoraNetwork/jsonrpc.git", packag jsonrpc-pubsub = { git = "https://github.com/FindoraNetwork/jsonrpc.git", package = "jsonrpc-pubsub" } jsonrpc-http-server = { git = "https://github.com/FindoraNetwork/jsonrpc.git", package = "jsonrpc-http-server" } jsonrpc-tcp-server = { git = "https://github.com/FindoraNetwork/jsonrpc.git", package = "jsonrpc-tcp-server" } -libsecp256k1 = { version = "0.5", features = ["static-context", "hmac"] } +libsecp256k1 = { version = "0.7", features = ["static-context", "hmac"] } lazy_static = "1.4.0" tracing = "0.1" rand = "0.8" @@ -39,8 +39,8 @@ rustc_version = "0.4.0" semver = "1.0.4" serde_json = "1.0" sha3 = "0.10" -tendermint = { git = "https://github.com/FindoraNetwork/tendermint-rs", tag = "v0.19.0a-fk" } -tendermint-rpc = { git = "https://github.com/FindoraNetwork/tendermint-rs", features = ["http-client", "websocket-client"], tag = "v0.19.0a-fk" } +tendermint = { git = "https://github.com/FindoraNetwork/tendermint-rs", tag = "v0.19.0c" } +tendermint-rpc = { git = "https://github.com/FindoraNetwork/tendermint-rs", features = ["http-client", "websocket-client"], tag = "v0.19.0c" } tokio = { version = "1.10.1", features = ["full"] } lru = "0.7" num_cpus = "1.13" diff --git a/src/components/finutils/Cargo.toml b/src/components/finutils/Cargo.toml index d630f1a58..4c427d9cb 100644 --- a/src/components/finutils/Cargo.toml +++ b/src/components/finutils/Cargo.toml @@ -17,16 +17,18 @@ serde = { version = "1.0.124", features = ["derive"] } rand = "0.8" rand_core = { version = "0.6", default-features = false, features = ["alloc"] } rand_chacha = "0.3" -noah-curve25519-dalek = { version = "4.0.0", features = ["serde"] } +curve25519-dalek = { package = "noah-curve25519-dalek", version = "4.0.0", default-features = false, features = ['serde'] } wasm-bindgen = { version = "=0.2.84", features = ["serde-serialize"] } sha2 = "0.10" +digest = '0.10' +parking_lot = "0.12" +getrandom = "0.2" zei = { package="platform-lib-noah", git = "https://github.com/FindoraNetwork/platform-lib-noah", branch = "develop" } ruc = "1.0" rucv4 = { package = "ruc", version = "4.0" } nix = "0.25" -ledger = { path = "../../ledger" } - +ledger = { path = "../../ledger", default-features = false } globutils = { git = "https://github.com/FindoraNetwork/platform-lib-utils", branch = "develop" } credentials = { git = "https://github.com/FindoraNetwork/platform-lib-credentials", branch = "develop" } @@ -35,11 +37,10 @@ fp-core = { path = "../contracts/primitives/core", default-features = false } fp-utils = { path = "../contracts/primitives/utils" } fp-types = { path = "../contracts/primitives/types" } -tendermint = { git = "https://github.com/FindoraNetwork/tendermint-rs", tag = "v0.19.0a-fk" } -tendermint-rpc = { git = "https://github.com/FindoraNetwork/tendermint-rs", features = ["http-client", "websocket-client"], optional = true, tag = "v0.19.0a-fk" } +tendermint = { git = "https://github.com/FindoraNetwork/tendermint-rs", tag = "v0.19.0c" } +tendermint-rpc = { git = "https://github.com/FindoraNetwork/tendermint-rs", features = ["http-client", "websocket-client"], optional = true, tag = "v0.19.0c" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -# chaindev = { path = "../../../../chaindev" } chaindev = { git = "https://github.com/FindoraNetwork/chaindev", branch = "platform", default-features = false, features = ["tendermint_based", "vsdb_sled_engine"] } web3 = "0.19.0" tokio = "1.10.1" diff --git a/src/components/finutils/src/bins/fn.rs b/src/components/finutils/src/bins/fn.rs index 40889dc9f..83c45275f 100644 --- a/src/components/finutils/src/bins/fn.rs +++ b/src/components/finutils/src/bins/fn.rs @@ -31,15 +31,16 @@ use fp_types::H160; use { clap::{crate_authors, load_yaml, App}, - finutils::common::{self, evm::*}, + finutils::common::{self, evm::*, get_keypair, utils}, fp_utils::ecdsa::SecpPair, globutils::wallet, ledger::{ - data_model::{AssetTypeCode, FRA_DECIMALS}, + data_model::{AssetTypeCode, ASSET_TYPE_FRA, FRA_DECIMALS}, staking::{StakerMemo, VALIDATORS_MIN}, }, ruc::*, std::{fmt, fs}, + zei::{noah_api::anon_xfr::structs::OpenAnonAssetRecordBuilder, XfrSecretKey}, }; fn main() { @@ -59,11 +60,13 @@ fn run() -> Result<()> { if matches.is_present("version") { println!("{}", env!("VERGEN_SHA")); - } else if matches.is_present("genkey") { - common::gen_key_and_print(); + } else if let Some(m) = matches.subcommand_matches("genkey") { + let gen_eth_address = m.is_present("gen-eth-address"); + common::gen_key_and_print(gen_eth_address); } else if let Some(m) = matches.subcommand_matches("wallet") { if m.is_present("create") { - common::gen_key_and_print(); + let is_address_eth = m.is_present("gen-eth-address"); + common::gen_key_and_print(is_address_eth); } else if m.is_present("show") { let seckey = match m.value_of("seckey") { Some(path) => { @@ -71,6 +74,8 @@ fn run() -> Result<()> { } None => None, }; + let is_address_eth = m.is_present("use-default-eth-address"); + // FRA asset is the default case let asset = if let Some(code) = m.value_of("asset") { match code.to_lowercase().as_str() { @@ -80,7 +85,7 @@ fn run() -> Result<()> { } else { None }; - common::show_account(seckey.as_deref(), asset).c(d!())?; + common::show_account(seckey.as_deref(), asset, is_address_eth).c(d!())?; } else { println!("{}", m.usage()); } @@ -94,16 +99,18 @@ fn run() -> Result<()> { let amount = m.value_of("amount"); let validator = m.value_of("validator"); let show_info = m.is_present("info"); + let is_address_eth = m.is_present("use-default-eth-address"); if amount.is_some() && validator.is_some() { common::delegate( seckey.as_deref(), amount.unwrap().parse::().c(d!())?, validator.unwrap(), + is_address_eth, ) .c(d!())?; } else if show_info { - common::show_delegations(seckey.as_deref()).c(d!())?; + common::show_delegations(seckey.as_deref(), is_address_eth).c(d!())?; } else { println!("{}", m.usage()); } @@ -116,6 +123,7 @@ fn run() -> Result<()> { }; let amount = m.value_of("amount"); let validator = m.value_of("validator"); + let is_address_eth = m.is_present("use-default-eth-address"); if (amount.is_none() && validator.is_some()) || (amount.is_some() && validator.is_none()) { @@ -127,21 +135,18 @@ fn run() -> Result<()> { } else { None }; - common::undelegate(seckey.as_deref(), param).c(d!())?; + common::undelegate(seckey.as_deref(), param, is_address_eth).c(d!())?; } else if let Some(m) = matches.subcommand_matches("asset") { if m.is_present("create") { - let seckey = match m.value_of("seckey") { - Some(path) => { - Some(fs::read_to_string(path).c(d!("Failed to read seckey file"))?) - } - None => None, - }; + let seckey = read_file_path(m.value_of("seckey")).c(d!())?; let memo = m.value_of("memo"); if memo.is_none() { println!("{}", m.usage()); return Ok(()); } let transferable = m.is_present("transferable"); + let is_address_eth = m.is_present("use-default-eth-address"); + let decimal = if let Some(num) = m.value_of("decimal") { num.parse::() .c(d!("decimal should be an 8-bits unsinged integer"))? @@ -164,6 +169,7 @@ fn run() -> Result<()> { max_units, transferable, token_code, + is_address_eth, ) .c(d!())?; } else if m.is_present("show") { @@ -192,12 +198,19 @@ fn run() -> Result<()> { .parse::() .c(d!("amount should be a 64-bits unsigned integer"))?; let hidden = m.is_present("hidden"); + let is_address_eth = m.is_present("use-default-eth-address"); - common::issue_asset(seckey.as_deref(), code.unwrap(), amount, hidden) - .c(d!())?; + common::issue_asset( + seckey.as_deref(), + code.unwrap(), + amount, + hidden, + is_address_eth, + ) + .c(d!())?; } else { let help = "fn asset [--create | --issue | --show]"; - println!("{help}"); + println!("{help}",); } } else if let Some(m) = matches.subcommand_matches("staker-update") { let vm = if let Some(memo) = m.value_of("validator-memo") { @@ -228,6 +241,7 @@ fn run() -> Result<()> { } } }; + let is_address_eth = m.is_present("use-eth-address"); let cr = m.value_of("commission-rate"); if vm.is_none() && cr.is_none() { @@ -236,7 +250,7 @@ fn run() -> Result<()> { "Tips: to update the information of your node, please specify commission-rate or memo" ); } else { - common::staker_update(cr, vm).c(d!())?; + common::staker_update(cr, vm, is_address_eth).c(d!())?; } } else if let Some(m) = matches.subcommand_matches("stake") { let am = m.value_of("amount"); @@ -248,22 +262,31 @@ fn run() -> Result<()> { None => None, }; let td_addr = m.value_of("validator-td-addr"); + let is_address_eth = m.is_present("use-default-eth-address"); if am.is_none() { println!("{}", m.usage()); } else { - common::stake_append(am.unwrap(), staker.as_deref(), td_addr).c(d!())?; + common::stake_append( + am.unwrap(), + staker.as_deref(), + td_addr, + is_address_eth, + ) + .c(d!())?; } } else { let cr = m.value_of("commission-rate"); let vm = m.value_of("validator-memo"); let force = m.is_present("force"); + let is_address_eth = m.is_present("use-default-eth-address"); if am.is_none() || cr.is_none() { println!("{}", m.usage()); println!( "Tips: if you want to raise the power of your node, please use `fn stake --append [OPTIONS]`" ); } else { - common::stake(am.unwrap(), cr.unwrap(), vm, force).c(d!())?; + common::stake(am.unwrap(), cr.unwrap(), vm, force, is_address_eth) + .c(d!())?; } } } else if let Some(m) = matches.subcommand_matches("unstake") { @@ -275,9 +298,11 @@ fn run() -> Result<()> { None => None, }; let td_addr = m.value_of("validator-td-addr"); - common::unstake(am, staker.as_deref(), td_addr).c(d!())?; + let is_address_eth = m.is_present("use-default-eth-address"); + common::unstake(am, staker.as_deref(), td_addr, is_address_eth).c(d!())?; } else if let Some(m) = matches.subcommand_matches("claim") { let am = m.value_of("amount"); + let is_address_eth = m.is_present("use-default-eth-address"); let seckey = match m.value_of("seckey") { Some(path) => { Some(fs::read_to_string(path).c(d!("Failed to read seckey file"))?) @@ -291,10 +316,11 @@ fn run() -> Result<()> { return Ok(()); } }; - common::claim(td_addr, am, seckey.as_deref()).c(d!())?; + common::claim(td_addr, am, seckey.as_deref(), is_address_eth).c(d!())?; } else if let Some(m) = matches.subcommand_matches("show") { let basic = m.is_present("basic"); - common::show(basic).c(d!())?; + let is_address_eth = m.is_present("eth-address"); + common::show(basic, is_address_eth).c(d!())?; } else if let Some(m) = matches.subcommand_matches("setup") { let sa = m.value_of("serv-addr"); let om = m.value_of("owner-mnemonic-path"); @@ -305,12 +331,7 @@ fn run() -> Result<()> { common::setup(sa, om, tp).c(d!())?; } } else if let Some(m) = matches.subcommand_matches("transfer") { - let f = match m.value_of("from-seckey") { - Some(path) => { - Some(fs::read_to_string(path).c(d!("Failed to read seckey file"))?) - } - None => None, - }; + let f = read_file_path(m.value_of("from-seckey")).c(d!())?; let asset = m.value_of("asset").unwrap_or("FRA"); let t = m .value_of("to-pubkey") @@ -322,6 +343,7 @@ fn run() -> Result<()> { }) })?; let am = m.value_of("amount"); + let is_address_eth = m.is_present("use-default-eth-address"); if am.is_none() { println!("{}", m.usage()); @@ -338,6 +360,7 @@ fn run() -> Result<()> { am.unwrap(), m.is_present("confidential-amount"), m.is_present("confidential-type"), + is_address_eth, ) .c(d!())?; } @@ -371,6 +394,7 @@ fn run() -> Result<()> { }) })?; let am = m.value_of("amount"); + let is_address_eth = m.is_present("use-default-eth-address"); if am.is_none() || t.is_empty() { println!("{}", m.usage()); @@ -382,6 +406,7 @@ fn run() -> Result<()> { am.unwrap(), m.is_present("confidential-amount"), m.is_present("confidential-type"), + is_address_eth, ) .c(d!())?; } @@ -396,26 +421,340 @@ fn run() -> Result<()> { ); } else if let Some(m) = matches.subcommand_matches("account") { let address = m.value_of("addr"); - let (account, info) = contract_account_info(address)?; - println!("AccountId: {account}\n{info:#?}\n"); + let sec_key = m.value_of("sec-key"); + let is_address_eth = m.is_present("use-default-eth-address"); + + // FRA asset is the default case + let asset = if let Some(code) = m.value_of("asset") { + match code.to_lowercase().as_str() { + "fra" => None, + _ => Some(code), + } + } else { + None + }; + if sec_key.is_some() { + // Asset defaults to fra + common::show_account(sec_key, asset, is_address_eth).c(d!())?; + } + if address.is_some() { + let (account, info) = contract_account_info(address, is_address_eth)?; + println!("AccountId: {account}\n{info:#?}\n"); + } } else if let Some(m) = matches.subcommand_matches("contract-deposit") { let amount = m.value_of("amount").c(d!())?; let address = m.value_of("addr"); let asset = m.value_of("asset"); let lowlevel_data = m.value_of("lowlevel-data"); + let is_address_eth = m.is_present("eth-address"); transfer_to_account( amount.parse::().c(d!())?, address, asset, lowlevel_data, - ) - .c(d!())? + is_address_eth, + )? } else if let Some(m) = matches.subcommand_matches("contract-withdraw") { let amount = m.value_of("amount").c(d!())?; let address = m.value_of("addr"); let eth_key = m.value_of("eth-key"); - transfer_from_account(amount.parse::().c(d!())?, address, eth_key) - .c(d!())? + let is_address_eth = m.is_present("eth-address"); + transfer_from_account( + amount.parse::().c(d!())?, + address, + eth_key, + is_address_eth, + )? + } else if let Some(m) = matches.subcommand_matches("convert-bar-to-abar") { + // sender Xfr secret key + let owner_sk = read_file_path(m.value_of("from-seckey")).c(d!())?; + + // the receiver Xfr address + let target_addr = m.value_of("to-address").c(d!())?; + + // The TxoSID to be spent for conversion to ABAR (Anon Blind Asset Record) + let txo_sid = m.value_of("txo-sid"); + let is_address_eth = m.is_present("use-default-eth-address"); + + if txo_sid.is_none() { + println!("{}", m.usage()); + } else { + // call the convert function to build and send transaction + // it takes owner Xfr secret key, Axfr address and TxoSID + let r = common::convert_bar2abar( + owner_sk.as_ref(), + target_addr, + txo_sid.unwrap(), + is_address_eth, + ) + .c(d!())?; + + // Print commitment to terminal + println!( + "\x1b[31;01m Commitment: {}\x1b[00m", + wallet::commitment_to_base58(&r) + ); + // write the commitment base64 form to the owned_commitments file + let mut file = fs::OpenOptions::new() + .append(true) + .create(true) + .open("owned_commitments") + .expect("cannot open commitments file"); + std::io::Write::write_all( + &mut file, + ("\n".to_owned() + &wallet::commitment_to_base58(&r)).as_bytes(), + ) + .expect("commitment write failed"); + } + } else if let Some(m) = matches.subcommand_matches("convert-abar-to-bar") { + let is_address_eth = m.is_present("use-default-eth-address"); + // sender Xfr secret key + let owner_sk = read_file_path(m.value_of("from-seckey")).c(d!())?; + + // get the BAR receiver address + let to = m + .value_of("to-pubkey") + .c(d!()) + .and_then(wallet::public_key_from_base64) + .or_else(|_| { + m.value_of("to-wallet-address").c(d!()).and_then(|addr| { + wallet::public_key_from_bech32(addr).c(d!("invalid wallet address")) + }) + })?; + + // get the commitments for abar conversion and anon_fee + let commitment = m.value_of("commitment"); + + if commitment.is_none() { + println!("{}", m.usage()); + } else { + // Build transaction and submit to network + common::convert_abar2bar( + owner_sk, + commitment.unwrap(), + &to, + m.is_present("confidential-amount"), + m.is_present("confidential-type"), + is_address_eth, + ) + .c(d!())?; + } + } else if let Some(m) = matches.subcommand_matches("owned-abars") { + let is_address_eth = m.is_present("use-default-eth-address"); + // sender Xfr secret key + let owner_sk = read_file_path(m.value_of("from-seckey")).c(d!())?; + // parse sender XfrSecretKey or generate from Mnemonic setup with wallet + let from = match owner_sk { + Some(str) => { + ruc::info!(serde_json::from_str::(&format!("\"{str}\""))) + .c(d!())? + .into_keypair() + } + None => get_keypair(is_address_eth).c(d!())?, + }; + + let commitments_list = m + .value_of("commitments") + .unwrap_or_else(|| panic!("Commitment list missing \n {}", m.usage())); + + common::get_owned_abars(from, commitments_list)?; + } else if let Some(m) = matches.subcommand_matches("anon-balance") { + let is_address_eth = m.is_present("use-default-eth-address"); + // Generates a list of owned Abars (both spent and unspent) + // sender Xfr secret key + let owner_sk = read_file_path(m.value_of("from-seckey")).c(d!())?; + // parse sender XfrSecretKey or generate from Mnemonic setup with wallet + let from = match owner_sk { + Some(str) => { + ruc::info!(serde_json::from_str::(&format!("\"{str}\""))) + .c(d!(str))? + .into_keypair() + } + None => get_keypair(is_address_eth).c(d!())?, + }; + let asset = m.value_of("asset"); + + let commitments_list = m + .value_of("commitments") + .unwrap_or_else(|| panic!("Commitment list missing \n {}", m.usage())); + + common::anon_balance(from, commitments_list, asset)?; + } else if let Some(m) = matches.subcommand_matches("owned-open-abars") { + let is_address_eth = m.is_present("use-default-eth-address"); + // sender Xfr secret key + let owner_sk = read_file_path(m.value_of("from-seckey")).c(d!())?; + // parse sender XfrSecretKey or generate from Mnemonic setup with wallet + let from = match owner_sk { + Some(str) => { + ruc::info!(serde_json::from_str::(&format!("\"{str}\""))) + .c(d!())? + .into_keypair() + } + None => get_keypair(is_address_eth).c(d!())?, + }; + let commitment_str = m.value_of("commitment"); + + // create derived public key + let commitment = wallet::commitment_from_base58(commitment_str.unwrap())?; + + // get results from query server and print + let (uid, abar) = utils::get_owned_abar(&commitment).c(d!())?; + let memo = utils::get_abar_memo(&uid).unwrap().unwrap(); + let oabar = + OpenAnonAssetRecordBuilder::from_abar(&abar, memo, &from.into_noah()) + .unwrap() + .build() + .unwrap(); + + println!( + "(AtxoSID, ABAR, OABAR) : {}", + serde_json::to_string(&(uid, abar, oabar)).c(d!())? + ); + } else if let Some(m) = matches.subcommand_matches("owned-utxos") { + // All assets are shown in the default case + let asset = m.value_of("asset"); + let is_address_eth = m.is_present("eth-address"); + + // fetch filtered list by asset + let list = common::get_owned_utxos(asset, is_address_eth)?; + let pk = wallet::public_key_to_base64( + get_keypair(is_address_eth).unwrap().get_pk_ref(), + ); + + // Print UTXO table + println!("Owned utxos for {pk:?}",); + println!("{:-^1$}", "", 100); + println!( + "{0: <8} | {1: <18} | {2: <45} ", + "ATxoSID", "Amount", "AssetType" + ); + for (a, b, c) in list.iter() { + let amt = b + .get_amount() + .map_or_else(|| "Confidential".to_string(), |a| a.to_string()); + let at = c.get_asset_type().map_or_else( + || "Confidential".to_string(), + |at| AssetTypeCode { val: at }.to_base64(), + ); + + println!("{0: <8} | {1: <18} | {2: <45} ", a.0, amt, at); + } + } else if let Some(m) = matches.subcommand_matches("anon-transfer") { + let is_eth_address = m.is_present("use-default-eth-address"); + // sender Xfr secret key + let owner_sk = read_file_path(m.value_of("from-seckey")).c(d!())?; + + // get commitments + let commitment = m.value_of("commitment"); + let fee_commitment = m.value_of("fra-commitment"); + + // get receiver keys and amount + let to_address = m.value_of("to-address"); + let amount = m.value_of("amount"); + + if commitment.is_none() || to_address.is_none() || amount.is_none() { + println!("{}", m.usage()); + } else { + // build transaction and submit + common::gen_anon_transfer_op( + owner_sk, + commitment.unwrap(), + fee_commitment, + amount.unwrap(), + to_address.unwrap(), + is_eth_address, + ) + .c(d!())?; + } + } else if let Some(m) = matches.subcommand_matches("anon-transfer-batch") { + let is_eth_address = m.is_present("use-default-eth-address"); + + // sender Xfr secret key + let owner_sk = read_file_path(m.value_of("from-seckey")).c(d!())?; + + let to_axfr_public_keys = + m.value_of("to-address-file").c(d!()).and_then(|f| { + fs::read_to_string(f).c(d!()).and_then(|pks| { + pks.lines() + .map(|pk| wallet::public_key_from_bech32(pk.trim())) + .collect::>>() + .c(d!("invalid file")) + }) + })?; + let mut commitments = m.value_of("commitment-file").c(d!()).and_then(|f| { + fs::read_to_string(f) + .c(d!()) + .map(|rms| rms.lines().map(String::from).collect::>()) + })?; + commitments.sort(); + commitments.dedup(); + let amounts = m.value_of("amount-file").c(d!()).and_then(|f| { + fs::read_to_string(f) + .c(d!()) + .map(|ams| ams.lines().map(String::from).collect::>()) + })?; + let assets = m.value_of("asset-file").c(d!()).and_then(|f| { + let token_code = |asset: &str| { + if asset.to_uppercase() == "FRA" { + AssetTypeCode { + val: ASSET_TYPE_FRA, + } + } else { + AssetTypeCode::new_from_base64(asset).unwrap_or(AssetTypeCode { + val: ASSET_TYPE_FRA, + }) + } + }; + fs::read_to_string(f) + .c(d!()) + .map(|ams| ams.lines().map(token_code).collect::>()) + })?; + + if to_axfr_public_keys.is_empty() + || commitments.is_empty() + || amounts.is_empty() + || assets.is_empty() + { + println!("{}", m.usage()); + } else { + common::gen_oabar_add_op_x( + owner_sk, + to_axfr_public_keys, + commitments, + amounts, + assets, + is_eth_address, + ) + .c(d!())?; + } + } else if let Some(m) = matches.subcommand_matches("anon-fetch-merkle-proof") { + let atxo_sid = m.value_of("atxo-sid"); + + if atxo_sid.is_none() { + println!("{}", m.usage()); + } else { + let mt_leaf_info = common::get_mtleaf_info(atxo_sid.unwrap()).c(d!())?; + println!("{:?}", serde_json::to_string_pretty(&mt_leaf_info)); + } + } else if let Some(m) = matches.subcommand_matches("check-abar-status") { + let is_address_eth = m.is_present("use-default-eth-address"); + // sender Xfr secret key + let owner_sk = read_file_path(m.value_of("from-seckey")).c(d!())?; + // parse sender XfrSecretKey or generate from Mnemonic setup with wallet + let from = match owner_sk { + Some(str) => { + ruc::info!(serde_json::from_str::(&format!("\"{str}\""))) + .c(d!())? + .into_keypair() + } + None => get_keypair(is_address_eth).c(d!())?, + }; + + let commitment_str = m.value_of("commitment"); + let commitment = wallet::commitment_from_base58(commitment_str.unwrap())?; + + let abar = utils::get_owned_abar(&commitment).c(d!())?; + common::check_abar_status(from, abar).c(d!())?; } else if let Some(m) = matches.subcommand_matches("replace_staker") { let target = m .value_of("target") @@ -428,7 +767,8 @@ fn run() -> Result<()> { return Ok(()); } }; - common::replace_staker(target, td_addr)?; + let is_address_eth = m.is_present("eth-address"); + common::replace_staker(target, td_addr, is_address_eth)?; } else if let Some(m) = matches.subcommand_matches("dev") { #[cfg(not(target_arch = "wasm32"))] { @@ -776,6 +1116,18 @@ fn run() -> Result<()> { Ok(()) } +fn read_file_path(path: Option<&str>) -> Result> { + Ok(match path { + Some(path) => Some( + fs::read_to_string(path) + .c(d!("Failed to read seckey file"))? + .trim() + .to_string(), + ), + None => None, + }) +} + fn tip_fail(e: impl fmt::Display) { eprintln!("\n\x1b[31;01mFAIL !!!\x1b[00m"); eprintln!( diff --git a/src/components/finutils/src/bins/fn.yml b/src/components/finutils/src/bins/fn.yml index 98a3a95b8..cbf96a6c3 100644 --- a/src/components/finutils/src/bins/fn.yml +++ b/src/components/finutils/src/bins/fn.yml @@ -10,19 +10,26 @@ args: subcommands: - genkey: - about: Generate a random Findora public key/private key Pair + about: Generate a new Findora key pair + args: + - gen-eth-address: + help: generate eth address + long: gen-eth-address - show: - about: View Validator status and accumulated rewards + about: View the validator status and accumulated rewards args: - basic: help: show basic account info short: b long: basic + - eth-address: + help: use the eth address + long: eth-address - setup: - about: Setup environment variables for staking transactions + about: Set up environment variables for staking transactions args: - serv-addr: - help: a node address of Findora Network + help: a node address of the Findora network short: S long: serv-addr takes_value: true @@ -40,7 +47,7 @@ subcommands: takes_value: true value_name: Path - stake: - about: Stake tokens (i.e. bond tokens) from a Findora account to a Validator + about: Stake tokens (i.e. bond tokens) from a Findora account to a validator args: - amount: help: how much `FRA unit`s you want to stake @@ -66,7 +73,7 @@ subcommands: short: a long: append - staker-priv-key: - help: the file which contains private key (in base64 format) of proposer + help: the file which contains the mnemonic of proposer short: S long: staker-priv-key takes_value: true @@ -80,6 +87,11 @@ subcommands: - force: help: ignore warning and stake FRAs to your target node long: force + - use-default-eth-address: + help: use a private key of the eth address if `staker-priv-key` is not provided + long: use-default-eth-address + conflicts_with: + - staker-priv-key groups: - staking-flags: args: @@ -119,11 +131,14 @@ subcommands: long: validator-memo-logo takes_value: true value_name: Logo + - use-eth-address: + help: use a private key of the eth address + long: use-eth-address - unstake: about: Unstake tokens (i.e. unbond tokens) from a Validator args: - staker-priv-key: - help: the file which contains private key (in base64 format) of proposer + help: the file which contains the mnemonic of proposer short: S long: staker-priv-key takes_value: true @@ -140,6 +155,11 @@ subcommands: long: amount takes_value: true value_name: Amount + - use-default-eth-address: + help: use a private key of the eth address if `staker-priv-key` is not provided + long: use-default-eth-address + conflicts-with: + - staker-priv-key - claim: about: Claim accumulated FRA rewards args: @@ -156,10 +176,15 @@ subcommands: takes_value: true value_name: Amount - seckey: - help: the file which contains base64-formated `XfrPrivateKey` of an existing wallet + help: the file which contains base64-formatted `XfrPrivateKey` of an existing wallet long: seckey takes_value: true value_name: SECRET KEY + - use-default-eth-address: + help: use a private key of the eth address if `seckey` is not provided + long: use-default-eth-address + conflicts-with: + - seckey - delegate: about: Delegating operations args: @@ -170,7 +195,7 @@ subcommands: takes_value: true value_name: AMOUNT - seckey: - help: the file which contains base64-formated `XfrPrivateKey` of an existing wallet + help: the file which contains base64-formatted `XfrPrivateKey` of an existing wallet long: seckey takes_value: true value_name: SECRET KEY @@ -185,6 +210,11 @@ subcommands: conflicts_with: - amount - validator + - use-default-eth-address: + help: use a private key of the eth address if `seckey` is not provided + long: use-default-eth-address + conflicts-with: + - seckey - undelegate: about: Undelegating operations args: @@ -195,7 +225,7 @@ subcommands: takes_value: true value_name: AMOUNT - seckey: - help: the file which contains base64-formated `XfrPrivateKey` of an existing wallet + help: the file which contains base64-formatted `XfrPrivateKey` of an existing wallet long: seckey takes_value: true value_name: SECRET KEY @@ -204,26 +234,33 @@ subcommands: long: validator takes_value: true value_name: VALIDATOR ADDRESS + - use-default-eth-address: + help: use a private key of the eth address if `seckey` is not provided + long: use-default-eth-address + conflicts-with: + - seckey - transfer: about: Transfer tokens from one address to another args: - asset: - help: asset code which you want to tansfer + help: asset code which you want to transfer long: asset takes_value: true value_name: ASSET + allow_hyphen_values: true - from-seckey: - help: the file which contains base64-formated `XfrPrivateKey` of the receiver + help: the file which contains base64-formatted `XfrPrivateKey` of the sender short: f long: from-seckey takes_value: true value_name: SecKey - to-pubkey: - help: base64-formated `XfrPublicKey` of the receiver + help: base64-formatted `XfrPublicKey` of the receiver short: t long: to-pubkey takes_value: true value_name: PubKey + allow_hyphen_values: true - to-wallet-address: help: fra prefixed address of FindoraNetwork short: T @@ -245,11 +282,16 @@ subcommands: - confidential-type: help: mask the asset type sent on the transaction log long: confidential-type + - use-default-eth-address: + help: use a private key of the eth address if `from-seckey` is not provided + long: use-default-eth-address + conflicts-with: + - from-seckey - transfer-batch: about: Transfer tokens from one address to many others args: - from-seckey: - help: the file which contains base64-formated `XfrPrivateKey` of the receiver + help: the file which contains base64-formatted `XfrPrivateKey` of the receiver short: f long: from-seckey takes_value: true @@ -279,6 +321,11 @@ subcommands: - confidential-type: help: mask the asset type sent on the transaction log long: confidential-type + - use-default-eth-address: + help: use a private key of the eth address if `from-seckey` is not provided + long: use-default-eth-address + conflicts-with: + - from-seckey - wallet: about: manipulates a findora wallet args: @@ -295,15 +342,29 @@ subcommands: long: asset takes_value: true value_name: ASSET + allow_hyphen_values: true conflicts_with: - create - seckey: - help: the file which contains base64-formated `XfrPrivateKey` of an existing wallet + help: the file which contains base64-formatted `XfrPrivateKey` of an existing wallet long: seckey takes_value: true value_name: SECRET KEY conflicts_with: - create + - gen-eth-address: + help: generate the keypair of an eth address + long: gen-eth-address + conflicts-with: + - show + - seckey + - asset + - use-default-eth-address: + help: use a private key of the eth address if `seckey` is not provided + long: use-default-eth-address + conflicts-with: + - seckey + - create - asset: about: manipulate custom asset groups: @@ -365,13 +426,14 @@ subcommands: long: code takes_value: true value_name: ASSET CODE + allow_hyphen_values: true - addr: help: Findora wallet address long: addr takes_value: true value_name: WALLET ADDRESS - seckey: - help: the file which contains base64-formated `XfrPrivateKey` of findora account + help: the file which contains base64-formatted `XfrPrivateKey` of findora account long: seckey takes_value: true value_name: SECRET KEY @@ -401,26 +463,13 @@ subcommands: - hidden: help: hidden asset amount when issuing asset on ledger long: hidden - #- history - # about: query operating history - # args: - # - coinbase: - # help: show coinbase history - # long: coinbase - # conflicts_with: - # - transaction - # - transaction: - # help: show transaction history - # conflicts_with: - # - coinbase - # - wallet: - # help: wallet nick name - # long: wallet - # takes_value: true - # value_name: WALLET - # required: true + - use-default-eth-address: + help: use a private key of the eth address if `seckey` is not provided + long: use-default-eth-address + conflicts-with: + - seckey - account: - about: Return user contract account information + about: Return user contract account information or the balance if secret key is provided args: - addr: help: findora account(eg:fra1rkv...) or Ethereum address(g:0xd3Bf...) @@ -428,19 +477,35 @@ subcommands: long: addr takes_value: true value_name: WALLET ADDRESS - required: true + - sec-key: + help: base64-formatted `XfrPrivateKey` + short: s + long: sec-key + takes_value: true + value_name: SECRET KEY + - asset: + help: code of asset, such as `fra` + long: asset + takes_value: true + value_name: ASSET + allow_hyphen_values: true + - use-default-eth-address: + help: use a private key of the eth address if `sec-key` is not provided + long: use-default-eth-address + conflicts-with: + - sec-key - contract-deposit: - about: Transfer FRA from a Findora account to the specified Ethereum address + about: Transfer an asset from the UTXO chain to the EVM chain args: - addr: - help: ethereum address to receive FRA, eg:0xd3Bf... + help: ethereum address to receive asset, eg:0xd3Bf... short: a long: addr takes_value: true value_name: WALLET ADDRESS required: true - amount: - help: deposit FRA amount + help: deposit asset amount short: n long: amount takes_value: true @@ -460,11 +525,14 @@ subcommands: takes_value: true value_name: LOWLEVEL required: false + - eth-address: + help: use the eth address + long: eth-address - contract-withdraw: about: Transfer FRA from an Ethereum address to the specified Findora account args: - addr: - help: findora account to receive FRA, eg:fra1rkv... + help: Findora account to receive FRA, eg:fra1rkv... short: a long: addr takes_value: true @@ -477,14 +545,297 @@ subcommands: value_name: AMOUNT required: true - eth-key: - help: ethereum account mnemonic phrase sign withdraw tx + help: mnemonic phrase for the EVM account to sign the withdraw tx short: e long: eth-key takes_value: true value_name: MNEMONIC required: true + - eth-address: + help: use the eth address + long: eth-address - gen-eth-key: about: Generate an Ethereum address + - owned-abars: + about: Get Anon UTXOs for a keypair using the commitment + args: + - commitments: + help: Commitment of the ABAR + short: c + long: commitments + takes_value: true + value_name: COMMITMENT + required: true + allow_hyphen_values: true + - from-seckey: + help: Xfr secret key file path of receiver + short: s + long: from-seckey + takes_value: true + value_name: SECRET KEY PATH + - asset: + help: code of asset, such as `fra` + long: asset + takes_value: true + value_name: ASSET + allow_hyphen_values: true + - use-default-eth-address: + help: use a private key of the eth address if `from-seckey` is not provided + long: use-default-eth-address + conflicts-with: + - from-seckey + - anon-balance: + about: List Anon balance and spending status for a public key and a list of commitments + args: + - commitments: + help: the list of commitments in base64 form. + short: c + long: commitments + takes_value: true + value_name: COMMITMENT + required: true + allow_hyphen_values: true + - from-seckey: + help: Xfr secret key file path of converter + short: s + long: from-seckey + takes_value: true + value_name: SECRET KEY PATH + required: true + - asset: + help: code of asset, such as `fra` + long: asset + takes_value: true + value_name: ASSET + allow_hyphen_values: true + - use-default-eth-address: + help: use a private key of the eth address if `from-seckey` is not provided + long: use-default-eth-address + conflicts-with: + - from-seckey + - owned-open-abars: + about: Get Open Anon UTXOs for a keypair using commitment + args: + - commitment: + help: The commitment of ABAR + short: c + long: commitment + takes_value: true + value_name: COMMITMENT + required: true + allow_hyphen_values: true + - from-seckey: + help: Xfr secret key file path of converter + short: s + long: from-seckey + takes_value: true + value_name: SECRET KEY PATH + required: true + - use-default-eth-address: + help: use a private key of the eth address if `from-seckey` is not provided + long: use-default-eth-address + conflicts-with: + - from-seckey + - owned-utxos: + about: List owned UTXOs for a public key + args: + - asset: + help: asset code which you want to tansfer + long: asset + takes_value: true + value_name: ASSET + allow_hyphen_values: true + - eth-address: + help: use the eth address + long: eth-address + - convert-bar-to-abar: + about: Convert a BAR to Anon BAR for yourself + args: + - from-seckey: + help: Xfr secret key file path of converter + short: s + long: from-seckey + takes_value: true + value_name: SECRET KEY PATH + - to-address: + help: bech32 address of receiver keys + short: a + long: to-address + takes_value: true + value_name: TO ADDRESS + required: true + - txo-sid: + help: Txo Sid of input to convert + short: t + long: txo-sid + takes_value: true + value_name: TXO SID + required: true + - use-default-eth-address: + help: use a private key of the eth address if `from-seckey` is not provided + long: use-default-eth-address + conflicts-with: + - from-seckey + - convert-abar-to-bar: + about: Convert an ABAR to BAR + args: + - from-seckey: + help: Xfr secret key file path of converter + short: s + long: from-seckey + takes_value: true + value_name: SECRET KEY PATH + - commitment: + help: Commitment for the input Anon BAR + short: c + long: commitment + takes_value: true + value_name: COMMITMENT + required: true + allow_hyphen_values: true + - to-pubkey: + help: base64-formatted `XfrPublicKey` of the receiver + short: t + long: to-pubkey + takes_value: true + value_name: PubKey + allow_hyphen_values: true + - to-wallet-address: + help: Xfr public key of the receiver + short: T + long: to-wallet-address + takes_value: true + value_name: XFR WALLET ADDRESS + conflicts_with: + - to-pubkey + - 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 + - use-default-eth-address: + help: use a private key of the eth address if `from-seckey` is not provided + long: use-default-eth-address + conflicts-with: + - from-seckey + - anon-transfer: + about: Perform an anonymous transfer + args: + - from-seckey: + help: Xfr secret key file path of sender + short: s + long: from-seckey + takes_value: true + value_name: SECRET KEY PATH + - commitment: + help: Commitment for the input Anon BAR + short: c + long: commitment + takes_value: true + value_name: COMMITMENT + required: true + allow_hyphen_values: true + - fra-commitment: + help: Commitment for the input FRA Anon BAR + long: fra-commitment + takes_value: true + value_name: FRA COMMITMENT + allow_hyphen_values: true + - to-address: + help: Address of the receiver + long: to-address + takes_value: true + value_name: ADDRESS + required: true + allow_hyphen_values: true + - amount: + help: how much units to transfer + short: n + long: amount + takes_value: true + value_name: Amount + required: true + - use-default-eth-address: + help: use a private key of the eth address if `from-seckey` is not provided + long: use-default-eth-address + conflicts-with: + - from-seckey + - anon-transfer-batch: + about: Anonymous Transfer of tokens from multiple inputs to multiple outputs + args: + - from-seckey: + help: Xfr secret key file path of sender + short: s + long: from-seckey + takes_value: true + value_name: SECRET KEY PATH + required: true + - commitment-file: + help: Commitments for the input Anon BARs + short: c + long: commitment-file + takes_value: true + value_name: COMMITMENT + required: true + - to-address-file: + help: Xfr public keys of the receivers + long: to-address-file + takes_value: true + value_name: PUBLIC KEY + required: true + - amount-file: + help: how much units to transfer for each receiver + short: n + long: amount-file + takes_value: true + value_name: Amount + required: true + - asset-file: + help: Relative asset code type. + short: a + long: asset-file + takes_value: true + value_name: Asset + required: true + - use-default-eth-address: + help: use a private key of the eth address if `from-seckey` is not provided + long: use-default-eth-address + conflicts-with: + - from-seckey + - anon-fetch-merkle-proof: + about: Query Merkle tree leaf info + args: + - atxo-sid: + help: ATXO SID of the ABAR + short: a + long: atxo-sid + takes_value: true + value_name: ATXO SID + required: true + - check-abar-status: + about: Check the spending status and balance of ABAR + args: + - commitment: + help: Commitment of the ABAR + short: c + long: commitment + takes_value: true + value_name: COMMITMENT + required: true + allow_hyphen_values: true + - from-seckey: + help: Xfr secret key file path of sender + short: s + long: from-seckey + takes_value: true + value_name: SECRET KEY PATH + required: true + - use-default-eth-address: + help: use a private key of the eth address if `from-seckey` is not provided + long: use-default-eth-address + conflicts-with: + - from-seckey - replace_staker: about: Replace the staker of the validator with target address args: @@ -501,6 +852,23 @@ subcommands: takes_value: true value_name: TARGET PUBLIC KEY required: true + allow_hyphen_values: true + - td_address: + help: the tendermint address that you may want to replace. + long: td_address + takes_value: true + value_name: TENDERMINT ADDRESS + required: false + - td_pubkey: + help: the tendermint public key that you may want to replace. + long: td_pubkey + takes_value: true + value_name: TENDERMINT PUBKEY + required: false + allow_hyphen_values: true + - eth-address: + help: use the eth address + long: eth-address - dev: about: Manage development clusters on your localhost args: diff --git a/src/components/finutils/src/bins/key_generator.rs b/src/components/finutils/src/bins/key_generator.rs index 71582f04c..c58945946 100644 --- a/src/components/finutils/src/bins/key_generator.rs +++ b/src/components/finutils/src/bins/key_generator.rs @@ -5,5 +5,5 @@ fn main() { .nth(1) .unwrap_or_else(|| "1".to_owned()) .parse::()); - (0..n).for_each(|_| gen_key_and_print()); + (0..n).for_each(|_| gen_key_and_print(false)); } diff --git a/src/components/finutils/src/bins/stt/stt.rs b/src/components/finutils/src/bins/stt/stt.rs index 082f61fe9..887f2bcc2 100644 --- a/src/components/finutils/src/bins/stt/stt.rs +++ b/src/components/finutils/src/bins/stt/stt.rs @@ -210,10 +210,12 @@ mod issue { }, rand_chacha::rand_core::SeedableRng, rand_chacha::ChaChaRng, - zei::noah_algebra::ristretto::PedersenCommitmentRistretto, - zei::noah_api::xfr::{ - asset_record::{build_blind_asset_record, AssetRecordType}, - structs::AssetRecordTemplate, + zei::{ + noah_algebra::ristretto::PedersenCommitmentRistretto, + noah_api::xfr::{ + asset_record::{build_blind_asset_record, AssetRecordType}, + structs::AssetRecordTemplate, + }, }, }; @@ -266,7 +268,7 @@ mod issue { IssueAsset::new(aib, &IssuerKeyPair { keypair: &root_kp }).c(d!())?; builder.add_operation(Operation::IssueAsset(asset_issuance_operation)); - Ok(builder.take_transaction()) + builder.build_and_take_transaction() } } @@ -303,7 +305,7 @@ mod delegate { builder.add_operation_delegation(owner_kp, amount, validator.to_owned()); })?; - let mut tx = builder.take_transaction(); + let mut tx = builder.build_and_take_transaction()?; tx.sign(owner_kp); Ok(tx) } @@ -341,7 +343,7 @@ mod undelegate { } })?; - Ok(builder.take_transaction()) + builder.build_and_take_transaction() } } @@ -358,7 +360,7 @@ mod claim { builder.add_operation_claim(None, owner_kp, amount); })?; - Ok(builder.take_transaction()) + builder.build_and_take_transaction() } } diff --git a/src/components/finutils/src/common/ddev/init.rs b/src/components/finutils/src/common/ddev/init.rs index 63261be3a..a6504448f 100644 --- a/src/components/finutils/src/common/ddev/init.rs +++ b/src/components/finutils/src/common/ddev/init.rs @@ -17,8 +17,9 @@ use ledger::{ }; use ruc::*; use serde::{Deserialize, Serialize}; -use zei::noah_api::xfr::asset_record::AssetRecordType; -use zei::{XfrKeyPair, XfrPublicKey, XfrSecretKey}; +use zei::{ + noah_api::xfr::asset_record::AssetRecordType, XfrKeyPair, XfrPublicKey, XfrSecretKey, +}; #[derive(Deserialize)] struct TmValidators { @@ -57,7 +58,7 @@ pub(super) fn init(env: &mut Env) -> Result<()> { .and_then(|b| serde_json::from_slice::(&b).c(d!()))?; tm_validators.result.validators.into_iter().for_each(|v| { - let xfr_key = common::gen_key(); + let xfr_key = common::gen_key(false); let iv = InitialValidator { tendermint_addr: v.address, tendermint_pubkey: v.pub_key.value, @@ -132,9 +133,13 @@ pub(super) fn init(env: &mut Env) -> Result<()> { v.tendermint_addr.clone(), ); })?; - let mut tx = builder.take_transaction(); - tx.sign(&v.xfr_keypair); - send_tx(env, &tx).c(d!())?; + builder + .build_and_take_transaction() + .c(d!()) + .and_then(|mut tx| { + tx.sign(&v.xfr_keypair); + send_tx(env, &tx).c(d!()) + })?; } println!("[ {} ] >>> Init work done !", &env.name); @@ -152,7 +157,10 @@ fn setup_initial_validators(env: &Env) -> Result<()> { .collect::>(); builder.add_operation_update_validator(&[], 1, vs).c(d!())?; - send_tx(env, &builder.take_transaction()).c(d!()) + builder + .build_and_take_transaction() + .c(d!()) + .and_then(|tx| send_tx(env, &tx).c(d!())) } fn send_tx(env: &Env, tx: &Transaction) -> Result<()> { @@ -191,10 +199,13 @@ fn transfer_batch( .c(d!())?; builder.add_operation(op); - let mut tx = builder.take_transaction(); - tx.sign(owner_kp); - - send_tx(env, &tx).c(d!()) + builder + .build_and_take_transaction() + .c(d!()) + .and_then(|mut tx| { + tx.sign(owner_kp); + send_tx(env, &tx).c(d!()) + }) } fn new_tx_builder(env: &Env) -> Result { diff --git a/src/components/finutils/src/common/evm.rs b/src/components/finutils/src/common/evm.rs index 6dd2ff291..586a23916 100644 --- a/src/components/finutils/src/common/evm.rs +++ b/src/components/finutils/src/common/evm.rs @@ -18,19 +18,15 @@ use fp_types::{ transaction::UncheckedTransaction, U256, }; -use fp_utils::ecdsa::SecpPair; -use fp_utils::tx::EvmRawTxWrapper; -use ledger::data_model::AssetTypeCode; -use ledger::data_model::ASSET_TYPE_FRA; -use ledger::data_model::BLACK_HOLE_PUBKEY_STAKING; +use fp_utils::{ecdsa::SecpPair, tx::EvmRawTxWrapper}; +use ledger::data_model::{AssetTypeCode, ASSET_TYPE_FRA, BLACK_HOLE_PUBKEY_STAKING}; use ruc::*; use std::str::FromStr; use tendermint::block::Height; use tendermint_rpc::endpoint::abci_query::AbciQuery; use tendermint_rpc::{Client, HttpClient}; use tokio::runtime::Runtime; -use zei::noah_api::xfr::asset_record::AssetRecordType; -use zei::{XfrKeyPair, XfrPublicKey}; +use zei::{noah_api::xfr::asset_record::AssetRecordType, XfrKeyPair, XfrPublicKey}; /// transfer utxo assets to account(ed25519 or ecdsa address) balance. pub fn transfer_to_account( @@ -38,10 +34,11 @@ pub fn transfer_to_account( address: Option<&str>, asset: Option<&str>, lowlevel_data: Option<&str>, + is_address_eth: bool, ) -> Result<()> { let mut builder = utils::new_tx_builder().c(d!())?; - let kp = get_keypair().c(d!())?; + let kp = get_keypair(is_address_eth).c(d!())?; let asset = if let Some(asset) = asset { let asset = AssetTypeCode::new_from_base64(asset)?; @@ -59,6 +56,7 @@ pub fn transfer_to_account( Some(AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType), ) .c(d!())?; + let target_address = match address { Some(s) => MultiSigner::from_str(s).c(d!())?, None => MultiSigner::Xfr(kp.get_pk()), @@ -77,7 +75,7 @@ pub fn transfer_to_account( .c(d!())? .sign(&kp); - let mut tx = builder.take_transaction(); + let mut tx = builder.build_and_take_transaction()?; tx.sign_to_map(&kp); utils::send_tx(&tx).c(d!()) @@ -106,8 +104,9 @@ pub fn transfer_from_account( amount: u64, address: Option<&str>, eth_phrase: Option<&str>, + is_address_eth: bool, ) -> Result<()> { - let fra_kp = get_keypair()?; + let fra_kp = get_keypair(is_address_eth)?; let target = match address { Some(s) => { @@ -205,8 +204,11 @@ fn one_shot_abci_query( } /// Query contract account info by abci/query -pub fn contract_account_info(address: Option<&str>) -> Result<(Address, SmartAccount)> { - let fra_kp = get_keypair()?; +pub fn contract_account_info( + address: Option<&str>, + is_address_eth: bool, +) -> Result<(Address, SmartAccount)> { + let fra_kp = get_keypair(is_address_eth)?; let address = match address { Some(s) => MultiSigner::from_str(s).c(d!())?, diff --git a/src/components/finutils/src/common/mod.rs b/src/components/finutils/src/common/mod.rs index c460a7847..573cc0159 100644 --- a/src/components/finutils/src/common/mod.rs +++ b/src/components/finutils/src/common/mod.rs @@ -17,12 +17,17 @@ pub mod utils; use { self::utils::{get_evm_staking_address, get_validator_memo_and_rate}, - crate::api::DelegationInfo, + crate::{ + api::DelegationInfo, + common::utils::{new_tx_builder, send_tx}, + txn_builder::TransactionBuilder, + }, globutils::wallet, lazy_static::lazy_static, ledger::{ data_model::{ - gen_random_keypair, AssetRules, AssetTypeCode, AssetTypePrefix, Transaction, + gen_random_keypair, get_abar_commitment, ATxoSID, AssetRules, AssetTypeCode, + AssetTypePrefix, Transaction, TxoSID, ASSET_TYPE_FRA, BLACK_HOLE_PUBKEY_STAKING, }, staking::{ @@ -31,14 +36,30 @@ use { TendermintAddrRef, }, }, + rand_chacha::ChaChaRng, + rand_core::SeedableRng, ruc::*, std::{env, fs}, tendermint::PrivateKey, utils::{get_block_height, get_local_block_height, parse_td_validator_keys}, web3::types::H160, zei::{ - noah_api::xfr::asset_record::AssetRecordType, XfrKeyPair, XfrPublicKey, - XfrSecretKey, + noah_api::{ + anon_xfr::{ + nullify, + structs::{ + AnonAssetRecord, Commitment, MTLeafInfo, OpenAnonAssetRecordBuilder, + }, + }, + xfr::{ + asset_record::{ + AssetRecordType, + AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType, + }, + structs::{XfrAmount, XfrAssetType}, + }, + }, + XfrKeyPair, XfrPublicKey, XfrSecretKey, }, }; @@ -58,7 +79,11 @@ lazy_static! { } /// Updating the information of a staker includes commission_rate and staker_memo -pub fn staker_update(cr: Option<&str>, memo: Option) -> Result<()> { +pub fn staker_update( + cr: Option<&str>, + memo: Option, + is_address_eth: bool, +) -> Result<()> { let pub_key = get_td_pubkey() .map(|i| td_pubkey_to_td_addr_bytes(&i)) .c(d!())?; @@ -80,7 +105,7 @@ pub fn staker_update(cr: Option<&str>, memo: Option) -> Result<()> { let td_pubkey = get_td_pubkey().c(d!())?; - let kp = get_keypair().c(d!())?; + let kp = get_keypair(is_address_eth).c(d!())?; let vkp = get_td_privkey().c(d!())?; let mut builder = utils::new_tx_builder().c(d!())?; @@ -92,7 +117,7 @@ pub fn staker_update(cr: Option<&str>, memo: Option) -> Result<()> { .c(d!()) .map(|op| builder.add_operation(op))?; - let mut tx = builder.take_transaction(); + let mut tx = builder.build_and_take_transaction()?; tx.sign_to_map(&kp); utils::send_tx(&tx).c(d!()) @@ -105,6 +130,7 @@ pub fn stake( commission_rate: &str, memo: Option<&str>, force: bool, + is_address_eth: bool, ) -> Result<()> { let am = amount.parse::().c(d!("'amount' must be an integer"))?; check_delegation_amount(am, false).c(d!())?; @@ -114,7 +140,7 @@ pub fn stake( .and_then(|cr| convert_commission_rate(cr).c(d!()))?; let td_pubkey = get_td_pubkey().c(d!())?; - let kp = get_keypair().c(d!())?; + let kp = get_keypair(is_address_eth).c(d!())?; let vkp = get_td_privkey().c(d!())?; macro_rules! diff { @@ -157,7 +183,7 @@ pub fn stake( .c(d!()) .map(|principal_op| builder.add_operation(principal_op))?; - let mut tx = builder.take_transaction(); + let mut tx = builder.build_and_take_transaction()?; tx.sign_to_map(&kp); utils::send_tx(&tx).c(d!()) @@ -168,6 +194,7 @@ pub fn stake_append( amount: &str, staker: Option<&str>, td_addr: Option, + is_address_eth: bool, ) -> Result<()> { let am = amount.parse::().c(d!("'amount' must be an integer"))?; check_delegation_amount(am, true).c(d!())?; @@ -181,7 +208,7 @@ pub fn stake_append( let kp = staker .c(d!()) .and_then(|sk| wallet::restore_keypair_from_mnemonic_default(sk).c(d!())) - .or_else(|_| get_keypair().c(d!()))?; + .or_else(|_| get_keypair(is_address_eth).c(d!()))?; let mut builder = utils::new_tx_builder().c(d!())?; builder.add_operation_delegation(&kp, am, td_addr); @@ -195,7 +222,8 @@ pub fn stake_append( ) .c(d!()) .map(|principal_op| builder.add_operation(principal_op))?; - let mut tx = builder.take_transaction(); + + let mut tx = builder.build_and_take_transaction()?; tx.sign_to_map(&kp); utils::send_tx(&tx).c(d!()) @@ -206,6 +234,7 @@ pub fn unstake( am: Option<&str>, staker: Option<&str>, td_addr: Option, + is_address_eth: bool, ) -> Result<()> { let am = if let Some(i) = am { Some(i.parse::().c(d!("'amount' must be an integer"))?) @@ -216,7 +245,7 @@ pub fn unstake( let kp = staker .c(d!()) .and_then(|sk| wallet::restore_keypair_from_mnemonic_default(sk).c(d!())) - .or_else(|_| get_keypair().c(d!()))?; + .or_else(|_| get_keypair(is_address_eth).c(d!()))?; let td_addr_bytes = td_addr .c(d!()) .and_then(|ta| td_addr_to_bytes(ta).c(d!())) @@ -245,14 +274,19 @@ pub fn unstake( } })?; - let mut tx = builder.take_transaction(); + let mut tx = builder.build_and_take_transaction()?; tx.sign_to_map(&kp); utils::send_tx(&tx).c(d!()) } /// Claim rewards from findora network -pub fn claim(td_addr: &str, am: Option<&str>, sk_str: Option<&str>) -> Result<()> { +pub fn claim( + td_addr: &str, + am: Option<&str>, + sk_str: Option<&str>, + is_address_eth: bool, +) -> Result<()> { let td_addr = hex::decode(td_addr).c(d!())?; let am = if let Some(i) = am { @@ -261,7 +295,7 @@ pub fn claim(td_addr: &str, am: Option<&str>, sk_str: Option<&str>) -> Result<() None }; - let kp = restore_keypair_from_str_with_default(sk_str)?; + let kp = restore_keypair_from_str_with_default(sk_str, is_address_eth)?; let mut builder = utils::new_tx_builder().c(d!())?; @@ -270,7 +304,7 @@ pub fn claim(td_addr: &str, am: Option<&str>, sk_str: Option<&str>) -> Result<() builder.add_operation_claim(Some(td_addr), &kp, am); })?; - let mut tx = builder.take_transaction(); + let mut tx = builder.build_and_take_transaction()?; tx.sign_to_map(&kp); utils::send_tx(&tx).c(d!()) @@ -285,14 +319,14 @@ pub fn claim(td_addr: &str, am: Option<&str>, sk_str: Option<&str>) -> Result<() /// Delegation Information /// Validator Detail (if already staked) /// -pub fn show(basic: bool) -> Result<()> { - let kp = get_keypair().c(d!())?; +pub fn show(basic: bool, is_address_eth: bool) -> Result<()> { + let kp = get_keypair(is_address_eth).c(d!())?; let serv_addr = ruc::info!(get_serv_addr()).map(|i| { println!("\x1b[31;01mServer URL:\x1b[00m\n{i}\n"); }); - let xfr_account = ruc::info!(get_keypair()).map(|i| { + let xfr_account = ruc::info!(get_keypair(is_address_eth)).map(|i| { println!( "\x1b[31;01mFindora Address:\x1b[00m\n{}\n", wallet::public_key_to_bech32(&i.get_pk()) @@ -301,6 +335,10 @@ pub fn show(basic: bool) -> Result<()> { "\x1b[31;01mFindora Public Key:\x1b[00m\n{}\n", wallet::public_key_to_base64(&i.get_pk()) ); + println!( + "\x1b[31;01mFindora Public Key in hex:\x1b[00m\n{}\n", + wallet::public_key_to_hex(&i.get_pk()) + ); }); let self_balance = ruc::info!(utils::get_balance(&kp)).map(|i| { @@ -412,6 +450,7 @@ pub fn transfer_asset( am: &str, confidential_am: bool, confidential_ty: bool, + is_address_eth: bool, ) -> Result<()> { transfer_asset_batch( owner_sk, @@ -420,6 +459,7 @@ pub fn transfer_asset( am, confidential_am, confidential_ty, + is_address_eth, ) .c(d!()) } @@ -452,8 +492,9 @@ pub fn transfer_asset_batch( am: &str, confidential_am: bool, confidential_ty: bool, + is_address_eth: bool, ) -> Result<()> { - let from = restore_keypair_from_str_with_default(owner_sk)?; + let from = restore_keypair_from_str_with_default(owner_sk, is_address_eth)?; let am = am.parse::().c(d!("'amount' must be an integer"))?; transfer_asset_batch_x( @@ -502,15 +543,21 @@ pub fn get_serv_addr() -> Result<&'static str> { } /// Get keypair from config file -pub fn get_keypair() -> Result { +pub fn get_keypair(is_address_eth: bool) -> Result { if let Some(m_path) = MNEMONIC.as_ref() { fs::read_to_string(m_path) .c(d!("can not read mnemonic from 'owner-mnemonic-path'")) .and_then(|m| { let k = m.trim(); - wallet::restore_keypair_from_mnemonic_default(k) - .c(d!("invalid 'owner-mnemonic'")) - .or_else(|e| wallet::restore_keypair_from_seckey_base64(k).c(d!(e))) + let kp = if is_address_eth { + wallet::restore_keypair_from_mnemonic_secp256k1(k) + .c(d!("invalid 'owner-mnemonic'")) + } else { + wallet::restore_keypair_from_mnemonic_default(k) + .c(d!("invalid 'owner-mnemonic'")) + }; + + kp.or_else(|e| wallet::restore_keypair_from_seckey_base64(k).c(d!(e))) }) } else { Err(eg!("'owner-mnemonic-path' has not been set")) @@ -556,10 +603,15 @@ pub fn convert_commission_rate(cr: f64) -> Result<[u64; 2]> { } #[allow(missing_docs)] -pub fn gen_key() -> (String, String, String, XfrKeyPair) { +pub fn gen_key(is_address_eth: bool) -> (String, String, String, XfrKeyPair) { let (mnemonic, key, kp) = loop { let mnemonic = pnk!(wallet::generate_mnemonic_custom(24, "en")); - let kp = pnk!(wallet::restore_keypair_from_mnemonic_default(&mnemonic)); + let kp = if is_address_eth { + pnk!(wallet::restore_keypair_from_mnemonic_secp256k1(&mnemonic)) + } else { + pnk!(wallet::restore_keypair_from_mnemonic_default(&mnemonic)) + }; + if let Some(key) = serde_json::to_string_pretty(&kp) .ok() .filter(|s| s.matches("\": \"-").next().is_none()) @@ -574,26 +626,33 @@ pub fn gen_key() -> (String, String, String, XfrKeyPair) { } #[allow(missing_docs)] -pub fn gen_key_and_print() { - let (wallet_addr, mnemonic, key, _) = gen_key(); +pub fn gen_key_and_print(is_address_eth: bool) { + let (wallet_addr, mnemonic, key, _) = gen_key(is_address_eth); println!( "\n\x1b[31;01mWallet Address:\x1b[00m {wallet_addr}\n\x1b[31;01mMnemonic:\x1b[00m {mnemonic}\n\x1b[31;01mKey:\x1b[00m {key}\n", ); } -fn restore_keypair_from_str_with_default(sk_str: Option<&str>) -> Result { +fn restore_keypair_from_str_with_default( + sk_str: Option<&str>, + is_address_eth: bool, +) -> Result { if let Some(sk) = sk_str { serde_json::from_str::(&format!("\"{}\"", sk.trim())) .map(|sk| sk.into_keypair()) .c(d!("Invalid secret key")) } else { - get_keypair().c(d!()) + get_keypair(is_address_eth).c(d!()) } } /// Show the asset balance of a findora account -pub fn show_account(sk_str: Option<&str>, _asset: Option<&str>) -> Result<()> { - let kp = restore_keypair_from_str_with_default(sk_str)?; +pub fn show_account( + sk_str: Option<&str>, + _asset: Option<&str>, + is_address_eth: bool, +) -> Result<()> { + let kp = restore_keypair_from_str_with_default(sk_str, is_address_eth)?; // let token_code = asset // .map(|asset| AssetTypeCode::new_from_base64(asset).c(d!("Invalid asset code"))) // .transpose()?; @@ -614,8 +673,13 @@ pub fn show_account(sk_str: Option<&str>, _asset: Option<&str>) -> Result<()> { #[inline(always)] #[allow(missing_docs)] -pub fn delegate(sk_str: Option<&str>, amount: u64, validator: &str) -> Result<()> { - restore_keypair_from_str_with_default(sk_str) +pub fn delegate( + sk_str: Option<&str>, + amount: u64, + validator: &str, + is_address_eth: bool, +) -> Result<()> { + restore_keypair_from_str_with_default(sk_str, is_address_eth) .c(d!()) .and_then(|kp| delegate_x(&kp, amount, validator).c(d!())) } @@ -630,8 +694,12 @@ pub fn delegate_x(kp: &XfrKeyPair, amount: u64, validator: &str) -> Result<()> { #[inline(always)] #[allow(missing_docs)] -pub fn undelegate(sk_str: Option<&str>, param: Option<(u64, &str)>) -> Result<()> { - restore_keypair_from_str_with_default(sk_str) +pub fn undelegate( + sk_str: Option<&str>, + param: Option<(u64, &str)>, + is_address_eth: bool, +) -> Result<()> { + restore_keypair_from_str_with_default(sk_str, is_address_eth) .c(d!()) .and_then(|kp| undelegate_x(&kp, param).c(d!())) } @@ -645,8 +713,8 @@ pub fn undelegate_x(kp: &XfrKeyPair, param: Option<(u64, &str)>) -> Result<()> { } /// Display delegation information of a findora account -pub fn show_delegations(sk_str: Option<&str>) -> Result<()> { - let pk = restore_keypair_from_str_with_default(sk_str)?.get_pk(); +pub fn show_delegations(sk_str: Option<&str>, is_address_eth: bool) -> Result<()> { + let pk = restore_keypair_from_str_with_default(sk_str, is_address_eth)?.get_pk(); println!( "{}", @@ -681,7 +749,7 @@ fn gen_undelegate_tx( builder.add_operation_undelegation(owner_kp, None); } - let mut tx = builder.take_transaction(); + let mut tx = builder.build_and_take_transaction()?; tx.sign_to_map(owner_kp); Ok(tx) @@ -708,7 +776,7 @@ fn gen_delegate_tx( builder.add_operation_delegation(owner_kp, amount, validator.to_owned()); })?; - let mut tx = builder.take_transaction(); + let mut tx = builder.build_and_take_transaction()?; tx.sign_to_map(owner_kp); @@ -723,8 +791,9 @@ pub fn create_asset( max_units: Option, transferable: bool, token_code: Option<&str>, + is_address_eth: bool, ) -> Result<()> { - let kp = restore_keypair_from_str_with_default(sk_str)?; + let kp = restore_keypair_from_str_with_default(sk_str, is_address_eth)?; let code = if token_code.is_none() { AssetTypeCode::gen_random() @@ -768,7 +837,7 @@ pub fn create_asset_x( .c(d!()) .map(|op| builder.add_operation(op))?; - let mut tx = builder.take_transaction(); + let mut tx = builder.build_and_take_transaction()?; tx.sign_to_map(kp); utils::send_tx(&tx).map(|_| asset_code) @@ -780,8 +849,9 @@ pub fn issue_asset( asset: &str, amount: u64, hidden: bool, + is_address_eth: bool, ) -> Result<()> { - let kp = restore_keypair_from_str_with_default(sk_str)?; + let kp = restore_keypair_from_str_with_default(sk_str, is_address_eth)?; let code = AssetTypeCode::new_from_base64(asset).c(d!())?; issue_asset_x(&kp, &code, amount, hidden).c(d!()) } @@ -809,7 +879,7 @@ pub fn issue_asset_x( .c(d!()) .map(|op| builder.add_operation(op))?; - let mut tx = builder.take_transaction(); + let mut tx = builder.build_and_take_transaction()?; tx.sign_to_map(kp); utils::send_tx(&tx) @@ -827,15 +897,651 @@ pub fn show_asset(addr: &str) -> Result<()> { Ok(()) } +/// Builds a transaction for a BAR to ABAR conversion with fees and sends it to network +/// # Arguments +/// * owner_sk - Optional secret key Xfr in json form +/// * target_addr - ABAR receiving AXfr pub key after conversion in base64 +/// * TxoSID - sid of BAR to convert +pub fn convert_bar2abar( + owner_sk: Option<&String>, + target_addr: &str, + txo_sid: &str, + is_address_eth: bool, +) -> Result { + // parse sender XfrSecretKey or generate from Mnemonic setup with wallet + let from = match owner_sk { + Some(str) => { + ruc::info!(serde_json::from_str::(&format!("\"{str}\"",))) + .c(d!())? + .into_keypair() + } + None => get_keypair(is_address_eth).c(d!())?, + }; + // parse receiver AxfrPubKey + let to = + wallet::public_key_from_bech32(target_addr).c(d!("invalid 'target-addr'"))?; + let sid = txo_sid.parse::().c(d!("error parsing TxoSID"))?; + + // Get OpenAssetRecord from given Owner XfrKeyPair and TxoSID + let record = + utils::get_oar(&from, TxoSID(sid)).c(d!("error fetching open asset record"))?; + let is_bar_transparent = + record.1.get_record_type() == NonConfidentialAmount_NonConfidentialAssetType; + + // Generate the transaction and transmit it to network + let c = utils::generate_bar2abar_op( + &from, + &to, + TxoSID(sid), + &record.0, + is_bar_transparent, + ) + .c(d!("Bar to abar failed"))?; + + Ok(c) +} + +/// Convert an ABAR to a Blind Asset Record +/// # Arguments +/// * axfr_secret_key - the anon_secret_key in base64 +/// * com - commitment of ABAR in base64 +/// * to - Bar receiver's XfrPublicKey pointer +/// * com_fra - commitment of the FRA ABAR to pay fee in base64 +/// * confidential_am - if the output BAR should have confidential amount +/// * confidential_ty - if the output BAR should have confidential type +pub fn convert_abar2bar( + owner_sk: Option, + com: &str, + to: &XfrPublicKey, + confidential_am: bool, + confidential_ty: bool, + is_address_eth: bool, +) -> Result<()> { + let from = match owner_sk { + Some(str) => { + ruc::info!(serde_json::from_str::(&format!("\"{str}\""))) + .c(d!())? + .into_keypair() + } + None => get_keypair(is_address_eth).c(d!())?, + }; + // Get the owned ABAR from pub_key and commitment + let com = wallet::commitment_from_base58(com).c(d!())?; + let axtxo_abar = utils::get_owned_abar(&com).c(d!())?; + + // get OwnerMemo and Merkle Proof of ABAR + let owner_memo = utils::get_abar_memo(&axtxo_abar.0).c(d!())?.unwrap(); + let mt_leaf_info = utils::get_abar_proof(&axtxo_abar.0).c(d!())?.unwrap(); + let mt_leaf_uid = mt_leaf_info.uid; + + // Open ABAR with OwnerMemo & attach merkle proof + let oabar_in = OpenAnonAssetRecordBuilder::from_abar( + &axtxo_abar.1, + owner_memo, + &from.into_noah(), + ) + .unwrap() + .mt_leaf_info(mt_leaf_info) + .build() + .unwrap(); + + // check oabar is unspent. If already spent return error + // create nullifier + let n = nullify( + &from.into_noah(), + oabar_in.get_amount(), + oabar_in.get_asset_type().as_scalar(), + mt_leaf_uid, + ) + .c(d!())?; + let hash = wallet::nullifier_to_base58(&n.0); + // check if hash is present in nullifier set + let null_status = utils::check_nullifier_hash(&hash) + .c(d!())? + .ok_or(d!("The ABAR corresponding to this commitment is missing"))?; + if null_status { + return Err(eg!( + "The ABAR corresponding to this commitment is already spent" + )); + } + println!("Nullifier: {}", wallet::nullifier_to_base58(&n.0)); + + // Create New AssetRecordType for new BAR + 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, + }; + + // Build AbarToBar Transaction and submit + utils::generate_abar2bar_op(&oabar_in, &from, to, art).c(d!())?; + + Ok(()) +} + +/// Generate OABAR and add anonymous transfer operation +/// # Arguments +/// * axfr_secret_key - AXfrKeyPair in base64 form +/// * com - Commitment in base64 form +/// * com_fra - Commitment for paying fee +/// * amount - amount to transfer +/// * to_axfr_public_key - AXfrPublicKey in base64 form +pub fn gen_anon_transfer_op( + owner_sk: Option, + com: &str, + com_fra: Option<&str>, + amount: &str, + to_address: &str, + is_address_eth: bool, +) -> Result<()> { + // parse sender keys + // parse sender XfrSecretKey or generate from Mnemonic setup with wallet + let from = match owner_sk { + Some(str) => { + ruc::info!(serde_json::from_str::(&format!("\"{str}\""))) + .c(d!())? + .into_keypair() + } + None => get_keypair(is_address_eth).c(d!())?, + }; + let axfr_amount = amount.parse::().c(d!("error parsing amount"))?; + + let to = wallet::public_key_from_bech32(to_address) + .c(d!("invalid 'to-xfr-public-key'"))?; + + let mut commitments = vec![com]; + if let Some(fra) = com_fra { + commitments.push(fra); + } + let mut inputs = vec![]; + // For each commitment add input to transfer operation + for com in commitments { + let c = wallet::commitment_from_base58(com).c(d!())?; + + // get unspent ABARs & their Merkle proof for commitment + let axtxo_abar = utils::get_owned_abar(&c).c(d!())?; + let owner_memo = utils::get_abar_memo(&axtxo_abar.0).c(d!())?.unwrap(); + let mt_leaf_info = utils::get_abar_proof(&axtxo_abar.0).c(d!())?.unwrap(); + let mt_leaf_uid = mt_leaf_info.uid; + + // Create Open ABAR from input information + let oabar_in = OpenAnonAssetRecordBuilder::from_abar( + &axtxo_abar.1, + owner_memo, + &from.into_noah(), + ) + .unwrap() + .mt_leaf_info(mt_leaf_info) + .build() + .unwrap(); + + // check oabar is unspent. + let n = nullify( + &from.into_noah(), + oabar_in.get_amount(), + oabar_in.get_asset_type().as_scalar(), + mt_leaf_uid, + ) + .c(d!())?; + let hash = wallet::nullifier_to_base58(&n.0); + let null_status = utils::check_nullifier_hash(&hash).c(d!())?.ok_or(d!( + "The ABAR corresponding to this commitment is missing {}", + com + ))?; + if null_status { + return Err(eg!( + "The ABAR corresponding to this commitment is already spent {}", + com + )); + } + + println!("Nullifier: {}", wallet::nullifier_to_base58(&n.0)); + inputs.push(oabar_in); + } + + // build output + let mut prng = ChaChaRng::from_entropy(); + let oabar_out = OpenAnonAssetRecordBuilder::new() + .amount(axfr_amount) + .asset_type(inputs[0].get_asset_type()) + .pub_key(&to.into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(); + + let mut builder: TransactionBuilder = new_tx_builder().c(d!())?; + let (_, note, rem_oabars) = builder + .add_operation_anon_transfer_fees_remainder(&inputs, &[oabar_out], &from) + .c(d!())?; + + send_tx(&builder.build_and_take_transaction()?).c(d!())?; + + let com_out = if !note.body.outputs.is_empty() { + Some(note.body.outputs[0].commitment) + } else { + None + }; + + if let Some(com) = com_out { + println!( + "\x1b[31;01m Commitment: {}\x1b[00m", + wallet::commitment_to_base58(&com) + ); + + // Append receiver's commitment to `sent_commitment` file + let mut file = fs::OpenOptions::new() + .append(true) + .create(true) + .open("sent_commitments") + .expect("cannot open commitments file"); + std::io::Write::write_all( + &mut file, + ("\n".to_owned() + &wallet::commitment_to_base58(&com)).as_bytes(), + ) + .expect("commitment write failed"); + } + + // Append sender's fee balance commitment to `owned_commitments` file + let mut file = fs::OpenOptions::new() + .append(true) + .create(true) + .open("owned_commitments") + .expect("cannot open commitments file"); + for rem_oabar in rem_oabars.iter() { + let c = get_abar_commitment(rem_oabar.clone()); + println!( + "\x1b[31;01m Remainder Commitment: {}\x1b[00m", + wallet::commitment_to_base58(&c) + ); + + std::io::Write::write_all( + &mut file, + ("\n".to_owned() + &wallet::commitment_to_base58(&c)).as_bytes(), + ) + .expect("commitment write failed"); + } + + println!("AxfrNote: {:?}", serde_json::to_string_pretty(¬e.body)); + Ok(()) +} + +/// Batch anon transfer - Generate OABAR and add anonymous transfer operation +/// Note - if multiple anon keys are used, we consider the last key in the list for remainder. +/// # Arguments +/// * axfr_secret_key - list of secret keys for senders' ABAR UTXOs +/// * to_axfr_public_keys - receiver AXfr Public keys +/// * to_enc_keys - List of receiver Encryption keys +/// * commitments - List of sender commitments in base64 format +/// * amounts - List of receiver amounts +/// * assets - List of receiver Asset Types +/// returns an error if Operation build fails +pub fn gen_oabar_add_op_x( + owner_sk: Option, + to_axfr_public_keys: Vec, + commitments: Vec, + amounts: Vec, + assets: Vec, + is_address_eth: bool, +) -> Result<()> { + let from = match owner_sk { + Some(str) => { + ruc::info!(serde_json::from_str::(&format!("\"{str}\""))) + .c(d!())? + .into_keypair() + } + None => get_keypair(is_address_eth).c(d!())?, + }; + let receiver_count = to_axfr_public_keys.len(); + + // check if input counts tally + if receiver_count != amounts.len() || receiver_count != assets.len() { + return Err(eg!( + "The Parameters: from-sk/dec-keys/commitments or to-pk/to-enc-keys not match!" + )); + } + + // Create Input Open Abars with input keys, radomizers and Owner memos + let mut oabars_in = Vec::new(); + for comm in commitments { + let c = wallet::commitment_from_base58(comm.as_str()).c(d!())?; + + // Get OwnerMemo + let axtxo_abar = utils::get_owned_abar(&c).c(d!())?; + let owner_memo = utils::get_abar_memo(&axtxo_abar.0).c(d!(comm))?.unwrap(); + // Get Merkle Proof + let mt_leaf_info = utils::get_abar_proof(&axtxo_abar.0).c(d!())?.unwrap(); + let mt_leaf_uid = mt_leaf_info.uid; + + // Build Abar + let oabar_in = OpenAnonAssetRecordBuilder::from_abar( + &axtxo_abar.1, + owner_memo, + &from.into_noah(), + ) + .unwrap() + .mt_leaf_info(mt_leaf_info) + .build() + .unwrap(); + + // check oabar is unspent. + let n = nullify( + &from.into_noah(), + oabar_in.get_amount(), + oabar_in.get_asset_type().as_scalar(), + mt_leaf_uid, + ) + .c(d!())?; + let hash = wallet::nullifier_to_base58(&n.0); + let null_status = utils::check_nullifier_hash(&hash) + .c(d!())? + .ok_or(d!("The ABAR corresponding to this commitment is missing"))?; + if null_status { + return Err(eg!( + "The ABAR corresponding to this commitment is already spent" + )); + } + println!("Nullifier: {}", wallet::nullifier_to_base58(&n.0)); + + oabars_in.push(oabar_in); + } + + // Create output Open ABARs + let mut oabars_out = Vec::new(); + for i in 0..receiver_count { + let mut prng = ChaChaRng::from_entropy(); + let to = to_axfr_public_keys[i]; + let axfr_amount = amounts[i].parse::().c(d!("error parsing amount"))?; + let asset_type = assets[i]; + + let oabar_out = OpenAnonAssetRecordBuilder::new() + .amount(axfr_amount) + .asset_type(asset_type.val) + .pub_key(&to.into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(); + + oabars_out.push(oabar_out); + } + + // Add a output for fees balance + let mut builder: TransactionBuilder = new_tx_builder().c(d!())?; + let (_, note, rem_oabars) = builder + .add_operation_anon_transfer_fees_remainder( + &oabars_in[..], + &oabars_out[..], + &from, + ) + .c(d!())?; + + // Send the transaction to the network + send_tx(&builder.build_and_take_transaction()?).c(d!())?; + + // Append receiver's commitment to `sent_commitments` file + let mut s_file = fs::OpenOptions::new() + .append(true) + .create(true) + .open("sent_commitments") + .expect("cannot open commitments file"); + for oabar_out in oabars_out { + let c_out = get_abar_commitment(oabar_out); + println!( + "\x1b[31;01m Commitment: {}\x1b[00m", + wallet::commitment_to_base58(&c_out) + ); + + std::io::Write::write_all( + &mut s_file, + ("\n".to_owned() + &wallet::commitment_to_base58(&c_out)).as_bytes(), + ) + .expect("commitment write failed"); + } + + let mut o_file = fs::OpenOptions::new() + .append(true) + .create(true) + .open("owned_commitments") + .expect("cannot open commitments file"); + for rem_oabar in rem_oabars.iter() { + let c_rem = get_abar_commitment(rem_oabar.clone()); + + println!( + "\x1b[31;01m Remainder Commitment: {}\x1b[00m", + wallet::commitment_to_base58(&c_rem) + ); + std::io::Write::write_all( + &mut o_file, + ("\n".to_owned() + &wallet::commitment_to_base58(&c_rem)).as_bytes(), + ) + .expect("commitment write failed"); + } + + println!("AxfrNote: {:?}", serde_json::to_string_pretty(¬e.body)); + Ok(()) +} + +/// Get merkle proof - Generate MTLeafInfo from ATxoSID +pub fn get_mtleaf_info(atxo_sid: &str) -> Result { + let asid = atxo_sid.parse::().c(d!("error parsing ATxoSID"))?; + let mt_leaf_info = utils::get_abar_proof(&ATxoSID(asid)) + .c(d!("error fetching abar proof"))? + .unwrap(); + Ok(mt_leaf_info) +} + +/// Fetches list of owned TxoSIDs from LedgerStatus +pub fn get_owned_utxos( + asset: Option<&str>, + is_address_eth: bool, +) -> Result> { + // get KeyPair from current setup wallet + let kp = get_keypair(is_address_eth).c(d!())?; + + // Parse Asset Type for filtering if provided + let mut asset_type = ASSET_TYPE_FRA; + if let Some(a) = asset { + asset_type = if a.to_uppercase() == "FRA" { + ASSET_TYPE_FRA + } else { + AssetTypeCode::new_from_base64(asset.unwrap()).unwrap().val + }; + } + + let list: Vec<(TxoSID, XfrAmount, XfrAssetType)> = + utils::get_owned_utxos(&kp.pub_key)? + .iter() + .filter(|a| { + // Filter by asset type if given or read all + if asset.is_none() { + true + } else { + match a.1.clone().0 .0.record.asset_type { + XfrAssetType::Confidential(_) => false, + XfrAssetType::NonConfidential(x) => asset_type == x, + } + } + }) + .map(|a| { + let record = a.1.clone().0 .0.record; + (*a.0, record.amount, record.asset_type) + }) + .collect(); + + Ok(list) +} + +/// Check the spending status of an ABAR from AnonKeys and commitment +pub fn check_abar_status( + from: XfrKeyPair, + axtxo_abar: (ATxoSID, AnonAssetRecord), +) -> Result<()> { + let owner_memo = utils::get_abar_memo(&axtxo_abar.0).c(d!())?.unwrap(); + let mt_leaf_info = utils::get_abar_proof(&axtxo_abar.0).c(d!())?.unwrap(); + let mt_leaf_uid = mt_leaf_info.uid; + + let oabar = OpenAnonAssetRecordBuilder::from_abar( + &axtxo_abar.1, + owner_memo, + &from.into_noah(), + ) + .unwrap() + .mt_leaf_info(mt_leaf_info) + .build() + .unwrap(); + + let n = nullify( + &from.into_noah(), + oabar.get_amount(), + oabar.get_asset_type().as_scalar(), + mt_leaf_uid, + ) + .c(d!())?; + let hash = wallet::nullifier_to_base58(&n.0); + let null_status = utils::check_nullifier_hash(&hash).c(d!())?.unwrap(); + if null_status { + println!("The ABAR corresponding to this commitment is already spent"); + } else { + println!("The ABAR corresponding to this commitment is unspent and has a balance {:?}", oabar.get_amount()); + } + Ok(()) +} + +/// Prints a dainty list of Abar info with spent status for a given AxfrKeyPair and a list of +/// commitments. +pub fn get_owned_abars( + axfr_secret_key: XfrKeyPair, + commitments_list: &str, +) -> Result<()> { + println!("Abar data for commitments: {commitments_list}",); + println!(); + println!( + "{0: <8} | {1: <18} | {2: <45} | {3: <9} | {4: <45}", + "ATxoSID", "Amount", "AssetType", "IsSpent", "Commitment" + ); + println!("{:-^1$}", "", 184); + commitments_list + .split(',') + .try_for_each(|com| -> ruc::Result<()> { + let commitment = wallet::commitment_from_base58(com).c(d!())?; + let (sid, abar) = utils::get_owned_abar(&commitment).c(d!())?; + let memo = utils::get_abar_memo(&sid).unwrap().unwrap(); + let oabar = OpenAnonAssetRecordBuilder::from_abar( + &abar, + memo, + &axfr_secret_key.into_noah(), + ) + .unwrap() + .build() + .unwrap(); + + let n = nullify( + &axfr_secret_key.into_noah(), + oabar.get_amount(), + oabar.get_asset_type().as_scalar(), + sid.0, + ) + .c(d!())?; + let hash = wallet::nullifier_to_base58(&n.0); + let null_status = utils::check_nullifier_hash(&hash).c(d!())?.unwrap(); + println!( + "{0: <8} | {1: <18} | {2: <45} | {3: <9} | {4: <45}", + sid.0, + oabar.get_amount(), + AssetTypeCode { + val: oabar.get_asset_type() + } + .to_base64(), + null_status, + com + ); + + Ok(()) + })?; + + Ok(()) +} + +/// Prints a dainty list of Abar info with spent status for a given AxfrKeyPair and a list of +/// commitments. +pub fn anon_balance( + axfr_secret_key: XfrKeyPair, + commitments_list: &str, + asset: Option<&str>, +) -> Result<()> { + // Parse Asset Type for filtering if provided + let mut asset_type = ASSET_TYPE_FRA; + if let Some(a) = asset { + asset_type = if a.to_uppercase() == "FRA" { + ASSET_TYPE_FRA + } else { + AssetTypeCode::new_from_base64(asset.unwrap()).unwrap().val + }; + } + + let mut balance = 0u64; + commitments_list + .split(',') + .try_for_each(|com| -> ruc::Result<()> { + let commitment = wallet::commitment_from_base58(com).c(d!())?; + + let result = utils::get_owned_abar(&commitment); + match result { + Err(e) => { + if e.msg_eq(eg!("missing abar").as_ref()) { + Ok(()) + } else { + Err(e) + } + } + Ok((sid, abar)) => { + let memo = utils::get_abar_memo(&sid).unwrap().unwrap(); + let oabar = OpenAnonAssetRecordBuilder::from_abar( + &abar, + memo, + &axfr_secret_key.into_noah(), + ) + .unwrap() + .build() + .unwrap(); + + let n = nullify( + &axfr_secret_key.into_noah(), + oabar.get_amount(), + oabar.get_asset_type().as_scalar(), + sid.0, + ) + .c(d!())?; + let hash = wallet::nullifier_to_base58(&n.0); + let is_spent = utils::check_nullifier_hash(&hash).c(d!())?.unwrap(); + if !is_spent && oabar.get_asset_type() == asset_type { + balance += oabar.get_amount(); + } + + Ok(()) + } + } + })?; + + println!("{}: {}", asset.unwrap_or("FRA"), balance); + Ok(()) +} + /// Return the built version. pub fn version() -> &'static str { concat!(env!("VERGEN_SHA"), " ", env!("VERGEN_BUILD_DATE")) } ///operation to replace the staker. -pub fn replace_staker(target_addr: fp_types::H160, td_addr: &str) -> Result<()> { +pub fn replace_staker( + target_addr: fp_types::H160, + td_addr: &str, + is_address_eth: bool, +) -> Result<()> { let td_addr = hex::decode(td_addr).c(d!())?; - let keypair = get_keypair()?; + let keypair = get_keypair(is_address_eth)?; let mut builder = utils::new_tx_builder().c(d!())?; @@ -844,7 +1550,7 @@ pub fn replace_staker(target_addr: fp_types::H160, td_addr: &str) -> Result<()> })?; builder.add_operation_replace_staker(&keypair, target_addr, td_addr)?; - let mut tx = builder.take_transaction(); + let mut tx = builder.build_and_take_transaction()?; tx.sign_to_map(&keypair); utils::send_tx(&tx).c(d!())?; diff --git a/src/components/finutils/src/common/utils.rs b/src/components/finutils/src/common/utils.rs index 0849dbf2d..0d794aa8a 100644 --- a/src/components/finutils/src/common/utils.rs +++ b/src/components/finutils/src/common/utils.rs @@ -11,9 +11,9 @@ use { globutils::{wallet, HashOf, SignatureOf}, ledger::{ data_model::{ - AssetType, AssetTypeCode, DefineAsset, Operation, StateCommitmentData, - Transaction, TransferType, TxoRef, TxoSID, Utxo, ASSET_TYPE_FRA, - BLACK_HOLE_PUBKEY, TX_FEE_MIN, + ABARData, ATxoSID, AssetType, AssetTypeCode, DefineAsset, Operation, + StateCommitmentData, Transaction, TransferType, TxoRef, TxoSID, Utxo, + ASSET_TYPE_FRA, BAR_TO_ABAR_TX_FEE_MIN, BLACK_HOLE_PUBKEY, TX_FEE_MIN, }, staking::{ init::get_inital_validators, StakerMemo, TendermintAddrRef, FRA_TOTAL_AMOUNT, @@ -36,11 +36,17 @@ use { Web3, }, zei::{ - noah_api::xfr::{ - asset_record::{open_blind_asset_record, AssetRecordType}, - structs::{AssetRecordTemplate, OwnerMemo}, + noah_api::{ + anon_xfr::structs::{ + AnonAssetRecord, AxfrOwnerMemo, Commitment, MTLeafInfo, + OpenAnonAssetRecord, + }, + xfr::{ + asset_record::{open_blind_asset_record, AssetRecordType}, + structs::{AssetRecordTemplate, OpenAssetRecord, OwnerMemo}, + }, }, - {XfrKeyPair, XfrPublicKey}, + BlindAssetRecord, XfrKeyPair, XfrPublicKey, }, }; @@ -83,7 +89,17 @@ pub fn set_initial_validators() -> Result<()> { let vs = get_inital_validators().c(d!())?; builder.add_operation_update_validator(&[], 1, vs).c(d!())?; - send_tx(&builder.take_transaction()).c(d!()) + send_tx(&builder.build_and_take_transaction()?).c(d!()) +} + +///load the tendermint key from the `priv_validator_key.json` file. +pub fn load_tendermint_priv_validator_key( + key_path: impl AsRef, +) -> Result { + let k = + std::fs::read_to_string(key_path).c(d!("can not read key file from path"))?; + let v_keys = parse_td_validator_keys(&k).c(d!())?; + Ok(v_keys) } #[inline(always)] @@ -133,7 +149,7 @@ pub fn transfer_batch( .c(d!())?; builder.add_operation(op); - let mut tx = builder.take_transaction(); + let mut tx = builder.build_and_take_transaction()?; tx.sign_to_map(owner_kp); send_tx(&tx).c(d!()) @@ -312,6 +328,92 @@ pub fn gen_fee_op(owner_kp: &XfrKeyPair) -> Result { gen_transfer_op(owner_kp, vec![], None, false, false, None).c(d!()) } +/// fee for bar to abar conversion +#[inline(always)] +pub fn gen_fee_bar_to_abar( + owner_kp: &XfrKeyPair, + avoid_input: TxoSID, +) -> Result { + let mut op_fee: u64 = BAR_TO_ABAR_TX_FEE_MIN; + let mut trans_builder = TransferOperationBuilder::new(); + trans_builder + .add_output( + &AssetRecordTemplate::with_no_asset_tracing( + BAR_TO_ABAR_TX_FEE_MIN, + ASSET_TYPE_FRA, + AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType, + *BLACK_HOLE_PUBKEY, + ), + None, + None, + None, + ) + .c(d!())?; + + let utxos = get_owned_utxos(owner_kp.get_pk_ref()).c(d!())?.into_iter(); + for (sid, (utxo, owner_memo)) in utxos { + let oar = open_blind_asset_record( + &utxo.0.record.into_noah(), + &owner_memo, + &owner_kp.into_noah(), + ) + .c(d!())?; + + if op_fee == 0 { + break; + } + if oar.asset_type == ASSET_TYPE_FRA + && oar.get_record_type() + == AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType + && op_fee != 0 + && sid != avoid_input + { + let i_am = oar.amount; + if oar.amount <= op_fee { + op_fee -= i_am; + + trans_builder + .add_input(TxoRef::Absolute(sid), oar, None, None, i_am) + .c(d!())?; + } else { + trans_builder + .add_input(TxoRef::Absolute(sid), oar, None, None, i_am) + .c(d!())?; + + trans_builder + .add_output( + &AssetRecordTemplate::with_no_asset_tracing( + i_am - op_fee, + ASSET_TYPE_FRA, + AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType, + owner_kp.pub_key.into_noah(), + ), + None, + None, + None, + ) + .c(d!())?; + + op_fee = 0; + } + } + } + + if op_fee != 0 { + return Err(eg!("Insufficient balance to pay Txn fees")); + } + + trans_builder + .balance(None) + .c(d!())? + .create(TransferType::Standard) + .c(d!())? + .sign(owner_kp) + .c(d!())? + .transaction() + .c(d!()) +} + ///////////////////////////////////////// // Part 2: utils for query infomations // ///////////////////////////////////////// @@ -516,7 +618,8 @@ pub fn get_asset_all(kp: &XfrKeyPair) -> Result> { Ok(set) } -fn get_owned_utxos( +#[allow(missing_docs)] +pub fn get_owned_utxos( addr: &XfrPublicKey, ) -> Result)>> { get_owned_utxos_x(None, addr).c(d!()) @@ -546,6 +649,33 @@ fn get_owned_utxos_x( }) } +/// Return the ABAR by commitment. +pub fn get_owned_abar(com: &Commitment) -> Result<(ATxoSID, AnonAssetRecord)> { + let url = format!( + "{}:8668/owned_abars/{}", + get_serv_addr().c(d!())?, + wallet::commitment_to_base58(com) + ); + + attohttpc::get(url) + .send() + .c(d!())? + .error_for_status() + .c(d!())? + .bytes() + .c(d!()) + .and_then(|b| { + serde_json::from_slice::>(&b) + .c(d!())? + .ok_or(eg!("missing abar")) + }) + .and_then(|(sid, data)| { + wallet::commitment_from_base58(&data.commitment) + .map(|commitment| (sid, AnonAssetRecord { commitment })) + .map_err(|_| eg!("commitment invalid")) + }) +} + #[inline(always)] fn get_seq_id() -> Result { type Resp = ( @@ -591,6 +721,61 @@ pub fn get_owner_memo_batch(ids: &[TxoSID]) -> Result>> { .and_then(|b| serde_json::from_slice(&b).c(d!())) } +#[inline(always)] +#[allow(missing_docs)] +pub fn get_abar_memo(id: &ATxoSID) -> Result> { + let id = id.0.to_string(); + let url = format!("{}:8667/get_abar_memo/{}", get_serv_addr().c(d!())?, id); + + attohttpc::get(url) + .send() + .c(d!())? + .error_for_status() + .c(d!())? + .bytes() + .c(d!()) + .and_then(|b| serde_json::from_slice(&b).c(d!())) +} + +#[inline(always)] +#[allow(missing_docs)] +pub fn get_abar_proof(atxo_sid: &ATxoSID) -> Result> { + let atxo_sid = atxo_sid.0.to_string(); + let url = format!( + "{}:8667/get_abar_proof/{}", + get_serv_addr().c(d!())?, + atxo_sid + ); + + attohttpc::get(url) + .send() + .c(d!())? + .error_for_status() + .c(d!())? + .bytes() + .c(d!()) + .and_then(|b| serde_json::from_slice(&b).c(d!())) +} + +#[inline(always)] +#[allow(missing_docs)] +pub fn check_nullifier_hash(null_hash: &str) -> Result> { + let url = format!( + "{}:8667/check_nullifier_hash/{}", + get_serv_addr().c(d!())?, + null_hash + ); + + attohttpc::get(url) + .send() + .c(d!())? + .error_for_status() + .c(d!())? + .bytes() + .c(d!()) + .and_then(|b| serde_json::from_slice(&b).c(d!())) +} + /// Delegation info(and staking info if `pk` is a validator). pub fn get_delegation_info(pk: &XfrPublicKey) -> Result { let url = format!( @@ -757,3 +942,113 @@ pub fn get_validator_memo_and_rate( }; Ok((memo, rate)) } + +#[inline(always)] +/// Generates a BarToAbar Operation and an accompanying FeeOP and sends it to the network and return the Randomizer +/// # Arguments +/// * `auth_key_pair` - XfrKeyPair of the owner BAR for conversion +/// * `abar_pub_key` - AXfrPubKey of the receiver ABAR after conversion +/// * `txo_sid` - TxoSID of the BAR to convert +/// * `input_record` - OpenAssetRecord of the BAR to convert +/// * `is_bar_transparent` - if transparent bar (ar) +pub fn generate_bar2abar_op( + auth_key_pair: &XfrKeyPair, + abar_pub_key: &XfrPublicKey, + txo_sid: TxoSID, + input_record: &OpenAssetRecord, + is_bar_transparent: bool, +) -> Result { + // add operation bar_to_abar in a new Tx Builder + + let mut seed = [0u8; 32]; + + getrandom::getrandom(&mut seed).c(d!())?; + + let mut builder: TransactionBuilder = new_tx_builder().c(d!())?; + let (_, c) = builder + .add_operation_bar_to_abar( + seed, + auth_key_pair, + abar_pub_key, + txo_sid, + input_record, + is_bar_transparent, + ) + .c(d!("Failed to generate operation bar to abar"))?; + + // Add a transparent fee operation for conversion which is required to process the bar + // In this step a transparent FRA AssetRecord is chosen from user owned UTXOs to pay the fee. + // If the user doesn't own such a UTXO then this method throws an error. + let feeop = + gen_fee_bar_to_abar(auth_key_pair, txo_sid).c(d!("Failed to generate fee"))?; + builder.add_operation(feeop); + + let mut tx = builder.build_and_take_transaction()?; + + tx.sign(auth_key_pair); + + // submit transaction to network + send_tx(&tx).c(d!("Failed to submit Bar to Abar txn"))?; + + Ok(c) +} + +#[inline(always)] +/// Create AbarToBar transaction with given Open ABAR & Open Bar and submit it to network +/// # Arguments +/// * oabar_in - Abar to convert in open form +/// * fee_oabar - Abar to pay anon fee in open form +/// * out_fee_oabar - Abar to get balance back after paying fee +/// * from - AXfrKeyPair of person converting ABAR +/// * to - XfrPublicKey of person receiving new BAR +/// * art - AssetRecordType of the new BAR +pub fn generate_abar2bar_op( + oabar_in: &OpenAnonAssetRecord, + from: &XfrKeyPair, + to: &XfrPublicKey, + art: AssetRecordType, +) -> Result<()> { + let mut builder: TransactionBuilder = new_tx_builder().c(d!())?; + // create and add AbarToBar Operation + builder + .add_operation_abar_to_bar(oabar_in, from, to, art) + .c(d!())?; + + // submit transaction + send_tx(&builder.build_and_take_transaction()?).c(d!())?; + Ok(()) +} + +#[inline(always)] +#[allow(missing_docs)] +pub fn get_oar( + owner_kp: &XfrKeyPair, + txo_sid: TxoSID, +) -> Result<(OpenAssetRecord, BlindAssetRecord)> { + let utxos = get_owned_utxos(owner_kp.get_pk_ref()).c(d!())?.into_iter(); + + for (sid, (utxo, owner_memo)) in utxos { + if sid != txo_sid { + continue; + } + + let oar = open_blind_asset_record( + &utxo.0.record.into_noah(), + &owner_memo, + &owner_kp.into_noah(), + ) + .c(d!())?; + + return Ok((oar, utxo.0.record)); + } + + Err(eg!("utxo not found")) +} + +#[inline(always)] +#[allow(missing_docs)] +pub fn get_abar_data(abar: AnonAssetRecord) -> ABARData { + ABARData { + commitment: wallet::commitment_to_base58(&abar.commitment), + } +} diff --git a/src/components/finutils/src/txn_builder/mod.rs b/src/components/finutils/src/txn_builder/mod.rs index 032a9ad9e..1bd6fc208 100644 --- a/src/components/finutils/src/txn_builder/mod.rs +++ b/src/components/finutils/src/txn_builder/mod.rs @@ -7,17 +7,20 @@ use { credentials::CredUserSecretKey, + curve25519_dalek::scalar::Scalar, + digest::Digest, fp_types::{crypto::MultiSigner, H160}, - globutils::SignatureOf, + globutils::{wallet, Serialized, SignatureOf}, ledger::{ converter::ConvertAccount, data_model::{ - AssetRules, AssetTypeCode, ConfidentialMemo, DefineAsset, DefineAssetBody, - IndexedSignature, IssueAsset, IssueAssetBody, IssuerKeyPair, - IssuerPublicKey, Memo, NoReplayToken, Operation, Transaction, + get_abar_commitment, AbarConvNote, AbarToBarOps, AnonTransferOps, + AssetRules, AssetTypeCode, BarAnonConvNote, BarToAbarOps, ConfidentialMemo, + DefineAsset, DefineAssetBody, IndexedSignature, IssueAsset, IssueAssetBody, + IssuerKeyPair, IssuerPublicKey, Memo, NoReplayToken, Operation, Transaction, TransactionBody, TransferAsset, TransferAssetBody, TransferType, TxOutput, - TxoRef, UpdateMemo, UpdateMemoBody, ASSET_TYPE_FRA, BLACK_HOLE_PUBKEY, - TX_FEE_MIN, + TxoRef, TxoSID, UpdateMemo, UpdateMemoBody, ASSET_TYPE_FRA, + BAR_TO_ABAR_TX_FEE_MIN, BLACK_HOLE_PUBKEY, FEE_CALCULATING_FUNC, TX_FEE_MIN, }, staking::{ is_valid_tendermint_addr, @@ -35,36 +38,51 @@ use { TendermintAddr, Validator, }, }, - noah_curve25519_dalek::scalar::Scalar, rand_chacha::ChaChaRng, - rand_core::{CryptoRng, RngCore, SeedableRng}, + rand_core::SeedableRng, ruc::*, serde::{Deserialize, Serialize}, + sha2::Sha512, std::{ cmp::Ordering, - collections::{BTreeMap, HashSet}, + collections::{BTreeMap, HashMap, HashSet}, }, tendermint::PrivateKey, - zei::noah_algebra::prelude::*, - zei::noah_algebra::ristretto::PedersenCommitmentRistretto, - zei::noah_api::{ - anon_creds::{ - ac_confidential_open_commitment, ACCommitment, ACCommitmentKey, - ConfidentialAC, Credential, - }, - xfr::{ - asset_record::{ - build_blind_asset_record, build_open_asset_record, - open_blind_asset_record, AssetRecordType, + zei::{ + noah_algebra::{prelude::*, ristretto::PedersenCommitmentRistretto}, + noah_api::{ + anon_creds::{ + ac_confidential_open_commitment, ACCommitment, ACCommitmentKey, + ConfidentialAC, Credential, + }, + anon_xfr::{ + abar_to_abar::{finish_anon_xfr_note, init_anon_xfr_note, AXfrPreNote}, + abar_to_ar::{ + finish_abar_to_ar_note, init_abar_to_ar_note, AbarToArPreNote, + }, + abar_to_bar::{ + finish_abar_to_bar_note, init_abar_to_bar_note, AbarToBarPreNote, + }, + ar_to_abar::gen_ar_to_abar_note, + bar_to_abar::gen_bar_to_abar_note, + structs::{Commitment, OpenAnonAssetRecord, OpenAnonAssetRecordBuilder}, }, - structs::{ - AssetRecord, AssetRecordTemplate, OpenAssetRecord, TracingPolicies, - TracingPolicy, + keys::SecretKey, + parameters::{AddressFormat, ProverParams}, + xfr::{ + asset_record::{ + build_blind_asset_record, build_open_asset_record, + open_blind_asset_record, AssetRecordType, + }, + structs::{ + AssetRecord, AssetRecordTemplate, AssetType, OpenAssetRecord, + TracingPolicies, TracingPolicy, + }, + XfrNotePolicies, }, - XfrNotePolicies, }, + BlindAssetRecord, OwnerMemo, XfrKeyPair, XfrPublicKey, }, - zei::{BlindAssetRecord, OwnerMemo, XfrKeyPair, XfrPublicKey}, }; macro_rules! no_transfer_err { @@ -119,6 +137,12 @@ pub struct TransactionBuilder { outputs: u64, #[allow(missing_docs)] pub no_replay_token: NoReplayToken, + #[serde(skip)] + abar_bar_cache: Vec, + #[serde(skip)] + abar_ar_cache: Vec, + #[serde(skip)] + abar_abar_cache: Vec, } impl TransactionBuilder { @@ -142,7 +166,7 @@ impl TransactionBuilder { .outputs .iter() .zip($d.body.transfer.owners_memos.iter()) - .map(|(r, om)| (r.clone(), om.clone())) + .map(|(r, om)| (r.clone(), om.clone().map(|it| it))) .collect() }; } @@ -221,32 +245,41 @@ impl TransactionBuilder { self.add_fee_custom(inputs, TX_FEE_MIN) } - /// As the last operation of any transaction, + /// As the last operation of bar_to_abar transaction, /// add a static fee to the transaction. - pub fn add_fee_custom(&mut self, inputs: FeeInputs, fee: u64) -> Result<&mut TransactionBuilder> { + pub fn add_fee_bar_to_abar( + &mut self, + inputs: FeeInputs, + ) -> Result<&mut TransactionBuilder> { + self.add_fee_custom(inputs, BAR_TO_ABAR_TX_FEE_MIN) + } + + /// As the last operation of any transaction, + /// add a custom static fee to the transaction. + pub fn add_fee_custom( + &mut self, + inputs: FeeInputs, + fee: u64, + ) -> Result<&mut TransactionBuilder> { let mut kps = vec![]; let mut opb = TransferOperationBuilder::default(); let mut am = fee; for i in inputs.inner.into_iter() { - open_blind_asset_record( - &i.ar.record.into_noah(), - &i.om.map(|o| o.into_noah()), - &i.kp.into_noah(), - ) - .c(d!()) - .and_then(|oar| { - if oar.asset_type != ASSET_TYPE_FRA { - return Err(eg!("Incorrect fee input asset_type, expected Findora AssetType record")); - } - let n = alt!(oar.amount > am, am, oar.amount); - am = am.saturating_sub(oar.amount); - opb.add_input(i.tr, oar, None, None, n) - .map(|_| { - kps.push(i.kp); - }) - .c(d!()) - })?; + open_blind_asset_record(&i.ar.record.into_noah(), &i.om.map(|o| o.into_noah()), &i.kp.into_noah()) + .c(d!()) + .and_then(|oar| { + if oar.asset_type != ASSET_TYPE_FRA { + return Err(eg!("Incorrect fee input asset_type, expected Findora AssetType record")); + } + let n = alt!(oar.amount > am, am, oar.amount); + am = am.saturating_sub(oar.amount); + opb.add_input(i.tr, oar, None, None, n) + .map(|_| { + kps.push(i.kp); + }) + .c(d!()) + })?; } opb.add_output( @@ -285,8 +318,8 @@ impl TransactionBuilder { } #[allow(missing_docs)] - pub fn get_owner_memo_ref(&self, idx: usize) -> Option<&OwnerMemo> { - self.txn.get_owner_memos_ref()[idx] + pub fn get_owner_memo_ref(&self, idx: usize) -> Option { + self.txn.get_owner_memos_ref()[idx].clone() } #[allow(missing_docs)] @@ -301,6 +334,9 @@ impl TransactionBuilder { TransactionBuilder { txn: Transaction::from_seq_id(seq_id), outputs: 0, + abar_abar_cache: vec![], + abar_bar_cache: vec![], + abar_ar_cache: vec![], no_replay_token, } } @@ -358,6 +394,157 @@ impl TransactionBuilder { self.txn } + #[allow(missing_docs)] + pub fn build_and_take_transaction(&mut self) -> Result { + self.build()?; + Ok(self.txn.clone()) + } + + /// Build a transaction from various pre-notes of operations + pub fn build(&mut self) -> Result<()> { + let mut prng = ChaChaRng::from_entropy(); + + // hasher txn. (IMPORTANT! KEEP THE same order) + let mut hasher = Sha512::new(); + let mut bytes = self.txn.body.digest(); + for i in &self.abar_abar_cache { + bytes.extend_from_slice(Serialized::new(&i.body).as_ref()); + } + for i in &self.abar_bar_cache { + bytes.extend_from_slice(Serialized::new(&i.body).as_ref()); + } + for i in &self.abar_ar_cache { + bytes.extend_from_slice(Serialized::new(&i.body).as_ref()); + } + hasher.update(bytes.as_slice()); + + // finish abar to abar + if !self.abar_abar_cache.is_empty() { + let mut params: HashMap<(usize, usize, u8), ProverParams> = HashMap::new(); + for pre_note in &self.abar_abar_cache { + let (af, ak) = match pre_note.input_keypair.get_sk_ref() { + SecretKey::Secp256k1(_) => (AddressFormat::SECP256K1, 0), + SecretKey::Ed25519(_) => (AddressFormat::ED25519, 1), + }; + let key = (pre_note.body.inputs.len(), pre_note.body.outputs.len(), ak); + let param = if let Some(key) = params.get(&key) { + key + } else { + let param = + ProverParams::gen_abar_to_abar(key.0, key.1, af).c(d!())?; + params.insert(key, param); + params.get(&key).unwrap() // safe, checked. + }; + + let note = finish_anon_xfr_note( + &mut prng, + param, + pre_note.clone(), + hasher.clone(), + ) + .c(d!())?; + + // Add operation + let inp = AnonTransferOps::new(note, self.no_replay_token).c(d!())?; + let op = Operation::TransferAnonAsset(Box::new(inp)); + self.txn.add_operation(op); + } + } + + // finish abar to bar + if !self.abar_bar_cache.is_empty() { + let mut abar_bar_params = [None, None]; + for pre_note in &self.abar_bar_cache { + let params = match pre_note.input_keypair.get_sk_ref() { + SecretKey::Secp256k1(_) => { + if abar_bar_params[0].is_none() { + abar_bar_params[0] = Some( + ProverParams::gen_abar_to_bar(AddressFormat::SECP256K1) + .c(d!())?, + ); + } + abar_bar_params[0].as_ref() + } + SecretKey::Ed25519(_) => { + if abar_bar_params[1].is_none() { + abar_bar_params[1] = Some( + ProverParams::gen_abar_to_bar(AddressFormat::ED25519) + .c(d!())?, + ); + } + abar_bar_params[1].as_ref() + } + }; + + let note = finish_abar_to_bar_note( + &mut prng, + params.unwrap(), + pre_note.clone(), + hasher.clone(), + ) + .c(d!())?; + + // Create operation + let conv = AbarToBarOps::new( + AbarConvNote::AbarToBar(Box::new(note)), + self.no_replay_token, + ) + .c(d!())?; + let op = Operation::AbarToBar(Box::from(conv)); + + // Add operation to transaction + self.txn.add_operation(op); + } + } + + // finish abar to ar + if !self.abar_ar_cache.is_empty() { + let mut abar_ar_params = [None, None]; + for pre_note in &self.abar_ar_cache { + let params = match pre_note.input_keypair.get_sk_ref() { + SecretKey::Secp256k1(_) => { + if abar_ar_params[0].is_none() { + abar_ar_params[0] = Some( + ProverParams::gen_abar_to_ar(AddressFormat::SECP256K1) + .c(d!())?, + ); + } + abar_ar_params[0].as_ref() + } + SecretKey::Ed25519(_) => { + if abar_ar_params[1].is_none() { + abar_ar_params[1] = Some( + ProverParams::gen_abar_to_ar(AddressFormat::ED25519) + .c(d!())?, + ); + } + abar_ar_params[1].as_ref() + } + }; + let note = finish_abar_to_ar_note( + &mut prng, + params.unwrap(), + pre_note.clone(), + hasher.clone(), + ) + .c(d!())?; + + // Create operation + let conv = AbarToBarOps::new( + AbarConvNote::AbarToAr(Box::new(note)), + self.no_replay_token, + ) + .c(d!())?; + let op = Operation::AbarToBar(Box::from(conv)); + + // Add operation to transaction + self.txn.add_operation(op); + } + } + + Ok(()) + } + /// Append a transaction memo pub fn add_memo(&mut self, memo: Memo) -> &mut Self { self.txn.body.memos.push(memo); @@ -489,7 +676,216 @@ impl TransactionBuilder { self } - /// Add a operation to delegating findora account to a tendermint validator. + /// Add an operation to convert a Blind Asset Record to a Anonymous record and return the Commitment + /// # Arguments + /// * `auth_key_pair` - XfrKeyPair of the owner BAR for conversion + /// * `abar_pub_key` - AXfrPubKey of the receiver ABAR after conversion + /// * `txo_sid` - TxoSID of the BAR to convert + /// * `input_record` - OpenAssetRecord of the BAR to convert + /// * `enc_key` - XPublicKey of OwnerMemo encryption of receiver + /// * `is_bar_transparent` - if transparent bar (ar) + #[allow(clippy::too_many_arguments)] + pub fn add_operation_bar_to_abar( + &mut self, + seed: [u8; 32], + auth_key_pair: &XfrKeyPair, + abar_pub_key: &XfrPublicKey, + txo_sid: TxoSID, + input_record: &OpenAssetRecord, + is_bar_transparent: bool, + ) -> Result<(&mut Self, Commitment)> { + // generate the BarToAbarNote with the ZKP + let (note, c) = gen_bar_conv_note( + seed, + input_record, + auth_key_pair, + abar_pub_key, + is_bar_transparent, + ) + .c(d!())?; + + // Create the BarToAbarOps + let bar_to_abar = BarToAbarOps::new(note, txo_sid, self.no_replay_token)?; + + // Add the generated operation to the transaction + let op = Operation::BarToAbar(Box::from(bar_to_abar)); + self.txn.add_operation(op); + Ok((self, c)) + } + + /// Create a new operation to convert from Anonymous record to Blind Asset Record + /// # Arguments + /// * input - ABAR to be converted + /// * input_keypair - owner keypair of ABAR to be converted + /// * bar_pub_key - Pubkey of the receiver of the new BAR + /// * asset_record_type - The type of confidentiality of new BAR + pub fn add_operation_abar_to_bar( + &mut self, + input: &OpenAnonAssetRecord, + input_keypair: &XfrKeyPair, + bar_pub_key: &XfrPublicKey, + asset_record_type: AssetRecordType, + ) -> Result<&mut Self> { + let mut prng = ChaChaRng::from_entropy(); + match asset_record_type { + AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType => { + let note = init_abar_to_ar_note( + &mut prng, + input, + &input_keypair.into_noah(), + &bar_pub_key.into_noah(), + ) + .c(d!())?; + self.abar_ar_cache.push(note); + } + _ => { + let note = init_abar_to_bar_note( + &mut prng, + input, + &input_keypair.into_noah(), + &bar_pub_key.into_noah(), + asset_record_type, + ) + .c(d!())?; + self.abar_bar_cache.push(note); + } + } + + Ok(self) + } + + /// Add an operation to transfer assets held in Anonymous Blind Asset Record. + /// Note - Input and output amounts should be balanced, including FRA implicit fee + /// Use op add_operation_anon_transfer_fees_remainder for automatic remainder and fee handling + /// # Arguments + /// * inputs - List of input ABARs to be used for the transfer + /// * outputs - List of output ABARs + /// * input_keypair - list of AXfrKeyPair of the sender + #[allow(dead_code)] + pub fn add_operation_anon_transfer( + &mut self, + inputs: &[OpenAnonAssetRecord], + outputs: &[OpenAnonAssetRecord], + input_keypair: &XfrKeyPair, + ) -> Result<(&mut Self, AXfrPreNote)> { + let fee = FEE_CALCULATING_FUNC(inputs.len() as u32, outputs.len() as u32); + + // generate anon transfer note + let note = init_anon_xfr_note(inputs, outputs, fee, &input_keypair.into_noah()) + .c(d!())?; + self.abar_abar_cache.push(note.clone()); + + Ok((self, note)) + } + + /// Create an operation for ABAR transfer and generates remainder abars for balance amounts + /// # Arguments + /// * inputs - List of input ABARs to be used for the transfer + /// * outputs - List of output ABARs + /// * input_keypair - list of AXfrKeyPair of the sender + /// * enc_key - The encryption key of the sender to send the remainder abar + pub fn add_operation_anon_transfer_fees_remainder( + &mut self, + inputs: &[OpenAnonAssetRecord], + outputs: &[OpenAnonAssetRecord], + input_keypair: &XfrKeyPair, + ) -> Result<(&mut Self, AXfrPreNote, Vec)> { + let mut prng = ChaChaRng::from_entropy(); + + let mut vec_outputs = outputs.to_vec(); + let mut vec_changes = vec![]; + let mut remainders = HashMap::new(); + let remainder_pk = input_keypair.get_pk(); + + // Create a remainders hashmap with remainder amount for each asset type + for input in inputs { + // add each input amount to the asset type entry + remainders + .entry(input.get_asset_type()) + .and_modify(|rem| *rem += input.get_amount() as i64) + .or_insert(input.get_amount() as i64); + } + for output in outputs { + // subtract each output amount from the asset type entry + remainders + .entry(output.get_asset_type()) + .and_modify(|rem| *rem -= output.get_amount() as i64) + .or_insert(-(output.get_amount() as i64)); + } + + // Check if at least one input is of FRA asset type + let fra_rem = remainders.remove(&ASSET_TYPE_FRA); + if fra_rem.is_none() { + return Err(eg!("Must include an FRA ABAR to pay FEE!")); + } + + // Create remainder OABARs for non-FRA asset types + for (asset_type, remainder) in remainders { + println!( + "Transaction Asset: {:?} Remainder Amount: {:?}", + base64::encode(&asset_type.0), + remainder + ); + + if remainder < 0 { + return Err(eg!("Transfer Asset token input less than output!")); + } + + if remainder > 0 { + let oabar_money_back = OpenAnonAssetRecordBuilder::new() + .amount(remainder as u64) + .asset_type(asset_type) + .pub_key(&remainder_pk.into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(); + + // Add the oabar to list of outputs and a list of new oabars created + vec_outputs.push(oabar_money_back.clone()); + vec_changes.push(oabar_money_back); + } + } + + // Calculate implicit fees that will get deducted and subtract from FRA remainder + let fees = + FEE_CALCULATING_FUNC(inputs.len() as u32, vec_outputs.len() as u32 + 1); + let fra_remainder = fra_rem.unwrap() - (fees as i64); // safe. checked. + if fra_remainder < 0 { + return Err(eg!("insufficient FRA to pay fees!")); + } + if fra_remainder > 0 { + println!("Transaction FRA Remainder Amount: {fra_remainder:?}",); + let oabar_money_back = OpenAnonAssetRecordBuilder::new() + .amount(fra_remainder as u64) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&remainder_pk.into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(); + + // Add FRA remainder oabar to outputs + vec_outputs.push(oabar_money_back.clone()); + vec_changes.push(oabar_money_back); + } + + if vec_outputs.len() > 5 { + return Err(eg!( + "Total outputs (incl. remainders) cannot be greater than 5" + )); + } + + let note = + init_anon_xfr_note(inputs, &vec_outputs, fees, &input_keypair.into_noah()) + .c(d!())?; + self.abar_abar_cache.push(note.clone()); + + // return a list of all new remainder abars generated + Ok((self, note, vec_changes)) + } + + /// Add a operation to delegating finddra accmount to a tendermint validator. /// The transfer operation to BLACK_HOLE_PUBKEY_STAKING should be sent along with. pub fn add_operation_delegation( &mut self, @@ -792,6 +1188,49 @@ pub(crate) fn build_record_and_get_blinds( )) } +fn gen_bar_conv_note( + seed: [u8; 32], + input_record: &OpenAssetRecord, + auth_key_pair: &XfrKeyPair, + abar_pub_key: &XfrPublicKey, + is_bar_transparent: bool, +) -> Result<(BarAnonConvNote, Commitment)> { + // let mut prng = ChaChaRng::from_entropy(); + let mut prng = ChaChaRng::from_seed(seed); + + if is_bar_transparent { + let prover_params = ProverParams::gen_ar_to_abar().c(d!())?; + + // generate the BarToAbarNote with the ZKP + let note = gen_ar_to_abar_note( + &mut prng, + &prover_params, + input_record, + &auth_key_pair.into_noah(), + &abar_pub_key.into_noah(), + ) + .c(d!())?; + + let c = note.body.output.commitment; + Ok((BarAnonConvNote::ArNote(Box::new(note)), c)) + } else { + // generate params for Bar to Abar conversion + let prover_params = ProverParams::gen_bar_to_abar().c(d!())?; + + // generate the BarToAbarNote with the ZKP + let note = gen_bar_to_abar_note( + &mut prng, + &prover_params, + input_record, + &auth_key_pair.into_noah(), + &abar_pub_key.into_noah(), + ) + .c(d!())?; + let c = note.body.output.commitment; + Ok((BarAnonConvNote::BarNote(Box::new(note)), c)) + } +} + /// TransferOperationBuilder constructs transfer operations using the factory pattern /// Inputs and outputs are added iteratively before being signed by all input record owners #[derive(Clone, Serialize, Deserialize, Default)] @@ -961,7 +1400,6 @@ impl TransferOperationBuilder { } // Check if outputs and inputs are balanced - fn check_balance(&self) -> Result<()> { let input_total: u64 = self .input_records @@ -1161,20 +1599,439 @@ impl TransferOperationBuilder { } } +/// AnonTransferOperationBuilder builders anon transfer operation using the factory pattern. +/// This is used for the wasm interface in building a multi-input/output anon transfer operation. +#[derive(Default)] +pub struct AnonTransferOperationBuilder { + inputs: Vec, + outputs: Vec, + keypair: Option, + pre_note: Option, + commitments: Vec, + + nonce: NoReplayToken, + txn: Transaction, +} + +impl AnonTransferOperationBuilder { + /// default returns a fresh default builder + pub fn new_from_seq_id(seq_id: u64) -> Self { + let mut prng = ChaChaRng::from_entropy(); + let no_replay_token = NoReplayToken::new(&mut prng, seq_id); + + AnonTransferOperationBuilder { + inputs: Vec::default(), + outputs: Vec::default(), + keypair: None, + pre_note: None, + commitments: Vec::default(), + nonce: no_replay_token, + txn: Transaction::from_seq_id(seq_id), + } + } + + /// add_input is used for adding an input source to the Anon Transfer Operation factory, it takes + /// an ABAR and a Keypair as input + pub fn add_input(&mut self, abar: OpenAnonAssetRecord) -> Result<&mut Self> { + if self.inputs.len() >= 5 { + return Err(eg!("Total inputs (incl. fees) cannot be greater than 5")); + } + self.inputs.push(abar); + Ok(self) + } + + /// add_output is used to add a output record to the Anon Transfer factory + pub fn add_output(&mut self, abar: OpenAnonAssetRecord) -> Result<&mut Self> { + if self.outputs.len() >= 5 { + return Err(eg!( + "Total outputs (incl. remainders) cannot be greater than 5" + )); + } + self.commitments.push(get_abar_commitment(abar.clone())); + self.outputs.push(abar); + Ok(self) + } + + /// add_keypair is used to specify the input keypair for nullifier generation + pub fn add_keypair(&mut self, keypair: XfrKeyPair) -> &mut Self { + self.keypair = Some(keypair); + self + } + + #[allow(missing_docs)] + pub fn extra_fee_estimation(&self) -> Result { + if self.inputs.len() > 5 { + return Err(eg!("Total inputs (incl. fees) cannot be greater than 5")); + } + + let input_sums = + self.inputs + .iter() + .fold(HashMap::new(), |mut asset_amounts, i| { + let sum = asset_amounts.entry(i.get_asset_type()).or_insert(0u64); + *sum += i.get_amount(); + asset_amounts + }); + let output_sums = + self.outputs + .iter() + .fold(HashMap::new(), |mut asset_amounts, o| { + let sum = asset_amounts.entry(o.get_asset_type()).or_insert(0u64); + *sum += o.get_amount(); + asset_amounts + }); + + let fra_input_sum = *input_sums.get(&ASSET_TYPE_FRA).unwrap_or(&0u64); + let fra_output_sum = *output_sums.get(&ASSET_TYPE_FRA).unwrap_or(&0u64); + if output_sums.len() > input_sums.len() { + return Err(eg!("Output assets cannot be more than input assets")); + } + + let extra_remainders = input_sums.iter().try_fold( + 0usize, + |extra_remainders, (asset, inp_amount)| { + if *asset == ASSET_TYPE_FRA { + Ok(extra_remainders) + } else { + match output_sums.get(asset) { + None => Ok(extra_remainders + 1), + Some(op_sum) => match op_sum.cmp(inp_amount) { + Ordering::Equal => Ok(extra_remainders), + Ordering::Less => Ok(extra_remainders + 1), + Ordering::Greater => { + Err(eg!("Output cannot be greater than Input")) + } + }, + } + } + }, + )?; + + let estimated_fees_without_extra_fra_ip_op = FEE_CALCULATING_FUNC( + self.inputs.len() as u32, + (self.outputs.len() + extra_remainders) as u32, + ) as u64; + + let fra_extra_output_sum = + fra_output_sum + estimated_fees_without_extra_fra_ip_op; + match fra_input_sum.cmp(&fra_extra_output_sum) { + Ordering::Equal => Ok(0u64), + Ordering::Greater => { + let estimated_fees_with_fra_remainder = FEE_CALCULATING_FUNC( + self.inputs.len() as u32, + (self.outputs.len() + extra_remainders + 1) as u32, + ) as u64; + + if fra_input_sum >= (fra_output_sum + estimated_fees_with_fra_remainder) + { + Ok(0u64) + } else { + let estimated_fees_with_extra_input_and_remainder = + FEE_CALCULATING_FUNC( + (self.inputs.len() + 1) as u32, + (self.outputs.len() + extra_remainders + 1) as u32, + ) as u64; + + Ok( + fra_output_sum + estimated_fees_with_extra_input_and_remainder + - fra_input_sum, + ) + } + } + Ordering::Less => { + // case where input is insufficient + let estimated_fees_with_extra_input_and_remainder = FEE_CALCULATING_FUNC( + (self.inputs.len() + 1) as u32, + (self.outputs.len() + extra_remainders + 1) as u32, + ) + as u64; + Ok( + fra_output_sum + estimated_fees_with_extra_input_and_remainder + - fra_input_sum, + ) + } + } + } + + #[allow(missing_docs)] + pub fn get_total_fee_estimation(&self) -> Result { + let input_sums = + self.inputs + .iter() + .fold(HashMap::new(), |mut asset_amounts, i| { + let sum = asset_amounts.entry(i.get_asset_type()).or_insert(0u64); + *sum += i.get_amount(); + asset_amounts + }); + let output_sums = + self.outputs + .iter() + .fold(HashMap::new(), |mut asset_amounts, o| { + let sum = asset_amounts.entry(o.get_asset_type()).or_insert(0u64); + *sum += o.get_amount(); + asset_amounts + }); + + let fra_input_sum = *input_sums.get(&ASSET_TYPE_FRA).unwrap_or(&0u64); + let fra_output_sum = *output_sums.get(&ASSET_TYPE_FRA).unwrap_or(&0u64); + if output_sums.len() > input_sums.len() { + return Err(eg!("Output assets cannot be more than input assets")); + } + + let extra_remainders = input_sums.iter().try_fold( + 0usize, + |extra_remainders, (asset, inp_amount)| { + if *asset == ASSET_TYPE_FRA { + Ok(extra_remainders) + } else { + match output_sums.get(asset) { + None => Ok(extra_remainders + 1), + Some(op_sum) => match op_sum.cmp(inp_amount) { + Ordering::Equal => Ok(extra_remainders), + Ordering::Less => Ok(extra_remainders + 1), + Ordering::Greater => { + Err(eg!("Output cannot be greater than Input")) + } + }, + } + } + }, + )?; + + let estimated_fees_without_extra_fra_ip_op = FEE_CALCULATING_FUNC( + self.inputs.len() as u32, + (self.outputs.len() + extra_remainders) as u32, + ) as u64; + + let fra_extra_output_sum = + fra_output_sum + estimated_fees_without_extra_fra_ip_op; + match fra_input_sum.cmp(&fra_extra_output_sum) { + Ordering::Equal => Ok(fra_input_sum - fra_output_sum), + Ordering::Greater => { + let estimated_fees_with_fra_remainder = FEE_CALCULATING_FUNC( + self.inputs.len() as u32, + (self.outputs.len() + extra_remainders + 1) as u32, + ) as u64; + + if fra_input_sum >= (fra_output_sum + estimated_fees_with_fra_remainder) + { + Ok(estimated_fees_with_fra_remainder) + } else { + let estimated_fees_with_extra_input_and_remainder = + FEE_CALCULATING_FUNC( + (self.inputs.len() + 1) as u32, + (self.outputs.len() + extra_remainders + 1) as u32, + ) as u64; + + Ok(estimated_fees_with_extra_input_and_remainder) + } + } + Ordering::Less => { + // case where input is insufficient + let estimated_fees_with_extra_input_and_remainder = FEE_CALCULATING_FUNC( + (self.inputs.len() + 1) as u32, + (self.outputs.len() + extra_remainders + 1) as u32, + ) + as u64; + Ok(estimated_fees_with_extra_input_and_remainder) + } + } + } + + /// get_commitments fetches the commitments for the different outputs. + pub fn get_commitments(&self) -> Vec { + self.commitments.clone() + } + + /// get a hashmap of commitment, public key, asset, amount + pub fn get_commitment_map(&self) -> HashMap { + let mut commitment_map = HashMap::new(); + for out_abar in self.outputs.iter() { + let abar_rand = + wallet::commitment_to_base58(&get_abar_commitment(out_abar.clone())); + let abar_pkey = *out_abar.pub_key_ref(); + let abar_asset = out_abar.get_asset_type(); + let abar_amt = out_abar.get_amount(); + commitment_map.insert( + abar_rand, + (XfrPublicKey::from_noah(&abar_pkey), abar_asset, abar_amt), + ); + } + commitment_map + } + + /// build generates the anon transfer body with the Zero Knowledge Proof. + pub fn build(&mut self) -> Result<&mut Self> { + if self.inputs.len() > 5 { + return Err(eg!("Total inputs (incl. fees) cannot be greater than 5")); + } + if self.outputs.len() > 5 { + return Err(eg!( + "Total outputs (incl. remainders) cannot be greater than 5" + )); + } + + if self.keypair.is_none() { + return Err(eg!("keypair not set for build")); + } + let keypair = self.keypair.as_ref().unwrap(); + + let mut prng = ChaChaRng::from_entropy(); + let input_asset_list: HashSet = self + .inputs + .iter() + .map(|a| a.get_asset_type()) + .collect::>() + .drain(..) + .collect(); + let mut fees_in_fra = 0u32; + + for asset in input_asset_list { + let mut sum_input = 0; + let mut sum_output = 0; + for input in self.inputs.clone() { + if asset == input.get_asset_type() { + sum_input += input.get_amount(); + } + } + for output in self.outputs.clone() { + if asset == output.get_asset_type() { + sum_output += output.get_amount(); + } + } + + let fees = if asset == ASSET_TYPE_FRA { + fees_in_fra = FEE_CALCULATING_FUNC( + self.inputs.len() as u32, + self.outputs.len() as u32, + ); + fees_in_fra as u64 + } else { + 0u64 + }; + + if sum_output + fees > sum_input { + return if asset == ASSET_TYPE_FRA { + Err(eg!( + "Insufficient FRA balance to pay fees {} + {} > {}", + sum_output, + fees, + sum_input + )) + } else { + Err(eg!( + "Insufficient {:?} balance to pay fees {} + {} > {}", + asset, + sum_output, + fees, + sum_input + )) + }; + } + + let remainder = sum_input - sum_output - fees; + if remainder > 0 { + let oabar_money_back = OpenAnonAssetRecordBuilder::new() + .amount(remainder) + .asset_type(asset) + .pub_key(&keypair.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(); + + let commitment = get_abar_commitment(oabar_money_back.clone()); + self.outputs.push(oabar_money_back); + self.commitments.push(commitment); + } + } + + if self.outputs.len() > 5 { + return Err(eg!( + "Total outputs (incl. remainders) cannot be greater than 5" + )); + } + let note = init_anon_xfr_note( + self.inputs.as_slice(), + self.outputs.as_slice(), + fees_in_fra, + &keypair.into_noah(), + ) + .c(d!("error from init_anon_xfr_note"))?; + + self.pre_note = Some(note); + + Ok(self) + } + + /// Add operation to the transaction + pub fn build_txn(&mut self) -> Result<()> { + let mut prng = ChaChaRng::from_entropy(); + let pre_note = self.pre_note.clone().unwrap(); + let af = match pre_note.input_keypair.get_sk_ref() { + SecretKey::Secp256k1(_) => AddressFormat::SECP256K1, + SecretKey::Ed25519(_) => AddressFormat::ED25519, + }; + let param = + ProverParams::gen_abar_to_abar(self.inputs.len(), self.outputs.len(), af) + .c(d!())?; + + let mut hasher = Sha512::new(); + + let mut bytes = self.txn.body.digest(); + bytes.extend_from_slice(Serialized::new(&pre_note.body).as_ref()); + hasher.update(bytes); + + let note = finish_anon_xfr_note(&mut prng, ¶m, pre_note, hasher).c(d!())?; + + // Add operation + let inp = AnonTransferOps::new(note, self.nonce).c(d!())?; + let op = Operation::TransferAnonAsset(Box::new(inp)); + self.txn.add_operation(op); + + Ok(()) + } + + /// Calculates the Anon fee given the number of inputs and outputs + pub fn get_anon_fee(n_inputs: u32, n_outputs: u32) -> u32 { + FEE_CALCULATING_FUNC(n_inputs, n_outputs) + } + + /// transaction method wraps the anon transfer note in an Operation and returns it + pub fn serialize_str(&self) -> Result { + if self.pre_note.is_none() { + return Err(eg!("Anon transfer not built and signed")); + } + // Unwrap is safe because the underlying transaction is guaranteed to be serializable. + serde_json::to_string(&self.txn).c(d!()) + } +} + #[cfg(test)] #[allow(missing_docs)] mod tests { use { super::*, - ledger::data_model::{TxnEffect, TxoRef}, - ledger::store::{utils::fra_gen_initial_tx, LedgerState}, + ledger::{ + data_model::{ATxoSID, BlockEffect, TxnEffect, TxoRef}, + store::utils::fra_gen_initial_tx, + store::LedgerState, + }, rand_chacha::ChaChaRng, rand_core::SeedableRng, - zei::noah_api::xfr::asset_record::{ - build_blind_asset_record, open_blind_asset_record, - AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType, + zei::{ + noah_algebra::ristretto::PedersenCommitmentRistretto, + noah_api::{ + anon_xfr::structs::{AnonAssetRecord, OpenAnonAssetRecordBuilder}, + xfr::{ + asset_record::{ + build_blind_asset_record, open_blind_asset_record, + AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType, + }, + structs::AssetType as AT, + }, + }, }, - zei::XfrKeyPair, }; // Defines an asset type @@ -1476,7 +2333,7 @@ mod tests { TX_FEE_MIN, TxoRef::Absolute(txo_sid[0]), utxo.utxo.0, - utxo.txn.txn.get_owner_memos_ref()[utxo.utxo_location.0].cloned(), + utxo.txn.txn.get_owner_memos_ref()[utxo.utxo_location.0].clone(), bob_kp.get_sk().into_keypair(), ); let mut tx3 = TransactionBuilder::from_seq_id(2); @@ -1506,7 +2363,7 @@ mod tests { TX_FEE_MIN, TxoRef::Absolute(txo_sid[0]), utxo.utxo.0, - utxo.txn.txn.get_owner_memos_ref()[utxo.utxo_location.0].cloned(), + utxo.txn.txn.get_owner_memos_ref()[utxo.utxo_location.0].clone(), bob_kp.get_sk().into_keypair(), ); let mut tx4 = TransactionBuilder::from_seq_id(3); @@ -1527,4 +2384,557 @@ mod tests { let mut block = ledger.start_block().unwrap(); assert!(ledger.apply_transaction(&mut block, effect).is_err()); } + + #[test] + fn test_operation_bar_to_abar() { + let mut builder = TransactionBuilder::from_seq_id(1); + + let mut prng = ChaChaRng::from_seed([0u8; 32]); + let from = XfrKeyPair::generate(&mut prng); + let to = XfrKeyPair::generate(&mut prng).get_pk(); + + let ar = AssetRecordTemplate::with_no_asset_tracing( + 10u64, + AT([1u8; 32]), + AssetRecordType::ConfidentialAmount_ConfidentialAssetType, + from.get_pk().into_noah(), + ); + let pc_gens = PedersenCommitmentRistretto::default(); + let (bar, _, memo) = build_blind_asset_record(&mut prng, &pc_gens, &ar, vec![]); + let dummy_input = + open_blind_asset_record(&bar, &memo, &from.into_noah()).unwrap(); + + let mut seed = [0u8; 32]; + + getrandom::getrandom(&mut seed).unwrap(); + + let _ = builder + .add_operation_bar_to_abar( + seed, + &from, + &to, + TxoSID(123), + &dummy_input, + false, + ) + .is_ok(); + + let txn = builder.build_and_take_transaction().unwrap(); + + if let Operation::BarToAbar(note) = txn.body.operations[0].clone() { + let result = note.verify(); + assert!(result.is_ok()); + } + } + + #[test] + // This test calls multiple functions used in the anonymous transfer workflow + fn test_axfr_workflow() { + let mut prng = ChaChaRng::from_seed([0u8; 32]); + let amount = 6000000u64; + //let fee_amount = FEE_CALCULATING_FUNC(2, 1) as u64; + let amount_output = 1000000u64; + let asset_type = ASSET_TYPE_FRA; + + // simulate input abar + let (mut oabar, keypair_in) = gen_oabar_and_keys(&mut prng, amount, asset_type); + let abar = AnonAssetRecord::from_oabar(&oabar); + let _test_owner_memo = oabar.get_owner_memo().unwrap(); + + // simulate output abar + let (oabar_out, _keypair_out) = + gen_oabar_and_keys(&mut prng, amount_output, asset_type); + + // initialize ledger and add abars to merkle tree + let mut ledger_state = LedgerState::tmp_ledger(); + let _ledger_status = ledger_state.get_status(); + let uid = ledger_state.add_abar(&abar).unwrap(); + ledger_state.compute_and_append_txns_hash(&BlockEffect::default()); + ledger_state.compute_and_save_state_commitment_data(1); + + let mt_leaf_info = ledger_state.get_abar_proof(uid).unwrap(); + oabar.update_mt_leaf_info(mt_leaf_info); + + // build and submit transaction + let vec_inputs = vec![oabar]; + let vec_outputs = vec![oabar_out]; + + let mut builder = TransactionBuilder::from_seq_id(1); + let result = builder.add_operation_anon_transfer_fees_remainder( + &vec_inputs, + &vec_outputs, + &keypair_in, + ); + assert!(result.is_ok()); + + // post transaction steps test + let txn = builder.build_and_take_transaction().unwrap(); + let compute_effect = TxnEffect::compute_effect(txn).unwrap(); + let mut block = BlockEffect::default(); + let block_result = block.add_txn_effect(compute_effect); + assert!(block_result.is_ok()); + + // test nullifier functions + for n in block.new_nullifiers.iter() { + let _str = wallet::nullifier_to_base58(&n); + } + + let txn_sid_result = ledger_state.finish_block(block); + assert!(txn_sid_result.is_ok()); + } + + // Negative tests added + #[test] + #[ignore] + fn axfr_create_verify_unit_with_negative_tests() { + let mut prng = ChaChaRng::from_seed([0u8; 32]); + let amount = 10u64; + // Here the Asset Type is generated as a 32 byte and each of them are zero + let asset_type = AT::from_identical_byte(0); + + // simulate input abar + let (oabar, keypair_in) = gen_oabar_and_keys(&mut prng, amount, asset_type); + // simulate another oabar just to get new keypair + let (_, another_keypair) = gen_oabar_and_keys(&mut prng, amount, asset_type); + // negative test for input keypairs + assert_eq!(keypair_in.get_pk().into_noah(), *oabar.pub_key_ref()); + assert_ne!(keypair_in.get_pk(), another_keypair.get_pk()); + assert_ne!(another_keypair.get_pk().into_noah(), *oabar.pub_key_ref()); + + // Simulate output abar + let (oabar_out, _keypair_out) = + gen_oabar_and_keys(&mut prng, amount, asset_type); + let _abar_out = AnonAssetRecord::from_oabar(&oabar_out); + let mut builder = TransactionBuilder::from_seq_id(1); + + let wrong_key_result = builder.add_operation_anon_transfer( + &[oabar], + &[oabar_out], + &another_keypair, + ); + // negative test for keys + assert!(wrong_key_result.is_err()); + + // negative test for asset type + let wrong_asset_type_out = AT::from_identical_byte(1); + let (oabar, keypair_in) = gen_oabar_and_keys(&mut prng, amount, asset_type); + let (oabar_out, _keypair_out) = + gen_oabar_and_keys(&mut prng, amount, wrong_asset_type_out); + let wrong_asset_type_result = + builder.add_operation_anon_transfer(&[oabar], &[oabar_out], &keypair_in); + // Here we have an error due to the asset type input being unequal to the asset type output + assert!(wrong_asset_type_result.is_err()); + + // The happy path + let (mut oabar, keypair_in) = gen_oabar_and_keys(&mut prng, amount, asset_type); + let (oabar_out, _keypair_out) = + gen_oabar_and_keys(&mut prng, amount, asset_type); + let abar = AnonAssetRecord::from_oabar(&oabar); + + let owner_memo = oabar.get_owner_memo().unwrap(); + + // Trying to decrypt asset type and amount from owner memo using wrong keys + let new_xfrkeys = XfrKeyPair::generate(&mut prng); + let result_decrypt = owner_memo.decrypt(&new_xfrkeys.get_sk().into_noah()); + assert!(result_decrypt.is_err()); + + // initialize ledger and add abar to merkle tree + let mut ledger_state = LedgerState::tmp_ledger(); + let _ledger_status = ledger_state.get_status(); + let uid = ledger_state.add_abar(&abar).unwrap(); + ledger_state.compute_and_append_txns_hash(&BlockEffect::default()); + ledger_state.compute_and_save_state_commitment_data(1); + + // negative test for merkle tree proof + let mt_leaf_result_fail = ledger_state.get_abar_proof(ATxoSID(100u64)); + // 100 is not a valid uid, so we will catch an error + assert!(mt_leaf_result_fail.is_err()); + + let mt_leaf_info = ledger_state.get_abar_proof(uid).unwrap(); + oabar.update_mt_leaf_info(mt_leaf_info); + + let result = + builder.add_operation_anon_transfer(&[oabar], &[oabar_out], &keypair_in); + assert!(result.is_ok()); + + let txn = builder.build_and_take_transaction().unwrap(); + let compute_effect = TxnEffect::compute_effect(txn).unwrap(); + let mut block = BlockEffect::default(); + let block_result = block.add_txn_effect(compute_effect); + assert!(block_result.is_ok()); + + for n in block.new_nullifiers.iter() { + let _str = wallet::nullifier_to_base58(&n); + } + + let txn_sid_result = ledger_state.finish_block(block); + assert!(txn_sid_result.is_ok()); + } + + fn gen_oabar_and_keys( + prng: &mut R, + amount: u64, + asset_type: AT, + ) -> (OpenAnonAssetRecord, XfrKeyPair) { + let keypair = XfrKeyPair::generate(prng); + let oabar = OpenAnonAssetRecordBuilder::new() + .amount(amount) + .asset_type(asset_type) + .pub_key(&keypair.get_pk().into_noah()) + .finalize(prng) + .unwrap() + .build() + .unwrap(); + (oabar, keypair) + } + + #[test] + pub fn test_extra_fee_estimation_only_fra() { + let mut prng = ChaChaRng::from_seed([0u8; 32]); + let k = XfrKeyPair::generate(&mut prng); + { + let mut b = AnonTransferOperationBuilder::new_from_seq_id(0); + + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(1000000) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let _fee = FEE_CALCULATING_FUNC(1, 1) as u64; + assert!(b.extra_fee_estimation().is_ok()); + assert_eq!(b.extra_fee_estimation().unwrap(), 0); + } + { + let mut b = AnonTransferOperationBuilder::new_from_seq_id(0); + + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(1000000) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + + let _ = b.add_output( + OpenAnonAssetRecordBuilder::new() + .amount(1000000) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + + let fee = FEE_CALCULATING_FUNC(2, 2) as u64; + assert!(b.extra_fee_estimation().is_ok()); + assert_eq!(b.extra_fee_estimation().unwrap(), fee); + } + { + // case with perfect balance for fee + let mut b = AnonTransferOperationBuilder::new_from_seq_id(0); + + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(1000000) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + + let _ = b.add_output( + OpenAnonAssetRecordBuilder::new() + .amount(200000) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + + assert!(b.extra_fee_estimation().is_ok()); + assert_eq!(b.extra_fee_estimation().unwrap(), 0); + } + { + // case where input is insufficient + let mut b = AnonTransferOperationBuilder::new_from_seq_id(0); + + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(10) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let _ = b.add_output( + OpenAnonAssetRecordBuilder::new() + .amount(200000) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + + let extra_fra = FEE_CALCULATING_FUNC(2, 2) as u64 + 200000 - 10; + assert!(b.extra_fee_estimation().is_ok()); + assert_eq!(b.extra_fee_estimation().unwrap(), extra_fra); + } + } + + #[test] + pub fn test_extra_fee_estimation_multi_asset() { + let mut prng = ChaChaRng::from_seed([0u8; 32]); + let k = XfrKeyPair::generate(&mut prng); + + let asset1 = AT::from_identical_byte(1u8); + let _asset2 = AT::from_identical_byte(2u8); + + { + let mut b = AnonTransferOperationBuilder::new_from_seq_id(0); + + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(1000000) + .asset_type(asset1) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let extra_fra = FEE_CALCULATING_FUNC(2, 2) as u64; + assert!(b.extra_fee_estimation().is_ok()); + assert_eq!(b.extra_fee_estimation().unwrap(), extra_fra); + } + { + let mut b = AnonTransferOperationBuilder::new_from_seq_id(0); + + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(1000000) + .asset_type(asset1) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(1100000) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let extra_fra = FEE_CALCULATING_FUNC(2, 2) as u64 - 1100000; + assert!(b.extra_fee_estimation().is_ok()); + assert_eq!(b.extra_fee_estimation().unwrap(), extra_fra); + } + { + let mut b = AnonTransferOperationBuilder::new_from_seq_id(0); + + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(1000000) + .asset_type(asset1) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let _ = b.add_output( + OpenAnonAssetRecordBuilder::new() + .amount(1000000) + .asset_type(asset1) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(1100000) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let extra_fra = FEE_CALCULATING_FUNC(2, 2) as u64 - 1100000; + assert!(b.extra_fee_estimation().is_ok()); + assert_eq!(b.extra_fee_estimation().unwrap(), extra_fra); + } + { + let mut b = AnonTransferOperationBuilder::new_from_seq_id(0); + + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(1000000) + .asset_type(asset1) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let _ = b.add_output( + OpenAnonAssetRecordBuilder::new() + .amount(1000000) + .asset_type(asset1) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let fee = FEE_CALCULATING_FUNC(2, 1) as u64; + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(fee) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let extra_fra = FEE_CALCULATING_FUNC(2, 1) as u64 - fee; + assert!(b.extra_fee_estimation().is_ok()); + assert_eq!(b.extra_fee_estimation().unwrap(), extra_fra); + } + { + let mut b = AnonTransferOperationBuilder::new_from_seq_id(0); + + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(1000000) + .asset_type(asset1) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let _ = b.add_output( + OpenAnonAssetRecordBuilder::new() + .amount(900000) + .asset_type(asset1) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let fee = FEE_CALCULATING_FUNC(2, 2) as u64; + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(fee) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let extra_fra = FEE_CALCULATING_FUNC(2, 2) as u64 - fee; + assert!(b.extra_fee_estimation().is_ok()); + assert_eq!(b.extra_fee_estimation().unwrap(), extra_fra); + } + { + let mut b = AnonTransferOperationBuilder::new_from_seq_id(0); + + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(800000) + .asset_type(asset1) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let _ = b.add_output( + OpenAnonAssetRecordBuilder::new() + .amount(900000) + .asset_type(asset1) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let fee = FEE_CALCULATING_FUNC(2, 2) as u64; + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(fee) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + assert!(b.extra_fee_estimation().is_err()); + } + { + let mut b = AnonTransferOperationBuilder::new_from_seq_id(0); + + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(1000000) + .asset_type(asset1) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let _ = b.add_output( + OpenAnonAssetRecordBuilder::new() + .amount(900000) + .asset_type(asset1) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + let fee = FEE_CALCULATING_FUNC(2, 3) as u64; + let _ = b.add_input( + OpenAnonAssetRecordBuilder::new() + .amount(2 * fee) + .asset_type(ASSET_TYPE_FRA) + .pub_key(&k.get_pk().into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(), + ); + assert!(b.extra_fee_estimation().is_ok()); + assert_eq!(b.extra_fee_estimation().unwrap(), 0); + } + } } diff --git a/src/components/wallet_mobile/Cargo.toml b/src/components/wallet_mobile/Cargo.toml index e6f4cc228..78472bc71 100644 --- a/src/components/wallet_mobile/Cargo.toml +++ b/src/components/wallet_mobile/Cargo.toml @@ -30,7 +30,7 @@ rand_core = { version = "0.6", default-features = false, features = ["alloc"] } ring = "0.16.19" ruc = "1.0" serde = { version = "1.0.124", features = ["derive"] } -serde_derive = "1.0" +serde_derive = "^1.0.59" serde_json = "1.0" zei = { package="platform-lib-noah", git = "https://github.com/FindoraNetwork/platform-lib-noah", branch = "develop" } @@ -66,5 +66,4 @@ safer-ffi = "0.0.10" [build-dependencies] cbindgen = "0.24" -vergen = "3.1.0" - +vergen = "3.1.0" \ No newline at end of file diff --git a/src/components/wallet_mobile/src/android/constructor.rs b/src/components/wallet_mobile/src/android/constructor.rs index 719501321..944fe10d4 100644 --- a/src/components/wallet_mobile/src/android/constructor.rs +++ b/src/components/wallet_mobile/src/android/constructor.rs @@ -4,8 +4,7 @@ use jni::sys::{jbyteArray, jlong}; use jni::JNIEnv; use rand_chacha::ChaChaRng; use rand_core::SeedableRng; -use zei::noah_api::xfr::structs::ASSET_TYPE_LENGTH; -use zei::XfrKeyPair as RawXfrKeyPair; +use zei::noah_api::{keys::KeyPair as RawXfrKeyPair, xfr::structs::ASSET_TYPE_LENGTH}; #[no_mangle] pub unsafe extern "system" fn Java_com_findora_JniApi_xfrKeyPairNew( @@ -17,7 +16,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_xfrKeyPairNew( let mut buf = [0u8; ASSET_TYPE_LENGTH]; buf.copy_from_slice(input.as_ref()); let mut prng = ChaChaRng::from_seed(buf); - let val = types::XfrKeyPair::from(RawXfrKeyPair::generate(&mut prng)); + let val = types::XfrKeyPair::from(RawXfrKeyPair::generate_ed25519(&mut prng)); Box::into_raw(Box::new(val)) as jlong } diff --git a/src/components/wallet_mobile/src/android/evm.rs b/src/components/wallet_mobile/src/android/evm.rs index ed038e5b7..7ac82504b 100644 --- a/src/components/wallet_mobile/src/android/evm.rs +++ b/src/components/wallet_mobile/src/android/evm.rs @@ -2,7 +2,7 @@ use crate::rust::account::{get_serialized_address, EVMTransactionBuilder}; use jni::objects::{JClass, JString}; use jni::sys::{jlong, jstring}; use jni::JNIEnv; -use zei::XfrPublicKey; +use zei::{noah_api::keys::PublicKey, XfrPublicKey}; use super::{jStringToString, parseU64}; @@ -27,10 +27,13 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferToUtxoFromAccount( let sk = jStringToString(env, sk); - let recipient = *(recipient as *mut XfrPublicKey); + let recipient = *(recipient as *mut PublicKey); let ser_tx = EVMTransactionBuilder::new_transfer_to_utxo_from_account( - recipient, amount, sk, nonce, + XfrPublicKey::from_noah(&recipient).unwrap(), + amount, + sk, + nonce, ) .unwrap(); diff --git a/src/components/wallet_mobile/src/android/mod.rs b/src/components/wallet_mobile/src/android/mod.rs index e68ec61f6..e63880d29 100644 --- a/src/components/wallet_mobile/src/android/mod.rs +++ b/src/components/wallet_mobile/src/android/mod.rs @@ -13,7 +13,8 @@ use jni::objects::{JClass, JString}; use jni::sys::{jboolean, jbyteArray, jint, jlong, jstring}; use jni::JNIEnv; use ledger::data_model::AssetTypeCode; -use zei::noah_api::xfr::structs::ASSET_TYPE_LENGTH; +use zei::{noah_api::xfr::structs::ASSET_TYPE_LENGTH, XfrKeyPair, XfrPublicKey}; + #[no_mangle] /// Returns the git commit hash and commit date of the commit this library was built against. pub extern "system" fn Java_com_findora_JniApi_buildId( @@ -150,7 +151,7 @@ pub extern "system" fn Java_com_findora_JniApi_keypairFromStr( .get_string(text) .expect("Couldn't get java string!") .into(); - let val = types::XfrKeyPair::from(keypair_from_str(text)); + let val = types::XfrKeyPair::from(keypair_from_str(text).into_noah().unwrap()); Box::into_raw(Box::new(val)) as jlong } @@ -164,7 +165,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_publicKeyToBech32( xfr_public_key_ptr: jlong, ) -> jstring { let key = &*(xfr_public_key_ptr as *mut types::XfrPublicKey); - let res = public_key_to_bech32(key); + let res = public_key_to_bech32(&XfrPublicKey::from_noah(key).unwrap()); let output = env.new_string(res).expect("Couldn't create java string!"); **output } @@ -179,7 +180,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_getPubKeyStr( xfr_keypair_ptr: jlong, ) -> jstring { let key = &*(xfr_keypair_ptr as *mut types::XfrKeyPair); - let pubkey = get_pub_key_str(key); + let pubkey = get_pub_key_str(&XfrKeyPair::from_noah(key).unwrap()); let output = env .new_string(pubkey) .expect("Couldn't create java string!"); @@ -196,7 +197,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_getPrivKeyStr( xfr_keypair_ptr: jlong, ) -> jstring { let key = &*(xfr_keypair_ptr as *mut types::XfrKeyPair); - let prikey = get_priv_key_str(key); + let prikey = get_priv_key_str(&XfrKeyPair::from_noah(key).unwrap()); let output = env .new_string(prikey) .expect("Couldn't create java string!"); @@ -218,7 +219,9 @@ pub extern "system" fn Java_com_findora_JniApi_restoreKeypairFromMnemonicDefault .expect("Couldn't get java string!") .into(); if let Ok(keypair) = rs_restore_keypair_from_mnemonic_default(phrase.as_str()) { - Box::into_raw(Box::new(types::XfrKeyPair::from(keypair))) as jlong + Box::into_raw(Box::new(types::XfrKeyPair::from( + keypair.into_noah().unwrap(), + ))) as jlong } else { ::std::ptr::null_mut::<()>() as jlong } @@ -235,7 +238,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_keypairToStr( xfr_keypair_ptr: jlong, ) -> jstring { let key = &*(xfr_keypair_ptr as *mut types::XfrKeyPair); - let res = keypair_to_str(key); + let res = keypair_to_str(&XfrKeyPair::from_noah(key).unwrap()); let output = env.new_string(res).expect("Couldn't create java string!"); **output } @@ -251,7 +254,9 @@ pub extern "system" fn Java_com_findora_JniApi_createKeypairFromSecret( .expect("Couldn't get java string!") .into(); if let Some(keypair) = create_keypair_from_secret(sk) { - Box::into_raw(Box::new(types::XfrKeyPair::from(keypair))) as jlong + Box::into_raw(Box::new(types::XfrKeyPair::from( + keypair.into_noah().unwrap(), + ))) as jlong } else { ::std::ptr::null_mut::<()>() as jlong } @@ -266,8 +271,8 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_getPkFromKeypair( xfr_keypair_ptr: jlong, ) -> jlong { let kp = &*(xfr_keypair_ptr as *mut types::XfrKeyPair); - let pk = get_pk_from_keypair(kp); - Box::into_raw(Box::new(types::XfrPublicKey::from(pk))) as jlong + let pk = get_pk_from_keypair(&XfrKeyPair::from_noah(kp).unwrap()); + Box::into_raw(Box::new(types::XfrPublicKey::from(pk.into_noah().unwrap()))) as jlong } #[no_mangle] @@ -277,7 +282,9 @@ pub extern "system" fn Java_com_findora_JniApi_newKeypair( _: JClass, ) -> jlong { let keypair = new_keypair(); - Box::into_raw(Box::new(types::XfrKeyPair::from(keypair))) as jlong + Box::into_raw(Box::new(types::XfrKeyPair::from( + keypair.into_noah().unwrap(), + ))) as jlong } #[no_mangle] @@ -340,7 +347,11 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_openClientAssetRecord( let keypair = &*(keypair_ptr as *mut types::XfrKeyPair); let oar = throw_exception!( env, - rs_open_client_asset_record(record, owner_memo, keypair) + rs_open_client_asset_record( + record, + owner_memo, + &XfrKeyPair::from_noah(keypair).unwrap() + ) ); Box::into_raw(Box::new(types::OpenAssetRecord::from(oar))) as jlong } diff --git a/src/components/wallet_mobile/src/android/transfer.rs b/src/components/wallet_mobile/src/android/transfer.rs index ecb615882..613c07e95 100644 --- a/src/components/wallet_mobile/src/android/transfer.rs +++ b/src/components/wallet_mobile/src/android/transfer.rs @@ -3,8 +3,13 @@ use jni::objects::{JClass, JString}; use jni::sys::{jboolean, jint, jlong, jstring, jvalue, JNI_TRUE}; use jni::JNIEnv; use ledger::data_model::AssetType as PlatformAssetType; -use zei::OwnerMemo as NoahOwnerMemo; -use zei::{XfrKeyPair, XfrPublicKey}; +use zei::{ + noah_api::{ + keys::{KeyPair, PublicKey}, + xfr::structs::OwnerMemo as NoahOwnerMemo, + }, + XfrKeyPair, XfrPublicKey, +}; use super::{jStringToString, parseU64}; @@ -273,7 +278,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferOperationBuilderAd Some(memo.clone()) }; let tracing_policies = &*(tracing_policies_ptr as *mut TracingPolicies); - let key = &*(key_ptr as *mut XfrKeyPair); + let key = &*(key_ptr as *mut KeyPair); let amount = parseU64(env, amount); let builder = builder @@ -283,7 +288,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferOperationBuilderAd asset_record.clone(), owner_memo, tracing_policies, - key, + &XfrKeyPair::from_noah(key).unwrap(), amount, ) .unwrap(); @@ -328,12 +333,18 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferOperationBuilderAd let memo = &*(owner_memo_ptr as *mut OwnerMemo); Some(memo.clone()) }; - let key = &*(key_ptr as *mut XfrKeyPair); + let key = &*(key_ptr as *mut KeyPair); let amount = parseU64(env, amount); let builder = builder .clone() - .add_input_no_tracing(txo_ref, asset_record, owner_memo, key, amount) + .add_input_no_tracing( + txo_ref, + asset_record, + owner_memo, + &XfrKeyPair::from_noah(key).unwrap(), + amount, + ) .unwrap(); Box::into_raw(Box::new(builder)) as jlong } @@ -369,7 +380,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferOperationBuilderAd ) -> jlong { let builder = &*(builder as *mut TransferOperationBuilder); let tracing_policies = &*(tracing_policies_ptr as *mut TracingPolicies); - let recipient = &*(recipient as *mut XfrPublicKey); + let recipient = &*(recipient as *mut PublicKey); let amount = parseU64(env, amount); let code = jStringToString(env, code); @@ -377,7 +388,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferOperationBuilderAd .clone() .add_output_with_tracing( amount, - recipient, + &XfrPublicKey::from_noah(recipient).unwrap(), tracing_policies, code, conf_amount == JNI_TRUE, @@ -413,7 +424,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferOperationBuilderAd conf_type: jboolean, ) -> jlong { let builder = &*(builder as *mut TransferOperationBuilder); - let recipient = &*(recipient as *mut XfrPublicKey); + let recipient = &*(recipient as *mut PublicKey); let amount = parseU64(env, amount); let code = jStringToString(env, code); @@ -421,7 +432,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferOperationBuilderAd .clone() .add_output_no_tracing( amount, - recipient, + &XfrPublicKey::from_noah(recipient).unwrap(), code, conf_amount == JNI_TRUE, conf_type == JNI_TRUE, @@ -459,7 +470,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferOperationBuilderAd let policies = &*(tracing_policies_ptr as *mut TracingPolicies); Some(policies) }; - let key = &*(key_ptr as *mut XfrKeyPair); + let key = &*(key_ptr as *mut KeyPair); let builder = builder .clone() @@ -468,7 +479,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferOperationBuilderAd asset_record, owner_memo, tracing_policies, - key, + &XfrKeyPair::from_noah(key).unwrap(), parseU64(env, amount), ) .unwrap(); @@ -496,7 +507,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferOperationBuilderAd let policies = &*(tracing_policies_ptr as *mut TracingPolicies); Some(policies) }; - let recipient = &*(recipient as *mut XfrPublicKey); + let recipient = &*(recipient as *mut PublicKey); let code: String = env .get_string(code) .expect("Couldn't get java string!") @@ -506,7 +517,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferOperationBuilderAd .clone() .add_output( parseU64(env, amount), - recipient, + &XfrPublicKey::from_noah(&recipient).unwrap(), tracing_policies, code, conf_amount == JNI_TRUE, @@ -562,9 +573,14 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transferOperationBuilderSi key_ptr: jlong, ) -> jlong { let builder = &*(builder as *mut TransferOperationBuilder); - let key = &*(key_ptr as *mut XfrKeyPair); + let key = &*(key_ptr as *mut KeyPair); - Box::into_raw(Box::new(builder.clone().sign(key).unwrap())) as jlong + Box::into_raw(Box::new( + builder + .clone() + .sign(&XfrKeyPair::from_noah(&key).unwrap()) + .unwrap(), + )) as jlong } #[no_mangle] diff --git a/src/components/wallet_mobile/src/android/tx_builder.rs b/src/components/wallet_mobile/src/android/tx_builder.rs index f935b75c5..c89a4b9e8 100644 --- a/src/components/wallet_mobile/src/android/tx_builder.rs +++ b/src/components/wallet_mobile/src/android/tx_builder.rs @@ -7,7 +7,7 @@ use ledger::data_model::AssetTypeCode; use zei::XfrKeyPair; #[no_mangle] /// # Safety -/// @param kp: owner's XfrKeyPair +/// @param kp: owner's KeyPair pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddFeeRelativeAuto( _env: JNIEnv, _: JClass, @@ -15,8 +15,11 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddFeeRe kp: jlong, ) -> jlong { let builder = &*(builder as *mut TransactionBuilder); - let kp = &*(kp as *mut XfrKeyPair); - let builder = builder.clone().add_fee_relative_auto(kp.clone()).unwrap(); + let kp = &*(kp as *mut KeyPair); + let builder = builder + .clone() + .add_fee_relative_auto(XfrKeyPair::from_noah(kp).unwrap()) + .unwrap(); Box::into_raw(Box::new(builder)) as jlong } @@ -91,7 +94,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderNew( /// console.log(err) /// } /// -/// @param {XfrKeyPair} key_pair - Issuer XfrKeyPair. +/// @param {KeyPair} key_pair - Issuer KeyPair. /// @param {string} memo - Text field for asset definition. /// @param {string} token_code - Optional Base64 string representing the token code of the asset to be issued. /// If empty, a token code will be chosen at random. @@ -107,7 +110,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera asset_rules: jlong, ) -> jlong { let builder = &*(builder as *mut TransactionBuilder); - let key_pair = &*(key_pair as *mut XfrKeyPair); + let key_pair = &*(key_pair as *mut KeyPair); let memo: String = env .get_string(memo) .expect("Couldn't get java string!") @@ -119,7 +122,12 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera let asset_rules = &*(asset_rules as *mut AssetRules); let builder = builder .clone() - .add_operation_create_asset(key_pair, memo, token_code, asset_rules.clone()) + .add_operation_create_asset( + &XfrKeyPair::from_noah(key_pair).unwrap(), + memo, + token_code, + asset_rules.clone(), + ) .unwrap(); Box::into_raw(Box::new(builder)) as jlong } @@ -130,13 +138,12 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera /// /// Use this function for simple one-shot issuances. /// -/// @param {XfrKeyPair} key_pair - Issuer XfrKeyPair. +/// @param {KeyPair} key_pair - Issuer KeyPair. /// and types of traced assets. /// @param {string} code - base64 string representing the token code of the asset to be issued. /// @param {BigInt} seq_num - Issuance sequence number. Every subsequent issuance of a given asset type must have a higher sequence number than before. /// @param {BigInt} amount - Amount to be issued. /// @param {boolean} conf_amount - `true` means the asset amount is confidential, and `false` means it's nonconfidential. -/// @param {PublicParams} zei_params - Public parameters necessary to generate asset records. pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddBasicIssueAsset( env: JNIEnv, _: JClass, @@ -146,24 +153,21 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddBasic seq_num: jlong, amount: JString, conf_amount: jboolean, - zei_params: jlong, ) -> jlong { let builder = &*(builder as *mut TransactionBuilder); - let key_pair = &*(key_pair as *mut XfrKeyPair); + let key_pair = &*(key_pair as *mut KeyPair); let code: String = env .get_string(code) .expect("Couldn't get java string!") .into(); - let zei_params = &*(zei_params as *mut PublicParams); let builder = builder .clone() .add_basic_issue_asset( - key_pair, + &XfrKeyPair::from_noah(key_pair).unwrap(), code, seq_num as u64, parseU64(env, amount), conf_amount == JNI_TRUE, - zei_params, ) .unwrap(); Box::into_raw(Box::new(builder)) as jlong @@ -173,7 +177,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddBasic /// # Safety /// Adds an operation to the transaction builder that adds a hash to the ledger's custom data /// store. -/// @param {XfrKeyPair} auth_key_pair - Asset creator key pair. +/// @param {KeyPair} auth_key_pair - Asset creator key pair. /// @param {String} code - base64 string representing token code of the asset whose memo will be updated. /// transaction validates. /// @param {String} new_memo - The new asset memo. @@ -188,7 +192,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera new_memo: JString, ) -> jlong { let builder = &*(builder as *mut TransactionBuilder); - let auth_key_pair = &*(auth_key_pair as *mut XfrKeyPair); + let auth_key_pair = &*(auth_key_pair as *mut KeyPair); let code: String = env .get_string(code) .expect("Couldn't get java string!") @@ -199,7 +203,11 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera .into(); let builder = builder .clone() - .add_operation_update_memo(auth_key_pair, code, new_memo) + .add_operation_update_memo( + &XfrKeyPair::from_noah(auth_key_pair).unwrap(), + code, + new_memo, + ) .unwrap(); Box::into_raw(Box::new(builder)) as jlong } @@ -216,7 +224,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera validator: JString, ) -> jlong { let builder = &*(builder as *mut TransactionBuilder); - let keypair = &*(keypair as *mut XfrKeyPair); + let keypair = &*(keypair as *mut KeyPair); let validator: String = env .get_string(validator) .expect("Couldn't get java string!") @@ -224,7 +232,11 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera let builder = builder .clone() - .add_operation_delegate(keypair, parseU64(env, amount), validator) + .add_operation_delegate( + &XfrKeyPair::from_noah(keypair).unwrap(), + parseU64(env, amount), + validator, + ) .unwrap(); Box::into_raw(Box::new(builder)) as jlong } @@ -239,8 +251,11 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera keypair: jlong, ) -> jlong { let builder = &*(builder as *mut TransactionBuilder); - let keypair = &*(keypair as *mut XfrKeyPair); - let builder = builder.clone().add_operation_undelegate(keypair).unwrap(); + let keypair = &*(keypair as *mut KeyPair); + let builder = builder + .clone() + .add_operation_undelegate(&XfrKeyPair::from_noah(keypair).unwrap()) + .unwrap(); Box::into_raw(Box::new(builder)) as jlong } @@ -256,7 +271,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera validator: JString, ) -> jlong { let builder = &*(builder as *mut TransactionBuilder); - let keypair = &*(keypair as *mut XfrKeyPair); + let keypair = &*(keypair as *mut KeyPair); let validator: String = env .get_string(validator) @@ -264,7 +279,11 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera .into(); let builder = builder .clone() - .add_operation_undelegate_partially(keypair, parseU64(env, am), validator) + .add_operation_undelegate_partially( + &XfrKeyPair::from_noah(keypair).unwrap(), + parseU64(env, am), + validator, + ) .unwrap(); Box::into_raw(Box::new(builder)) as jlong } @@ -306,7 +325,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera am: JString, ) -> jlong { let builder = &*(builder as *mut TransactionBuilder); - let keypair = &*(keypair as *mut XfrKeyPair); + let keypair = &*(keypair as *mut KeyPair); let td_addr: String = env .get_string(td_addr) .expect("Couldn't get java string!") @@ -347,7 +366,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddTrans /// /// Adds a serialized transfer-account operation to transaction builder instance. /// @param {string} amount - amount to transfer. -/// @param {XfrKeyPair} keypair - FRA account key pair. +/// @param {KeyPair} keypair - FRA account key pair. /// @param {String} address - FRA account key pair. /// @throws Will throw an error if `address` is invalid. pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOperationConvertAccount( @@ -366,7 +385,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera .expect("Couldn't get java string!") .into(); - let fra_kp = &*(keypair as *mut XfrKeyPair); + let fra_kp = &*(keypair as *mut KeyPair); let asset_str: String = env .get_string(asset) @@ -393,7 +412,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderAddOpera .add_transfer_to_account_operation( parseU64(env, amount), Some(addr), - fra_kp, + &XfrKeyPair::from_noah(fra_kp).unwrap(), asset, lowlevel_data, ) @@ -411,8 +430,11 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderSign( kp: jlong, ) -> jlong { let builder = &*(builder as *mut TransactionBuilder); - let kp = &*(kp as *mut XfrKeyPair); - let builder = builder.clone().sign(kp).unwrap(); + let kp = &*(kp as *mut KeyPair); + let builder = builder + .clone() + .sign(&XfrKeyPair::from_noah(kp).unwrap()) + .unwrap(); Box::into_raw(Box::new(builder)) as jlong } @@ -425,7 +447,7 @@ pub unsafe extern "system" fn Java_com_findora_JniApi_transactionBuilderTransact _: JClass, builder: jlong, ) -> jstring { - let builder = &*(builder as *mut TransactionBuilder); + let builder = &mut *(builder as *mut TransactionBuilder); let output = env .new_string(builder.transaction()) .expect("Couldn't create java string!"); diff --git a/src/components/wallet_mobile/src/ios/tx_builder.rs b/src/components/wallet_mobile/src/ios/tx_builder.rs index 2cba61ae0..9d3a05c9d 100644 --- a/src/components/wallet_mobile/src/ios/tx_builder.rs +++ b/src/components/wallet_mobile/src/ios/tx_builder.rs @@ -1,7 +1,7 @@ use super::parse_u64; use crate::rust::{ c_char_to_string, string_to_c_char, AssetRules, ClientAssetRecord, FeeInputs, - OwnerMemo, PublicParams, TransactionBuilder, + OwnerMemo, TransactionBuilder, }; use ledger::data_model::AssetTypeCode; use std::os::raw::c_char; @@ -108,7 +108,6 @@ pub extern "C" fn findora_ffi_transaction_builder_add_operation_create_asset( /// @param {BigInt} seq_num - Issuance sequence number. Every subsequent issuance of a given asset type must have a higher sequence number than before. /// @param {BigInt} amount - Amount to be issued. /// @param {boolean} conf_amount - `true` means the asset amount is confidential, and `false` means it's nonconfidential. -/// @param {PublicParams} zei_params - Public parameters necessary to generate asset records. #[no_mangle] pub extern "C" fn findora_ffi_transaction_builder_add_basic_issue_asset( builder: &TransactionBuilder, @@ -117,7 +116,6 @@ pub extern "C" fn findora_ffi_transaction_builder_add_basic_issue_asset( seq_num: u64, amount: *const c_char, conf_amount: bool, - zei_params: &PublicParams, ) -> *mut TransactionBuilder { let amount = parse_u64(amount); if let Ok(info) = builder.clone().add_basic_issue_asset( @@ -126,7 +124,6 @@ pub extern "C" fn findora_ffi_transaction_builder_add_basic_issue_asset( seq_num, amount, conf_amount, - zei_params, ) { Box::into_raw(Box::new(info)) } else { diff --git a/src/components/wallet_mobile/src/rust/crypto.rs b/src/components/wallet_mobile/src/rust/crypto.rs index 390c0a4ea..98d31ddca 100644 --- a/src/components/wallet_mobile/src/rust/crypto.rs +++ b/src/components/wallet_mobile/src/rust/crypto.rs @@ -14,29 +14,26 @@ use cryptohash::sha256; use getrandom::getrandom; use globutils::wallet; use ledger::{ - data_model::{ - AssetTypeCode, ASSET_TYPE_FRA, BLACK_HOLE_PUBKEY_STAKING, - TX_FEE_MIN, - }, + data_model::{AssetTypeCode, ASSET_TYPE_FRA, BLACK_HOLE_PUBKEY_STAKING, TX_FEE_MIN}, staking::{MAX_DELEGATION_AMOUNT, MIN_DELEGATION_AMOUNT}, }; use rand_chacha::ChaChaRng; use rand_core::SeedableRng; use ring::pbkdf2; -use ruc::{ - d, Result, RucResult -}; +use ruc::{d, Result, RucResult}; use std::num::NonZeroU32; use std::str; -use zei::noah_algebra::serialization::NoahFromToBytes; -use zei::noah_api::{ - xfr::{ +use zei::{ + noah_algebra::serialization::NoahFromToBytes, + noah_api::xfr::{ asset_record::open_blind_asset_record as open_bar, - structs::{AssetType as NoahAssetType, OpenAssetRecord, XfrBody, ASSET_TYPE_LENGTH}, + structs::{ + AssetType as NoahAssetType, OpenAssetRecord, XfrBody, ASSET_TYPE_LENGTH, + }, trace_assets as noah_trace_assets, }, + XfrKeyPair, XfrPublicKey, XfrSecretKey, }; -use zei::{XfrKeyPair, XfrPublicKey, XfrSecretKey}; #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] /// Generates random Base64 encoded asset type as a Base64 string. Used in asset definitions. @@ -91,7 +88,7 @@ pub fn rs_open_client_asset_record( &owner_memo.map(|memo| memo.get_memo_ref().clone()), &keypair.into_noah(), ) - .c(d!()) + .c(d!()) } #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] diff --git a/src/components/wallet_mobile/src/rust/data_model.rs b/src/components/wallet_mobile/src/rust/data_model.rs index 967e1fd3f..d630af796 100644 --- a/src/components/wallet_mobile/src/rust/data_model.rs +++ b/src/components/wallet_mobile/src/rust/data_model.rs @@ -20,14 +20,14 @@ use rand_core::SeedableRng; use ruc::Result as RUCResult; use ruc::{d, err::RucResult}; use serde::{Deserialize, Serialize}; -use zei::noah_api::xfr::structs::{ - AssetTracerDecKeys, AssetTracerEncKeys, - AssetTracerKeyPair as NoahAssetTracerKeyPair, IdentityRevealPolicy, - OwnerMemo as NoahOwnerMemo, TracingPolicies as NoahTracingPolicies, - TracingPolicy as NoahTracingPolicy, -}; use zei::{ - BlindAssetRecord, XfrPublicKey + noah_api::xfr::structs::{ + AssetTracerDecKeys, AssetTracerEncKeys, + AssetTracerKeyPair as NoahAssetTracerKeyPair, IdentityRevealPolicy, + OwnerMemo as NoahOwnerMemo, TracingPolicies as NoahTracingPolicies, + TracingPolicy as NoahTracingPolicy, + }, + BlindAssetRecord, XfrPublicKey, }; #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] @@ -272,10 +272,7 @@ impl OwnerMemo { let noah_owner_memo: NoahOwnerMemo = val.into_serde().c(d!()).map_err(error_to_jsvalue)?; Ok(OwnerMemo { - memo: NoahOwnerMemo { - blind_share: noah_owner_memo.blind_share, - lock: noah_owner_memo.lock, - }, + memo: noah_owner_memo, }) } diff --git a/src/components/wallet_mobile/src/rust/transaction.rs b/src/components/wallet_mobile/src/rust/transaction.rs index 575859c93..03a637404 100644 --- a/src/components/wallet_mobile/src/rust/transaction.rs +++ b/src/components/wallet_mobile/src/rust/transaction.rs @@ -15,13 +15,15 @@ use ledger::{ }, staking::{td_addr_to_bytes, PartialUnDelegation, TendermintAddr}, }; -use ruc::{d, eg, Result as RucResult, err::RucResult as NewRucResult}; +use ruc::{d, eg, err::RucResult as NewRucResult, Result as RucResult}; use serde_json::Result; -use zei::noah_api::xfr::{ - asset_record::{open_blind_asset_record as open_bar, AssetRecordType}, - structs::AssetRecordTemplate, +use zei::{ + noah_api::xfr::{ + asset_record::{open_blind_asset_record as open_bar, AssetRecordType}, + structs::AssetRecordTemplate, + }, + OwnerMemo as NoahOwnerMemo, XfrKeyPair, XfrPublicKey, }; -use zei::{OwnerMemo as NoahOwnerMemo, XfrKeyPair, XfrPublicKey}; /// Given a serialized state commitment and transaction, returns true if the transaction correctly /// hashes up to the state commitment and false otherwise. @@ -340,7 +342,8 @@ impl TransactionBuilder { } /// Extracts the serialized form of a transaction. - pub fn transaction(&self) -> String { + pub fn transaction(&mut self) -> String { + self.get_builder_mut().build().unwrap(); self.get_builder().serialize_str() } @@ -362,7 +365,9 @@ impl TransactionBuilder { pub fn get_owner_memo(&self, idx: usize) -> Option { self.get_builder() .get_owner_memo_ref(idx) - .map(|memo| OwnerMemo { memo: memo.into_noah() }) + .map(|memo| OwnerMemo { + memo: memo.into_noah(), + }) } } @@ -397,7 +402,7 @@ impl TransferOperationBuilder { &owner_memo.map(|memo| memo.get_memo_ref().clone()), &key.into_noah(), ) - .c(d!())?; + .c(d!())?; self.get_builder_mut().add_input( *txo_ref.get_txo(), oar, diff --git a/src/components/wallet_mobile/src/rust/types.rs b/src/components/wallet_mobile/src/rust/types.rs index 71a26fe15..d63b50e0a 100644 --- a/src/components/wallet_mobile/src/rust/types.rs +++ b/src/components/wallet_mobile/src/rust/types.rs @@ -5,21 +5,23 @@ use credentials::{ CredUserSecretKey as PlatformCredUserSecretKey, }; use std::ops::{Deref, DerefMut}; -use zei::noah_api::xfr::structs::OpenAssetRecord as ZeiOpenAssetRecord; -use zei::{XfrKeyPair as ZeiXfrKeyPair, XfrPublicKey as ZeiXfrPublicKey}; +use zei::noah_api::{ + keys::{KeyPair as NoahXfrKeyPair, PublicKey as NoahXfrPublicKey}, + xfr::structs::OpenAssetRecord as NoahOpenAssetRecord, +}; //////////////////////////////////////////////////////////////////////////////// -pub struct XfrPublicKey(ZeiXfrPublicKey); +pub struct XfrPublicKey(NoahXfrPublicKey); -impl From for XfrPublicKey { - fn from(v: ZeiXfrPublicKey) -> XfrPublicKey { +impl From for XfrPublicKey { + fn from(v: NoahXfrPublicKey) -> XfrPublicKey { XfrPublicKey(v) } } impl Deref for XfrPublicKey { - type Target = ZeiXfrPublicKey; + type Target = NoahXfrPublicKey; fn deref(&self) -> &Self::Target { &self.0 @@ -35,16 +37,16 @@ impl DerefMut for XfrPublicKey { //////////////////////////////////////////////////////////////////////////////// #[derive(Clone)] -pub struct XfrKeyPair(ZeiXfrKeyPair); +pub struct XfrKeyPair(NoahXfrKeyPair); -impl From for XfrKeyPair { - fn from(v: ZeiXfrKeyPair) -> XfrKeyPair { +impl From for XfrKeyPair { + fn from(v: NoahXfrKeyPair) -> XfrKeyPair { XfrKeyPair(v) } } impl Deref for XfrKeyPair { - type Target = ZeiXfrKeyPair; + type Target = NoahXfrKeyPair; fn deref(&self) -> &Self::Target { &self.0 @@ -60,16 +62,16 @@ impl DerefMut for XfrKeyPair { //////////////////////////////////////////////////////////////////////////////// #[derive(Clone)] -pub struct OpenAssetRecord(ZeiOpenAssetRecord); +pub struct OpenAssetRecord(NoahOpenAssetRecord); -impl From for OpenAssetRecord { - fn from(v: ZeiOpenAssetRecord) -> OpenAssetRecord { +impl From for OpenAssetRecord { + fn from(v: NoahOpenAssetRecord) -> OpenAssetRecord { OpenAssetRecord(v) } } impl Deref for OpenAssetRecord { - type Target = ZeiOpenAssetRecord; + type Target = NoahOpenAssetRecord; fn deref(&self) -> &Self::Target { &self.0 diff --git a/src/components/wallet_mobile/src/rust/util.rs b/src/components/wallet_mobile/src/rust/util.rs index b81986a94..7f101e254 100644 --- a/src/components/wallet_mobile/src/rust/util.rs +++ b/src/components/wallet_mobile/src/rust/util.rs @@ -21,6 +21,6 @@ pub fn string_to_c_char(r_string: String) -> *mut c_char { #[cfg(target_arch = "wasm32")] #[inline(always)] -pub fn error_to_jsvalue(e: T) -> JsVal { +pub fn error_to_jsvalue(e: T) -> JsValue { JsValue::from_str(&e.to_string()) } diff --git a/src/components/wallet_mobile/src/wasm/mod.rs b/src/components/wallet_mobile/src/wasm/mod.rs index 491c621a8..1361fcd5b 100644 --- a/src/components/wallet_mobile/src/wasm/mod.rs +++ b/src/components/wallet_mobile/src/wasm/mod.rs @@ -6,8 +6,7 @@ use credentials::{ }; use ruc::{d, err::RucResult}; use wasm_bindgen::prelude::*; -use zei::noah_api::xfr::structs::ASSET_TYPE_LENGTH; -use zei::{XfrKeyPair, XfrPublicKey}; +use zei::{noah_api::xfr::structs::ASSET_TYPE_LENGTH, XfrKeyPair, XfrPublicKey}; #[wasm_bindgen] /// Generates asset type as a Base64 string from a JSON-serialized JavaScript value. @@ -163,7 +162,6 @@ impl TransactionBuilder { /// @param {BigInt} seq_num - Issuance sequence number. Every subsequent issuance of a given asset type must have a higher sequence number than before. /// @param {BigInt} amount - Amount to be issued. /// @param {boolean} conf_amount - `true` means the asset amount is confidential, and `false` means it's nonconfidential. - /// @param {PublicParams} zei_params - Public parameters necessary to generate asset records. pub fn add_basic_issue_asset( self, key_pair: &XfrKeyPair, @@ -171,18 +169,10 @@ impl TransactionBuilder { seq_num: u64, amount: u64, conf_amount: bool, - zei_params: &PublicParams, ) -> Result { let builder = self .0 - .add_basic_issue_asset( - key_pair, - code, - seq_num, - amount, - conf_amount, - zei_params, - ) + .add_basic_issue_asset(key_pair, code, seq_num, amount, conf_amount) .c(d!()) .map_err(error_to_jsvalue)?; @@ -234,7 +224,7 @@ impl TransactionBuilder { } /// Extracts the serialized form of a transaction. - pub fn transaction(&self) -> String { + pub fn transaction(&mut self) -> String { self.0.transaction() } @@ -403,7 +393,7 @@ impl TransferOperationBuilder { /// @throws Will throw an error if the transaction cannot be balanced. pub fn balance(self) -> Result { let builder = - self.0.balance().c(d!()).map_err(|e| { + self.0.balance(None).c(d!()).map_err(|e| { JsValue::from_str(&format!("Error balancing txn: {}", e)) })?; Ok(TransferOperationBuilder(builder)) diff --git a/src/components/wasm/Cargo.toml b/src/components/wasm/Cargo.toml index 2461fbd16..6feae46eb 100644 --- a/src/components/wasm/Cargo.toml +++ b/src/components/wasm/Cargo.toml @@ -19,22 +19,23 @@ hex = "0.4.3" js-sys = "0.3.27" rand_chacha = "0.3" rand_core = { version = "0.6", default-features = false, features = ["alloc"] } -rand = { version = "0.7", features = ["wasm-bindgen"] } serde = { version = "1.0.124", features = ["derive"] } serde_json = "1.0" wasm-bindgen = { version = "=0.2.84", features = ["serde-serialize"] } +wasm-bindgen-futures = "^0.4.34" fbnc = { version = "0.2.9", default-features = false} ring = "0.16.19" aes-gcm = "^0.10.1" bech32 = "0.7.2" +ruc = "1.0" +bs58 = "0.4" # Must enable the "js"-feature, # OR the compiling will fail. getrandom = { version = "0.2", features = ["js"] } zei = { package="platform-lib-noah", git = "https://github.com/FindoraNetwork/platform-lib-noah", branch = "develop" } -ruc = "1.0" finutils = { path = "../finutils", default-features = false } @@ -68,9 +69,12 @@ features = [ serde = "1.0.124" serde_json = "1.0.41" vergen = "=3.1.0" -wasm-bindgen = { version = "=0.2.84", features = ["serde-serialize"] } [dev-dependencies] # Must enable the "js"-feature, # OR the compiling will fail. getrandom = { version = "0.2", features = ["js"] } +wasm-bindgen-test = "0.3.0" + +[features] +lightweight = ["zei/lightweight"] # Minimize size for only AR2ABAR and ABAR2AR. diff --git a/src/components/wasm/src/wasm.rs b/src/components/wasm/src/wasm.rs index a9bfc7647..6e477d542 100644 --- a/src/components/wasm/src/wasm.rs +++ b/src/components/wasm/src/wasm.rs @@ -16,12 +16,14 @@ mod wasm_data_model; use { crate::wasm_data_model::{ - error_to_jsvalue, AssetRules, AssetTracerKeyPair, AttributeAssignment, - AttributeDefinition, ClientAssetRecord, Credential, CredentialCommitment, - CredentialCommitmentData, CredentialCommitmentKey, CredentialIssuerKeyPair, - CredentialPoK, CredentialRevealSig, CredentialSignature, CredentialUserKeyPair, + error_to_jsvalue, AssetRules, AssetTracerKeyPair, AssetType, + AttributeAssignment, AttributeDefinition, AxfrOwnerMemo, AxfrOwnerMemoInfo, + ClientAssetRecord, Credential, CredentialCommitment, CredentialCommitmentData, + CredentialCommitmentKey, CredentialIssuerKeyPair, CredentialPoK, + CredentialRevealSig, CredentialSignature, CredentialUserKeyPair, MTLeafInfo, OwnerMemo, TracingPolicies, TxoRef, }, + core::str::FromStr, credentials::{ credential_commit, credential_issuer_key_gen, credential_open_commitment, credential_reveal, credential_sign, credential_user_key_gen, credential_verify, @@ -31,6 +33,7 @@ use { cryptohash::sha256, fbnc::NumKey, finutils::txn_builder::{ + AnonTransferOperationBuilder as PlatformAnonTransferOperationBuilder, FeeInput as PlatformFeeInput, FeeInputs as PlatformFeeInputs, TransactionBuilder as PlatformTransactionBuilder, TransferOperationBuilder as PlatformTransferOperationBuilder, @@ -48,7 +51,7 @@ use { globutils::{wallet, HashOf}, ledger::{ data_model::{ - gen_random_keypair, AssetTypeCode, AssetTypePrefix, + gen_random_keypair, get_abar_commitment, AssetTypeCode, AssetTypePrefix, AuthenticatedTransaction, Operation, TransferType, TxOutput, ASSET_TYPE_FRA, BLACK_HOLE_PUBKEY, BLACK_HOLE_PUBKEY_STAKING, TX_FEE_MIN, }, @@ -59,23 +62,36 @@ use { }, rand_chacha::ChaChaRng, rand_core::SeedableRng, - ruc::{d, err::RucResult}, - std::str::FromStr, + ruc::{d, eg, err::RucResult}, + serde::{Deserialize, Serialize}, + std::convert::From, wasm_bindgen::prelude::*, zei::{ noah_algebra::{ + bn254::BN254Scalar, prelude::{NoahFromToBytes, Scalar}, - ristretto::PedersenCommitmentRistretto, }, - noah_api::xfr::{ - asset_record::{open_blind_asset_record as open_bar, AssetRecordType}, - structs::{ - AssetRecordTemplate, AssetType as NoahAssetType, - ASSET_TYPE_LENGTH, + noah_api::{ + anon_xfr::{ + decrypt_memo, nullify, parse_memo, + structs::{ + AnonAssetRecord, Commitment, OpenAnonAssetRecord, + OpenAnonAssetRecordBuilder, + }, + }, + xfr::{ + asset_record::{ + open_blind_asset_record as open_bar, AssetRecordType, + AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType, + }, + structs::{ + AssetRecordTemplate, AssetType as NoahAssetType, ASSET_TYPE_LENGTH, + }, + trace_assets as noah_trace_assets, }, - trace_assets as noah_trace_assets, }, - OwnerMemo as NoahOwnerMemo, XfrKeyPair, XfrPublicKey, XfrSecretKey, XfrBody + noah_crypto::hybrid_encryption::{XPublicKey, XSecretKey}, + OwnerMemo as NoahOwnerMemo, XfrBody, XfrKeyPair, XfrPublicKey, XfrSecretKey, }, }; @@ -83,6 +99,13 @@ use { /// against. const BUILD_ID: &str = concat!(env!("VERGEN_SHA_SHORT"), " ", env!("VERGEN_BUILD_DATE")); +/// Init noah anon xfr +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub async fn init_noah() -> Result<(), JsValue> { + zei::noah_api::anon_xfr::init_anon_xfr().await +} + #[wasm_bindgen] /// Returns the git commit hash and commit date of the commit this library was built against. pub fn build_id() -> String { @@ -146,10 +169,17 @@ pub fn get_null_pk() -> XfrPublicKey { XfrPublicKey::noah_from_bytes(&[0; 32]).unwrap() } +/// struct to return list of commitment strings +#[derive(Serialize, Deserialize)] +pub struct CommitmentStringArray { + commitments: Vec, +} + #[wasm_bindgen] /// Structure that allows users to construct arbitrary transactions. pub struct TransactionBuilder { transaction_builder: PlatformTransactionBuilder, + commitments: Vec, } impl TransactionBuilder { @@ -209,7 +239,7 @@ impl FeeInputs { #[allow(missing_docs)] pub fn new() -> Self { FeeInputs { - inner: Vec::with_capacity(1), + inner: Vec::with_capacity(10), } } @@ -232,9 +262,15 @@ impl FeeInputs { tr: TxoRef, ar: ClientAssetRecord, om: Option, - kp: XfrKeyPair, + kp: &XfrKeyPair, ) -> Self { - self.inner.push(FeeInput { am, tr, ar, om, kp }); + self.inner.push(FeeInput { + am, + tr, + ar, + om, + kp: kp.clone(), + }); self } } @@ -288,6 +324,19 @@ impl TransactionBuilder { Ok(self) } + /// As the last operation of BarToAbar transaction, + /// add a static fee to the transaction. + pub fn add_fee_bar_to_abar( + mut self, + inputs: FeeInputs, + ) -> Result { + self.transaction_builder + .add_fee_bar_to_abar(inputs.into()) + .c(d!()) + .map_err(error_to_jsvalue)?; + Ok(self) + } + /// A simple fee checker for mainnet v1.0. /// /// SEE [check_fee](ledger::data_model::Transaction::check_fee) @@ -300,9 +349,20 @@ impl TransactionBuilder { pub fn new(seq_id: u64) -> Self { TransactionBuilder { transaction_builder: PlatformTransactionBuilder::from_seq_id(seq_id), + commitments: Default::default(), } } + /// Deserialize transaction builder from string. + pub fn from_string(s: String) -> Result { + let transaction_builder = serde_json::from_str(&s).map_err(error_to_jsvalue)?; + + Ok(TransactionBuilder { + transaction_builder, + commitments: Default::default(), + }) + } + /// Wraps around TransactionBuilder to add an asset definition operation to a transaction builder instance. /// @example Error handling /// try { @@ -373,7 +433,6 @@ impl TransactionBuilder { /// @param {BigInt} seq_num - Issuance sequence number. Every subsequent issuance of a given asset type must have a higher sequence number than before. /// @param {BigInt} amount - Amount to be issued. /// @param {boolean} conf_amount - `true` means the asset amount is confidential, and `false` means it's nonconfidential. - /// @param {PublicParams} zei_params - Public parameters necessary to generate asset records. pub fn add_basic_issue_asset( mut self, key_pair: &XfrKeyPair, @@ -427,6 +486,208 @@ impl TransactionBuilder { Ok(self) } + /// Adds an operation to the transaction builder that converts a bar to abar. + /// + /// @param {XfrKeyPair} auth_key_pair - input bar owner key pair + /// @param {AXfrPubKey} abar_pubkey - abar receiver's public key + /// @param {TxoSID} input_sid - txo sid of input bar + /// @param {ClientAssetRecord} input_record - + pub fn add_operation_bar_to_abar( + mut self, + seed: String, + auth_key_pair: &XfrKeyPair, + abar_pubkey: &XfrPublicKey, + txo_sid: u64, + input_record: &ClientAssetRecord, + owner_memo: Option, + ) -> Result { + use hex::FromHex; + + let oar = open_bar( + &input_record.get_bar_ref().into_noah(), + &owner_memo.map(|memo| memo.get_memo_ref().clone()), + &auth_key_pair.into_noah(), + ) + .c(d!()) + .map_err(|e| { + JsValue::from_str(&format!("Could not open asset record: {}", e)) + })?; + let is_bar_transparent = + oar.get_record_type() == NonConfidentialAmount_NonConfidentialAssetType; + + let mut seed = <[u8; 32]>::from_hex(seed).c(d!()).map_err(|e| { + JsValue::from_str(&format!("Failed to parse seed from hex: {}", e)) + })?; + + let (_, c) = self + .get_builder_mut() + .add_operation_bar_to_abar( + seed, + &auth_key_pair.clone(), + &abar_pubkey.clone(), + TxoSID(txo_sid), + &oar, + is_bar_transparent, + ) + .c(d!()) + .map_err(|e| { + JsValue::from_str(&format!("Could not add operation: {}", e)) + })?; + + self.commitments.push(c); + Ok(self) + } + + /// Adds an operation to transaction builder which converts an abar to a bar. + /// + /// @param {AnonAssetRecord} input - the ABAR to be converted + /// @param {AxfrOwnerMemo} axfr 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 {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: AnonAssetRecord, + owner_memo: AxfrOwnerMemo, + mt_leaf_info: MTLeafInfo, + from_keypair: &XfrKeyPair, + recipient: &XfrPublicKey, + conf_amount: bool, + conf_type: bool, + ) -> Result { + let oabar = OpenAnonAssetRecordBuilder::from_abar( + &input, + owner_memo.memo, + &from_keypair.into_noah(), + ) + .c(d!()) + .map_err(|e| { + JsValue::from_str(&format!( + "Builder from_abar error: {}", + e.get_lowest_msg() + )) + })? + .mt_leaf_info(mt_leaf_info.get_noah_mt_leaf_info().clone()) + .build() + .c(d!()) + .map_err(|e| { + JsValue::from_str(&format!("Builder build error: {}", e.get_lowest_msg())) + })?; + + 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.clone(), + &recipient.clone(), + art, + ) + .c(d!()) + .map_err(|e| { + JsValue::from_str(&format!( + "builder add_operation_abar_to_bar error: {}", + e.get_lowest_msg() + )) + })?; + + Ok(self) + } + + /// Returns a list of commitment base64 strings as json + pub fn get_commitments(&self) -> JsValue { + let r = CommitmentStringArray { + commitments: self + .commitments + .iter() + .map(wallet::commitment_to_base58) + .collect(), + }; + + JsValue::from_serde(&r).unwrap() + } + + /// Adds an operation to transaction builder which transfer a Anon Blind Asset Record + /// + /// @param {AnonAssetRecord} input - input abar + /// @param {AxfrOwnerMemo} axfr owner_memo - input owner memo + /// @param {AXfrKeyPair} from_keypair - abar sender's private key + /// @param {AXfrPubKey} to_pub_key - receiver's Anon public key + /// @param {u64} to_amount - amount to send to receiver + #[allow(clippy::too_many_arguments)] + pub fn add_operation_anon_transfer( + mut self, + input: AnonAssetRecord, + owner_memo: AxfrOwnerMemo, + mt_leaf_info: MTLeafInfo, + from_keypair: &XfrKeyPair, + to_pub_key: &XfrPublicKey, + to_amount: u64, + ) -> Result { + let mut prng = ChaChaRng::from_entropy(); + let input_oabar = OpenAnonAssetRecordBuilder::from_abar( + &input, + owner_memo.memo, + &from_keypair.into_noah(), + ) + .c(d!()) + .map_err(|e| JsValue::from_str(&format!("Could not add operation: {}", e)))? + .mt_leaf_info(mt_leaf_info.get_noah_mt_leaf_info().clone()) + .build() + .c(d!()) + .map_err(|e| JsValue::from_str(&format!("Could not add operation: {}", e)))?; + + if input_oabar.get_amount() <= to_amount { + return Err(JsValue::from_str(&format!( + "Insufficient amount for the input abar: {}", + input_oabar.get_amount() + ))); + } + + let output_oabar = OpenAnonAssetRecordBuilder::new() + .amount(to_amount) + .asset_type(input_oabar.get_asset_type()) + .pub_key(&to_pub_key.into_noah()) + .finalize(&mut prng) + .c(d!()) + .map_err(|e| JsValue::from_str(&format!("Could not add operation: {}", e)))? + .build() + .map_err(|e| { + JsValue::from_str(&format!("Could not add operation: {}", e)) + })?; + let r1 = get_abar_commitment(output_oabar.clone()); + self.commitments.push(r1); + + let (_, note, rem_oabars) = self + .get_builder_mut() + .add_operation_anon_transfer_fees_remainder( + &[input_oabar], + &[output_oabar], + &from_keypair.clone(), + ) + .c(d!()) + .map_err(|e| { + JsValue::from_str(&format!("Could not add operation: {}", e)) + })?; + + for rem_oabar in rem_oabars { + self.commitments.push(get_abar_commitment(rem_oabar)); + } + + Ok(self) + } + #[allow(missing_docs)] pub fn add_operation_delegate( mut self, @@ -549,8 +810,12 @@ impl TransactionBuilder { Ok(self) } - /// Do nothing, compatible with frontend + /// Builds the anon operations from pre-notes pub fn build(mut self) -> Result { + self.get_builder_mut() + .build() + .c(d!()) + .map_err(error_to_jsvalue)?; Ok(self) } @@ -572,7 +837,7 @@ impl TransactionBuilder { } /// Extracts the serialized form of a transaction. - pub fn transaction(&self) -> String { + pub fn transaction(&mut self) -> String { self.get_builder().serialize_str() } @@ -594,7 +859,9 @@ impl TransactionBuilder { pub fn get_owner_memo(&self, idx: usize) -> Option { self.get_builder() .get_owner_memo_ref(idx) - .map(|memo| OwnerMemo { memo: memo.into_noah() }) + .map(|memo| OwnerMemo { + memo: memo.into_noah(), + }) } } @@ -614,6 +881,11 @@ pub fn transfer_to_utxo_from_account( sk: String, nonce: u64, ) -> Result { + if !recipient.is_ed25519() { + return Err(eg!("recipient can only be ed25519 address")) + .map_err(error_to_jsvalue); + } + let seed = hex::decode(sk).map_err(error_to_jsvalue)?; let mut s = [0u8; 32]; s.copy_from_slice(&seed); @@ -677,6 +949,93 @@ pub fn get_serialized_address(address: String) -> Result { String::from_utf8(sa).map_err(error_to_jsvalue) } +/// Get balance for an Anonymous Blind Asset Record +/// @param {AnonAssetRecord} abar - ABAR for which balance needs to be queried +/// @param {AxfrOwnerMemo} memo - memo corresponding to the abar +/// @param keypair {AXfrKeyPair} - AXfrKeyPair of the ABAR owner +/// @param MTLeafInfo {mt_leaf_info} - the Merkle proof of the ABAR from commitment tree +/// @throws Will throw an error if abar fails to open +#[wasm_bindgen] +pub fn get_anon_balance( + abar: AnonAssetRecord, + memo: AxfrOwnerMemo, + keypair: XfrKeyPair, + mt_leaf_info: MTLeafInfo, +) -> Result { + let oabar = + OpenAnonAssetRecordBuilder::from_abar(&abar, memo.memo, &keypair.into_noah()) + .c(d!()) + .map_err(error_to_jsvalue)? + .mt_leaf_info(mt_leaf_info.get_noah_mt_leaf_info().clone()) + .build() + .c(d!()) + .map_err(error_to_jsvalue)?; + + Ok(oabar.get_amount()) +} + +/// Get OABAR (Open ABAR) using the ABAR, OwnerMemo and MTLeafInfo +/// @param {AnonAssetRecord} abar - ABAR which needs to be opened +/// @param {AxfrOwnerMemo} memo - memo corresponding to the abar +/// @param keypair {AXfrKeyPair} - AXfrKeyPair of the ABAR owner +/// @param MTLeafInfo {mt_leaf_info} - the Merkle proof of the ABAR from commitment tree +/// @throws Will throw an error if abar fails to open +#[wasm_bindgen] +pub fn get_open_abar( + abar: AnonAssetRecord, + memo: AxfrOwnerMemo, + keypair: XfrKeyPair, + mt_leaf_info: MTLeafInfo, +) -> Result { + let oabar = + OpenAnonAssetRecordBuilder::from_abar(&abar, memo.memo, &keypair.into_noah()) + .c(d!()) + .map_err(error_to_jsvalue)? + .mt_leaf_info(mt_leaf_info.get_noah_mt_leaf_info().clone()) + .build() + .c(d!()) + .map_err(error_to_jsvalue)?; + + let json = JsValue::from_serde(&oabar) + .c(d!()) + .map_err(error_to_jsvalue)?; + Ok(json) +} + +/// Generate nullifier hash using ABAR, OwnerMemo and MTLeafInfo +/// @param {AnonAssetRecord} abar - ABAR for which balance needs to be queried +/// @param {AxfrOwnerMemo} memo - memo corresponding to the abar +/// @param keypair {AXfrKeyPair} - AXfrKeyPair of the ABAR owner +/// @param MTLeafInfo {mt_leaf_info} - the Merkle proof of the ABAR from commitment tree +/// @throws Will throw an error if abar fails to open +#[wasm_bindgen] +pub fn gen_nullifier_hash( + abar: AnonAssetRecord, + memo: AxfrOwnerMemo, + keypair: XfrKeyPair, + mt_leaf_info: MTLeafInfo, +) -> Result { + let oabar = + OpenAnonAssetRecordBuilder::from_abar(&abar, memo.memo, &keypair.into_noah()) + .c(d!()) + .map_err(error_to_jsvalue)? + .mt_leaf_info(mt_leaf_info.get_noah_mt_leaf_info().clone()) + .build() + .c(d!()) + .map_err(error_to_jsvalue)?; + + let n = nullify( + &keypair.into_noah(), + oabar.get_amount(), + oabar.get_asset_type().as_scalar(), + mt_leaf_info.get_noah_mt_leaf_info().uid, + ) + .c(d!()) + .map_err(error_to_jsvalue)?; + let hash = wallet::nullifier_to_base58(&n.0); + Ok(hash) +} + #[wasm_bindgen] #[derive(Default)] /// Structure that enables clients to construct complex transfers. @@ -712,7 +1071,7 @@ impl TransferOperationBuilder { &owner_memo.map(|memo| memo.get_memo_ref().clone()), &key.into_noah(), ) - .c(d!()) + .c(d!()) .map_err(|e| { JsValue::from_str(&format!("Could not open asset record: {}", e)) })?; @@ -927,6 +1286,12 @@ impl TransferOperationBuilder { serde_json::to_string(self.get_builder()).unwrap() } + #[allow(missing_docs)] + pub fn from_string(s: String) -> Result { + let op_builder = serde_json::from_str(&s).c(d!()).map_err(error_to_jsvalue)?; + Ok(TransferOperationBuilder { op_builder }) + } + /// Wraps around TransferOperationBuilder to extract an operation expression as JSON. pub fn transaction(&self) -> Result { let op = self @@ -938,6 +1303,167 @@ impl TransferOperationBuilder { } } +#[wasm_bindgen] +/// Structure that enables clients to construct complex transfers. +pub struct AnonTransferOperationBuilder { + op_builder: PlatformAnonTransferOperationBuilder, +} + +impl AnonTransferOperationBuilder { + #[allow(missing_docs)] + pub fn get_builder(&self) -> &PlatformAnonTransferOperationBuilder { + &self.op_builder + } + + #[allow(missing_docs)] + pub fn get_builder_mut(&mut self) -> &mut PlatformAnonTransferOperationBuilder { + &mut self.op_builder + } +} + +#[wasm_bindgen] +impl AnonTransferOperationBuilder { + /// new is a constructor for AnonTransferOperationBuilder + pub fn new(seq_id: u64) -> Self { + AnonTransferOperationBuilder { + op_builder: PlatformAnonTransferOperationBuilder::new_from_seq_id(seq_id), + } + } + + /// add_input is used to add a new input source for Anon Transfer + /// @param {AnonAssetRecord} abar - input ABAR to transfer + /// @param {AxfrOwnerMemo} memo - memo corresponding to the input abar + /// @param keypair {AXfrKeyPair} - AXfrKeyPair of the ABAR owner + /// @param MTLeafInfo {mt_leaf_info} - the Merkle proof of the ABAR from commitment tree + /// @throws Will throw an error if abar fails to open, input fails to get added to Operation + pub fn add_input( + mut self, + abar: &AnonAssetRecord, + memo: &AxfrOwnerMemo, + keypair: &XfrKeyPair, + mt_leaf_info: MTLeafInfo, + ) -> Result { + let oabar = OpenAnonAssetRecordBuilder::from_abar( + &abar.clone(), + memo.memo.clone(), + &keypair.into_noah(), + ) + .c(d!()) + .map_err(error_to_jsvalue)? + .mt_leaf_info(mt_leaf_info.get_noah_mt_leaf_info().clone()) + .build() + .c(d!()) + .map_err(error_to_jsvalue)?; + + self.get_builder_mut() + .add_input(oabar) + .c(d!()) + .map_err(error_to_jsvalue)?; + + Ok(self) + } + + /// add_output is used to add a output to the Anon Transfer + /// @param amount {u64} - amount to be sent to the receiver + /// @param to {AXfrPubKey} - original pub key of receiver + /// @throws error if ABAR fails to be built + pub fn add_output( + mut self, + amount: u64, + asset_type: String, + to: XfrPublicKey, + ) -> Result { + let mut prng = ChaChaRng::from_entropy(); + + let at = AssetTypeCode::new_from_base64(asset_type.as_str()) + .map_err(error_to_jsvalue)?; + + let oabar_out = OpenAnonAssetRecordBuilder::new() + .amount(amount) + .asset_type(at.val) + .pub_key(&to.into_noah()) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(); + + self.get_builder_mut() + .add_output(oabar_out) + .c(d!()) + .map_err(error_to_jsvalue)?; + + Ok(self) + } + + /// add_keypair is used to add the sender's keypair for the nullifier generation + /// @param to {AXfrKeyPair} - original keypair of sender + /// @throws error if ABAR fails to be built + pub fn add_keypair(mut self, keypair: &XfrKeyPair) -> AnonTransferOperationBuilder { + self.get_builder_mut().add_keypair(keypair.clone()); + + self + } + + /// get_expected_fee is used to gather extra FRA that needs to be spent to make the transaction + /// have enough fees. + pub fn get_expected_fee(&self) -> Result { + self.get_builder() + .extra_fee_estimation() + .map_err(error_to_jsvalue) + } + + /// get_total_fee_estimate + pub fn get_total_fee_estimate(&self) -> Result { + self.get_builder() + .get_total_fee_estimation() + .map_err(error_to_jsvalue) + } + + /// get_commitments returns a list of all the commitments for receiver public keys + pub fn get_commitments(&self) -> JsValue { + let r = CommitmentStringArray { + commitments: self + .get_builder() + .get_commitments() + .iter() + .map(wallet::commitment_to_base58) + .collect(), + }; + + JsValue::from_serde(&r).unwrap() + } + + /// get_commitment_map returns a hashmap of all the commitments mapped to public key, asset, amount + pub fn get_commitment_map(&self) -> JsValue { + let commitment_map = self.get_builder().get_commitment_map(); + JsValue::from_serde(&commitment_map).unwrap() + } + + /// build is used to build proof the Transfer Operation + pub fn build(mut self) -> Result { + self.get_builder_mut() + .build() + .c(d!("error in txn_builder: build")) + .map_err(error_to_jsvalue)?; + + self.get_builder_mut() + .build_txn() + .c(d!()) + .map_err(error_to_jsvalue)?; + + Ok(self) + } + + /// transaction returns the prepared Anon Transfer Operation + /// @param nonce {NoReplayToken} - nonce of the txn to be added to the operation + pub fn transaction(self) -> Result { + self.get_builder() + .serialize_str() + .c(d!()) + .map_err(error_to_jsvalue) + } +} + ///////////// CRYPTO ////////////////////// #[wasm_bindgen] /// Returns a JavaScript object containing decrypted owner record information, @@ -975,12 +1501,35 @@ pub fn get_priv_key_str(key_pair: &XfrKeyPair) -> String { serde_json::to_string(key_pair.get_sk_ref()).unwrap() } +#[wasm_bindgen] +/// +pub fn get_priv_key_hex_str_by_mnemonic( + phrase: &str, + num: u32, +) -> Result { + let key_pair = wallet::restore_keypair_from_mnemonic_cus(phrase, 0, 0, num) + .map_err(error_to_jsvalue)?; + let data = key_pair.get_sk_ref().to_bytes(); + Ok(format!("0x{}", hex::encode(&data[1..]))) +} + +#[wasm_bindgen] +/// Extracts the private key as a string from a transfer key pair. +pub fn get_priv_key_str_old(key_pair: &XfrKeyPair) -> String { + base64::encode_config(&key_pair.get_sk_ref().to_bytes(), base64::URL_SAFE) +} + #[wasm_bindgen] /// Creates a new transfer key pair. pub fn new_keypair() -> XfrKeyPair { gen_random_keypair() } +#[wasm_bindgen] +/// Creates a new transfer key pair. +pub fn new_keypair_old() -> XfrKeyPair { + XfrKeyPair::generate(&mut ChaChaRng::from_entropy()) +} #[wasm_bindgen] /// Generates a new keypair deterministically from a seed string and an optional name. pub fn new_keypair_from_seed(seed_str: String, name: Option) -> XfrKeyPair { @@ -1249,12 +1798,7 @@ pub fn trace_assets( // let candidate_assets: Vec = // candidate_assets.into_serde().c(d!()).map_err(error_to_jsvalue)?; let xfr_body: XfrBody = xfr_body.into_serde().c(d!()).map_err(error_to_jsvalue)?; - // let candidate_assets: Vec = candidate_assets - // .iter() - // .map(|asset_type_str| { - // AssetTypeCode::new_from_str(&asset_type_str.to_string()).val - // }) - // .collect(); + let record_data = noah_trace_assets(&xfr_body.into_noah(), tracer_keypair.get_keys()) .c(d!()) @@ -1276,6 +1820,7 @@ pub fn trace_assets( // Author: Chao Ma, github.com/chaosma. // ////////////////////////////////////////// +use crate::wasm_data_model::{AmountAssetType, AnonKeys}; use aes_gcm::{ aead::{generic_array::GenericArray, Aead, KeyInit}, Aes256Gcm, @@ -1284,6 +1829,7 @@ use base64::URL_SAFE; use fp_types::H160; use getrandom::getrandom; use js_sys::JsString; +use ledger::data_model::{ABARData, TxoSID, BAR_TO_ABAR_TX_FEE_MIN}; use ledger::staking::Amount; use rand_core::{CryptoRng, RngCore}; @@ -1312,6 +1858,13 @@ pub fn bech32_to_base64(pk: &str) -> Result { Ok(public_key_to_base64(&pub_key)) } +#[wasm_bindgen] +#[allow(missing_docs)] +pub fn bech32_to_base64_old(pk: &str) -> Result { + public_key_from_bech32(pk) + .map(|pk| base64::encode_config(&pk.to_bytes(), base64::URL_SAFE)) +} + #[wasm_bindgen] #[allow(missing_docs)] pub fn base64_to_bech32(pk: &str) -> Result { @@ -1319,6 +1872,17 @@ pub fn base64_to_bech32(pk: &str) -> Result { Ok(public_key_to_bech32(&pub_key)) } +#[wasm_bindgen] +#[allow(missing_docs)] +pub fn base64_to_base58(data: &str) -> Result { + let byts = base64::decode_config(data, URL_SAFE) + .c(d!()) + .map_err(error_to_jsvalue)?; + + let dat = bs58::encode(byts).into_string(); + Ok(dat) +} + #[wasm_bindgen] #[allow(missing_docs)] pub fn encryption_pbkdf2_aes256gcm(key_pair: String, password: String) -> Vec { @@ -1386,10 +1950,12 @@ pub fn decryption_pbkdf2_aes256gcm(enc_key_pair: Vec, password: String) -> S #[wasm_bindgen] #[allow(missing_docs)] -pub fn create_keypair_from_secret(sk_str: String) -> Option { - serde_json::from_str::(&sk_str) - .map(|sk| sk.into_keypair()) - .ok() +pub fn create_keypair_from_secret(sk_str: String) -> Result { + let sk = serde_json::from_str::(&sk_str) + .c(d!()) + .map_err(error_to_jsvalue)?; + + Ok(sk.into_keypair()) } #[wasm_bindgen] @@ -1456,6 +2022,28 @@ pub fn restore_keypair_from_mnemonic_default( .map_err(error_to_jsvalue) } +#[wasm_bindgen] +/// Restore the XfrKeyPair from a mnemonic with a default bip44-path, +/// that is "m/44'/917'/0'/0/0" ("m/44'/coin'/account'/change/address"). +pub fn restore_keypair_from_mnemonic_ed25519( + phrase: &str, +) -> Result { + wallet::restore_keypair_from_mnemonic_ed25519(phrase) + .c(d!()) + .map_err(error_to_jsvalue) +} + +#[wasm_bindgen] +/// Restore the XfrKeyPair from a mnemonic with a default bip44-path, +/// that is "m/44'/917'/0'/0/0" ("m/44'/coin'/account'/change/address"). +pub fn restore_keypair_from_mnemonic_secp256k1( + phrase: &str, +) -> Result { + wallet::restore_keypair_from_mnemonic_secp256k1(phrase) + .c(d!()) + .map_err(error_to_jsvalue) +} + #[wasm_bindgen] /// Restore the XfrKeyPair from a mnemonic with custom params, /// in bip44 form. @@ -1497,8 +2085,20 @@ pub fn fra_get_minimal_fee() -> u64 { TX_FEE_MIN } +/// Fee smaller than this value will be denied. +#[wasm_bindgen] +pub fn fra_get_minimal_fee_for_bar_to_abar() -> u64 { + BAR_TO_ABAR_TX_FEE_MIN +} + +/// Anon fee for a given number of inputs & outputs #[wasm_bindgen] +pub fn get_anon_fee(n_inputs: u32, n_outputs: u32) -> u32 { + PlatformAnonTransferOperationBuilder::get_anon_fee(n_inputs, n_outputs) +} + /// The destination for fee to be transfered to. +#[wasm_bindgen] pub fn fra_get_dest_pubkey() -> XfrPublicKey { XfrPublicKey::from_noah(&BLACK_HOLE_PUBKEY) } @@ -1533,10 +2133,197 @@ pub fn get_delegation_max_amount() -> u64 { MAX_DELEGATION_AMOUNT } +#[wasm_bindgen] +#[allow(missing_docs)] +pub fn x_pubkey_from_string(key_str: &str) -> Result { + wallet::x_public_key_from_base64(key_str) + .c(d!()) + .map_err(error_to_jsvalue) +} + +#[wasm_bindgen] +#[allow(missing_docs)] +pub fn x_secretkey_from_string(key_str: &str) -> Result { + wallet::x_secret_key_from_base64(key_str) + .c(d!()) + .map_err(error_to_jsvalue) +} + +#[wasm_bindgen] +#[allow(missing_docs)] +pub fn abar_from_json(json: JsValue) -> Result { + let abar: ABARData = json.into_serde().c(d!()).map_err(error_to_jsvalue)?; + let c = wallet::commitment_from_base58(abar.commitment.as_str()) + .c(d!()) + .map_err(error_to_jsvalue)?; + + Ok(AnonAssetRecord { commitment: c }) +} + +#[wasm_bindgen] +/// Decrypts an ABAR with owner memo and decryption key +pub fn open_abar( + abar: AnonAssetRecord, + memo: AxfrOwnerMemo, + keypair: &XfrKeyPair, +) -> Result { + let oabar = + OpenAnonAssetRecordBuilder::from_abar(&abar, memo.memo, &keypair.into_noah()) + .map_err(error_to_jsvalue)? + .build() + .map_err(error_to_jsvalue)?; + + let at = AssetTypeCode { + val: oabar.get_asset_type(), + }; + + Ok(AmountAssetType { + amount: oabar.get_amount(), + asset_type: at.to_base64(), + }) +} + +#[wasm_bindgen] +/// Decrypts the owner anon memo. +/// * `memo` - Owner anon memo to decrypt +/// * `key_pair` - Owner anon keypair +/// * `abar` - Associated anonymous blind asset record to check memo info against. +/// Return Error if memo info does not match the commitment or public key. +/// Return Ok(amount, asset_type, blinding) otherwise. +pub fn decrypt_axfr_memo( + memo: &AxfrOwnerMemo, + key_pair: &XfrKeyPair, + abar: &AnonAssetRecord, +) -> Result { + let (amount, asset_type, blind) = + decrypt_memo(&memo.memo, &key_pair.into_noah(), abar) + .c(d!()) + .map_err(error_to_jsvalue)?; + Ok(AxfrOwnerMemoInfo { + amount, + blind, + asset_type: AssetTypeCode { val: asset_type }.to_base64(), + }) +} + +#[wasm_bindgen] +/// Try to decrypt the owner memo to check if it is own. +/// * `memo` - Owner anon memo need to decrypt. +/// * `key_pair` - the memo bytes. +/// Return Ok(amount, asset_type, blinding) if memo is own. +pub fn try_decrypt_axfr_memo( + memo: &AxfrOwnerMemo, + key_pair: &XfrKeyPair, +) -> Result, JsValue> { + let secret_key = key_pair.get_sk_ref().into_noah(); + let res = memo + .get_memo_ref() + .decrypt(&secret_key) + .c(d!()) + .map_err(error_to_jsvalue)?; + Ok(res) +} + +#[wasm_bindgen] +/// Parse the owner memo from bytes. +/// * `bytes` - the memo plain bytes. +/// * `key_pair` - the memo bytes. +/// * `abar` - Associated anonymous blind asset record to check memo info against. +/// Return Error if memo info does not match the commitment. +/// Return Ok(amount, asset_type, blinding) otherwise. +pub fn parse_axfr_memo( + bytes: &[u8], + key_pair: &XfrKeyPair, + abar: &AnonAssetRecord, +) -> Result { + let (amount, asset_type, blind) = parse_memo(bytes, &key_pair.into_noah(), abar) + .c(d!()) + .map_err(error_to_jsvalue)?; + Ok(AxfrOwnerMemoInfo { + amount, + blind, + asset_type: AssetTypeCode { val: asset_type }.to_base64(), + }) +} + +#[wasm_bindgen] +/// Convert Commitment to AnonAssetRecord. +pub fn commitment_to_aar(commitment: Commitment) -> AnonAssetRecord { + AnonAssetRecord { commitment } +} + #[cfg(test)] #[allow(missing_docs)] mod test { use super::*; + use wasm_bindgen_test::*; + + #[wasm_bindgen_test] + //This contains only the positive tests with the fees included + fn extra_fee_test() { + let mut prng = ChaChaRng::from_seed([0u8; 32]); + + let amount = 6000000000u64; + + //let amount_output = amount / 3; + let amount_output = amount; + + let asset_type = ASSET_TYPE_FRA; + + // simulate input abar + let (mut oabar, keypair_in) = gen_oabar_and_keys(&mut prng, amount, asset_type); + + let asset_type_out = ASSET_TYPE_FRA; + + //Simulate output abar + let (mut oabar_out, _keypair_out) = + gen_oabar_and_keys(&mut prng, amount_output, asset_type_out); + + let mut ts = AnonTransferOperationBuilder::new(1); + + ts.get_builder_mut().add_input(oabar); + + ts.get_builder_mut().add_output(oabar_out); + + /* + Extra_fee_estimation works as follows + 1.- compute estimated_fees + 2.- compute FRA_excess + fra_excess = fra_input_sum - fra_output_sum; + if (fra_excess >= estimated_fees) => 0 + else (estimated_fees > fra_excess) => new_fees_estimation(n + 1 inputs, m + 1 outputs) + */ + + let estimated_fees_gt_fra_excess = ts.get_expected_fee(); + + assert!(estimated_fees_gt_fra_excess.unwrap() > 0); + + let (mut oabar_2, keypair_in_2) = + gen_oabar_and_keys(&mut prng, 2 * amount, asset_type); + + ts.get_builder_mut().add_input(oabar_2); + + let fra_excess_gt_fees_estimation = ts.get_expected_fee(); + + assert_eq!(fra_excess_gt_fees_estimation, Ok(0)); + } + + fn gen_oabar_and_keys( + prng: &mut R, + amount: u64, + asset_type: NoahAssetType, + ) -> (OpenAnonAssetRecord, XfrKeyPair) { + let keypair = XfrKeyPair::generate(prng); + let oabar = OpenAnonAssetRecordBuilder::new() + .amount(u64::from(amount)) + .asset_type(asset_type) + .pub_key(&keypair.get_pk().into_noah()) + .finalize(prng) + .unwrap() + .build() + .unwrap(); + (oabar, keypair) + } #[test] fn t_keypair_conversion() { @@ -1624,4 +2411,26 @@ mod test { serde_json::from_str::(&actual_serialized_json).unwrap(); assert_eq!(res.max_units, None); } + + #[test] + fn test_keypair_from_mnemonic() { + let phrase1 = "museum combine night carry artefact actress sugar amount kitchen change ill room walk potato beef similar claw fossil gate chalk domain chronic utility engage"; + let phrase2 = "museum combine night carry artefact actress sugar amount kitchen change ill room walk potato beef similar claw fossil gate chalk domain chronic utility engage"; + + let kp1 = restore_keypair_from_mnemonic_default(phrase1).unwrap(); + println!( + "{} {}", + serde_json::to_string_pretty(&kp1).unwrap(), + wallet::public_key_to_bech32(kp1.get_pk_ref()) + ); + + let kp2 = restore_keypair_from_mnemonic_default(phrase2).unwrap(); + println!( + "{} {}", + serde_json::to_string_pretty(&kp2).unwrap(), + wallet::public_key_to_bech32(kp2.get_pk_ref()) + ); + + assert_eq!(kp1.get_sk(), kp2.get_sk()); + } } diff --git a/src/components/wasm/src/wasm_data_model.rs b/src/components/wasm/src/wasm_data_model.rs index 13eefd6fa..cb3eaed5e 100644 --- a/src/components/wasm/src/wasm_data_model.rs +++ b/src/components/wasm/src/wasm_data_model.rs @@ -6,6 +6,7 @@ use { Credential as PlatformCredential, }, globutils::{wallet, HashOf}, + js_sys::JsString, ledger::data_model::{ AssetRules as PlatformAssetRules, AssetType as PlatformAssetType, AuthenticatedUtxo, SignatureRules as PlatformSignatureRules, TxOutput, @@ -17,17 +18,20 @@ use { serde::{Deserialize, Serialize}, wasm_bindgen::prelude::*, zei::{ - noah_algebra::ristretto::PedersenCommitmentRistretto, - noah_api::xfr::{ - structs::{ + noah_algebra::{bn254::BN254Scalar, ristretto::PedersenCommitmentRistretto}, + noah_api::{ + anon_xfr::structs::{ + AnonAssetRecord, AxfrOwnerMemo as NoahAxfrOwnerMemo, + MTLeafInfo as NoahMTLeafInfo, + }, + xfr::structs::{ AssetTracerDecKeys, AssetTracerEncKeys, - AssetTracerKeyPair as NoahAssetTracerKeyPair, - IdentityRevealPolicy, OwnerMemo as NoahOwnerMemo, - TracingPolicies as NoahTracingPolicies, + AssetTracerKeyPair as NoahAssetTracerKeyPair, IdentityRevealPolicy, + OwnerMemo as NoahOwnerMemo, TracingPolicies as NoahTracingPolicies, TracingPolicy as NoahTracingPolicy, }, }, - XfrPublicKey, BlindAssetRecord, + BlindAssetRecord, XfrPublicKey, }, }; @@ -157,7 +161,10 @@ impl ClientAssetRecord { /// fetch an asset record from the ledger server. pub fn from_json(val: &JsValue) -> Result { Ok(ClientAssetRecord { - txo: val.into_serde().c(d!()).map_err(error_to_jsvalue)?, + txo: val + .into_serde() + .c(d!()) + .map_err(|_| JsValue::from_str("format json error"))?, }) } @@ -256,6 +263,73 @@ impl OwnerMemo { } } +#[wasm_bindgen] +#[derive(Deserialize, Clone)] +/// Asset owner memo. Contains information needed to decrypt an asset record. +/// @see {@link module:Findora-Wasm.ClientAssetRecord|ClientAssetRecord} for more details about asset records. +pub struct AxfrOwnerMemo { + pub(crate) memo: NoahAxfrOwnerMemo, +} + +#[wasm_bindgen] +impl AxfrOwnerMemo { + /// Builds an owner memo from a JSON-serialized JavaScript value. + /// @param {JsValue} val - JSON owner memo fetched from query server with the `get_owner_memo/{sid}` route, + /// where `sid` can be fetched from the query server with the `get_owned_utxos/{address}` route. See the example below. + /// + /// @example + /// { + /// "blind_share":[91,251,44,28,7,221,67,155,175,213,25,183,70,90,119,232,212,238,226,142,159,200,54,19,60,115,38,221,248,202,74,248], + /// "lock":{"ciphertext":[119,54,117,136,125,133,112,193],"encoded_rand":"8KDql2JphPB5WLd7-aYE1bxTQAcweFSmrqymLvPDntM="} + /// } + pub fn from_json(val: &JsValue) -> Result { + let noah_owner_memo: NoahAxfrOwnerMemo = + val.into_serde().c(d!()).map_err(error_to_jsvalue)?; + Ok(AxfrOwnerMemo { + memo: noah_owner_memo, + }) + } + + /// Creates a clone of the owner memo. + pub fn clone(&self) -> Self { + AxfrOwnerMemo { + memo: self.memo.clone(), + } + } +} + +impl AxfrOwnerMemo { + pub fn get_memo_ref(&self) -> &NoahAxfrOwnerMemo { + &self.memo + } +} + +#[wasm_bindgen] +/// Asset owner memo decrypted info. contains amount, asset_type and blind. +pub struct AxfrOwnerMemoInfo { + pub(crate) amount: u64, + pub(crate) asset_type: String, + pub(crate) blind: BN254Scalar, +} + +#[wasm_bindgen] +#[allow(missing_docs)] +impl AxfrOwnerMemoInfo { + #[wasm_bindgen(getter)] + pub fn amount(&self) -> u64 { + self.amount + } + + #[wasm_bindgen(getter)] + pub fn asset_type(&self) -> String { + self.asset_type.clone() + } + + #[wasm_bindgen(getter)] + pub fn blind(&self) -> BN254Scalar { + self.blind + } +} #[derive(Serialize, Deserialize)] pub(crate) struct AttributeDefinition { @@ -695,3 +769,94 @@ impl AssetRules { pub(crate) fn error_to_jsvalue(e: T) -> JsValue { JsValue::from_str(&e.to_string()) } + +#[wasm_bindgen] +#[derive(Default, Clone)] +pub struct MTLeafInfo { + object: NoahMTLeafInfo, +} + +impl MTLeafInfo { + pub fn get_noah_mt_leaf_info(&self) -> &NoahMTLeafInfo { + &self.object + } +} + +#[wasm_bindgen] +impl MTLeafInfo { + pub fn from_json(json: &JsValue) -> Result { + let mt_leaf_info: NoahMTLeafInfo = json + .into_serde() + .c(d!()) + .map_err(|_| JsValue::from_str("format json error"))?; + Ok(MTLeafInfo { + object: mt_leaf_info, + }) + } + + pub fn to_json(&self) -> Result { + serde_json::to_string(&self.object) + .map(|s| JsValue::from_str(&s)) + .c(d!()) + .map_err(error_to_jsvalue) + } +} + +#[wasm_bindgen] +pub struct AmountAssetType { + pub amount: u64, + pub(crate) asset_type: String, +} + +#[wasm_bindgen] +impl AmountAssetType { + #[wasm_bindgen(getter)] + pub fn asset_type(&self) -> String { + self.asset_type.clone() + } +} + +/// AnonKeys is used to store keys for Anon proofs +#[wasm_bindgen] +#[derive(Serialize, Deserialize)] +pub struct AnonKeys { + pub(crate) secret_key: String, + pub(crate) pub_key: String, +} + +/// AnonKeys is a struct to store keys required for anon transfer +#[wasm_bindgen] +#[allow(missing_docs)] +impl AnonKeys { + pub fn from_json(json: &JsValue) -> Result { + let anon_keys: AnonKeys = json.into_serde().c(d!()).map_err(error_to_jsvalue)?; + Ok(anon_keys) + } + + pub fn to_json(&self) -> Result { + serde_json::to_string(&self) + .map(|s| JsValue::from_str(&s)) + .c(d!()) + .map_err(error_to_jsvalue) + } + + #[wasm_bindgen(getter)] + pub fn secret_key(&self) -> String { + self.secret_key.clone() + } + + #[wasm_bindgen(setter)] + pub fn set_secret_key(&mut self, secret_key: String) { + self.secret_key = secret_key; + } + + #[wasm_bindgen(getter)] + pub fn pub_key(&self) -> String { + self.pub_key.clone() + } + + #[wasm_bindgen(setter)] + pub fn set_pub_key(&mut self, pub_key: String) { + self.pub_key = pub_key; + } +} diff --git a/src/ledger/Cargo.toml b/src/ledger/Cargo.toml index 5d37a3afb..3fb8a1e77 100644 --- a/src/ledger/Cargo.toml +++ b/src/ledger/Cargo.toml @@ -7,10 +7,12 @@ build = "build.rs" [dependencies] base64 = "0.13" +bs58 = "0.4" bincode = "1.3.1" -byteorder = "1.0.0" +byteorder = "1.0.0" curve25519-dalek = { package = "noah-curve25519-dalek", version = "4.0.0", default-features = false, features = ['serde'] } ed25519-dalek = { package = "noah-ed25519-dalek", git = "https://github.com/FindoraNetwork/ed25519-dalek", tag = "v4.0.0" } +digest = '0.10' hex = "0.4.3" lazy_static = { version = "1.2.0" } tracing = "0.1" @@ -24,7 +26,7 @@ serde-strz = "1.1.1" sha2 = "0.10" unicode-normalization = "0.1.13" time = "0.3" -tendermint = { git = "https://github.com/FindoraNetwork/tendermint-rs", tag = "v0.19.0a-fk" } +tendermint = { git = "https://github.com/FindoraNetwork/tendermint-rs", tag = "v0.19.0c" } indexmap = { version = "1.6.2", features = ["serde"] } config = { path = "../components/config" } fp-types = { path = "../components/contracts/primitives/types" } @@ -32,6 +34,7 @@ fp-utils = { path = "../components/contracts/primitives/utils" } ruc = "1.0" zei = { package="platform-lib-noah", git = "https://github.com/FindoraNetwork/platform-lib-noah", branch = "develop" } bulletproofs = { package = "bulletproofs", git = "https://github.com/FindoraNetwork/bp", rev = "57633a", features = ["yoloproofs"] } +itertools = "0.10" fbnc = { version = "0.2.9", default-features = false} once_cell = "1" num-bigint = "0.4.3" @@ -44,10 +47,11 @@ merkle_tree = { git = "https://github.com/FindoraNetwork/platform-lib-merkle", b sliding_set = { git = "https://github.com/FindoraNetwork/platform-lib-slidingset", branch = "develop" } [features] -default = [] +default = ["fin_storage"] diskcache = ["fbnc/diskcache"] debug_env = ["config/debug_env"] abci_mock = [] +fin_storage = ["storage", "fin_db"] [dev-dependencies] lazy_static = "1.4.0" @@ -58,11 +62,13 @@ features = ["f16", "serde"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] parking_lot = "0.12" -# sodiumoxide = "0.2.1" fs2 = "0.4" +storage = { git = "https://github.com/FindoraNetwork/storage.git", tag = "v1.1.6", optional = true } +fin_db = { git = "https://github.com/FindoraNetwork/storage.git", tag = "v1.1.6", optional = true } +sparse_merkle_tree = { git = "https://github.com/FindoraNetwork/platform-lib-sparse-merkle", branch = "develop" } [target.'cfg(target_arch = "wasm32")'.dependencies] parking_lot = { version = "0.11.1", features = ["wasm-bindgen"] } [build-dependencies] -vergen = "=3.1.0" +vergen = "=3.1.0" \ No newline at end of file diff --git a/src/ledger/src/data_model/__trash__.rs b/src/ledger/src/data_model/__trash__.rs index ee27a584d..b73009afc 100644 --- a/src/ledger/src/data_model/__trash__.rs +++ b/src/ledger/src/data_model/__trash__.rs @@ -12,8 +12,7 @@ use { crate::data_model::AssetTypeCode, fixed::types::I20F12, serde::{Deserialize, Serialize}, - zei::noah_api::xfr::structs::AssetType, - zei::XfrPublicKey, + zei::{noah_api::xfr::structs::AssetType, XfrPublicKey}, }; #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] diff --git a/src/ledger/src/data_model/effects.rs b/src/ledger/src/data_model/effects.rs index 09ccea520..e086a2034 100644 --- a/src/ledger/src/data_model/effects.rs +++ b/src/ledger/src/data_model/effects.rs @@ -1,10 +1,10 @@ -use zei::noah_api::parameters::bulletproofs::BulletproofParams; use { crate::{ data_model::{ - AssetType, AssetTypeCode, DefineAsset, IssueAsset, IssuerPublicKey, Memo, - NoReplayToken, Operation, Transaction, TransferAsset, TransferType, - TxOutput, TxnTempSID, TxoRef, TxoSID, UpdateMemo, + AbarConvNote, AbarToBarOps, AnonTransferOps, AssetType, AssetTypeCode, + BarToAbarOps, DefineAsset, IssueAsset, IssuerPublicKey, Memo, NoReplayToken, + Operation, Transaction, TransferAsset, TransferType, TxOutput, TxnTempSID, + TxoRef, TxoSID, UpdateMemo, }, staking::{ self, @@ -30,9 +30,16 @@ use { }, zei::{ noah_algebra::serialization::NoahFromToBytes, - noah_api::xfr::{ - structs::{XfrAmount, XfrAssetType}, - verify_xfr_body, + noah_api::{ + anon_xfr::{ + abar_to_abar::AXfrNote, + structs::{AnonAssetRecord, Nullifier}, + }, + parameters::bulletproofs::BulletproofParams, + xfr::{ + structs::{XfrAmount, XfrAssetType}, + verify_xfr_body, + }, }, XfrPublicKey, }, @@ -91,6 +98,12 @@ pub struct TxnEffect { pub fra_distributions: Vec, /// Staking operations pub update_stakers: Vec, + /// Newly created Anon Blind Asset Records + pub bar_conv_abars: Vec, + /// Body of Abar to Bar conversions + pub abar_conv_inputs: Vec, + /// New anon transfer bodies + pub axfr_bodies: Vec, /// replace staker operations pub replace_stakers: Vec, } @@ -197,6 +210,18 @@ impl TxnEffect { Operation::ConvertAccount(i) => { check_nonce!(i) } + Operation::BarToAbar(i) => { + 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!())?; + } } } @@ -323,10 +348,10 @@ impl TxnEffect { // 1) The signatures on the body (a) all are valid and (b) // there is a signature for each input key // - Fully checked here - // 2) The UTXOs (a) exist on the ledger and (b) match the zei transaction. + // 2) The UTXOs (a) exist on the ledger and (b) match the noah transaction. // - Partially checked here -- anything which hasn't // been checked will appear in `input_txos` - // 3) The zei transaction is valid. + // 3) The noah transaction is valid. // - Checked here and in check_txn_effects // 4) Lien assignments match up // - Checked within a transaction here, recorded for @@ -348,14 +373,72 @@ impl TxnEffect { return Err(eg!()); } - // Transfer outputs must match outputs zei transaction + // Refuse any transfer with policies for now + let c1 = trn + .body + .policies + .inputs_tracing_policies + .iter() + .any(|x| !x.is_empty()); + let c2 = trn + .body + .policies + .outputs_tracing_policies + .iter() + .any(|x| !x.is_empty()); + let c3 = trn + .body + .policies + .inputs_sig_commitments + .iter() + .any(|x| !x.is_none()); + let c4 = trn + .body + .policies + .outputs_sig_commitments + .iter() + .any(|x| !x.is_none()); + let c5 = trn + .body + .transfer + .asset_tracing_memos + .iter() + .any(|x| !x.is_empty()); + let c6 = trn + .body + .transfer + .proofs + .asset_tracing_proof + .inputs_identity_proofs + .iter() + .any(|x| !x.is_empty()); + let c7 = trn + .body + .transfer + .proofs + .asset_tracing_proof + .outputs_identity_proofs + .iter() + .any(|x| !x.is_empty()); + let c8 = !trn + .body + .transfer + .proofs + .asset_tracing_proof + .asset_type_and_amount_proofs + .is_empty(); + if c1 || c2 || c3 || c4 || c5 || c6 || c7 || c8 { + return Err(eg!()); + } + + // Transfer outputs must match outputs noah transaction for (output, record) in trn .body .outputs .iter() .zip(trn.body.transfer.outputs.iter()) { - if output.record != *record { + if output.record != record.clone() { return Err(eg!()); } } @@ -455,7 +538,8 @@ impl TxnEffect { } Some(txo) => { // (2).(b) - if &txo.record != record || txo.lien != lien.cloned() { + if txo.record != record.clone() || txo.lien != lien.cloned() + { return Err(eg!()); } self.internally_spent_txos.push(txo.clone()); @@ -535,6 +619,75 @@ 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 txos are unspent. (checked in finish block) + /// # Arguments + /// * `bar_to_abar` - the BarToAbar Operation body + /// returns error if validation fails + fn add_bar_to_abar(&mut self, bar_to_abar: &BarToAbarOps) -> Result<()> { + // verify the note signature & Plonk proof + bar_to_abar.verify()?; + + // list input_txo to spend + self.input_txos.insert( + bar_to_abar.txo_sid, + TxOutput { + id: None, + record: bar_to_abar.input_record(), + lien: None, + }, + ); + // push new ABAR created + self.bar_conv_abars.push(bar_to_abar.output_record()); + Ok(()) + } + + /// An abar to bar note is valid iff + /// 1. the signature is correct, + /// 2. the ZKP can be verified, + /// 3. the input ABARs are unspent. (checked in finish block) + /// # Arguments + /// * abar_to_bar - The Operation for AbarToBar + /// returns an error if validation fails + fn add_abar_to_bar(&mut self, abar_to_bar: &AbarToBarOps) -> Result<()> { + // collect body in TxnEffect to verify ZKP later with merkle root + self.abar_conv_inputs.push(abar_to_bar.note.clone()); + // collect newly created BARs + self.txos.push(Some(TxOutput { + id: None, + record: abar_to_bar.note.get_output(), + lien: None, + })); + + Ok(()) + } + + /// An anon transfer note is valid iff + /// 1. no double spending in the txn, + /// 2. the signature is correct, + /// 3. ZKP can be verified, + /// 4. the input ABARs are unspent. (checked in finish block) + /// # Arguments + /// * anon_transfer - The Operation for Anon Transfer + /// returns an error if validation fails + fn add_anon_transfer(&mut self, anon_transfer: &AnonTransferOps) -> Result<()> { + // verify nullifiers not double spent within txn + for i in &anon_transfer.note.body.inputs { + if self + .axfr_bodies + .iter() + .flat_map(|ab| ab.body.inputs.iter()) + .any(|n| n == i) + { + return Err(eg!("Transaction has duplicate nullifiers")); + } + } + self.axfr_bodies.push(anon_transfer.note.clone()); + Ok(()) + } } /// Check tx in the context of a block, partially. @@ -550,8 +703,12 @@ pub struct BlockEffect { /// Internally-spent TXOs are None, UTXOs are Some(...) /// Should line up element-wise with `txns` pub txos: Vec>>, + /// New ABARs created + pub output_abars: Vec>, /// Which TXOs this consumes pub input_txos: HashMap, + /// Which new nullifiers are created + pub new_nullifiers: Vec, /// Which new asset types this defines pub new_asset_codes: HashMap, /// Which new TXO issuance sequence numbers are used, in sorted order @@ -614,6 +771,29 @@ impl BlockEffect { self.memo_updates.insert(code, memo); } + // collect ABARs generated from BAR to ABAR + let mut current_txn_abars: Vec = vec![]; + for abar in txn_effect.bar_conv_abars { + current_txn_abars.push(abar); + } + + // collect Nullifiers generated from ABAR to BAR + for inputs in txn_effect.abar_conv_inputs.iter() { + self.new_nullifiers.push(inputs.get_input()); + } + + // collect ABARs and Nullifiers from Anon Transfers + for axfr_note in txn_effect.axfr_bodies { + for n in axfr_note.body.inputs { + self.new_nullifiers.push(n); + } + for abar in axfr_note.body.outputs { + current_txn_abars.push(abar) + } + } + + self.output_abars.push(current_txn_abars); + Ok(temp_sid) } @@ -625,6 +805,21 @@ impl BlockEffect { } } + // Check that no nullifier is created twice in the same block + // for anon_transfer and abar to bar conversion + for axfr_note in txn_effect.axfr_bodies.iter() { + for nullifier in axfr_note.body.inputs.iter() { + if self.new_nullifiers.contains(nullifier) { + return Err(eg!()); + } + } + } + for inputs in txn_effect.abar_conv_inputs.iter() { + if self.new_nullifiers.contains(&inputs.get_input()) { + 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 22542b9fd..25f4d3cce 100644 --- a/src/ledger/src/data_model/mod.rs +++ b/src/ledger/src/data_model/mod.rs @@ -12,21 +12,24 @@ mod test; pub use effects::{BlockEffect, TxnEffect}; use { - crate::converter::ConvertAccount, - crate::staking::{ - ops::{ - claim::ClaimOps, delegation::DelegationOps, - fra_distribution::FraDistributionOps, governance::GovernanceOps, - mint_fra::MintFraOps, replace_staker::ReplaceStakerOps, - undelegation::UnDelegationOps, update_staker::UpdateStakerOps, - update_validator::UpdateValidatorOps, + crate::{ + converter::ConvertAccount, + staking::{ + ops::{ + claim::ClaimOps, delegation::DelegationOps, + fra_distribution::FraDistributionOps, governance::GovernanceOps, + mint_fra::MintFraOps, replace_staker::ReplaceStakerOps, + undelegation::UnDelegationOps, update_staker::UpdateStakerOps, + update_validator::UpdateValidatorOps, + }, + Staking, }, - Staking, }, __trash__::{Policy, PolicyGlobals, TxnPolicyData}, bitmap::SparseMap, config::abci::CheckPointConfig, cryptohash::{sha256::Digest as BitDigest, HashValue}, + digest::{consts::U64, Digest}, fbnc::NumKey, globutils::wallet::public_key_to_base64, globutils::{HashOf, ProofOf, Serialized, SignatureOf}, @@ -52,7 +55,20 @@ use { traits::Scalar, }, noah_api::{ + anon_xfr::{ + abar_to_abar::AXfrNote, + abar_to_ar::{verify_abar_to_ar_note, AbarToArNote}, + abar_to_bar::{verify_abar_to_bar_note, AbarToBarNote}, + ar_to_abar::{verify_ar_to_abar_note, ArToAbarNote}, + bar_to_abar::{verify_bar_to_abar_note, BarToAbarNote}, + commit, + structs::{ + AnonAssetRecord, AxfrOwnerMemo, Nullifier, OpenAnonAssetRecord, + }, + AXfrAddressFoldingInstance, + }, keys::PublicKey as NoahXfrPublicKey, + parameters::{AddressFormat, VerifierParams}, xfr::{ gen_xfr_body, structs::{ @@ -479,7 +495,7 @@ pub struct XfrAddress { } impl XfrAddress { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "fin_storage"))] pub(crate) fn to_base64(self) -> String { b64enc(&self.key.to_bytes()) } @@ -505,7 +521,7 @@ pub struct IssuerPublicKey { } impl IssuerPublicKey { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "fin_storage"))] pub(crate) fn to_base64(self) -> String { b64enc(&self.key.noah_to_bytes().as_slice()) } @@ -578,7 +594,7 @@ impl SignatureRules { /// Keyset must store XfrPublicKeys in byte form. pub fn check_signature_set(&self, keyset: &HashSet>) -> Result<()> { let mut sum: u64 = 0; - let mut weight_map = HashMap::new(); + let mut weight_map: HashMap, u64> = HashMap::new(); // Convert to map for (key, weight) in self.weights.iter() { weight_map.insert(key.to_bytes(), *weight); @@ -586,7 +602,7 @@ impl SignatureRules { // Calculate weighted sum for key in keyset.iter() { sum = sum - .checked_add(*weight_map.get(&key[..]).unwrap_or(&0)) + .checked_add(*weight_map.get::<[u8]>(&key.as_slice()).unwrap_or(&0)) .c(d!())?; } @@ -795,6 +811,22 @@ impl NumKey for TxoSID { #[allow(missing_docs)] pub type TxoSIDList = Vec; +#[derive( + Clone, + Copy, + Debug, + Default, + Deserialize, + Eq, + Hash, + PartialEq, + Serialize, + Ord, + PartialOrd, +)] +#[allow(missing_docs)] +pub struct ATxoSID(pub u64); + #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct OutputPosition(pub usize); @@ -869,7 +901,7 @@ pub enum UtxoStatus { pub struct Utxo(pub TxOutput); impl Utxo { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "fin_storage"))] #[inline(always)] pub(crate) fn get_nonconfidential_balance(&self) -> u64 { if let XfrAmount::NonConfidential(n) = self.0.record.amount { @@ -1014,10 +1046,10 @@ impl TransferAssetBody { keypair: &XfrKeyPair, input_idx: Option, ) -> IndexedSignature { - let public_key = keypair.get_pk_ref(); + let public_key = keypair.get_pk(); IndexedSignature { - signature: SignatureOf::new(keypair, &(self.clone(), input_idx)), - address: XfrAddress { key: *public_key }, + signature: SignatureOf::new(&keypair, &(self.clone(), input_idx)), + address: XfrAddress { key: public_key }, input_idx, } } @@ -1232,13 +1264,8 @@ impl TransferAsset { #[inline(always)] #[allow(missing_docs)] - pub fn get_owner_memos_ref(&self) -> Vec> { - self.body - .transfer - .owners_memos - .iter() - .map(|mem| mem.as_ref()) - .collect() + pub fn get_owner_memos_ref(&self) -> Vec> { + self.body.transfer.owners_memos.to_vec() } #[inline(always)] @@ -1286,11 +1313,11 @@ impl IssueAsset { #[inline(always)] #[allow(missing_docs)] - pub fn get_owner_memos_ref(&self) -> Vec> { + pub fn get_owner_memos_ref(&self) -> Vec> { self.body .records .iter() - .map(|(_, memo)| memo.as_ref()) + .map(|(_, memo)| memo.clone()) .collect() } @@ -1346,7 +1373,7 @@ impl UpdateMemo { update_memo_body: UpdateMemoBody, signing_key: &XfrKeyPair, ) -> UpdateMemo { - let signature = SignatureOf::new(signing_key, &update_memo_body); + let signature = SignatureOf::new(&signing_key, &update_memo_body); UpdateMemo { body: update_memo_body, pubkey: *signing_key.get_pk_ref(), @@ -1355,6 +1382,281 @@ impl UpdateMemo { } } +/// A note which enumerates the transparent and confidential BAR to +/// Anon Asset record conversion. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum BarAnonConvNote { + /// A transfer note with ZKP for a confidential asset record + BarNote(Box), + /// A transfer note with ZKP for a non-confidential asset record + ArNote(Box), +} + +/// Operation for converting a Blind Asset Record to an Anonymous record +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct BarToAbarOps { + /// the note which contains the inp/op and ZKP + pub note: BarAnonConvNote, + /// The TxoSID of the the input BAR + pub txo_sid: TxoSID, + nonce: NoReplayToken, +} + +impl BarToAbarOps { + /// Generates a new BarToAbarOps object + /// # Arguments + /// * bar_to_abar_note - The BarToAbarNote of the conversion + /// * txo_sid - the TxoSID of the converting BAR + /// * nonce + pub fn new( + note: BarAnonConvNote, + txo_sid: TxoSID, + nonce: NoReplayToken, + ) -> Result { + Ok(BarToAbarOps { + note, + txo_sid, + nonce, + }) + } + + /// verifies the signatures and proof of the note + pub fn verify(&self) -> Result<()> { + match &self.note { + BarAnonConvNote::BarNote(note) => { + // fetch the verifier Node Params for PlonkProof + let node_params = VerifierParams::get_bar_to_abar().c(d!())?; + // verify the Plonk proof and signature + verify_bar_to_abar_note(&node_params, ¬e, ¬e.body.input.public_key) + .c(d!()) + } + BarAnonConvNote::ArNote(note) => { + // fetch the verifier Node Params for PlonkProof + let node_params = VerifierParams::get_ar_to_abar().c(d!())?; + // verify the Plonk proof and signature + verify_ar_to_abar_note(&node_params, note).c(d!()) + } + } + } + + /// provides a copy of the input record in the note + pub fn input_record(&self) -> BlindAssetRecord { + match &self.note { + BarAnonConvNote::BarNote(n) => BlindAssetRecord::from_noah(&n.body.input), + BarAnonConvNote::ArNote(n) => BlindAssetRecord::from_noah(&n.body.input), + } + } + + /// provides a copy of the output record of the note. + pub fn output_record(&self) -> AnonAssetRecord { + match &self.note { + BarAnonConvNote::BarNote(n) => n.body.output.clone(), + BarAnonConvNote::ArNote(n) => n.body.output.clone(), + } + } + + /// provides a copy of the AxfrOwnerMemo in the note + pub fn axfr_memo(&self) -> AxfrOwnerMemo { + match &self.note { + BarAnonConvNote::BarNote(n) => n.body.memo.clone(), + BarAnonConvNote::ArNote(n) => n.body.memo.clone(), + } + } + + #[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 + } +} + +/// AbarConvNote +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum AbarConvNote { + /// Conversion to a amount or type confidential BAR + AbarToBar(Box), + /// Conversion to a transparent BAR + AbarToAr(Box), +} + +impl AbarConvNote { + /// Verifies the ZKP based on the type of conversion + pub fn verify + Default>( + &self, + merkle_root: BN254Scalar, + hasher: D, + ) -> ruc::Result<()> { + match self { + AbarConvNote::AbarToBar(note) => { + let af = match note.folding_instance { + AXfrAddressFoldingInstance::Secp256k1(_) => AddressFormat::SECP256K1, + AXfrAddressFoldingInstance::Ed25519(_) => AddressFormat::ED25519, + }; + let abar_to_bar_verifier_params = + VerifierParams::get_abar_to_bar(af).c(d!())?; + // An axfr_abar_conv requires versioned merkle root hash for verification. + // verify zk proof with merkle root + verify_abar_to_bar_note( + &abar_to_bar_verifier_params, + ¬e, + &merkle_root, + hasher, + ) + .c(d!("Abar to Bar conversion proof verification failed")) + } + AbarConvNote::AbarToAr(note) => { + let af = match note.folding_instance { + AXfrAddressFoldingInstance::Secp256k1(_) => AddressFormat::SECP256K1, + AXfrAddressFoldingInstance::Ed25519(_) => AddressFormat::ED25519, + }; + let abar_to_ar_verifier_params = + VerifierParams::get_abar_to_ar(af).c(d!())?; + // An axfr_abar_conv requires versioned merkle root hash for verification. + // verify zk proof with merkle root + verify_abar_to_ar_note( + &abar_to_ar_verifier_params, + ¬e, + &merkle_root, + hasher, + ) + .c(d!("Abar to AR conversion proof verification failed")) + } + } + } + + /// input nullifier in the note body + pub fn get_input(&self) -> Nullifier { + match self { + AbarConvNote::AbarToBar(note) => note.body.input, + AbarConvNote::AbarToAr(note) => note.body.input, + } + } + + /// merkle root version of the proof + pub fn get_merkle_root_version(&self) -> u64 { + match self { + AbarConvNote::AbarToBar(note) => note.body.merkle_root_version, + AbarConvNote::AbarToAr(note) => note.body.merkle_root_version, + } + } + + /// public key of the note body + pub fn get_public_key(&self) -> XfrPublicKey { + match self { + AbarConvNote::AbarToBar(note) => { + XfrPublicKey::from_noah(¬e.body.output.public_key) + } + AbarConvNote::AbarToAr(note) => { + XfrPublicKey::from_noah(¬e.body.output.public_key) + } + } + } + + /// output BAR of the note body + pub fn get_output(&self) -> BlindAssetRecord { + match self { + AbarConvNote::AbarToBar(note) => { + BlindAssetRecord::from_noah(¬e.body.output) + } + AbarConvNote::AbarToAr(note) => { + BlindAssetRecord::from_noah(¬e.body.output) + } + } + } + + /// gets address of owner memo in the note body + pub fn get_owner_memos_ref(&self) -> Vec> { + match self { + AbarConvNote::AbarToBar(note) => { + vec![note + .body + .memo + .as_ref() + .map(|om| OwnerMemo::from_noah(om).unwrap())] + } + AbarConvNote::AbarToAr(note) => { + vec![note + .body + .memo + .as_ref() + .map(|om| OwnerMemo::from_noah(om).unwrap())] + } + } + } + + /// get serialized bytes for signature and prove (only ABAR body). + pub fn digest(&self) -> Vec { + match self { + AbarConvNote::AbarToBar(note) => { + Serialized::new(¬e.body).as_ref().to_vec() + } + AbarConvNote::AbarToAr(note) => { + Serialized::new(¬e.body).as_ref().to_vec() + } + } + } +} + +/// 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: AbarConvNote, + nonce: NoReplayToken, +} + +impl AbarToBarOps { + /// Generates a new BarToAbarOps object + pub fn new(note: AbarConvNote, nonce: NoReplayToken) -> Result { + Ok(AbarToBarOps { note, 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 { + /// The note which holds the signatures, the ZKF and memo + pub note: AXfrNote, + nonce: NoReplayToken, +} +impl AnonTransferOps { + /// Generates the anon transfer note + pub fn new(note: AXfrNote, nonce: NoReplayToken) -> Result { + Ok(AnonTransferOps { note, nonce }) + } + + /// Sets the nonce for the operation + #[inline(always)] + #[allow(dead_code)] + fn set_nonce(&mut self, nonce: NoReplayToken) { + self.nonce = nonce; + } + + /// Fetches the nonce of the operation + #[inline(always)] + fn get_nonce(&self) -> NoReplayToken { + self.nonce + } +} + /// Operation list supported in findora network #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum Operation { @@ -1384,35 +1686,57 @@ pub enum Operation { MintFra(MintFraOps), /// Convert UTXOs to EVM Account balance ConvertAccount(ConvertAccount), + /// Anonymous conversion operation + BarToAbar(Box), + /// De-anonymize ABAR operation + AbarToBar(Box), + /// Anonymous transfer operation + TransferAnonAsset(Box), ///replace staker. ReplaceStaker(ReplaceStakerOps), } +impl Operation { + /// get serialized bytes for signature and prove. + pub fn digest(&self) -> Vec { + match self { + Operation::UpdateStaker(i) => Serialized::new(i).as_ref().to_vec(), + Operation::Delegation(i) => Serialized::new(i).as_ref().to_vec(), + Operation::UnDelegation(i) => Serialized::new(i).as_ref().to_vec(), + Operation::Claim(i) => Serialized::new(i).as_ref().to_vec(), + Operation::FraDistribution(i) => Serialized::new(i).as_ref().to_vec(), + Operation::UpdateValidator(i) => Serialized::new(i).as_ref().to_vec(), + Operation::Governance(i) => Serialized::new(i).as_ref().to_vec(), + Operation::UpdateMemo(i) => Serialized::new(i).as_ref().to_vec(), + Operation::ConvertAccount(i) => Serialized::new(i).as_ref().to_vec(), + Operation::BarToAbar(i) => Serialized::new(i).as_ref().to_vec(), + Operation::ReplaceStaker(i) => Serialized::new(i).as_ref().to_vec(), + Operation::TransferAsset(i) => Serialized::new(i).as_ref().to_vec(), + Operation::IssueAsset(i) => Serialized::new(i).as_ref().to_vec(), + Operation::DefineAsset(i) => Serialized::new(i).as_ref().to_vec(), + Operation::MintFra(i) => Serialized::new(i).as_ref().to_vec(), + Operation::AbarToBar(i) => i.note.digest(), + Operation::TransferAnonAsset(i) => { + Serialized::new(&i.note.body).as_ref().to_vec() + } + } + } +} + fn set_no_replay_token(op: &mut Operation, no_replay_token: NoReplayToken) { match op { - Operation::UpdateStaker(i) => { - i.set_nonce(no_replay_token); - } - Operation::Delegation(i) => { - i.set_nonce(no_replay_token); - } - Operation::UnDelegation(i) => { - i.set_nonce(no_replay_token); - } - Operation::Claim(i) => { - i.set_nonce(no_replay_token); - } - Operation::FraDistribution(i) => { - i.set_nonce(no_replay_token); - } - Operation::UpdateValidator(i) => { - i.set_nonce(no_replay_token); - } - Operation::Governance(i) => { - i.set_nonce(no_replay_token); - } + Operation::UpdateStaker(i) => i.set_nonce(no_replay_token), + Operation::Delegation(i) => i.set_nonce(no_replay_token), + Operation::UnDelegation(i) => i.set_nonce(no_replay_token), + Operation::Claim(i) => i.set_nonce(no_replay_token), + Operation::FraDistribution(i) => i.set_nonce(no_replay_token), + Operation::UpdateValidator(i) => i.set_nonce(no_replay_token), + Operation::Governance(i) => i.set_nonce(no_replay_token), 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), _ => {} } } @@ -1440,6 +1764,19 @@ impl TransactionBody { result.no_replay_token = no_replay_token; result } + + /// get serialized bytes for signature and prove. + pub fn digest(&self) -> Vec { + let mut bytes = vec![]; + bytes.extend_from_slice(Serialized::new(&self.no_replay_token).as_ref()); + bytes.extend_from_slice(Serialized::new(&self.credentials).as_ref()); + bytes.extend_from_slice(Serialized::new(&self.policy_options).as_ref()); + bytes.extend_from_slice(Serialized::new(&self.memos).as_ref()); + for o in &self.operations { + bytes.extend_from_slice(&o.digest()); + } + bytes + } } #[allow(missing_docs)] @@ -1460,6 +1797,8 @@ pub struct FinalizedTransaction { pub txn: Transaction, pub tx_id: TxnSID, pub txo_ids: Vec, + #[serde(default)] + pub atxo_ids: Vec, pub merkle_id: u64, } @@ -1743,7 +2082,15 @@ lazy_static! { } /// see [**mainnet-v0.1 defination**](https://www.notion.so/findora/Transaction-Fees-Analysis-d657247b70f44a699d50e1b01b8a2287) -pub const TX_FEE_MIN: u64 = 1_0000; +pub const TX_FEE_MIN: u64 = 10_000; // 0.01 FRA +/// Double the regular fee +pub const BAR_TO_ABAR_TX_FEE_MIN: u64 = 20_000; // 0.02 FRA (2*TX_FEE_MIN) + +/// Calculate the FEE with inputs and outputs number. +pub const FEE_CALCULATING_FUNC: fn(u32, u32) -> u32 = |x: u32, y: u32| { + let extra_outputs = y.saturating_sub(x); + 50_0000 + 10_0000 * x + 20_0000 * y + (10_000 * extra_outputs) +}; impl Transaction { #[inline(always)] @@ -1761,6 +2108,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: @@ -1775,6 +2123,15 @@ impl Transaction { // // But it seems enough when we combine it with limiting // the payload size of submission-server's http-requests. + + let mut min_fee = TX_FEE_MIN; + // Charge double the min fee if the transaction is BarToAbar + for op in self.body.operations.iter() { + if let Operation::BarToAbar(_a) = op { + min_fee = BAR_TO_ABAR_TX_FEE_MIN; + } + } + self.is_coinbase_tx() || self.body.operations.iter().any(|ops| { if let Operation::TransferAsset(ref x) = ops { @@ -1785,12 +2142,13 @@ impl Transaction { == o.record.public_key { if let XfrAmount::NonConfidential(am) = o.record.amount { - if am > (TX_FEE_MIN - 1) { + if am > (min_fee - 1) { return true; } } } } + tracing::error!("Txn failed in check_fee {:?}", self); false }); } else if let Operation::DefineAsset(ref x) = ops { @@ -1801,9 +2159,16 @@ impl Transaction { if x.body.code.val == ASSET_TYPE_FRA { return true; } + } else if let Operation::TransferAnonAsset(_) = ops { + return true; + } else if let Operation::BarToAbar(_) = ops { + return true; + } else if let Operation::AbarToBar(_) = ops { + return true; } else if matches!(ops, Operation::UpdateValidator(_)) { return true; } + tracing::error!("Txn failed in check_fee {:?}", self); false }) } @@ -1900,7 +2265,7 @@ impl Transaction { #[inline(always)] #[allow(missing_docs)] - pub fn get_owner_memos_ref(&self) -> Vec> { + pub fn get_owner_memos_ref(&self) -> Vec> { let mut memos = Vec::new(); for op in self.body.operations.iter() { match op { @@ -1913,6 +2278,9 @@ impl Transaction { Operation::IssueAsset(issue_asset) => { memos.append(&mut issue_asset.get_owner_memos_ref()); } + Operation::AbarToBar(abar_to_bar) => { + memos.append(&mut abar_to_bar.note.get_owner_memos_ref()); + } _ => {} } } @@ -1949,7 +2317,7 @@ impl Transaction { pub fn check_has_signature(&self, public_key: &XfrPublicKey) -> Result<()> { let serialized = Serialized::new(&self.body); for sig in self.signatures.iter() { - match sig.0.verify(public_key, &serialized) { + match sig.0.verify(&public_key, &serialized) { Err(_) => {} Ok(_) => { return Ok(()); @@ -1975,6 +2343,7 @@ impl Transaction { /// NOTE: This method is used to verify the signature in the transaction, /// when the user constructs the transaction not only needs to sign each `operation`, /// but also needs to sign the whole transaction, otherwise it will not be passed here + #[allow(missing_docs)] #[inline(always)] pub fn check_tx(&self) -> Result<()> { let select_check = |tx: &Transaction, pk: &XfrPublicKey| -> Result<()> { @@ -2027,6 +2396,9 @@ impl Transaction { } } } + Operation::BarToAbar(_) => {} + Operation::AbarToBar(_) => {} + Operation::TransferAnonAsset(_) => {} } } @@ -2069,6 +2441,23 @@ impl StateCommitmentData { } } +/// Commitment data for Anon merkle trees +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct AnonStateCommitmentData { + /// Root hash of the latest committed version of abar merkle tree + pub abar_root_hash: BN254Scalar, + /// Root hash of the nullifier set merkle tree + pub nullifier_root_hash: BitDigest, +} + +impl AnonStateCommitmentData { + #[inline(always)] + #[allow(missing_docs)] + pub fn compute_commitment(&self) -> HashOf> { + HashOf::new(&Some(self).cloned()) + } +} + /// Used in `Staking` logic to create consensus-tmp XfrPublicKey #[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq, Default)] pub struct ConsensusRng(u32); @@ -2094,3 +2483,22 @@ impl RngCore for ConsensusRng { pub fn gen_random_keypair() -> XfrKeyPair { XfrKeyPair::generate(&mut ChaChaRng::from_entropy()) } + +#[inline(always)] +#[allow(missing_docs)] +pub fn get_abar_commitment(oabar: OpenAnonAssetRecord) -> BN254Scalar { + let c = commit( + oabar.pub_key_ref(), + oabar.get_blind(), + oabar.get_amount(), + oabar.get_asset_type().as_scalar(), + ) + .unwrap(); + c.0 +} + +#[derive(Serialize, Deserialize)] +#[allow(missing_docs)] +pub struct ABARData { + pub commitment: String, +} diff --git a/src/ledger/src/data_model/test.rs b/src/ledger/src/data_model/test.rs old mode 100644 new mode 100755 index 6476bba6c..01e157ea2 --- a/src/ledger/src/data_model/test.rs +++ b/src/ledger/src/data_model/test.rs @@ -10,7 +10,7 @@ use { ristretto, xfr::structs::{AssetTypeAndAmountProof, XfrProofs}, }, - XfrBody + XfrBody, }, }; @@ -27,7 +27,6 @@ macro_rules! msg_eq { }; } - const UTF8_ASSET_TYPES_WORK: bool = false; // This test may fail as it is a statistical test that sometimes fails (but very rarely) diff --git a/src/ledger/src/lib.rs b/src/ledger/src/lib.rs index 8d15bac66..10b83a333 100644 --- a/src/ledger/src/lib.rs +++ b/src/ledger/src/lib.rs @@ -10,7 +10,7 @@ pub mod data_model; pub mod converter; pub mod staking; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), feature = "fin_storage"))] pub mod store; use {ruc::*, std::sync::atomic::AtomicI64}; diff --git a/src/ledger/src/staking/mod.rs b/src/ledger/src/staking/mod.rs index 60633a796..7a42430b2 100644 --- a/src/ledger/src/staking/mod.rs +++ b/src/ledger/src/staking/mod.rs @@ -12,7 +12,7 @@ #![deny(missing_docs)] #![allow(clippy::upper_case_acronyms)] -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), feature = "fin_storage"))] use {num_bigint::BigUint, std::convert::TryFrom}; pub mod cosig; @@ -51,8 +51,7 @@ use { Arc, }, }, - zei::noah_api::keys::PublicKey as NoahXfrPublicKey, - zei::{XfrKeyPair, XfrPublicKey}, + zei::{noah_api::keys::PublicKey as NoahXfrPublicKey, XfrKeyPair, XfrPublicKey}, }; // height, reward rate @@ -1605,7 +1604,7 @@ impl Staking { &self.coinbase.distribution_plan } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "fin_storage"))] /// set_proposer_rewards sets the rewards for the block proposer /// All rewards are allocated to the proposer only pub(crate) fn set_proposer_rewards( @@ -1647,7 +1646,7 @@ impl Staking { .map(|_| ()) } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "fin_storage"))] fn get_proposer_rewards_rate(vote_percent: [u64; 2]) -> Result<[u128; 2]> { let p = [vote_percent[0] as u128, vote_percent[1] as u128]; // p[0] = Validator power which voted for this block @@ -2100,7 +2099,7 @@ impl Delegation { } #[inline(always)] - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "fin_storage"))] pub(crate) fn validator_entry_exists(&self, validator: &XfrPublicKey) -> bool { self.delegations.contains_key(validator) } @@ -2120,7 +2119,7 @@ impl Delegation { // > **NOTE:** // > use 'AssignAdd' instead of 'Assign' // > to keep compatible with the logic of governance penalty. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "fin_storage"))] #[allow(clippy::too_many_arguments)] pub(crate) fn set_delegation_rewards( &mut self, @@ -2209,7 +2208,7 @@ impl Delegation { // Calculate the amount(in FRA units) that // should be paid to the owner of this delegation. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), feature = "fin_storage"))] fn calculate_delegation_rewards( return_rate: [u128; 2], amount: Amount, diff --git a/src/ledger/src/staking/ops/delegation.rs b/src/ledger/src/staking/ops/delegation.rs index c0af11244..635706da2 100644 --- a/src/ledger/src/staking/ops/delegation.rs +++ b/src/ledger/src/staking/ops/delegation.rs @@ -23,7 +23,7 @@ use { tendermint::{signature::Ed25519Signature, PrivateKey, PublicKey, Signature}, zei::{ noah_api::xfr::structs::{XfrAmount, XfrAssetType}, - {XfrKeyPair, XfrPublicKey, XfrSignature}, + XfrKeyPair, XfrPublicKey, XfrSignature, }, }; diff --git a/src/ledger/src/staking/ops/mint_fra.rs b/src/ledger/src/staking/ops/mint_fra.rs index 33c86e2a3..46b07fb0c 100644 --- a/src/ledger/src/staking/ops/mint_fra.rs +++ b/src/ledger/src/staking/ops/mint_fra.rs @@ -48,7 +48,7 @@ impl MintFraOps { #[inline(always)] #[allow(missing_docs)] - pub fn get_owner_memos_ref(&self) -> Vec> { + pub fn get_owner_memos_ref(&self) -> Vec> { vec![None; self.entries.len()] } } diff --git a/src/ledger/src/store/api_cache.rs b/src/ledger/src/store/api_cache.rs index 2479cbb59..ad7077bf5 100644 --- a/src/ledger/src/store/api_cache.rs +++ b/src/ledger/src/store/api_cache.rs @@ -4,8 +4,9 @@ use { crate::{ data_model::{ - AssetTypeCode, AssetTypePrefix, DefineAsset, IssueAsset, IssuerPublicKey, - Operation, Transaction, TxOutput, TxnIDHash, TxnSID, TxoSID, XfrAddress, + ATxoSID, AssetTypeCode, AssetTypePrefix, DefineAsset, IssueAsset, + IssuerPublicKey, Operation, StateCommitmentData, Transaction, TxOutput, + TxnIDHash, TxnSID, TxoSID, XfrAddress, }, staking::{ ops::mint_fra::MintEntry, Amount, BlockHeight, DelegationRwdDetail, @@ -15,11 +16,11 @@ use { }, config::abci::global_cfg::CFG, fbnc::{new_mapx, new_mapxnk, Mapx, Mapxnk}, - globutils::wallet, + globutils::{wallet, HashOf}, ruc::*, serde::{Deserialize, Serialize}, std::collections::HashSet, - zei::{OwnerMemo, XfrPublicKey}, + zei::{noah_api::anon_xfr::structs::AxfrOwnerMemo, OwnerMemo, XfrPublicKey}, }; type Issuances = Vec<(TxOutput, Option)>; @@ -44,14 +45,20 @@ pub struct ApiCache { pub token_code_issuances: Mapx, /// used in confidential tx pub owner_memos: Mapxnk, + /// used in anonymous tx + pub abar_memos: Mapx, /// ownship of txo pub utxos_to_map_index: Mapxnk, /// txo(spent, unspent) to authenticated txn (sid, hash) pub txo_to_txnid: Mapxnk, + /// atxo to authenticated txn (sid, hash) + pub atxo_to_txnid: Mapx, /// txn sid to txn hash pub txn_sid_to_hash: Mapxnk, /// txn hash to txn sid pub txn_hash_to_sid: Mapx, + /// max (latest) atxo sid at block height + pub height_to_max_atxo: Mapxnk>, /// global rate history pub staking_global_rate_hist: Mapxnk, /// - self-delegation amount history @@ -66,6 +73,9 @@ pub struct ApiCache { Mapx>, /// there are no transactions lost before last_sid pub last_sid: Mapx, + /// State commitment history. + /// The BitDigest at index i is the state commitment of the ledger at block height i + 1. + pub state_commitment_version: Option>>, } impl ApiCache { @@ -88,15 +98,20 @@ impl ApiCache { "api_cache/{prefix}token_code_issuances", )), owner_memos: new_mapxnk!(format!("api_cache/{prefix}owner_memos",)), + abar_memos: new_mapx!(format!("api_cache/{prefix}abar_memos",)), utxos_to_map_index: new_mapxnk!(format!( "api_cache/{prefix}utxos_to_map_index", )), txo_to_txnid: new_mapxnk!(format!("api_cache/{prefix}txo_to_txnid",)), + atxo_to_txnid: new_mapx!(format!("api_cache/{prefix}atxo_to_txnid",)), txn_sid_to_hash: new_mapxnk!(format!("api_cache/{prefix}txn_sid_to_hash",)), txn_hash_to_sid: new_mapx!(format!("api_cache/{prefix}txn_hash_to_sid",)), staking_global_rate_hist: new_mapxnk!(format!( "api_cache/{prefix}staking_global_rate_hist", )), + height_to_max_atxo: new_mapxnk!(format!( + "api_cache/{prefix}height_to_max_atxo", + )), staking_self_delegation_hist: new_mapx!(format!( "api_cache/{prefix}staking_self_delegation_hist", )), @@ -107,6 +122,7 @@ impl ApiCache { "api_cache/{prefix}staking_delegation_rwd_hist", )), last_sid: new_mapx!(format!("api_cache/{prefix}last_sid",)), + state_commitment_version: None, } } @@ -259,7 +275,19 @@ where Operation::Governance(i) => staking_gen!(i), Operation::FraDistribution(i) => staking_gen!(i), Operation::MintFra(i) => staking_gen!(i), - + Operation::BarToAbar(i) => { + related_addresses.insert(XfrAddress { + key: i.input_record().public_key, + }); + } + Operation::AbarToBar(i) => { + related_addresses.insert(XfrAddress { + key: i.note.get_public_key(), + }); + } + Operation::TransferAnonAsset(_) => { + // Anon + } Operation::ConvertAccount(i) => { related_addresses.insert(XfrAddress { key: i.get_related_address(), @@ -468,38 +496,45 @@ pub fn update_api_cache(ledger: &mut LedgerState) -> Result<()> { check_lost_data(ledger)?; - ledger.api_cache.as_mut().unwrap().cache_hist_data(); + let mut api_cache = ledger.api_cache.take().unwrap(); + + api_cache.cache_hist_data(); let block = if let Some(b) = ledger.blocks.last() { b } else { + ledger.api_cache = Some(api_cache); return Ok(()); }; - let prefix = ledger.api_cache.as_mut().unwrap().prefix.clone(); + let prefix = api_cache.prefix.clone(); + + // Update state commitment versions + api_cache.state_commitment_version = ledger.status.state_commitment_versions.last(); // Update ownership status - for (txn_sid, txo_sids) in block.txns.iter().map(|v| (v.tx_id, v.txo_ids.as_slice())) + for (txn_sid, txo_sids, atxo_sids) in block + .txns + .iter() + .map(|v| (v.tx_id, v.txo_ids.as_slice(), v.atxo_ids.as_slice())) { let curr_txn = ledger.get_transaction_light(txn_sid).c(d!())?.txn; // get the transaction, ownership addresses, and memos associated with each transaction let (addresses, owner_memos) = { - let addresses: Vec = txo_sids - .iter() - .map(|sid| XfrAddress { - key: ((ledger - .get_utxo_light(*sid) - .or_else(|| ledger.get_spent_utxo_light(*sid)) - .unwrap() - .utxo) - .0) - .record - .public_key, - }) - .collect(); + let mut addresses: Vec = vec![]; + for sid in txo_sids.iter() { + let key = ledger + .get_utxo_light(*sid) + .or_else(|| ledger.get_spent_utxo_light(*sid)) + .c(d!())? + .utxo + .0 + .record + .public_key; + addresses.push(XfrAddress { key }); + } let owner_memos = curr_txn.get_owner_memos_ref(); - (addresses, owner_memos) }; @@ -509,10 +544,7 @@ pub fn update_api_cache(ledger: &mut LedgerState) -> Result<()> { let key = XfrAddress { key: i.get_claim_publickey(), }; - ledger - .api_cache - .as_mut() - .unwrap() + api_cache .claim_hist_txns .entry(key) .or_insert_with(|| { @@ -529,13 +561,8 @@ pub fn update_api_cache(ledger: &mut LedgerState) -> Result<()> { key: me.utxo.record.public_key, }; #[allow(unused_mut)] - let mut hist = ledger - .api_cache - .as_mut() - .unwrap() - .coinbase_oper_hist - .entry(key) - .or_insert_with(|| { + let mut hist = + api_cache.coinbase_oper_hist.entry(key).or_insert_with(|| { new_mapxnk!(format!( "api_cache/{}coinbase_oper_hist/{}", prefix, @@ -552,10 +579,7 @@ pub fn update_api_cache(ledger: &mut LedgerState) -> Result<()> { // Apply classify_op for each operation in curr_txn let related_addresses = get_related_addresses(&curr_txn, classify_op); for address in &related_addresses { - ledger - .api_cache - .as_mut() - .unwrap() + api_cache .related_transactions .entry(*address) .or_insert_with(|| { @@ -571,10 +595,7 @@ pub fn update_api_cache(ledger: &mut LedgerState) -> Result<()> { // Update transferred nonconfidential assets let transferred_assets = get_transferred_nonconfidential_assets(&curr_txn); for asset in &transferred_assets { - ledger - .api_cache - .as_mut() - .unwrap() + api_cache .related_transfers .entry(*asset) .or_insert_with(|| { @@ -591,17 +612,13 @@ pub fn update_api_cache(ledger: &mut LedgerState) -> Result<()> { for op in &curr_txn.body.operations { match op { Operation::DefineAsset(define_asset) => { - ledger.api_cache.as_mut().unwrap().add_created_asset( + api_cache.add_created_asset( &define_asset, ledger.status.td_commit_height, ); } Operation::IssueAsset(issue_asset) => { - ledger - .api_cache - .as_mut() - .unwrap() - .cache_issuance(&issue_asset); + api_cache.cache_issuance(&issue_asset); } _ => {} }; @@ -612,41 +629,41 @@ pub fn update_api_cache(ledger: &mut LedgerState) -> Result<()> { .iter() .zip(addresses.iter().zip(owner_memos.iter())) { - ledger - .api_cache - .as_mut() - .unwrap() - .utxos_to_map_index - .insert(*txo_sid, *address); + api_cache.utxos_to_map_index.insert(*txo_sid, *address); let hash = curr_txn.hash_tm().hex().to_uppercase(); - ledger - .api_cache - .as_mut() - .unwrap() + api_cache .txo_to_txnid .insert(*txo_sid, (txn_sid, hash.clone())); - ledger - .api_cache - .as_mut() - .unwrap() - .txn_sid_to_hash - .insert(txn_sid, hash.clone()); - ledger - .api_cache - .as_mut() - .unwrap() - .txn_hash_to_sid - .insert(hash.clone(), txn_sid); + api_cache.txn_sid_to_hash.insert(txn_sid, hash.clone()); + api_cache.txn_hash_to_sid.insert(hash.clone(), txn_sid); if let Some(owner_memo) = owner_memo { - ledger - .api_cache - .as_mut() - .unwrap() + api_cache .owner_memos .insert(*txo_sid, (*owner_memo).clone()); } } + + let abar_memos = curr_txn.body.operations.iter().flat_map(|o| match o { + Operation::BarToAbar(b) => { + vec![b.axfr_memo()] + } + Operation::TransferAnonAsset(b) => b.note.body.owner_memos.clone(), + _ => vec![], + }); + + for (a, id) in abar_memos.zip(atxo_sids) { + api_cache.abar_memos.insert(*id, a); + let hash = curr_txn.hash_tm().hex().to_uppercase(); + api_cache.atxo_to_txnid.insert(*id, (txn_sid, hash.clone())); + } } + // Update block height to max atxo mapping + let max_atxo = api_cache.abar_memos.len().checked_sub(1); + let block_height = ledger.status.td_commit_height; + api_cache.height_to_max_atxo.insert(block_height, max_atxo); + + ledger.api_cache = Some(api_cache); + Ok(()) } diff --git a/src/ledger/src/store/helpers.rs b/src/ledger/src/store/helpers.rs index 0ba1d196f..57aa65b9f 100644 --- a/src/ledger/src/store/helpers.rs +++ b/src/ledger/src/store/helpers.rs @@ -4,13 +4,13 @@ use { super::{ - IssuerKeyPair, IssuerPublicKey, LedgerState, TracingPolicies, TracingPolicy, - TransferType, XfrNotePolicies, + IssuerPublicKey, LedgerState, TracingPolicies, TracingPolicy, XfrNotePolicies, }, crate::data_model::{ Asset, AssetRules, AssetTypeCode, ConfidentialMemo, DefineAsset, - DefineAssetBody, IssueAsset, IssueAssetBody, Memo, Operation, Transaction, - TransferAsset, TransferAssetBody, TxOutput, TxnEffect, TxnSID, TxoRef, TxoSID, + DefineAssetBody, IssueAsset, IssueAssetBody, IssuerKeyPair, Memo, Operation, + Transaction, TransferAsset, TransferAssetBody, TransferType, TxOutput, + TxnEffect, TxnSID, TxoRef, TxoSID, }, globutils::SignatureOf, rand_core::{CryptoRng, RngCore}, diff --git a/src/ledger/src/store/mod.rs b/src/ledger/src/store/mod.rs index c9ccf6660..20420d741 100644 --- a/src/ledger/src/store/mod.rs +++ b/src/ledger/src/store/mod.rs @@ -12,12 +12,12 @@ pub use fbnc; use { crate::{ data_model::{ - AssetType, AssetTypeCode, AssetTypePrefix, AuthenticatedBlock, - AuthenticatedTransaction, AuthenticatedUtxo, AuthenticatedUtxoStatus, - BlockEffect, BlockSID, FinalizedBlock, FinalizedTransaction, IssuerKeyPair, - IssuerPublicKey, OutputPosition, StateCommitmentData, Transaction, - TransferType, TxnEffect, TxnSID, TxnTempSID, TxoSID, UnAuthenticatedUtxo, - Utxo, UtxoStatus, BLACK_HOLE_PUBKEY, + ATxoSID, AnonStateCommitmentData, AssetType, AssetTypeCode, AssetTypePrefix, + AuthenticatedBlock, AuthenticatedTransaction, AuthenticatedUtxo, + AuthenticatedUtxoStatus, BlockEffect, BlockSID, FinalizedBlock, + FinalizedTransaction, IssuerPublicKey, Operation, OutputPosition, + StateCommitmentData, Transaction, TxnEffect, TxnSID, TxnTempSID, TxoSID, + UnAuthenticatedUtxo, Utxo, UtxoStatus, BLACK_HOLE_PUBKEY, }, staking::{ Amount, Power, Staking, TendermintAddrRef, FF_PK_EXTRA_120_0000, FF_PK_LIST, @@ -29,7 +29,10 @@ use { bitmap::{BitMap, SparseMap}, config::abci::global_cfg::CFG, cryptohash::sha256::Digest as BitDigest, + digest::Digest, fbnc::{new_mapx, new_mapxnk, new_vecx, Mapx, Mapxnk, Vecx}, + fin_db::RocksDB, + globutils::wallet, globutils::{HashOf, ProofOf}, merkle_tree::AppendOnlyMerkle, parking_lot::RwLock, @@ -37,7 +40,9 @@ use { rand_core::SeedableRng, ruc::*, serde::{Deserialize, Serialize}, + sha2::Sha512, sliding_set::SlidingSet, + sparse_merkle_tree::{Key, SmtMap256}, std::{ collections::{BTreeMap, HashMap, HashSet}, env, @@ -47,16 +52,39 @@ use { ops::{Deref, DerefMut}, sync::Arc, }, + storage::{ + state::{ChainState, State}, + store::{ImmutablePrefixedStore, PrefixedStore}, + }, zei::{ - noah_api::xfr::{ - structs::{TracingPolicies, TracingPolicy}, - XfrNotePolicies, + noah_accumulators::merkle_tree::{ + ImmutablePersistentMerkleTree, PersistentMerkleTree, Proof, TreePath, + }, + noah_algebra::{bn254::BN254Scalar, prelude::*}, + noah_api::{ + anon_xfr::{ + abar_to_abar::verify_anon_xfr_note, + structs::{ + AnonAssetRecord, AxfrOwnerMemo, Commitment, MTLeafInfo, MTNode, + MTPath, Nullifier, + }, + AXfrAddressFoldingInstance, TREE_DEPTH as MERKLE_TREE_DEPTH, + }, + parameters::{AddressFormat, VerifierParams}, + xfr::{ + structs::{TracingPolicies, TracingPolicy}, + XfrNotePolicies, + }, }, + noah_crypto::anemoi_jive::{AnemoiJive, AnemoiJive254}, OwnerMemo, XfrPublicKey, }, }; const TRANSACTION_WINDOW_WIDTH: u64 = 128; +const VERSION_WINDOW: u64 = 100; +const GENESIS_ANON_HASH: &str = + "2501917d72f915a3afb91ae561a0e4230d5d4edbb9b62fb7e2ea41f18c3038b5"; type TmpSidMap = HashMap)>; @@ -85,6 +113,10 @@ pub struct LedgerState { txn_merkle: Arc>, // Bitmap tracing all the live TXOs utxo_map: Arc>, + // Merkle Tree with all the ABARs created till now + abar_state: Arc>>, + // Sparse Merkle Tree to hold nullifier Set + nullifier_set: Arc>>, } impl LedgerState { @@ -119,7 +151,7 @@ impl LedgerState { ) -> Result { let tx = txe.txn.clone(); self.status - .check_txn_effects(&txe) + .check_txn_effects(&txe, &self.abar_state) .c(d!()) .and_then(|_| block.add_txn_effect(txe).c(d!())) .map(|tmpid| { @@ -184,7 +216,12 @@ impl LedgerState { Ok(()) } - fn update_state(&mut self, mut block: BlockEffect, tsm: &TmpSidMap) -> Result<()> { + fn update_state( + &mut self, + mut block: BlockEffect, + tsm: &TmpSidMap, + next_txn_sid: usize, + ) -> Result<()> { let mut tx_block = Vec::new(); let height = block.staking_simulator.cur_height(); @@ -213,6 +250,7 @@ impl LedgerState { txn: txn.clone(), tx_id: txn_sid, txo_ids: txo_sids.clone(), + atxo_ids: vec![], merkle_id, }); @@ -224,10 +262,21 @@ impl LedgerState { } drop(txn_merkle); + tx_block = self + .update_anon_stores( + block.new_nullifiers.clone(), + block.output_abars.clone(), + next_txn_sid, + tx_block, + ) + .c(d!())?; + // Checkpoint let block_merkle_id = self.checkpoint(&block).c(d!())?; block.temp_sids.clear(); block.txns.clear(); + block.output_abars.clear(); + block.new_nullifiers.clear(); let block_idx = self.blocks.len(); tx_block.iter().enumerate().for_each(|(tx_idx, tx)| { @@ -263,14 +312,61 @@ impl LedgerState { } } + let backup_next_txn_sid = self.status.next_txn.0; let (tsm, base_sid, max_sid) = self.status.apply_block_effects(&mut block); self.update_utxo_map(base_sid, max_sid, &block.temp_sids, &tsm) .c(d!()) - .and_then(|_| self.update_state(block, &tsm).c(d!())) + .and_then(|_| self.update_state(block, &tsm, backup_next_txn_sid).c(d!())) .map(|_| tsm) } + /// Apply the changes from current block + /// to the merkle trees holding anonymous data + pub fn update_anon_stores( + &mut self, + new_nullifiers: Vec, + output_abars: Vec>, + backup_next_txn_sid: usize, + mut tx_block: Vec, + ) -> Result> { + for n in new_nullifiers.iter() { + let d: Key = Key::from_bytes(n.noah_to_bytes()).c(d!())?; + + // if the nullifier hash is present in our nullifier set, fail the block + if self.nullifier_set.read().get(&d).c(d!())?.is_some() { + return Err(eg!("Nullifier hash already present in set")); + } + self.nullifier_set + .write() + .set(&d, Some(n.noah_to_bytes())) + .c(d!())?; + self.status.spent_abars.insert(*n, ()); + } + + let mut txn_sid = TxnSID(backup_next_txn_sid); + for (txn_abars, txn) in output_abars.iter().zip(tx_block.iter_mut()) { + let mut op_position = OutputPosition(0); + let mut atxo_ids: Vec = vec![]; + for abar in txn_abars { + let uid = self.add_abar(&abar).c(d!())?; + self.status.ax_utxos.insert(uid, abar.clone()); + self.status.owned_ax_utxos.insert(abar.commitment, uid); + self.status + .ax_txo_to_txn_location + .insert(uid, (txn_sid, op_position)); + + atxo_ids.push(uid); + self.status.next_atxo = ATxoSID(uid.0 + 1); + op_position = OutputPosition(op_position.0 + 1); + } + txn.atxo_ids = atxo_ids; + txn_sid = TxnSID(txn_sid.0 + 1); + } + + Ok(tx_block) + } + #[inline(always)] #[allow(missing_docs)] pub fn get_staking_mut(&mut self) -> &mut Staking { @@ -311,6 +407,7 @@ impl LedgerState { pub fn tmp_ledger() -> LedgerState { fbnc::clear(); let tmp_dir = globutils::fresh_tmp_dir().to_string_lossy().into_owned(); + env::set_var("FINDORAD_KEEP_HIST", "1"); LedgerState::new(&tmp_dir, Some("test")).unwrap() } @@ -318,7 +415,8 @@ impl LedgerState { // 1. Compute the hash of transactions in the block and update txns_in_block_hash // 2. Append txns_in_block_hash to block_merkle #[inline(always)] - fn compute_and_append_txns_hash(&mut self, block: &BlockEffect) -> u64 { + #[allow(missing_docs)] + pub fn compute_and_append_txns_hash(&mut self, block: &BlockEffect) -> u64 { // 1. Compute the hash of transactions in the block and update txns_in_block_hash let txns_in_block_hash = block.compute_txns_in_block_hash(); self.status.txns_in_block_hash = Some(txns_in_block_hash.clone()); @@ -334,7 +432,8 @@ impl LedgerState { ret } - fn compute_and_save_state_commitment_data(&mut self, pulse_count: u64) { + #[allow(missing_docs)] + pub fn compute_and_save_state_commitment_data(&mut self, pulse_count: u64) { let staking_data = if self.get_tendermint_height() < CFG.checkpoint.remove_fake_staking_hash && self.get_staking().has_been_inited() @@ -365,9 +464,97 @@ impl LedgerState { .state_commitment_versions .push(state_commitment_data.compute_commitment()); self.status.state_commitment_data = Some(state_commitment_data); + + // Commit Anon tree changes here following Tendermint protocol + pnk!(self.commit_anon_changes().c(d!())); + pnk!(self.commit_nullifier_changes().c(d!())); + + let abar_root_hash = + self.get_abar_root_hash().expect("failed to read root hash"); + + let anon_state_commitment_data = AnonStateCommitmentData { + abar_root_hash, + nullifier_root_hash: self + .nullifier_set + .read() + .merkle_root() + .unwrap_or(sparse_merkle_tree::ZERO_DIGEST), + }; + + let anon_hash = anon_state_commitment_data.compute_commitment(); + // don't push anon_state_commitment until any anon transactions is committed. + // This is to make sure the app hash changes occur for all nodes at the same time + if anon_hash.hex() != GENESIS_ANON_HASH { + self.status.anon_state_commitment_versions.push(anon_hash); + } + self.status.anon_state_commitment_data = Some(anon_state_commitment_data); + self.status.incr_block_commit_count(); } + #[inline(always)] + /// Adds a new abar to session cache and updates merkle hashes of ancestors + pub fn add_abar(&mut self, abar: &AnonAssetRecord) -> Result { + let mut abar_state_val = self.abar_state.write(); + let store = PrefixedStore::new("abar_store", &mut abar_state_val); + let mut mt = PersistentMerkleTree::new(store).c(d!())?; + + let leaf = hash_abar(mt.entry_count(), abar); + mt.add_commitment_hash(leaf).map(ATxoSID).c(d!()) + } + + #[inline(always)] + /// writes the changes from session cache to the RocksDB store + pub fn commit_anon_changes(&mut self) -> Result { + let mut abar_state_val = self.abar_state.write(); + let store = PrefixedStore::new("abar_store", &mut abar_state_val); + let mut mt = PersistentMerkleTree::new(store).c(d!())?; + + mt.commit().c(d!()) + } + + #[inline(always)] + /// writes the changes from session cache to the RocksDB store + pub fn commit_nullifier_changes(&mut self) -> Result { + self.nullifier_set.write().commit() + } + + #[inline(always)] + /// Fetches the root hash of the committed merkle tree of abar commitments directly from committed + /// state and ignore session cache + pub fn get_abar_root_hash(&self) -> Result { + let abar_query_state = State::new(self.abar_state.read().chain_state(), false); + let store = ImmutablePrefixedStore::new("abar_store", &abar_query_state); + let mt = ImmutablePersistentMerkleTree::new(store).c(d!())?; + + mt.get_root_with_depth(MERKLE_TREE_DEPTH).c(d!( + "probably due to badly constructed tree or data corruption" + )) + } + + #[inline(always)] + /// Generates a MTLeafInfo from the latest committed version of tree from committed state and + /// ignore session cache + pub fn get_abar_proof(&self, id: ATxoSID) -> Result { + let abar_query_state = State::new(self.abar_state.read().chain_state(), false); + let store = ImmutablePrefixedStore::new("abar_store", &abar_query_state); + let mt = ImmutablePersistentMerkleTree::new(store).c(d!())?; + + let t = mt + .generate_proof_with_depth(id.0, MERKLE_TREE_DEPTH) + .c(d!())?; + Ok(build_mt_leaf_info_from_proof(t, id.0)) + } + + /// Check if the nullifier hash is present in nullifier set + #[inline(always)] + pub fn check_nullifier_hash(&self, hash: String) -> Result { + let n = wallet::nullifier_from_base58(hash.as_str())?; + let d: Key = Key::from_bytes(n.noah_to_bytes()).c(d!())?; + let is_null_present = self.nullifier_set.read().get(&d).c(d!())?.is_some(); + Ok(is_null_present) + } + // Initialize a logged Merkle tree for the ledger. // We might be creating a new tree or opening an existing one. #[inline(always)] @@ -389,6 +576,25 @@ impl LedgerState { .and_then(|f| BitMap::open(f).c(d!())) } + // Initialize a persistent merkle tree for ABAR store. + #[inline(always)] + fn init_abar_state(path: &str) -> Result> { + let fdb = RocksDB::open(path).c(d!("failed to open db"))?; + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "abar_db".to_string(), + VERSION_WINDOW, + ))); + Ok(State::new(cs, false)) + } + + // Initialize persistent Sparse Merkle tree for the Nullifier set + #[inline(always)] + fn init_nullifier_smt(path: &str) -> Result> { + let rdb = RocksDB::open(path).c(d!("failed to open db"))?; + Ok(SmtMap256::new(rdb)) + } + /// Initialize a new Ledger structure. pub fn new(basedir: &str, prefix: Option<&str>) -> Result { let prefix = if let Some(p) = prefix { @@ -400,6 +606,8 @@ impl LedgerState { let block_merkle_path = format!("{}/{}block_merkle", basedir, &prefix); let txn_merkle_path = format!("{}/{}txn_merkle", basedir, &prefix); let utxo_map_path = format!("{}/{}utxo_map", basedir, &prefix); + let abar_store_path = format!("{}/{}abar_store", basedir, &prefix); + let nullifier_store_path = format!("{}/{}nullifier_store", basedir, &prefix); // These iterms will be set under ${BNC_DATA_DIR} fs::create_dir_all(&basedir).c(d!())?; @@ -411,6 +619,12 @@ impl LedgerState { let blocks_path = prefix.clone() + "blocks"; let tx_to_block_location_path = prefix.clone() + "tx_to_block_location"; + let mut abar_state = LedgerState::init_abar_state(&abar_store_path).c(d!())?; + + // Initializing Merkle tree to set Empty tree root hash, which is a hash of null children + let store = PrefixedStore::new("abar_store", &mut abar_state); + let _ = PersistentMerkleTree::new(store).c(d!())?; + let mut ledger = LedgerState { status: LedgerStatus::new(&basedir, &snapshot_file).c(d!())?, block_merkle: Arc::new(RwLock::new( @@ -426,6 +640,10 @@ impl LedgerState { )), block_ctx: Some(BlockEffect::default()), api_cache: alt!(*KEEP_HIST, Some(ApiCache::new(&prefix)), None), + abar_state: Arc::new(RwLock::new(abar_state)), + nullifier_set: Arc::new(RwLock::new( + LedgerState::init_nullifier_smt(&nullifier_store_path).c(d!())?, + )), }; ledger.status.refresh_data(); @@ -819,7 +1037,7 @@ impl LedgerState { .txn .get_owner_memos_ref() .get(au.utxo_location.0) - .and_then(|i| i.cloned()), + .and_then(|i| i.clone()), ), ) }) @@ -828,6 +1046,46 @@ impl LedgerState { Ok(res) } + /// Get all abars with sid which are associated with a diversified public key + #[allow(dead_code)] + pub fn get_owned_abar(&self, com: &Commitment) -> Option { + self.status.owned_ax_utxos.get(com) + } + + /// Get abar commitment with sid + pub fn get_abar(&self, sid: &ATxoSID) -> Option { + self.status.get_abar(sid).map(|v| v.commitment) + } + + /// Get the owner memo of a abar by ATxoSID + #[allow(dead_code)] + pub fn get_abar_memo(&self, ax_id: ATxoSID) -> Option { + if let Some(txn_location) = self.status.ax_txo_to_txn_location.get(&ax_id) { + if let Ok(authenticated_txn) = self.get_transaction(txn_location.0) { + let memo: Vec = authenticated_txn + .finalized_txn + .txn + .body + .operations + .iter() + .flat_map(|o| match o { + Operation::BarToAbar(body) => vec![body.axfr_memo()], + Operation::TransferAnonAsset(body) => { + body.note.body.owner_memos.clone() + } + _ => vec![], + }) + .collect::>(); + + if memo.is_empty() { + return None; + } + return memo.get(txn_location.1 .0).cloned(); + }; + }; + None + } + #[inline(always)] #[allow(missing_docs)] pub fn get_issuance_num(&self, code: &AssetTypeCode) -> Option { @@ -864,6 +1122,16 @@ impl LedgerState { (commitment, block_count) } + #[inline(always)] + #[allow(missing_docs)] + pub fn get_anon_state_commitment(&self) -> (Vec, u64) { + let block_count = self.status.block_commit_count; + let commitment = self.status.anon_state_commitment_versions.last(); + + let hash = commitment.map_or_else(Vec::new, |c| c.as_ref().to_vec()); + (hash, block_count) + } + /// Get utxo status and its proof data pub fn get_utxo_status(&self, addr: TxoSID) -> AuthenticatedUtxoStatus { let state_commitment_data = self.status.state_commitment_data.as_ref().unwrap(); @@ -980,38 +1248,78 @@ impl LedgerState { pub struct LedgerStatus { /// the file path of the snapshot pub snapshot_file: String, - // all currently-unspent TXOs + /// all currently-unspent TXOs + #[serde(default = "default_status_utxos")] utxos: Mapxnk, + /// all non-confidential balances + #[serde(default = "default_status_nonconfidential_balances")] nonconfidential_balances: Mapx, + /// all owned utxos + #[serde(default = "default_status_owned_utxos")] owned_utxos: Mapx>, + /// all existing ax_utxos + #[serde(default = "default_status_ax_utxos")] + ax_utxos: Mapx, + /// all owned abars + #[serde(default = "default_status_owned_ax_utxos")] + owned_ax_utxos: Mapx, /// all spent TXOs + #[serde(default = "default_status_spent_utxos")] pub spent_utxos: Mapxnk, - // Map a TXO to its output position in a transaction + /// all spent abars + #[serde(default = "default_status_spent_abars")] + pub spent_abars: Mapx, + /// Map a TXO to its output position in a transaction + #[serde(default = "default_status_txo_to_txn_location")] txo_to_txn_location: Mapxnk, - // State commitment history. - // The BitDigest at index i is the state commitment of the ledger at block height i + 1. + /// Map a Anonymous TXO to its output position in a transaction + #[serde(default = "default_status_ax_txo_to_txn_location")] + ax_txo_to_txn_location: Mapx, + /// State commitment history. + /// The BitDigest at index i is the state commitment of the ledger at block height i + 1. + #[serde(default = "default_status_state_commitment_versions")] state_commitment_versions: Vecx>>, - // Registered asset types + /// Anon state commitment versions + #[serde(default = "default_status_anon_state_commitment_versions")] + anon_state_commitment_versions: Vecx>>, + /// Registered asset types + #[serde(default = "default_status_asset_types")] asset_types: Mapx, - // Issuance number is always increasing + /// Issuance number is always increasing + #[serde(default = "default_status_issuance_num")] issuance_num: Mapx, - // Issuance amounts for assets with limits + /// Issuance amounts for assets with limits + #[serde(default = "default_status_issuance_amounts")] issuance_amounts: Mapx, - // Should be equal to the count of transactions + /// Should be equal to the count of transactions + #[serde(default = "default_status_next_txn")] next_txn: TxnSID, - // Should be equal to the count of TXOs + /// Should be equal to the count of TXOs + #[serde(default = "default_status_next_txo")] next_txo: TxoSID, - // Each block corresponds to such a summary structure + /// Should be equal to the count of ABARs + #[serde(default = "default_status_next_atxo")] + next_atxo: ATxoSID, + /// Each block corresponds to such a summary structure + #[serde(default = "default_status_state_commitment_data")] state_commitment_data: Option, - // number of non-empty blocks, equal to: - + /// Anon state commitment + #[serde(default = "default_status_anon_state_commitment_data")] + anon_state_commitment_data: Option, + /// number of non-empty blocks, equal to: - + #[serde(default = "default_status_block_commit_count")] block_commit_count: u64, - // Hash of the transactions in the most recent block + /// Hash of the transactions in the most recent block + #[serde(default = "default_status_txns_in_block_hash")] txns_in_block_hash: Option>>, - // Sliding window of operations for replay attack prevention + /// Sliding window of operations for replay attack prevention + #[serde(default = "default_status_sliding_set")] sliding_set: SlidingSet<[u8; 8]>, - // POS-related implementations + /// POS-related implementations + #[serde(default = "default_status_staking")] staking: Staking, - // tendermint commit height + /// tendermint commit height + #[serde(default = "default_status_td_commit_height")] td_commit_height: u64, } @@ -1025,6 +1333,18 @@ impl LedgerStatus { .unwrap_or_default() } + #[inline(always)] + #[allow(missing_docs)] + pub fn get_owned_abar(&self, com: &Commitment) -> Option { + self.owned_ax_utxos.get(com) + } + + #[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 { @@ -1086,42 +1406,34 @@ impl LedgerStatus { } fn create(snapshot_file: &str) -> Result { - let utxos_path = SNAPSHOT_ENTRIES_DIR.to_owned() + "/utxo"; - let nonconfidential_balances_path = - SNAPSHOT_ENTRIES_DIR.to_owned() + "/nonconfidential_balances"; - let spent_utxos_path = SNAPSHOT_ENTRIES_DIR.to_owned() + "/spent_utxos"; - let txo_to_txn_location_path = - SNAPSHOT_ENTRIES_DIR.to_owned() + "/txo_to_txn_location"; - let issuance_amounts_path = - SNAPSHOT_ENTRIES_DIR.to_owned() + "/issuance_amounts"; - let state_commitment_versions_path = - SNAPSHOT_ENTRIES_DIR.to_owned() + "/state_commitment_versions"; - let asset_types_path = SNAPSHOT_ENTRIES_DIR.to_owned() + "/asset_types"; - let issuance_num_path = SNAPSHOT_ENTRIES_DIR.to_owned() + "/issuance_num"; - let owned_utxos_path = SNAPSHOT_ENTRIES_DIR.to_owned() + "/owned_utxos"; - - let ledger = LedgerStatus { + Ok(LedgerStatus { snapshot_file: snapshot_file.to_owned(), - sliding_set: SlidingSet::<[u8; 8]>::new(TRANSACTION_WINDOW_WIDTH as usize), - utxos: new_mapxnk!(utxos_path.as_str()), - nonconfidential_balances: new_mapx!(nonconfidential_balances_path.as_str()), - owned_utxos: new_mapx!(owned_utxos_path.as_str()), - spent_utxos: new_mapxnk!(spent_utxos_path.as_str()), - txo_to_txn_location: new_mapxnk!(txo_to_txn_location_path.as_str()), - issuance_amounts: new_mapx!(issuance_amounts_path.as_str()), - state_commitment_versions: new_vecx!(state_commitment_versions_path.as_str()), - asset_types: new_mapx!(asset_types_path.as_str()), - issuance_num: new_mapx!(issuance_num_path.as_str()), - next_txn: TxnSID(0), - next_txo: TxoSID(0), - txns_in_block_hash: None, - state_commitment_data: None, - block_commit_count: 0, - staking: Staking::new(), - td_commit_height: 0, - }; - - Ok(ledger) + sliding_set: default_status_sliding_set(), + utxos: default_status_utxos(), + nonconfidential_balances: default_status_nonconfidential_balances(), + owned_utxos: default_status_owned_utxos(), + ax_utxos: default_status_ax_utxos(), + owned_ax_utxos: default_status_owned_ax_utxos(), + spent_utxos: default_status_spent_utxos(), + spent_abars: default_status_spent_abars(), + txo_to_txn_location: default_status_txo_to_txn_location(), + ax_txo_to_txn_location: default_status_ax_txo_to_txn_location(), + issuance_amounts: default_status_issuance_amounts(), + state_commitment_versions: default_status_state_commitment_versions(), + anon_state_commitment_versions: + default_status_anon_state_commitment_versions(), + asset_types: default_status_asset_types(), + issuance_num: default_status_issuance_num(), + next_txn: default_status_next_txn(), + next_txo: default_status_next_txo(), + next_atxo: default_status_next_atxo(), + txns_in_block_hash: default_status_txns_in_block_hash(), + state_commitment_data: default_status_state_commitment_data(), + anon_state_commitment_data: default_status_anon_state_commitment_data(), + block_commit_count: default_status_block_commit_count(), + staking: default_status_staking(), + td_commit_height: default_status_td_commit_height(), + }) } #[inline(always)] @@ -1139,7 +1451,11 @@ impl LedgerStatus { // // ledger.check_txn_effects(txn_effect); // block.add_txn_effect(txn_effect); - fn check_txn_effects(&self, txn_effect: &TxnEffect) -> Result<()> { + fn check_txn_effects( + &self, + txn_effect: &TxnEffect, + abar_state: &Arc>>, + ) -> Result<()> { // The current transactions seq_id must be within the sliding window over seq_ids let (rand, seq_id) = ( txn_effect.txn.body.no_replay_token.get_rand(), @@ -1326,6 +1642,69 @@ impl LedgerStatus { } } + // current merkle tree version. + let abar_query_state = State::new(abar_state.read().chain_state(), false); + let store = ImmutablePrefixedStore::new("abar_store", &abar_query_state); + let abar_mt = ImmutablePersistentMerkleTree::new(store).c(d!())?; + + let mut hasher = Sha512::new(); + hasher.update(txn_effect.txn.body.digest()); + + // An axfr_body requires versioned merkle root hash for verification. + // here with LedgerStatus available. + for axfr_note in txn_effect.axfr_bodies.iter() { + for input in &axfr_note.body.inputs { + if self.spent_abars.get(&input).is_some() { + return Err(eg!("Input abar must be unspent")); + } + } + + let af = match axfr_note.folding_instance { + AXfrAddressFoldingInstance::Secp256k1(_) => AddressFormat::SECP256K1, + AXfrAddressFoldingInstance::Ed25519(_) => AddressFormat::ED25519, + }; + let verifier_params = VerifierParams::get_abar_to_abar( + axfr_note.body.inputs.len(), + axfr_note.body.outputs.len(), + af, + ) + .c(d!())?; + let abar_version = axfr_note.body.merkle_root_version; + if abar_mt.version() - abar_version > VERSION_WINDOW { + return Err(eg!("Proof is old, need rebuild!")); + } + let version_root = abar_mt + .get_root_with_depth_and_version(MERKLE_TREE_DEPTH, abar_version) + .c(d!())?; + + verify_anon_xfr_note( + &verifier_params, + axfr_note, + &version_root, + hasher.clone(), + ) + .c(d!("Anon Transfer proof verification failed"))?; + } + + // An axfr_abar_conv requires versioned merkle root hash for verification. + for abar_conv in &txn_effect.abar_conv_inputs { + if self.spent_abars.get(&abar_conv.get_input()).is_some() { + return Err(eg!("Input abar must be unspent")); + } + + // Get verifier params + let abar_version = abar_conv.get_merkle_root_version(); + if abar_mt.version() - abar_version > VERSION_WINDOW { + return Err(eg!("Proof is old, need rebuild!")); + } + let version_root = abar_mt + .get_root_with_depth_and_version(MERKLE_TREE_DEPTH, abar_version) + .c(d!())?; + + // verify zk proof with merkle root + abar_conv.verify(version_root, hasher.clone())?; + } + Ok(()) } @@ -1335,6 +1714,7 @@ impl LedgerStatus { // that is ever false, it's a bug). // // This drains every field of `block` except `txns` and `temp_sids`. + #[allow(unused_mut)] fn apply_block_effects(&mut self, block: &mut BlockEffect) -> (TmpSidMap, u64, u64) { let base_sid = self.next_txo.0; let handle_asset_type_code = |code: AssetTypeCode| { @@ -1480,3 +1860,127 @@ pub struct LoggedBlock { pub fn flush_data() { fbnc::flush_data(); } + +fn build_mt_leaf_info_from_proof(proof: Proof, uid: u64) -> MTLeafInfo { + return MTLeafInfo { + path: MTPath { + nodes: proof + .nodes + .iter() + .map(|e| MTNode { + left: e.left, + mid: e.mid, + right: e.right, + is_left_child: (e.path == TreePath::Left) as u8, + is_mid_child: (e.path == TreePath::Middle) as u8, + is_right_child: (e.path == TreePath::Right) as u8, + }) + .collect(), + }, + root: proof.root, + root_version: proof.root_version, + uid, + }; +} + +fn hash_abar(uid: u64, abar: &AnonAssetRecord) -> BN254Scalar { + AnemoiJive254::eval_variable_length_hash(&[BN254Scalar::from(uid), abar.commitment]) +} + +fn default_status_utxos() -> Mapxnk { + new_mapxnk!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/utxo") +} + +fn default_status_owned_utxos() -> Mapx> { + new_mapx!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/owned_utxos") +} + +fn default_status_nonconfidential_balances() -> Mapx { + new_mapx!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/nonconfidential_balances") +} + +fn default_status_ax_utxos() -> Mapx { + new_mapx!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/ax_utxos") +} + +fn default_status_owned_ax_utxos() -> Mapx { + new_mapx!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/owned_ax_utxos") +} + +fn default_status_spent_utxos() -> Mapxnk { + new_mapxnk!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/spent_utxos") +} + +fn default_status_spent_abars() -> Mapx { + new_mapx!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/spent_abars") +} + +fn default_status_txo_to_txn_location() -> Mapxnk { + new_mapxnk!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/txo_to_txn_location") +} + +fn default_status_ax_txo_to_txn_location() -> Mapx { + new_mapx!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/atxo_to_txn_location") +} + +fn default_status_issuance_amounts() -> Mapx { + new_mapx!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/issuance_amounts") +} + +fn default_status_state_commitment_versions() -> Vecx>> +{ + new_vecx!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/state_commitment_versions") +} + +fn default_status_anon_state_commitment_versions( +) -> Vecx>> { + new_vecx!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/anon_state_commitment_versions") +} + +fn default_status_asset_types() -> Mapx { + new_mapx!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/asset_types") +} + +fn default_status_issuance_num() -> Mapx { + new_mapx!(SNAPSHOT_ENTRIES_DIR.to_owned() + "/issuance_num") +} + +fn default_status_next_txn() -> TxnSID { + TxnSID(0) +} + +fn default_status_next_txo() -> TxoSID { + TxoSID(0) +} + +fn default_status_next_atxo() -> ATxoSID { + ATxoSID(0) +} + +fn default_status_txns_in_block_hash() -> Option>> { + None +} + +fn default_status_state_commitment_data() -> Option { + None +} + +fn default_status_anon_state_commitment_data() -> Option { + None +} + +fn default_status_block_commit_count() -> u64 { + 0 +} + +fn default_status_staking() -> Staking { + Staking::new() +} + +fn default_status_td_commit_height() -> u64 { + 0 +} + +fn default_status_sliding_set() -> SlidingSet<[u8; 8]> { + SlidingSet::<[u8; 8]>::new(TRANSACTION_WINDOW_WIDTH as usize) +} diff --git a/src/ledger/src/store/test.rs b/src/ledger/src/store/test.rs old mode 100644 new mode 100755 index 064b322ac..fb1677abc --- a/src/ledger/src/store/test.rs +++ b/src/ledger/src/store/test.rs @@ -1,21 +1,30 @@ #![cfg(test)] #![allow(missing_docs)] - use { super::{helpers::*, *}, - crate::data_model::{ - AssetRules, AssetTypeCode, IssueAsset, IssueAssetBody, Memo, Operation, - Transaction, TransferAsset, TransferAssetBody, TxOutput, TxnEffect, TxoRef, - TxoSID, ASSET_TYPE_FRA, BLACK_HOLE_PUBKEY, TX_FEE_MIN, + crate::{ + data_model::{ + get_abar_commitment, AssetRules, AssetTypeCode, IssueAsset, IssueAssetBody, + IssuerKeyPair, Memo, Operation, Transaction, TransferAsset, + TransferAssetBody, TransferType, TxOutput, TxnEffect, TxoRef, TxoSID, + ASSET_TYPE_FRA, BLACK_HOLE_PUBKEY, TX_FEE_MIN, + }, + store::{helpers::create_definition_transaction, utils::fra_gen_initial_tx}, }, rand_core::SeedableRng, zei::{ - noah_algebra::ristretto::PedersenCommitmentRistretto, - noah_api::xfr::{ - asset_record::{ - build_blind_asset_record, open_blind_asset_record, AssetRecordType, + noah_algebra::{ + prelude::{One, Zero}, + ristretto::PedersenCommitmentRistretto, + }, + noah_api::{ + anon_xfr::structs::OpenAnonAssetRecordBuilder, + xfr::{ + asset_record::{ + build_blind_asset_record, open_blind_asset_record, AssetRecordType, + }, + structs::{AssetRecord, AssetRecordTemplate}, }, - structs::{AssetRecord, AssetRecordTemplate}, }, BlindAssetRecord, XfrKeyPair, }, @@ -29,6 +38,8 @@ fn abort_block(block: BlockEffect) -> HashMap { block.temp_sids.drain(..).zip(txns).collect(); block.txos.clear(); + block.output_abars.clear(); + block.new_nullifiers.clear(); block.input_txos.clear(); block.new_asset_codes.clear(); block.new_issuance_nums.clear(); @@ -109,12 +120,6 @@ fn test_asset_creation_valid() { } assert!(state.get_asset_type(&token_code).is_some()); - - assert_eq!( - *asset_body.asset, - state.get_asset_type(&token_code).unwrap().properties - ); - assert_eq!(0, state.get_asset_type(&token_code).unwrap().units); } @@ -144,6 +149,7 @@ fn test_asset_creation_invalid_public_key() { } #[test] +#[allow(clippy::redundant_clone)] fn test_asset_transfer() { let mut ledger = LedgerState::tmp_ledger(); @@ -182,12 +188,8 @@ fn test_asset_transfer() { key_pair.get_pk().into_noah(), ); let pc_gens = PedersenCommitmentRistretto::default(); - let (ba, _, _) = build_blind_asset_record( - &mut ledger.get_prng(), - &pc_gens, - &template, - vec![], - ); + let (ba, _, _) = + build_blind_asset_record(&mut ledger.get_prng(), &pc_gens, &template, vec![]); let asset_issuance_body = IssueAssetBody::new( &new_code, @@ -231,6 +233,8 @@ fn test_asset_transfer() { .unwrap() .remove(&temp_sid) .unwrap(); + ledger.api_cache.as_mut().unwrap().state_commitment_version = + ledger.status.state_commitment_versions.last(); let state_commitment = ledger.get_state_commitment().0; for txo_id in &txos { @@ -296,6 +300,8 @@ fn test_asset_transfer() { .unwrap() .remove(&temp_sid) .unwrap(); + ledger.api_cache.as_mut().unwrap().state_commitment_version = + ledger.status.state_commitment_versions.last(); // Ensure that previous txo is now spent let state_commitment = ledger.get_state_commitment().0; let utxo_status = ledger.get_utxo_status(TxoSID(0)); @@ -428,6 +434,8 @@ fn asset_issued() { let transaction = ledger.get_transaction(txn_sid).unwrap(); let txn_id = transaction.finalized_txn.tx_id; + ledger.api_cache.as_mut().unwrap().state_commitment_version = + ledger.status.state_commitment_versions.last(); let state_commitment_and_version = ledger.get_state_commitment(); println!("utxos = {:?}", ledger.status.utxos); @@ -786,7 +794,7 @@ fn test_check_fee_with_ledger() { let mut ledger = LedgerState::tmp_ledger(); let fra_owner_kp = XfrKeyPair::generate(&mut ChaChaRng::from_entropy()); - let tx = utils::fra_gen_initial_tx(&fra_owner_kp); + let tx = fra_gen_initial_tx(&fra_owner_kp); assert!(tx.check_fee()); let effect = TxnEffect::compute_effect(tx.clone()).unwrap(); @@ -815,3 +823,86 @@ fn test_check_fee_with_ledger() { let mut block = ledger.start_block().unwrap(); assert!(ledger.apply_transaction(&mut block, effect).is_err()); } + +#[test] +fn test_update_anon_stores() { + let mut prng = ChaChaRng::from_seed([0u8; 32]); + + let mut state = LedgerState::tmp_ledger(); + + let nullifiers = vec![ + Nullifier::zero() as Nullifier, + Nullifier::one() as Nullifier, + ]; + + let pub_key = XfrKeyPair::generate(&mut prng).get_pk().into_noah(); + let oabar = OpenAnonAssetRecordBuilder::new() + .amount(123) + .asset_type(zei::noah_api::xfr::structs::AssetType([39u8; 32])) + .pub_key(&pub_key) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(); + let oabar2 = OpenAnonAssetRecordBuilder::new() + .amount(123) + .asset_type(zei::noah_api::xfr::structs::AssetType([39u8; 32])) + .pub_key(&pub_key) + .finalize(&mut prng) + .unwrap() + .build() + .unwrap(); + let output_abars = vec![ + vec![AnonAssetRecord::from_oabar(&oabar)], + vec![AnonAssetRecord::from_oabar(&oabar2)], + ]; + let new_com = get_abar_commitment(oabar); + let new_com2 = get_abar_commitment(oabar2); + let tx_block = vec![ + FinalizedTransaction { + txn: Default::default(), + tx_id: Default::default(), + txo_ids: vec![], + atxo_ids: vec![], + merkle_id: 0, + }, + FinalizedTransaction { + txn: Default::default(), + tx_id: Default::default(), + txo_ids: vec![], + atxo_ids: vec![], + merkle_id: 0, + }, + ]; + + let str0 = bs58::encode(&BN254Scalar::zero().noah_to_bytes()).into_string(); + let d0: Key = Key::from_base58(&str0).unwrap(); + assert!(state.nullifier_set.read().get(&d0).unwrap().is_none()); + + let str1 = bs58::encode(&BN254Scalar::one().noah_to_bytes()).into_string(); + let d1: Key = Key::from_base58(&str1).unwrap(); + assert!(state.nullifier_set.read().get(&d1).unwrap().is_none()); + + let res = state.update_anon_stores(nullifiers, output_abars, 0, tx_block); + assert!(res.is_ok()); + + let res2 = state.commit_anon_changes(); + assert!(res2.is_ok()); + assert_eq!(res2.unwrap(), 1); + + assert!(state.nullifier_set.read().get(&d0).unwrap().is_some()); + assert!(state.nullifier_set.read().get(&d1).unwrap().is_some()); + + assert_eq!(state.status.next_atxo.0, 2); + assert_eq!( + state.status.ax_txo_to_txn_location.get(&ATxoSID(0)), + Some((TxnSID(0), OutputPosition(0))) + ); + assert_eq!( + state.status.ax_txo_to_txn_location.get(&ATxoSID(1)), + Some((TxnSID(1), OutputPosition(0))) + ); + + assert_eq!(state.status.owned_ax_utxos.get(&new_com), Some(ATxoSID(0))); + assert_eq!(state.status.owned_ax_utxos.get(&new_com2), Some(ATxoSID(1))); +} diff --git a/src/ledger/src/store/utils.rs b/src/ledger/src/store/utils.rs index d1496c472..c036b9397 100644 --- a/src/ledger/src/store/utils.rs +++ b/src/ledger/src/store/utils.rs @@ -26,6 +26,7 @@ use { /// Define and Issue FRA. /// Currently this should only be used for tests. +#[allow(unused)] pub fn fra_gen_initial_tx(fra_owner_kp: &XfrKeyPair) -> Transaction { /* * Define FRA diff --git a/tools/devnet/snapshot.sh b/tools/devnet/snapshot.sh index c03ee0007..32f5b48c4 100755 --- a/tools/devnet/snapshot.sh +++ b/tools/devnet/snapshot.sh @@ -41,6 +41,7 @@ $BIN/fn setup -S http://0.0.0.0 > /dev/null $BIN/fn setup -O $WALLET/mnenomic.key > /dev/null echo -e "host: http://0.0.0.0" echo -e "key : $WALLET/mnenomic.key" +sleep 3 #echo -e "$BIN/stt" $BIN/stt init -i $BLOCK_INTERVAL -s #./$stopnodes