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 b6af1695a..12daa86dc 100644 --- a/src/components/abciapp/src/abci/server/callback/mod.rs +++ b/src/components/abciapp/src/abci/server/callback/mod.rs @@ -29,7 +29,7 @@ use { lazy_static::lazy_static, ledger::{ converter::is_convert_account, - data_model::{Operation, ASSET_TYPE_FRA}, + 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, @@ -52,8 +52,8 @@ use { Arc, }, }, - tracing::info, - zei::noah_api::xfr::asset_record::AssetRecordType + tracing::{error, info}, + zei::noah_api::xfr::asset_record::AssetRecordType, }; pub(crate) static TENDERMINT_BLOCK_HEIGHT: AtomicI64 = AtomicI64::new(0); @@ -98,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, + )); } } @@ -138,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 { @@ -169,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(); @@ -270,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 { @@ -334,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 = @@ -373,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(_) => { @@ -593,8 +638,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); @@ -739,3 +787,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 5d6427003..4ae1042d0 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 3b84833ba..6083b7fbb 100644 --- a/src/components/contracts/baseapp/src/staking.rs +++ b/src/components/contracts/baseapp/src/staking.rs @@ -16,8 +16,7 @@ use module_evm::{ use ruc::{d, eg, 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 ad31b79d9..02a67ca6a 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, @@ -32,10 +31,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; @@ -48,8 +47,7 @@ pub use runtime::*; use std::marker::PhantomData; use std::str::FromStr; use system_contracts::{SystemContracts, SYSTEM_ADDR}; -use zei::noah_algebra::serialization::NoahFromToBytes; -use zei::XfrPublicKey; +use zei::{noah_algebra::serialization::NoahFromToBytes, XfrPublicKey}; use crate::utils::{deposit_asset_event, parse_deposit_asset_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 6dd5f2345..f036e77a0 100644 --- a/src/components/contracts/primitives/core/src/module.rs +++ b/src/components/contracts/primitives/core/src/module.rs @@ -1,5 +1,6 @@ use crate::context::Context; use abci::*; +use fp_types::U256; use ruc::Result; /// AppModuleBasic is the standard form for basic non-dependant elements of an application module. @@ -44,4 +45,13 @@ pub trait AppModule: AppModuleBasic { ) -> ResponseEndBlock { 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 ba35a29e9..b1c2e79b3 100644 --- a/src/components/finutils/src/bins/stt/stt.rs +++ b/src/components/finutils/src/bins/stt/stt.rs @@ -234,10 +234,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, + }, }, }; @@ -290,7 +292,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() } } @@ -327,7 +329,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) } @@ -365,7 +367,7 @@ mod undelegate { } })?; - Ok(builder.take_transaction()) + builder.build_and_take_transaction() } } @@ -382,7 +384,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 a83128b99..967139fd7 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!())?; diff --git a/src/components/finutils/src/common/utils.rs b/src/components/finutils/src/common/utils.rs index 53acba81d..94e16f216 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(staking_info_file: Option<&str>) -> Result<()> { let vs = get_inital_validators(staking_info_file).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 6a97b3b22..0ab44add2 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, @@ -794,6 +1190,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)] @@ -963,7 +1402,6 @@ impl TransferOperationBuilder { } // Check if outputs and inputs are balanced - fn check_balance(&self) -> Result<()> { let input_total: u64 = self .input_records @@ -1163,20 +1601,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 @@ -1478,7 +2335,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); @@ -1508,7 +2365,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); @@ -1529,4 +2386,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