diff --git a/.cargo/config.toml b/.cargo/config similarity index 100% rename from .cargo/config.toml rename to .cargo/config diff --git a/Cargo.lock b/Cargo.lock index da0a0c4f8..e0078a179 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,10 +330,11 @@ checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" [[package]] name = "built" -version = "0.7.4" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" +checksum = "c8f1b029cb3929cb0c99780b0c10fe512f60be5438adf5f757e4afa1bc75a984" dependencies = [ + "cargo-lock", "git2", ] @@ -377,6 +378,18 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "cargo-lock" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8504b63dd1249fd1745b7b4ef9b6f7b107ddeb3c95370043c7dbcc38653a2679" +dependencies = [ + "semver 0.9.0", + "serde", + "toml 0.5.11", + "url", +] + [[package]] name = "cc" version = "1.0.98" @@ -669,41 +682,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2 1.0.83", - "quote 1.0.36", - "strsim 0.10.0", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote 1.0.36", - "syn 1.0.109", -] - [[package]] name = "dashmap" version = "5.5.3" @@ -935,6 +913,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + [[package]] name = "fastrand" version = "2.1.0" @@ -1206,11 +1190,11 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "git2" -version = "0.19.0" +version = "0.13.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" dependencies = [ - "bitflags 2.5.0", + "bitflags 1.3.2", "libc", "libgit2-sys", "log", @@ -1416,7 +1400,7 @@ dependencies = [ [[package]] name = "grin_wallet" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" dependencies = [ "built", "clap", @@ -1438,7 +1422,7 @@ dependencies = [ "remove_dir_all", "rpassword", "rustyline", - "semver", + "semver 0.10.0", "serde", "serde_derive", "serde_json", @@ -1448,7 +1432,7 @@ dependencies = [ [[package]] name = "grin_wallet_api" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" dependencies = [ "base64 0.12.3", "chrono", @@ -1473,7 +1457,7 @@ dependencies = [ [[package]] name = "grin_wallet_config" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" dependencies = [ "dirs 2.0.2", "grin_core", @@ -1488,7 +1472,7 @@ dependencies = [ [[package]] name = "grin_wallet_controller" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" dependencies = [ "chrono", "easy-jsonrpc-mw", @@ -1524,7 +1508,7 @@ dependencies = [ [[package]] name = "grin_wallet_impls" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" dependencies = [ "base64 0.12.3", "blake2-rfc", @@ -1563,7 +1547,7 @@ dependencies = [ [[package]] name = "grin_wallet_libwallet" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" dependencies = [ "age", "base64 0.9.3", @@ -1571,18 +1555,15 @@ dependencies = [ "blake2-rfc", "bs58", "byteorder", - "chacha20", "chrono", "curve25519-dalek 2.1.3", "ed25519-dalek", "grin_core", "grin_keychain", - "grin_secp256k1zkp", "grin_store", "grin_util", "grin_wallet_config", "grin_wallet_util", - "hmac 0.12.1", "lazy_static", "log", "num-bigint", @@ -1592,8 +1573,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "serde_with", - "sha2 0.10.8", + "sha2 0.8.2", "strum", "strum_macros", "thiserror", @@ -1603,7 +1583,7 @@ dependencies = [ [[package]] name = "grin_wallet_util" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" dependencies = [ "data-encoding", "ed25519-dalek", @@ -1918,12 +1898,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.5.0" @@ -2075,9 +2049,9 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libgit2-sys" -version = "0.17.0+1.8.1" +version = "0.12.26+1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" dependencies = [ "cc", "libc", @@ -3544,6 +3518,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", + "serde", +] + [[package]] name = "semver" version = "0.10.0" @@ -3621,29 +3605,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" -dependencies = [ - "chrono", - "serde", - "serde_with_macros", -] - -[[package]] -name = "serde_with_macros" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -dependencies = [ - "darling", - "proc-macro2 1.0.83", - "quote 1.0.36", - "syn 1.0.109", -] - [[package]] name = "serde_yaml" version = "0.8.26" @@ -3656,6 +3617,18 @@ dependencies = [ "yaml-rust 0.4.5", ] +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + [[package]] name = "sha2" version = "0.9.9" diff --git a/Cargo.toml b/Cargo.toml index 2e50aece3..fdd4342f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" authors = ["Grin Developers "] description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." license = "Apache-2.0" @@ -30,21 +30,21 @@ semver = "0.10" rustyline = "6" lazy_static = "1" -grin_wallet_api = { path = "./api", version = "5.4.0-contracts.0" } -grin_wallet_impls = { path = "./impls", version = "5.4.0-contracts.0" } -grin_wallet_libwallet = { path = "./libwallet", version = "5.4.0-contracts.0" } -grin_wallet_controller = { path = "./controller", version = "5.4.0-contracts.0" } -grin_wallet_config = { path = "./config", version = "5.4.0-contracts.0" } -grin_wallet_util = { path = "./util", version = "5.4.0-contracts.0" } +grin_wallet_api = { path = "./api", version = "5.4.0-alpha.1" } +grin_wallet_impls = { path = "./impls", version = "5.4.0-alpha.1" } +grin_wallet_libwallet = { path = "./libwallet", version = "5.4.0-alpha.1" } +grin_wallet_controller = { path = "./controller", version = "5.4.0-alpha.1" } +grin_wallet_config = { path = "./config", version = "5.4.0-alpha.1" } +grin_wallet_util = { path = "./util", version = "5.4.0-alpha.1" } ##### Grin Imports # For Release -# grin_core = "5.4.0-contracts.0" -# grin_keychain = "5.4.0-contracts.0" -# grin_util = "5.4.0-contracts.0" -# grin_api = "5.4.0-contracts.0" +# grin_core = "5.4.0-alpha.1" +# grin_keychain = "5.4.0-alpha.1" +# grin_util = "5.4.0-alpha.1" +# grin_api = "5.4.0-alpha.1" # For beta release @@ -68,7 +68,7 @@ grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" } ###### [build-dependencies] -built = { version = "0.7", features = ["git2"]} +built = { version = "0.4", features = ["git2"]} [dev-dependencies] url = "2.1" diff --git a/api/Cargo.toml b/api/Cargo.toml index 4fcf61762..abbff36d8 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_api" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" authors = ["Grin Developers "] description = "Grin Wallet API" license = "Apache-2.0" @@ -22,17 +22,17 @@ ring = "0.16" base64 = "0.12" ed25519-dalek = "1.0.0-pre.4" -grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-contracts.0" } -grin_wallet_config = { path = "../config", version = "5.4.0-contracts.0" } -grin_wallet_impls = { path = "../impls", version = "5.4.0-contracts.0" } -grin_wallet_util = { path = "../util", version = "5.4.0-contracts.0" } +grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-alpha.1" } +grin_wallet_config = { path = "../config", version = "5.4.0-alpha.1" } +grin_wallet_impls = { path = "../impls", version = "5.4.0-alpha.1" } +grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" } ##### Grin Imports # For Release -# grin_core = "5.4.0-contracts.0" -# grin_keychain = "5.4.0-contracts.0" -# grin_util = "5.4.0-contracts.0" +# grin_core = "5.4.0-alpha.1" +# grin_keychain = "5.4.0-alpha.1" +# grin_util = "5.4.0-alpha.1" # For beta release diff --git a/api/src/foreign.rs b/api/src/foreign.rs index 2d70e6373..77c65754e 100644 --- a/api/src/foreign.rs +++ b/api/src/foreign.rs @@ -17,8 +17,6 @@ use crate::config::TorConfig; use crate::keychain::Keychain; use crate::libwallet::api_impl::foreign; -use crate::libwallet::contract::proofs::InvoiceProof; -use crate::libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; use crate::libwallet::{ BlockFees, CbData, Error, NodeClient, NodeVersionInfo, Slate, VersionInfo, WalletInst, WalletLCProvider, @@ -26,7 +24,6 @@ use crate::libwallet::{ use crate::try_slatepack_sync_workflow; use crate::util::secp::key::SecretKey; use crate::util::Mutex; -use ed25519_dalek::PublicKey as DalekPublicKey; use std::sync::Arc; /// ForeignAPI Middleware Check callback @@ -453,43 +450,6 @@ where post_automatically, ) } - - // Below is a foreign wrapper around owner calls to 'new' and 'sign' which are only executed - // if this is a receiving contract. This preserves the ability to receive on a foreign interface. - /// TODO - pub fn contract_new( - &self, - keychain_mask: Option<&SecretKey>, - args: &ContractNewArgsAPI, - ) -> Result { - let mut w_lock = self.wallet_inst.lock(); - let w = w_lock.lc_provider()?.wallet_inst()?; - // TODO: self.doctest_mode ? - foreign::contract_new(&mut **w, keychain_mask, &args) - } - - /// TODO - pub fn contract_sign( - &self, - keychain_mask: Option<&SecretKey>, - slate: &Slate, - args: &ContractSetupArgsAPI, - ) -> Result { - let mut w_lock = self.wallet_inst.lock(); - let w = w_lock.lc_provider()?.wallet_inst()?; - foreign::contract_sign(&mut **w, keychain_mask, &args, &slate) - } - - /// TODO - pub fn verify_payment_proof_invoice( - &self, - recipient_address: &DalekPublicKey, - proof: &InvoiceProof, - ) -> Result<(), Error> { - let mut w_lock = self.wallet_inst.lock(); - let w = w_lock.lc_provider()?.wallet_inst()?; - foreign::verify_payment_proof_invoice(&mut **w, recipient_address, proof) - } } #[doc(hidden)] diff --git a/api/src/foreign_rpc.rs b/api/src/foreign_rpc.rs index 9c89b4e26..1d2b526a0 100644 --- a/api/src/foreign_rpc.rs +++ b/api/src/foreign_rpc.rs @@ -52,7 +52,6 @@ pub trait ForeignRpc { "Ok": { "foreign_api_version": 2, "supported_slate_versions": [ - "V5", "V4" ] } @@ -142,7 +141,7 @@ pub trait ForeignRpc { } ], "sta": "S1", - "ver": "5:2" + "ver": "4:2" }, null, null @@ -177,7 +176,7 @@ pub trait ForeignRpc { } ], "sta": "S2", - "ver": "5:2" + "ver": "4:2" } } } @@ -206,7 +205,7 @@ pub trait ForeignRpc { "method": "finalize_tx", "id": 1, "params": [{ - "ver": "5:2", + "ver": "4:2", "id": "0436430c-2b02-624c-2032-570501212b00", "sta": "I2", "off": "383bc9df0dd332629520a0a72f8dd7f0e97d579dccb4dbdc8592aa3d424c846c", @@ -276,7 +275,7 @@ pub trait ForeignRpc { } ], "sta": "I3", - "ver": "5:2" + "ver": "4:2" } } } diff --git a/api/src/owner.rs b/api/src/owner.rs index 2c5215c76..f27aaa1fb 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -16,7 +16,6 @@ use chrono::prelude::*; use ed25519_dalek::SecretKey as DalekSecretKey; -use grin_wallet_libwallet::contract::proofs::InvoiceProof; use grin_wallet_libwallet::RetrieveTxQueryArgs; use uuid::Uuid; @@ -28,10 +27,6 @@ use crate::impls::SlateSender as _; use crate::keychain::{Identifier, Keychain}; use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage}; use crate::libwallet::api_impl::{owner, owner_updater}; -use crate::libwallet::contract::types::{ - ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI, -}; -use crate::libwallet::mwmixnet::types::{MixnetReqCreationParams, SwapReq}; use crate::libwallet::{ AcctPathMapping, BuiltOutput, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult, OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress, @@ -771,78 +766,6 @@ where owner::issue_invoice_tx(&mut **w, keychain_mask, args, self.doctest_mode) } - /// TODO - pub fn contract_new( - &self, - keychain_mask: Option<&SecretKey>, - args: &ContractNewArgsAPI, - ) -> Result { - let mut w_lock = self.wallet_inst.lock(); - let w = w_lock.lc_provider()?.wallet_inst()?; - // TODO: self.doctest_mode ? - owner::contract_new(&mut **w, keychain_mask, &args) - } - - // /// TODO - // pub fn contract_setup( - // &self, - // keychain_mask: Option<&SecretKey>, - // slate: &Slate, - // args: &ContractSetupArgsAPI, - // ) -> Result { - // let mut w_lock = self.wallet_inst.lock(); - // let w = w_lock.lc_provider()?.wallet_inst()?; - // // TODO: self.doctest_mode ? - // owner::contract_setup(&mut **w, keychain_mask, &args, &slate) - // } - - /// TODO - pub fn contract_sign( - &self, - keychain_mask: Option<&SecretKey>, - slate: &Slate, - args: &ContractSetupArgsAPI, - ) -> Result { - let mut w_lock = self.wallet_inst.lock(); - let w = w_lock.lc_provider()?.wallet_inst()?; - owner::contract_sign(&mut **w, keychain_mask, &args, &slate) - } - - /// TODO - pub fn get_slate_index_matching_my_context( - &self, - keychain_mask: Option<&SecretKey>, - slate: &Slate, - ) -> Result { - let mut w_lock = self.wallet_inst.lock(); - let w = w_lock.lc_provider()?.wallet_inst()?; - owner::get_slate_index_matching_my_context(&mut **w, keychain_mask, &slate) - } - - /// TODO - pub fn contract_revoke( - &self, - keychain_mask: Option<&SecretKey>, - args: &ContractRevokeArgsAPI, - ) -> Result, Error> { - let mut w_lock = self.wallet_inst.lock(); - let w = w_lock.lc_provider()?.wallet_inst()?; - owner::contract_revoke(&mut **w, keychain_mask, &args) - } - - /// Create MXMixnet request - pub fn create_mwmixnet_req( - &self, - keychain_mask: Option<&SecretKey>, - params: &MixnetReqCreationParams, - slate: &Slate, - // use_test_rng: bool, - ) -> Result { - let mut w_lock = self.wallet_inst.lock(); - let w = w_lock.lc_provider()?.wallet_inst()?; - owner::create_mwmixnet_req(&mut **w, keychain_mask, params, slate) - } - /// Processes an invoice tranaction created by another party, essentially /// a `request for payment`. The incoming slate should contain a requested /// amount, an output created by the invoicer convering the amount, and @@ -2432,32 +2355,6 @@ where ) } - /// TODO: Temporary, likely should merge with above - pub fn retrieve_payment_proof_invoice( - &self, - keychain_mask: Option<&SecretKey>, - refresh_from_node: bool, - tx_id: Option, - tx_slate_id: Option, - ) -> Result { - let tx = { - let t = self.status_tx.lock(); - t.clone() - }; - let refresh_from_node = match self.updater_running.load(Ordering::Relaxed) { - true => false, - false => refresh_from_node, - }; - owner::retrieve_payment_proof_invoice( - self.wallet_inst.clone(), - keychain_mask, - &tx, - refresh_from_node, - tx_id, - tx_slate_id, - ) - } - /// Verifies a [PaymentProof](../grin_wallet_libwallet/api_impl/types/struct.PaymentProof.html) /// This process entails: /// diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index 18a25cc22..23fc6b63a 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -20,9 +20,6 @@ use crate::config::{TorConfig, WalletConfig}; use crate::core::core::OutputFeatures; use crate::core::global; use crate::keychain::{Identifier, Keychain}; -use crate::libwallet::contract::types::{ - ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI, -}; use crate::libwallet::{ AcctPathMapping, Amount, BuiltOutput, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult, OutputCommitMapping, PaymentProof, Slate, SlateVersion, Slatepack, @@ -504,7 +501,7 @@ pub trait OwnerRpc { } ], "sta": "S1", - "ver": "5:2" + "ver": "4:2" } } } @@ -513,49 +510,8 @@ pub trait OwnerRpc { ``` */ - /** - TODO: Full docs once API has stabilised - */ fn init_send_tx(&self, token: Token, args: InitTxArgs) -> Result; - /** - TODO: Implement - */ - // fn contract_setup( - // &self, - // token: Token, - // slate: VersionedSlate, - // args: ContractSetupArgsAPI, - // ) -> Result; - - /** - TODO: Full docs once API has stabilised - */ - - fn contract_new(&self, token: Token, args: ContractNewArgsAPI) - -> Result; - - /** - TODO: Full docs once API has stabilised - */ - - fn contract_sign( - &self, - token: Token, - slate: VersionedSlate, - args: ContractSetupArgsAPI, - ) -> Result; - - /** - TODO: Full docs once API has stabilised - */ - - fn contract_revoke( - &self, - token: Token, - args: ContractRevokeArgsAPI, - ) -> Result, Error>; - /** ;Networked version of [Owner::issue_invoice_tx](struct.Owner.html#method.issue_invoice_tx). @@ -592,7 +548,7 @@ pub trait OwnerRpc { } ], "sta": "I1", - "ver": "5:2" + "ver": "4:2" } } } @@ -963,7 +919,7 @@ pub trait OwnerRpc { "id": "0436430c-2b02-624c-2032-570501212b00", "sigs": [], "sta": "S3", - "ver": "5:3" + "ver": "4:3" } } } @@ -2096,65 +2052,6 @@ where VersionedSlate::into_version(slate, version) } - fn contract_new( - &self, - token: Token, - args: ContractNewArgsAPI, - ) -> Result { - let slate = Owner::contract_new(self, (&token.keychain_mask).as_ref(), &args)?; - let version = SlateVersion::V4; - VersionedSlate::into_version(slate, version) - } - - // fn contract_setup( - // &self, - // token: Token, - // in_slate: VersionedSlate, - // args: ContractSetupArgsAPI, - // ) -> Result { - // let slate = Owner::contract_setup( - // self, - // (&token.keychain_mask).as_ref(), - // &Slate::from(in_slate), - // &args, - // )?; - // let version = SlateVersion::V4; - // VersionedSlate::into_version(slate, version) - // } - - fn contract_sign( - &self, - token: Token, - in_slate: VersionedSlate, - args: ContractSetupArgsAPI, - ) -> Result { - let slate = Owner::contract_sign( - self, - (&token.keychain_mask).as_ref(), - &Slate::from(in_slate), - &args, - )?; - let version = SlateVersion::V4; - VersionedSlate::into_version(slate, version) - } - - fn contract_revoke( - &self, - token: Token, - args: ContractRevokeArgsAPI, - ) -> Result, Error> { - let slate_opt = Owner::contract_revoke(self, (&token.keychain_mask).as_ref(), &args)?; - let version = SlateVersion::V4; - // We return a slate only when we had to perform a self-spend safe cancel - if slate_opt.is_some() { - return Ok(Some(VersionedSlate::into_version( - slate_opt.unwrap(), - version, - )?)); - } - Ok(None) - } - fn issue_invoice_tx( &self, token: Token, diff --git a/config/Cargo.toml b/config/Cargo.toml index 9d956f804..7adc768dc 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_config" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" authors = ["Grin Developers "] description = "Configuration for grin wallet , a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." license = "Apache-2.0" @@ -16,13 +16,13 @@ serde_derive = "1" toml = "0.5" dirs = "2.0" -grin_wallet_util = { path = "../util", version = "5.4.0-contracts.0" } +grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" } ##### Grin Imports # For Release -# grin_core = "5.4.0-contracts.0" -# grin_util = "5.4.0-contracts.0" +# grin_core = "5.4.0-alpha.1" +# grin_util = "5.4.0-alpha.1" # For beta release diff --git a/config/src/types.rs b/config/src/types.rs index f7266f00b..6a57c5428 100644 --- a/config/src/types.rs +++ b/config/src/types.rs @@ -28,9 +28,6 @@ pub struct WalletConfig { pub chain_type: Option, /// The port this wallet will run on pub api_listen_port: u16, - /// Listen interface for the owner API, should be hidden from config by default - #[serde(skip_serializing)] - pub owner_api_listen_interface: Option, /// The port this wallet's owner API will run on pub owner_api_listen_port: Option, /// Location of the secret for basic auth on the Owner API @@ -64,7 +61,6 @@ impl Default for WalletConfig { WalletConfig { chain_type: Some(ChainTypes::Mainnet), api_listen_port: 3415, - owner_api_listen_interface: Some(WalletConfig::default_owner_api_listen_interface()), owner_api_listen_port: Some(WalletConfig::default_owner_api_listen_port()), api_secret_path: Some(".owner_api_secret".to_string()), node_api_secret_path: Some(".foreign_api_secret".to_string()), @@ -86,11 +82,6 @@ impl WalletConfig { format!("127.0.0.1:{}", self.api_listen_port) } - /// Default listener port - pub fn default_owner_api_listen_interface() -> String { - "127.0.0.1".to_string() - } - /// Default listener port pub fn default_owner_api_listen_port() -> u16 { 3420 @@ -101,13 +92,6 @@ impl WalletConfig { 500_000 } - /// Use value from config file, defaulting to sensible value if missing. - pub fn owner_api_listen_interface(&self) -> String { - self.owner_api_listen_interface - .clone() - .unwrap_or_else(|| WalletConfig::default_owner_api_listen_interface()) - } - /// Use value from config file, defaulting to sensible value if missing. pub fn owner_api_listen_port(&self) -> u16 { self.owner_api_listen_port @@ -116,11 +100,7 @@ impl WalletConfig { /// Owner API listen address pub fn owner_api_listen_addr(&self) -> String { - format!( - "{}:{}", - self.owner_api_listen_interface(), - self.owner_api_listen_port() - ) + format!("127.0.0.1:{}", self.owner_api_listen_port()) } /// Accept fee base diff --git a/controller/Cargo.toml b/controller/Cargo.toml index 7973f007b..c3185ef40 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_controller" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" authors = ["Grin Developers "] description = "Controllers for grin wallet instantiation" license = "Apache-2.0" @@ -30,19 +30,19 @@ lazy_static = "1" thiserror = "1" qr_code = "1.1.0" -grin_wallet_util = { path = "../util", version = "5.4.0-contracts.0" } -grin_wallet_api = { path = "../api", version = "5.4.0-contracts.0" } -grin_wallet_impls = { path = "../impls", version = "5.4.0-contracts.0" } -grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-contracts.0" } -grin_wallet_config = { path = "../config", version = "5.4.0-contracts.0" } +grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" } +grin_wallet_api = { path = "../api", version = "5.4.0-alpha.1" } +grin_wallet_impls = { path = "../impls", version = "5.4.0-alpha.1" } +grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-alpha.1" } +grin_wallet_config = { path = "../config", version = "5.4.0-alpha.1" } ##### Grin Imports # For Release -# grin_core = "5.4.0-contracts.0" -# grin_keychain = "5.4.0-contracts.0" -# grin_util = "5.4.0-contracts.0" -# grin_api = "5.4.0-contracts.0" +# grin_core = "5.4.0-alpha.1" +# grin_keychain = "5.4.0-alpha.1" +# grin_util = "5.4.0-alpha.1" +# grin_api = "5.4.0-alpha.1" # For beta release @@ -72,7 +72,7 @@ remove_dir_all = "0.7" ##### Grin Imports # For Release -# grin_chain = "5.4.0-contracts.0" +# grin_chain = "5.4.0-alpha.1" # For beta release diff --git a/controller/src/command.rs b/controller/src/command.rs index 1c5b02676..938eb8291 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -13,6 +13,7 @@ // limitations under the License. //! Grin wallet command-line function implementations + use crate::api::TLSConfig; use crate::apiwallet::{try_slatepack_sync_workflow, Owner}; use crate::config::{TorConfig, WalletConfig, WALLET_CONFIG_FILE_NAME}; @@ -21,11 +22,6 @@ use crate::error::Error; use crate::impls::PathToSlatepack; use crate::impls::SlateGetter as _; use crate::keychain; -use crate::libwallet::api_impl::owner; -use crate::libwallet::contract::can_finalize; -use crate::libwallet::contract::types::{ - ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs, -}; use crate::libwallet::{ self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState, Slatepack, SlatepackAddress, Slatepacker, SlatepackerArgs, WalletLCProvider, @@ -35,12 +31,9 @@ use crate::util::{Mutex, ZeroingString}; use crate::{controller, display}; use ::core::time; use qr_code::QrCode; -use serde::{Deserialize, Serialize}; use serde_json as json; use std::convert::TryFrom; -use std::fmt; use std::fs::File; -use std::io; use std::io::{Read, Write}; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -565,140 +558,6 @@ where Ok(()) } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct SlatepackOut { - /// Is slatepack encrypted - pub is_encrypted: bool, - /// Is slatepack finalized - pub is_finalized: bool, - /// File where slatepack is saved - pub out_file: String, - /// Slatepack message. Encrypted or not. - pub message: String, -} - -impl fmt::Display for SlatepackOut { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let start_meta = "--------------- SLATEPACK METADATA --------------"; - let meta = format!( - "Slate encrypted: {}\nSlate finalized: {}\nSlate saved to file: {}", - self.is_encrypted, self.is_finalized, self.out_file - ); - let start_slatepack = "-------------- CUT BELOW THIS LINE --------------"; - let end_slatepack = "-------------- CUT ABOVE THIS LINE --------------"; - write!( - f, - "{start_meta}\n\n{meta}\n\n{start_slatepack}\n\n{}\n\n{end_slatepack}", - self.message - ) - } -} - -impl SlatepackOut { - fn as_json(&self) -> String { - serde_json::to_string_pretty(&self).unwrap() - } - - pub fn print(&self, as_json: bool) -> () { - if !self.is_finalized { - if as_json { - println!("{}", self.as_json()); - } else { - println!("{}", self); - } - } else { - println!("Transaction was broadcasted."); // TODO: as_json makes no sense here, fix later. - } - } -} - -pub fn print_slatepack( - api: &mut Owner, - keychain_mask: Option<&SecretKey>, - slate: &Slate, - counterparty_addr: &str, - out_file: Option, - as_json: bool, -) -> () -where - L: WalletLCProvider<'static, C, K> + 'static, - C: NodeClient + 'static, - K: keychain::Keychain + 'static, -{ - // For now, we don't compact slates with sl.compact(). We first make them work without compaction. - let slate_out = - prepare_slatepack(api, keychain_mask, &slate, &counterparty_addr, out_file).unwrap(); - slate_out.print(as_json); -} - -pub fn prepare_slatepack( - owner_api: &mut Owner, - keychain_mask: Option<&SecretKey>, - slate: &Slate, - dest: &str, - out_file_override: Option, -) -> Result -where - L: WalletLCProvider<'static, C, K> + 'static, - C: NodeClient + 'static, - K: keychain::Keychain + 'static, -{ - // Same as output_slatepack except that we don't write to stdout, care about locking or whether the slate was finalized. - - // Output the slatepack file to stdout and to a file - let mut message = String::from(""); - let mut address = None; - let mut tld = String::from(""); - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - address = match SlatepackAddress::try_from(dest) { - Ok(a) => Some(a), - Err(_) => None, - }; - // encrypt for recipient by default - let recipients = match address.clone() { - Some(a) => vec![a], - None => vec![], - }; - // TODO: what is sender_index? - message = api.create_slatepack_message(m, &slate, Some(0), recipients)?; - // Trim the \n at the end. - let len_withoutcrlf = message.trim_end().len(); - message.truncate(len_withoutcrlf); - - tld = api.get_top_level_directory()?; - Ok(()) - })?; - - // create a directory to which files will be output - let slate_dir = format!("{}/{}", tld, "slatepack"); - let _ = std::fs::create_dir_all(slate_dir.clone()); - let out_file_name = match out_file_override { - None => format!("{}/{}.{}.slatepack", slate_dir, slate.id, slate.state), - Some(f) => f, - }; - - let mut output = File::create(out_file_name.clone())?; - output.write_all(&message.as_bytes())?; - output.sync_all()?; - - // Since we always finalize if we can, we can also use this to know if the tx is finalized - let is_finalized = can_finalize(slate); - - let slate_out = SlatepackOut { - is_encrypted: address.is_some(), - is_finalized: is_finalized, - out_file: out_file_name, - message: message, - }; - - // TODO: We save the slatepack, but it is encrypted for the counterparty. It seems hard to - // know which slatepack is which if we can't decrypt them. Either add some more metadata - // to slatepacks e.g. timestamp, counterparty address or save also a version that is encrypted with - // our own address so we can view it. - - Ok(slate_out) -} - // Parse a slate and slatepack from a message pub fn parse_slatepack( owner_api: &mut Owner, @@ -1615,341 +1474,3 @@ where })?; Ok(()) } - -/// Create new contract command arguments -#[derive(Clone)] -pub struct ContractNewArgs { - /// Address of the counterparty - pub counterparty_addr: String, - /// Receive amount - pub receive: Option, - /// Send amount - pub send: Option, - /// The human readable account name from which to draw outputs - /// for the transaction, overriding whatever the active account is as set via the - /// [`set_active_account`](../grin_wallet_api/owner/struct.Owner.html#method.set_active_account) method. - pub src_acct_name: Option, - /// Number of participants in a contract (either 1 or 2) - pub num_participants: u8, - /// Show the resulting slatepack as JSON - pub as_json: bool, - /// Use the specified inputs (comma separated input commitments) - pub use_inputs: Option, - /// How to separate outputs (command separated amounts) - pub make_outputs: Option, - - // Future features - /// Custom fee contribution - pub fee_rate: Option, - /// Save slatepack to a specific filename - pub outfile: Option, - /// Select outputs early - pub add_outputs: bool, -} - -impl ContractNewArgs { - fn get_net_change(&self) -> i64 { - // TODO: could the cast 'as i64' overflow or something? - match self.receive { - None => match self.send { - None => panic!("Send or receive not specified."), - Some(v) => -(v as i64), // negative net change on send - }, - Some(v) => v as i64, // positive net change on receive - } - } - - // Create a ContractNewArgsAPI from the ContractNewArgs - fn to_api_args(&self) -> ContractNewArgsAPI { - let net_change = self.get_net_change(); - ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - src_acct_name: match self.src_acct_name.as_ref() { - Some(v) => Some(v.to_string()), - None => None, - }, - net_change: Some(net_change), - num_participants: self.num_participants, - add_outputs: self.add_outputs, - selection_args: OutputSelectionArgs { - use_inputs: match self.use_inputs.as_ref() { - Some(v) => Some(v.to_string()), - None => None, - }, - make_outputs: match self.make_outputs.as_ref() { - Some(v) => Some(v.to_string()), - None => None, - }, - ..Default::default() - }, - proof_args: Default::default(), - }, - ..Default::default() - } - } -} - -pub fn contract_new( - owner_api: &mut Owner, - keychain_mask: Option<&SecretKey>, - args: ContractNewArgs, -) -> Result<(), Error> -where - L: WalletLCProvider<'static, C, K>, - C: NodeClient + 'static, - K: keychain::Keychain + 'static, -{ - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let contract_new_args = args.to_api_args(); - - let slate = api.contract_new(m, &contract_new_args)?; - - print_slatepack( - api, - keychain_mask, - &slate, - &args.counterparty_addr, - args.outfile, - args.as_json, - ); - - Ok(()) - })?; - - Ok(()) -} - -/// Sign contract command argument -#[derive(Clone)] -pub struct ContractSetupArgs { - /// Address of the counterparty - pub counterparty_addr: Option, - /// Receive amount - pub receive: Option, - /// Send amount - pub send: Option, - /// Show the resulting slatepack as JSON - pub as_json: bool, - /// Use the specified inputs (comma separated input commitments) - pub use_inputs: Option, - /// How to separate outputs (command separated amounts) - pub make_outputs: Option, - - // Future features - /// Whether we should automatically sign a receive of any value - // pub auto_receive: Option, - /// Custom fee contribution - pub fee_rate: Option, - /// Save slatepack to a specific filename - pub outfile: Option, - /// Add outputs - pub add_outputs: bool, // lock outputs early -} - -impl ContractSetupArgs { - fn get_net_change(&self) -> Option { - let mut net_change: Option = None; - // TODO: Check bounds before casting to i64. - if self.receive.is_some() && self.send.is_some() { - panic!("Can't pass both --receive and --send parameters."); - } - if self.receive.is_some() { - net_change = Some(self.receive.unwrap() as i64); - } - if self.send.is_some() { - net_change = Some(-(self.send.unwrap() as i64)); - } - net_change - } - - // Create a ContractSetupArgsAPI from the ContractSetupArgs - fn to_api_args(&self) -> ContractSetupArgsAPI { - let net_change = self.get_net_change(); - ContractSetupArgsAPI { - // TODO: num_participants is derived here. It should be an Option and read from the slate. - // Need to check no attack are possible regarding kernel fee contribution. - net_change: net_change, - add_outputs: self.add_outputs, - selection_args: OutputSelectionArgs { - use_inputs: match self.use_inputs.as_ref() { - Some(v) => Some(v.to_string()), - None => None, - }, - make_outputs: match self.make_outputs.as_ref() { - Some(v) => Some(v.to_string()), - None => None, - }, - ..Default::default() - }, - ..Default::default() - } - } -} - -// pub fn contract_setup( -// owner_api: &mut Owner, -// keychain_mask: Option<&SecretKey>, -// args: ContractSetupArgs, -// ) -> Result<(), Error> -// where -// L: WalletLCProvider<'static, C, K>, -// C: NodeClient + 'static, -// K: keychain::Keychain + 'static, -// { -// controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { -// // Read the slatepack from stdin -// println!("Paste slatepack:"); -// let mut slatepack_msg = String::new(); -// io::stdin() -// .read_line(&mut slatepack_msg) -// .expect("Failed to read from stdin"); - -// let mut contract_setup_args = args.to_api_args(); - -// // Decrypt the slate, perform setup on it and encrypt it for the next party -// // TODO: Make sure you get the counterparty_addr and slate with 1 call. -// let slatepack = owner::decode_slatepack_message( -// api.wallet_inst.clone(), -// keychain_mask, -// String::from(slatepack_msg.clone()), -// vec![0], -// )?; - -// let counterparty_addr = -// if args.counterparty_addr.is_some() { -// args.counterparty_addr.unwrap() -// } else { -// if !slatepack.sender.is_some() { -// panic!("No address to encrypt for. Contracts only support encrypted slates right now."); -// } -// String::try_from(&slatepack.sender.unwrap())? -// }; -// let mut slate = owner::slate_from_slatepack_message( -// api.wallet_inst.clone(), -// keychain_mask, -// String::from(slatepack_msg), -// vec![0], -// )?; -// // We read the number of participants from the slate that was already created. We need this to -// // avoid taking the default value of 2 in case of 3-party computation and thus incorrectly computing -// // our kernel cost contribution. -// contract_setup_args.num_participants = slate.num_participants; - -// slate = api.contract_setup(m, &slate, &contract_setup_args)?; - -// print_slatepack( -// api, -// keychain_mask, -// &slate, -// &counterparty_addr, -// args.outfile, -// args.as_json, -// ); - -// Ok(()) -// })?; - -// Ok(()) -// } - -pub fn contract_sign( - owner_api: &mut Owner, - keychain_mask: Option<&SecretKey>, - args: ContractSetupArgs, - broadcast_tx: bool, -) -> Result<(), Error> -where - L: WalletLCProvider<'static, C, K>, - C: NodeClient + 'static, - K: keychain::Keychain + 'static, -{ - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - // Read the slatepack from stdin - println!("Paste slatepack:"); - let mut slatepack_msg = String::new(); - io::stdin() - .read_line(&mut slatepack_msg) - .expect("Failed to read from stdin"); - - // Args for signing are just setup args - let contract_sign_args = args.to_api_args(); - - // Decrypt the slate, sign it and encrypt it for the next party - // TODO: Make sure you get the counterparty_addr and slate with 1 call. - let slatepack = owner::decode_slatepack_message( - api.wallet_inst.clone(), - keychain_mask, - String::from(slatepack_msg.clone()), - vec![0], - )?; - - let counterparty_addr = - if args.counterparty_addr.is_some() { - args.counterparty_addr.unwrap() - } else { - if !slatepack.sender.is_some() { - panic!("No address to encrypt for. Contracts only support encrypted slates right now."); - } - String::try_from(&slatepack.sender.unwrap())? - }; - let mut slate = owner::slate_from_slatepack_message( - api.wallet_inst.clone(), - keychain_mask, - String::from(slatepack_msg), - vec![0], - )?; - - slate = api.contract_sign(m, &slate, &contract_sign_args)?; - - print_slatepack( - api, - keychain_mask, - &slate, - &counterparty_addr, - args.outfile, - args.as_json, - ); - - if broadcast_tx { - let is_finalized = can_finalize(&slate); - if is_finalized { - api.post_tx(keychain_mask, &slate, true)?; - } - } - - Ok(()) - })?; - - Ok(()) -} - -#[derive(Clone)] -pub struct ContractRevokeArgs { - /// Id of a transaction we want to cancel - pub tx_id: u32, -} - -pub fn contract_revoke( - owner_api: &mut Owner, - keychain_mask: Option<&SecretKey>, - args: ContractRevokeArgs, -) -> Result<(), Error> -where - L: WalletLCProvider<'static, C, K>, - C: NodeClient + 'static, - K: keychain::Keychain + 'static, -{ - controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let slate_opt = api.contract_revoke(m, &ContractRevokeArgsAPI { tx_id: args.tx_id })?; - // TODO: replace dest="nope" with our own address and add --as-json support - if slate_opt.is_some() { - let slate_out = - prepare_slatepack(api, keychain_mask, &slate_opt.unwrap(), "nope", None)?; - println!("{}", slate_out); - } - - Ok(()) - })?; - - Ok(()) -} diff --git a/controller/tests/common/mod.rs b/controller/tests/common/mod.rs index e015c5c8f..114667a0d 100644 --- a/controller/tests/common/mod.rs +++ b/controller/tests/common/mod.rs @@ -15,24 +15,18 @@ extern crate grin_wallet_controller as wallet; extern crate grin_wallet_impls as impls; extern crate grin_wallet_libwallet as libwallet; -extern crate log; -use grin_chain as chain; use grin_core as core; use grin_keychain as keychain; use grin_util as util; use self::core::global; use self::core::global::ChainTypes; -use self::keychain::{ExtKeychain, Keychain}; +use self::keychain::ExtKeychain; use self::libwallet::WalletInst; -use chain::Chain; -use grin_wallet_controller::Error; -use impls::test_framework::{self, LocalWalletClient, WalletProxy}; +use impls::test_framework::{LocalWalletClient, WalletProxy}; use impls::{DefaultLCProvider, DefaultWalletImpl}; -use std::sync::atomic::AtomicBool; use std::sync::Arc; -use std::thread; use util::secp::key::SecretKey; use util::{Mutex, ZeroingString}; @@ -182,133 +176,3 @@ pub fn open_local_wallet( .unwrap(); (Arc::new(Mutex::new(wallet)), mask) } - -// Creates the given number of wallets and spawns a thread that runs the wallet proxy -#[allow(dead_code)] -pub fn create_wallets( - wallets_def: Vec>, // a vector of boolean that represent whether we mine into a wallet - test_dir: &'static str, -) -> Result< - ( - Vec<( - Arc< - Mutex< - Box< - dyn WalletInst< - 'static, - DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, - LocalWalletClient, - ExtKeychain, - >, - >, - >, - >, - Option, - )>, // wallets - Arc, // chain - Arc, // stopper - u64, // block height - ), - Error, -> { - // Create a new proxy to simulate server and wallet responses - let mut wallet_proxy = create_wallet_proxy(test_dir); - let chain = wallet_proxy.chain.clone(); - let stopper = wallet_proxy.running.clone(); - - let mut wallets = vec![]; - for i in 0..wallets_def.len() { - let name = format!("wallet{}", i + 1); - let wclient = LocalWalletClient::new(&name, wallet_proxy.tx.clone()); - let (wallet1, mask1) = create_local_wallet( - test_dir, - &name, - None, - // $seed_phrase.clone(), - wclient.clone(), - true, - ); - wallet_proxy.add_wallet( - &name, - wclient.get_send_instance(), - wallet1.clone(), - mask1.clone(), - ); - wallets.push((wallet1, mask1)); - } - // Set the wallet proxy listener running - thread::spawn(move || { - if let Err(e) = wallet_proxy.run() { - log::error!("Wallet Proxy error: {}", e); - } - }); - - // // Mine values into wallets - // // few values to keep things shorter - let reward = core::consensus::REWARD; - let mut bh = 0u64; - - for (idx, accs) in wallets_def.iter().enumerate() { - let wallet1 = wallets[idx].0.clone(); - let mask1 = wallets[idx].1.as_ref(); - - for (acc_idx, (acc_name, num_mined_blocks)) in accs.iter().enumerate() { - // create the account - if acc_name.to_string() != "default" { - wallet::controller::owner_single_use( - Some(wallet1.clone()), - mask1, - None, - |api, m| { - let new_path = api.create_account_path(m, acc_name)?; - assert_eq!( - new_path, - ExtKeychain::derive_key_id(2, acc_idx as u32, 0, 0, 0) // NOTE: default should always be at 0 and is already created - ); - Ok(()) - }, - )?; - } - - // Get some mining done - if *num_mined_blocks == 0 { - continue; - } - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name(acc_name)?; - } - let _ = test_framework::award_blocks_to_wallet( - &chain, - wallet1.clone(), - mask1, - *num_mined_blocks as usize, - false, - ); - bh += num_mined_blocks; - - // Sanity check wallet 1 contents - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, num_mined_blocks * reward); - Ok(()) - })?; - } - - // Sanity check the number of accounts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let accounts = api.accounts(m)?; - assert_eq!(accounts.len(), accs.len()); - Ok(()) - })?; - // Set the account on the wallet to "default" - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name("default")?; - } - } - - Ok((wallets, chain, stopper, bh)) -} diff --git a/controller/tests/contract/mod.rs b/controller/tests/contract/mod.rs deleted file mode 100644 index f0ca31a6f..000000000 --- a/controller/tests/contract/mod.rs +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright 2022 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Test contract utils -#[macro_use] -extern crate log; -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; - -use grin_wallet_libwallet as libwallet; -use grin_wallet_util::grin_core as core; -use grin_wallet_util::grin_keychain as keychain; -use grin_wallet_util::grin_util as util; - -use self::keychain::ExtKeychain; -use self::libwallet::WalletInst; -// use impls::test_framework::{LocalWalletClient, WalletProxy}; -use crate::chain::Chain; -use grin_wallet_util::grin_chain as chain; -use impls::{DefaultLCProvider, DefaultWalletImpl}; -use std::sync::Arc; -use util::secp::key::SecretKey; -use util::{Mutex, ZeroingString}; - -use impls::test_framework::{self, LocalWalletClient, WalletProxy}; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; -use libwallet::{Slate, SlateState}; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -// #[macro_use] -mod common; -use common::{clean_output_dir, create_local_wallet, create_wallet_proxy, setup}; - -pub fn create_wallets( - test_dir: &'static str, -) -> ( - Vec<( - Arc< - Mutex< - Box< - dyn WalletInst< - 'static, - DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, - LocalWalletClient, - ExtKeychain, - >, - >, - >, - >, - Option, - )>, - Arc, // chain - Arc, // stopper -) { - // Create a new proxy to simulate server and wallet responses - let mut wallet_proxy = common::create_wallet_proxy(test_dir); - let chain = wallet_proxy.chain.clone(); - let stopper = wallet_proxy.running.clone(); - - let mut wallets = vec![]; - for i in 0..2 { - let name = format!("wallet{}", i + 1); - let wclient = LocalWalletClient::new(&name, wallet_proxy.tx.clone()); - let (wallet1, mask1) = common::create_local_wallet( - test_dir, - &name, - None, - // $seed_phrase.clone(), - wclient.clone(), - true, - ); - wallet_proxy.add_wallet( - &name, - wclient.get_send_instance(), - wallet1.clone(), - mask1.clone(), - ); - // create_wallet_and_add!( - // client1, - // wallet1, - // mask1_i, - // test_dir, - // "wallet1", - // None, - // &mut wallet_proxy, - // true - // ); - wallets.push((wallet1, mask1)); - } - // Set the wallet proxy listener running - thread::spawn(move || { - if let Err(e) = wallet_proxy.run() { - error!("Wallet Proxy error: {}", e); - } - }); - (wallets, chain, stopper) - - // let $client = LocalWalletClient::new($name, $proxy.tx.clone()); - // let ($wallet, $mask) = common::create_local_wallet( - // $test_dir, - // $name, - // $seed_phrase.clone(), - // $client.clone(), - // $create_mask, - // ); - // $proxy.add_wallet( - // $name, - // $client.get_send_instance(), - // $wallet.clone(), - // $mask.clone(), - // ); -} - -// #[macro_export] -// macro_rules! create_wallets { -// ($client:ident, $wallet: ident, $mask: ident, $test_dir: expr, $name: expr, $seed_phrase: expr, $proxy: expr, $create_mask: expr) => { -// // Create a new proxy to simulate server and wallet responses -// let mut wallet_proxy = create_wallet_proxy(test_dir); -// let chain = wallet_proxy.chain.clone(); -// let stopper = wallet_proxy.running.clone(); - -// let rv = vec![]; -// for i in 0..wallets.len() { -// let name = format!("wallet{}", i + 1); -// let wclient = LocalWalletClient::new(name, wallet_proxy.tx.clone()); -// let (wallet1, mask1) = common::create_local_wallet( -// $test_dir, -// name, -// None, -// // $seed_phrase.clone(), -// wallet_proxy.clone(), -// true, -// ); -// wallet_proxy.add_wallet( -// name, -// wclient.get_send_instance(), -// wallet1.clone(), -// mask1.clone(), -// ); -// // create_wallet_and_add!( -// // client1, -// // wallet1, -// // mask1_i, -// // test_dir, -// // "wallet1", -// // None, -// // &mut wallet_proxy, -// // true -// // ); -// rv.push((wallet1, mask1)); -// } -// rv - -// // let $client = LocalWalletClient::new($name, $proxy.tx.clone()); -// // let ($wallet, $mask) = common::create_local_wallet( -// // $test_dir, -// // $name, -// // $seed_phrase.clone(), -// // $client.clone(), -// // $create_mask, -// // ); -// // $proxy.add_wallet( -// // $name, -// // $client.get_send_instance(), -// // $wallet.clone(), -// // $mask.clone(), -// // ); -// }; -// } - -// prepare wallets -// fn create_wallets( -// wallets: Vec, -// test_dir: &'static str, -// // wallet_proxy: WalletProxy< -// // 'static, -// // DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, -// // LocalWalletClient, -// // ExtKeychain, -// // >, -// ) -> Vec<( -// Arc< -// Mutex< -// Box< -// dyn WalletInst< -// 'static, -// DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, -// LocalWalletClient, -// ExtKeychain, -// >, -// >, -// >, -// >, -// Option, -// )> { -// // Create a new proxy to simulate server and wallet responses -// let mut wallet_proxy = create_wallet_proxy(test_dir); -// let chain = wallet_proxy.chain.clone(); -// let stopper = wallet_proxy.running.clone(); - -// let rv = vec![]; -// for _ in 0..wallets.len() { -// create_wallet_and_add!( -// client1, -// wallet1, -// mask1_i, -// test_dir, -// "wallet1", -// None, -// &mut wallet_proxy, -// true -// ); -// rv.push((wallet1, mask1_i)); -// } -// rv -// } - -// /// prepare two wallets for testing -// fn prepare_wallets(n_wallets: u8, test_dir: &'static str) -> Result, libwallet::Error> { -// // Create a new proxy to simulate server and wallet responses -// let mut wallet_proxy = create_wallet_proxy(test_dir); -// let chain = wallet_proxy.chain.clone(); -// let stopper = wallet_proxy.running.clone(); - -// create_wallet_and_add!( -// client1, -// wallet1, -// mask1_i, -// test_dir, -// "wallet1", -// None, -// &mut wallet_proxy, -// true -// ); -// let mask1 = (&mask1_i).as_ref(); -// create_wallet_and_add!( -// client2, -// wallet2, -// mask2_i, -// test_dir, -// "wallet2", -// None, -// &mut wallet_proxy, -// true -// ); -// let mask2 = (&mask2_i).as_ref(); - -// // Set the wallet proxy listener running -// thread::spawn(move || { -// if let Err(e) = wallet_proxy.run() { -// error!("Wallet Proxy error: {}", e); -// } -// }); - -// // few values to keep things shorter -// let reward = core::consensus::REWARD; - -// // add some accounts -// wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { -// api.create_account_path(m, "mining")?; -// api.create_account_path(m, "listener")?; -// Ok(()) -// })?; - -// // Get some mining done -// { -// wallet_inst!(wallet1, w); -// w.set_parent_key_id_by_name("mining")?; -// } -// let mut bh = 10u64; -// let _ = -// test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); - -// // Sanity check wallet 1 contents -// wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { -// let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; -// assert!(wallet1_refreshed); -// assert_eq!(wallet1_info.last_confirmed_height, bh); -// assert_eq!(wallet1_info.total, bh * reward); -// Ok(()) -// })?; - -// // let logging finish -// stopper.store(false, Ordering::Relaxed); -// thread::sleep(Duration::from_millis(200)); - -// Ok(()) -// } - -// #[test] -// fn wallet_contract_rsr_tx() -> Result<(), libwallet::Error> { -// let test_dir = "test_output/contract_rsr_tx"; -// setup(test_dir); -// contract_rsr_tx_impl(test_dir)?; -// clean_output_dir(test_dir); -// Ok(()) -// } diff --git a/controller/tests/contract_accounts.rs b/controller/tests/contract_accounts.rs deleted file mode 100644 index ba2e52ed7..000000000 --- a/controller/tests/contract_accounts.rs +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright 2022 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Test a wallet doing contracts with different accounts -// #[macro_use] -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; -extern crate log; - -use grin_core as core; -use grin_keychain as keychain; - -use self::core::global; -use self::keychain::{ExtKeychain, Keychain}; -use grin_wallet_libwallet as libwallet; -use impls::test_framework::{self}; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; -use libwallet::{Slate, SlateState}; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -#[macro_use] -mod common; -use common::{clean_output_dir, create_wallets, setup}; - -/// contract accounts testing (mostly the same as accounts.rs) -fn contract_accounts_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - // create two wallets with some extra accounts and don't mine anything in them - let (wallets, chain, stopper, _bh) = create_wallets( - vec![ - vec![ - ("default", 0), - ("account1", 0), - ("account2", 0), - ("account3", 0), - ], - vec![("default", 0), ("listener_account", 0)], - ], - test_dir, - ) - .unwrap(); - let wallet1 = wallets[0].0.clone(); - let mask1 = wallets[0].1.as_ref(); - let wallet2 = wallets[1].0.clone(); - let mask2 = wallets[1].1.as_ref(); - - // few values to keep things shorter - let reward = core::consensus::REWARD; - let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height - - // Default wallet 2 to listen on that account - { - wallet_inst!(wallet2, w); - w.set_parent_key_id_by_name("listener_account")?; - } - - // Mine into two different accounts in the same wallet - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name("account1")?; - assert_eq!(w.parent_key_id(), ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); - } - let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 7, false); - - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name("account2")?; - assert_eq!(w.parent_key_id(), ExtKeychain::derive_key_id(2, 2, 0, 0, 0)); - } - let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false); - - // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 12); - assert_eq!(wallet1_info.total, 5 * reward); - assert_eq!(wallet1_info.amount_currently_spendable, (5 - cm) * reward); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 5); - Ok(()) - })?; - - // now check second account - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name("account1")?; - } - - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // check last confirmed height on this account is different from above (should be 0) - let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 0); - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 12); - assert_eq!(wallet1_info.total, 7 * reward); - assert_eq!(wallet1_info.amount_currently_spendable, 7 * reward); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 7); - Ok(()) - })?; - - // should be nothing in default account - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name("default")?; - } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 0); - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 12); - assert_eq!(wallet1_info.total, 0,); - assert_eq!(wallet1_info.amount_currently_spendable, 0,); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 0); - Ok(()) - })?; - - // TODO: check what send_tx_slate_direct call does in accounts.rs test - // TODO: check that you can't call send on the default account because you have no funds - - // Send a tx from wallet1::account1 -> wallet2::listener_account - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name("account1")?; - } - - let mut slate = Slate::blank(0, true); // this gets overriden below - - let mut sender_address = None; - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // Send wallet inititates a standard transaction with --send=5 - let args = &ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - net_change: Some(-5_000_000_000), - ..Default::default() - }, - ..Default::default() - }; - slate = api.contract_new(m, args)?; - sender_address = Some(api.get_slatepack_address(mask1, 0)?.pub_key); - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard1); - - let mut recipient_address = None; - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - // Receive wallet calls --receive=5 - let args = &mut ContractSetupArgsAPI { - net_change: Some(5_000_000_000), - ..Default::default() - }; - args.proof_args.sender_address = sender_address; - slate = api.contract_sign(m, &slate, args)?; - recipient_address = Some(api.get_slatepack_address(mask2, 0)?.pub_key); - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard2); - - // Send wallet finalizes and posts - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let args = &ContractSetupArgsAPI { - ..Default::default() - }; - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard3); - - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - })?; - - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 13); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 9); - Ok(()) - })?; - - // other account should be untouched - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name("account2")?; - } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 12); - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 13); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - println!("{:?}", txs); - assert_eq!(txs.len(), 5); - Ok(()) - })?; - - // wallet 2 should only have this tx on the listener account - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, 13); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 1); - Ok(()) - })?; - // Default account on wallet 2 should be untouched - { - wallet_inst!(wallet2, w); - w.set_parent_key_id_by_name("default")?; - } - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (_, wallet2_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet2_info.last_confirmed_height, 0); - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, 13); - assert_eq!(wallet2_info.total, 0,); - assert_eq!(wallet2_info.amount_currently_spendable, 0,); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 0); - Ok(()) - })?; - - // let logging finish - stopper.store(false, Ordering::Relaxed); - thread::sleep(Duration::from_millis(200)); - - Ok(()) -} - -#[test] -fn wallet_contract_accounts() -> Result<(), libwallet::Error> { - let test_dir = "test_output/contract_accounts"; - setup(test_dir); - contract_accounts_impl(test_dir)?; - clean_output_dir(test_dir); - Ok(()) -} diff --git a/controller/tests/contract_accounts_switch.rs b/controller/tests/contract_accounts_switch.rs deleted file mode 100644 index 7b47dfbb7..000000000 --- a/controller/tests/contract_accounts_switch.rs +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2022 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Test a wallet doing contracts with different accounts and switching between them -// #[macro_use] -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; -extern crate log; - -use grin_core as core; -use grin_wallet_libwallet as libwallet; -use libwallet::contract::my_fee_contribution; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; -use libwallet::{Slate, SlateState}; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -#[macro_use] -mod common; -use common::{clean_output_dir, create_wallets, setup}; - -/// contract accounts testing when switching between accounts during transaction building -fn contract_accounts_switch_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - // create two wallets with some extra accounts and don't mine anything in them - let (wallets, _chain, stopper, _bh) = create_wallets( - vec![ - vec![("default", 0), ("account1", 1), ("account2", 2)], - vec![("default", 0), ("account1", 3), ("account2", 4)], - ], - test_dir, - ) - .unwrap(); - let wallet1 = wallets[0].0.clone(); - let mask1 = wallets[0].1.as_ref(); - let wallet2 = wallets[1].0.clone(); - let mask2 = wallets[1].1.as_ref(); - - let reward = core::consensus::REWARD; - - // wallet1::account1 should have 1 in account1 (1 spendable) - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name("account1")?; - } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 10); - assert_eq!(wallet1_info.total, 1 * reward); - assert_eq!(wallet1_info.amount_currently_spendable, 1 * reward); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 1); - Ok(()) - })?; - - // wallet1::account2 should have 2 in account1 (2 spendable) - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name("account2")?; - } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // check last confirmed height on this account is different from above (should be 0) - let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 3); - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 10); - assert_eq!(wallet1_info.total, 2 * reward); - assert_eq!(wallet1_info.amount_currently_spendable, 2 * reward); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 2); - Ok(()) - })?; - - // Make a transaction by sending 5 coins from wallet1::account1 -> wallet2::account2 - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name("account1")?; - } - - let mut slate = Slate::blank(0, true); // this gets overriden below - - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - // Send wallet inititates a standard transaction with --send=5 - let args = &ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - net_change: Some(-5_000_000_000), - ..Default::default() - }, - ..Default::default() - }; - slate = api.contract_new(m, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard1); - - // Receiver gets their coins on account2 where they can payjoin - { - wallet_inst!(wallet2, w); - w.set_parent_key_id_by_name("account2")?; - } - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - // Receive wallet calls --receive=5 - let args = &mut ContractSetupArgsAPI { - net_change: Some(5_000_000_000), - ..Default::default() - }; - args.proof_args.suppress_proof = true; - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard2); - - // Switch account for wallet1 to account2 and finish the transaction (should use account1 to complete) - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name("account2")?; - } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let args = &ContractSetupArgsAPI { - ..Default::default() - }; - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard3); - - // post tx and mine a block to wallet1::account2 - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - })?; - - // The currently set account (account2) should not be affected by this transaction because they weren't a part of it, - // but it did mine a block and pick the transaction fees - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 11); - assert_eq!( - wallet1_info.total, - 3 * reward + core::libtx::tx_fee(2, 2, 1) // we have received a block reward and the tx fee (payjoin) - ); - assert_eq!(wallet1_info.amount_currently_spendable, 2 * reward); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 3); - Ok(()) - })?; - - // Switch to wallet1::account1 and check that it sent 5 coins - { - wallet_inst!(wallet1, w); - w.set_parent_key_id_by_name("account1")?; - } - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 11); - assert_eq!( - wallet1_info.total, - 1 * reward - 5_000_000_000 - my_fee_contribution(1, 1, 1, 2)?.fee() // we subtract also our fee contribution - ); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 2); - Ok(()) - })?; - - // Switch to wallet2::account2 and check that it received 5 coins - { - wallet_inst!(wallet2, w); - w.set_parent_key_id_by_name("account2")?; - } - wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, 11); - assert_eq!( - wallet2_info.total, - 4 * reward + 5_000_000_000 - my_fee_contribution(1, 1, 1, 2)?.fee() // we subtract also our fee contribution for a payjoin - ); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 5); - Ok(()) - })?; - - // let logging finish - stopper.store(false, Ordering::Relaxed); - thread::sleep(Duration::from_millis(200)); - - Ok(()) -} - -#[test] -fn wallet_contract_accounts_switch() -> Result<(), libwallet::Error> { - let test_dir = "test_output/contract_accounts_switch"; - setup(test_dir); - contract_accounts_switch_impl(test_dir)?; - clean_output_dir(test_dir); - Ok(()) -} diff --git a/controller/tests/contract_early_lock.rs b/controller/tests/contract_early_lock.rs deleted file mode 100644 index fc6df9377..000000000 --- a/controller/tests/contract_early_lock.rs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2022 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Test a wallet doing contract early lock when using --add-outputs -// #[macro_use] -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; -extern crate log; - -use grin_wallet_libwallet as libwallet; - -use grin_core::consensus; -use libwallet::contract::my_fee_contribution; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; -use libwallet::{OutputStatus, Slate, SlateState}; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -#[macro_use] -mod common; -use common::{clean_output_dir, create_wallets, setup}; - -/// contract new with --add-outputs -fn contract_early_lock_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - // create a single wallet and mine 5 blocks - let (wallets, _chain, stopper, _bh) = - create_wallets(vec![vec![("default", 5)]], test_dir).unwrap(); - let send_wallet = wallets[0].0.clone(); - let send_mask = wallets[0].1.as_ref(); - - // Confirm all our outputs are unspent - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let (_, commits) = api.retrieve_outputs(m, true, false, None)?; - for commit in commits.iter() { - assert_eq!(commit.output.status, OutputStatus::Unspent); - } - Ok(()) - })?; - - let mut slate = Slate::blank(0, false); // this gets overriden below - - // Call contract 'new' with --add-outputs - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - // Send wallet inititates a standard transaction with --send=80 - let args = &ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - net_change: Some(-80_000_000_000), - num_participants: 2, - add_outputs: true, - ..Default::default() - }, - ..Default::default() - }; - slate = api.contract_new(m, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard1); - - // Assert we locked 2 inputs and prepared an unconfirmed change output - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let (_, commits) = api.retrieve_outputs(m, true, false, None)?; - // we locked the first two coinbase outputs - assert_eq!(commits[0].output.status, OutputStatus::Locked); - assert_eq!(commits[1].output.status, OutputStatus::Locked); - // we added a new unconfirmed change output - let new_output_idx = commits.len() - 1; - assert_eq!( - commits[new_output_idx].output.status, - OutputStatus::Unconfirmed - ); - assert_eq!( - commits[new_output_idx].output.value, - 2 * consensus::REWARD - 80_000_000_000 - my_fee_contribution(2, 1, 1, 2)?.fee() - ); - Ok(()) - })?; - - // let logging finish - stopper.store(false, Ordering::Relaxed); - thread::sleep(Duration::from_millis(200)); - - Ok(()) -} - -#[test] -fn wallet_contract_early_lock_tx() -> Result<(), libwallet::Error> { - let test_dir = "test_output/contract_early_lock_tx"; - setup(test_dir); - contract_early_lock_tx_impl(test_dir)?; - clean_output_dir(test_dir); - Ok(()) -} diff --git a/controller/tests/contract_early_proofs_rsr.rs b/controller/tests/contract_early_proofs_rsr.rs deleted file mode 100644 index 5a01ea0eb..000000000 --- a/controller/tests/contract_early_proofs_rsr.rs +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2023 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Development and testing of early payment proofs, restricted at the moment -//! to contract-style transactions for experimental purposes -//! -//! https://github.com/mimblewimble/grin-rfcs/pull/70 -//! -//! - -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; -extern crate log; - -use grin_wallet_libwallet as libwallet; - -use impls::test_framework::{self}; -use libwallet::contract::my_fee_contribution; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; -use libwallet::{Slate, SlateState, Slatepack, Slatepacker, SlatepackerArgs, TxLogEntryType}; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -#[macro_use] -mod common; -use common::{clean_output_dir, create_wallets, setup}; - -/// Development + Tests of early payment proof functionality - RSR workflow -fn contract_early_proofs_rsr_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - // create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin) - let (wallets, chain, stopper, mut bh) = - create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap(); - let send_wallet = wallets[0].0.clone(); - let send_mask = wallets[0].1.as_ref(); - let recv_wallet = wallets[1].0.clone(); - let recv_mask = wallets[1].1.as_ref(); - - let mut slate = Slate::blank(0, true); // this gets overriden below - - let mut sender_address = None; - // Get sender address explicitly - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - sender_address = Some(api.get_slatepack_address(send_mask, 0)?.pub_key); - Ok(()) - })?; - - let mut recipient_address = None; - wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| { - // Receive wallet (invoice) calls --receive=5 - let args = &mut ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - net_change: Some(5_000_000_000), - ..Default::default() - }, - ..Default::default() - }; - args.setup_args.proof_args.sender_address = sender_address; - println!("SENDER ADDRESS: {:?}", sender_address); - slate = api.contract_new(m, args)?; - recipient_address = Some(api.get_slatepack_address(recv_mask, 0)?.pub_key); - Ok(()) - })?; - - assert_eq!(slate.state, SlateState::Invoice1); - println!("I1 State slate: {}", slate); - - // Serialize slate into slatepack - let slatepacker_args = SlatepackerArgs { - sender: None, - recipients: vec![], - dec_key: None, - }; - - let slate_packer = Slatepacker::new(slatepacker_args); - let slate_packed = slate_packer.create_slatepack(&slate).unwrap(); - - let slate_unpacked = slate_packer.get_slate(&slate_packed).unwrap(); - println!("I2 Slate unpacked: {}", slate_unpacked); - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - // Sending wallet (invoice) signs - let args = &ContractSetupArgsAPI { - net_change: Some(-5_000_000_000), - ..Default::default() - }; - slate = api.contract_sign(m, &slate_unpacked, args)?; - Ok(()) - })?; - println!("I2 State slate: {}", slate); - - assert_eq!(slate.state, SlateState::Invoice2); - - // Send wallet finalizes and posts - wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| { - let args = &ContractSetupArgsAPI { - ..Default::default() - }; - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Invoice3); - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - })?; - bh += 1; - - let _ = - test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false); - bh += 3; - - // Assert changes in receive wallet - wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| { - let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(wallet_info.last_confirmed_height, bh); - assert!(refreshed); - assert_eq!(txs.len(), 5); // 4 mined and 1 received - let tx_log = txs[4].clone(); - assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived); - assert_eq!(tx_log.amount_credited, 5_000_000_000); - assert_eq!(tx_log.amount_debited, 0); - assert_eq!(tx_log.num_inputs, 1); - assert_eq!(tx_log.num_outputs, 1); - let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?); - assert_eq!(tx_log.fee, expected_fees_paid); - assert_eq!( - wallet_info.amount_currently_spendable, - 4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid - ); - Ok(()) - })?; - - // Assert changes in send wallet - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(wallet_info.last_confirmed_height, bh); - assert!(refreshed); - assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx - let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4? - assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent); - assert_eq!(tx_log.amount_credited, 0); - assert_eq!(tx_log.amount_debited, 5_000_000_000); - assert_eq!(tx_log.num_inputs, 1); - assert_eq!(tx_log.num_outputs, 1); - assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?)); - Ok(()) - })?; - - let mut invoice_proof = None; - // Now some time has passed, sender retrieves and verify the payment proof - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, _m| { - // Extract the stored data as an invoice proof - invoice_proof = - Some(api.retrieve_payment_proof_invoice(send_mask, true, None, Some(slate.id))?); - Ok(()) - })?; - - let invoice_proof = invoice_proof.unwrap(); - let invoice_proof_json = serde_json::to_string(&invoice_proof).unwrap(); - - // Should have all proof fields filled out - println!("INVOICE PROOF: {}", invoice_proof_json); - - wallet::controller::foreign_single_use(recv_wallet.clone(), recv_mask.cloned(), |api| { - let mut proof = serde_json::from_str(&invoice_proof_json).unwrap(); - api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof)?; - // tweak something and it shouldn't verify - proof.amount = 400000; - let retval = api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof); - assert!(retval.is_err()); - Ok(()) - })?; - - // let logging finish - stopper.store(false, Ordering::Relaxed); - thread::sleep(Duration::from_millis(200)); - - Ok(()) -} - -#[test] -fn contract_early_proofs_rsr() -> Result<(), libwallet::Error> { - let test_dir = "test_output/contract_early_proofs_rsr"; - setup(test_dir); - contract_early_proofs_rsr_test_impl(test_dir)?; - clean_output_dir(test_dir); - Ok(()) -} diff --git a/controller/tests/contract_early_proofs_srs.rs b/controller/tests/contract_early_proofs_srs.rs deleted file mode 100644 index ffcbb71d4..000000000 --- a/controller/tests/contract_early_proofs_srs.rs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2023 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Development and testing of early payment proofs, restricted at the moment -//! to contract-style transactions for experimental purposes -//! -//! https://github.com/mimblewimble/grin-rfcs/pull/70 -//! -//! - -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; -extern crate log; - -use grin_wallet_libwallet as libwallet; - -use impls::test_framework::{self}; -use libwallet::contract::my_fee_contribution; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; -use libwallet::{Slate, SlateState, TxLogEntryType}; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -#[macro_use] -mod common; -use common::{clean_output_dir, create_wallets, setup}; - -/// Development + Tests of early payment proof functionality -fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - // create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin) - let (wallets, chain, stopper, mut bh) = - create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap(); - let send_wallet = wallets[0].0.clone(); - let send_mask = wallets[0].1.as_ref(); - let recv_wallet = wallets[1].0.clone(); - let recv_mask = wallets[1].1.as_ref(); - - let mut slate = Slate::blank(0, true); // this gets overriden below - - let mut sender_address = None; - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - // Send wallet inititates a standard transaction with --send=5 - let args = &ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - net_change: Some(-5_000_000_000), - ..Default::default() - }, - ..Default::default() - }; - slate = api.contract_new(m, args)?; - sender_address = Some(api.get_slatepack_address(send_mask, 0)?.pub_key); - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard1); - - let mut recipient_address = None; - wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| { - // Receive wallet calls --receive=5 - let args = &mut ContractSetupArgsAPI { - net_change: Some(5_000_000_000), - ..Default::default() - }; - // Note sender address explicity added here - args.proof_args.sender_address = sender_address; - slate = api.contract_sign(m, &slate, args)?; - recipient_address = Some(api.get_slatepack_address(recv_mask, 0)?.pub_key); - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard2); - - // Send wallet finalizes and posts - //let mut sender_part_sig = None; - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let args = &ContractSetupArgsAPI { - ..Default::default() - }; - - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard3); - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - })?; - bh += 1; - - let _ = - test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false); - bh += 3; - - // Assert changes in receive wallet - wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| { - let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(wallet_info.last_confirmed_height, bh); - assert!(refreshed); - assert_eq!(txs.len(), 5); // 4 mined and 1 received - let tx_log = txs[4].clone(); - assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived); - assert_eq!(tx_log.amount_credited, 5_000_000_000); - assert_eq!(tx_log.amount_debited, 0); - assert_eq!(tx_log.num_inputs, 1); - assert_eq!(tx_log.num_outputs, 1); - let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?); - assert_eq!(tx_log.fee, expected_fees_paid); - assert_eq!( - wallet_info.amount_currently_spendable, - 4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid - ); - Ok(()) - })?; - - // Assert changes in send wallet - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(wallet_info.last_confirmed_height, bh); - assert!(refreshed); - assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx - let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4? - assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent); - assert_eq!(tx_log.amount_credited, 0); - assert_eq!(tx_log.amount_debited, 5_000_000_000); - assert_eq!(tx_log.num_inputs, 1); - assert_eq!(tx_log.num_outputs, 1); - assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?)); - Ok(()) - })?; - - let mut invoice_proof = None; - // Now some time has passed, sender retrieves and verify the payment proof - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, _m| { - // Extract the stored data as an invoice proof - invoice_proof = - Some(api.retrieve_payment_proof_invoice(send_mask, true, None, Some(slate.id))?); - Ok(()) - })?; - - let invoice_proof = invoice_proof.unwrap(); - let invoice_proof_json = serde_json::to_string(&invoice_proof).unwrap(); - - // Should have all proof fields filled out - println!("INVOICE PROOF: {}", invoice_proof_json); - - wallet::controller::foreign_single_use(recv_wallet.clone(), recv_mask.cloned(), |api| { - let mut proof = serde_json::from_str(&invoice_proof_json).unwrap(); - api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof)?; - // tweak something and it shouldn't verify - proof.amount = 400000; - let retval = api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof); - assert!(retval.is_err()); - Ok(()) - })?; - - // let logging finish - stopper.store(false, Ordering::Relaxed); - thread::sleep(Duration::from_millis(200)); - - Ok(()) -} - -#[test] -fn contract_early_proofs_srs() -> Result<(), libwallet::Error> { - let test_dir = "test_output/contract_early_proofs_src"; - setup(test_dir); - contract_early_proofs_srs_test_impl(test_dir)?; - clean_output_dir(test_dir); - Ok(()) -} diff --git a/controller/tests/contract_rsr.rs b/controller/tests/contract_rsr.rs deleted file mode 100644 index 2821950b4..000000000 --- a/controller/tests/contract_rsr.rs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2022 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Test a wallet doing contract RSR flow -// #[macro_use] -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; -extern crate log; - -use grin_wallet_libwallet as libwallet; - -use impls::test_framework::{self}; -use libwallet::contract::my_fee_contribution; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; -use libwallet::{Slate, SlateState, TxLogEntryType}; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -#[macro_use] -mod common; -use common::{clean_output_dir, create_wallets, setup}; - -/// contract RSR flow -fn contract_rsr_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - // create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin) - let (wallets, chain, stopper, mut bh) = - create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap(); - let send_wallet = wallets[0].0.clone(); - let send_mask = wallets[0].1.as_ref(); - let recv_wallet = wallets[1].0.clone(); - let recv_mask = wallets[1].1.as_ref(); - - let mut slate = Slate::blank(0, true); // this gets overriden below - - wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| { - // Receive wallet inititates an invoice transaction with --receive=5 - let args = &mut ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - net_change: Some(5_000_000_000), - ..Default::default() - }, - ..Default::default() - }; - args.setup_args.proof_args.suppress_proof = true; - slate = api.contract_new(m, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Invoice1); - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - // Send Wallet calls --send=5 - let args = &ContractSetupArgsAPI { - net_change: Some(-5_000_000_000), - ..Default::default() - }; - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Invoice2); - - // Receive wallet finalizes and posts - wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| { - let args = &mut ContractSetupArgsAPI { - ..Default::default() - }; - // TODO: This possibly shouldn't be needed here if no proof? - args.proof_args.suppress_proof = true; - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Invoice3); - - // Send wallet posts so receive wallet doesn't get the mined amount - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - })?; - bh += 1; - - let _ = - test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false); - bh += 3; - - // Assert changes in receive wallet - wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| { - let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(wallet_info.last_confirmed_height, bh); - assert!(refreshed); - assert_eq!(txs.len(), 5); // 4 mined and 1 received - let tx_log = txs[4].clone(); - assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived); - assert_eq!(tx_log.amount_credited, 5_000_000_000); - assert_eq!(tx_log.amount_debited, 0); - assert_eq!(tx_log.num_inputs, 1); - assert_eq!(tx_log.num_outputs, 1); - let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?); - assert_eq!(tx_log.fee, expected_fees_paid); - assert_eq!( - wallet_info.amount_currently_spendable, - 4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid - ); - // println!("txlogentry: {:#?}", tx_log); - // println!("wallet info: {:#?}", wallet_info); - // let (validated, commits) = api.retrieve_outputs(m, true, false, Some(tx_log.id))?; - // println!("commits: {:#?}", commits); - // panic!("lala"); - Ok(()) - })?; - - // Assert changes in send wallet - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(wallet_info.last_confirmed_height, bh); - assert!(refreshed); - assert_eq!(txs.len() as u64, bh - 4 + 1); // send_wallet didn't mine 4 blocks and made 1 tx - let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4? - assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent); - assert_eq!(tx_log.amount_credited, 0); - assert_eq!(tx_log.amount_debited, 5_000_000_000); - assert_eq!(tx_log.num_inputs, 1); - assert_eq!(tx_log.num_outputs, 1); - assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?)); - Ok(()) - })?; - - // let logging finish - stopper.store(false, Ordering::Relaxed); - thread::sleep(Duration::from_millis(200)); - - Ok(()) -} - -#[test] -fn wallet_contract_rsr_tx() -> Result<(), libwallet::Error> { - let test_dir = "test_output/contract_rsr_tx"; - setup(test_dir); - contract_rsr_tx_impl(test_dir)?; - clean_output_dir(test_dir); - Ok(()) -} diff --git a/controller/tests/contract_self_spend.rs b/controller/tests/contract_self_spend.rs deleted file mode 100644 index bb90ce03a..000000000 --- a/controller/tests/contract_self_spend.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2022 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Test a wallet doing contract self-spend flow -// #[macro_use] -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; -extern crate log; - -use grin_wallet_libwallet as libwallet; - -use impls::test_framework::{self}; -use libwallet::contract::my_fee_contribution; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; -use libwallet::{Slate, SlateState, TxLogEntryType}; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -#[macro_use] -mod common; -use common::{clean_output_dir, create_wallets, setup}; - -/// contract self-spend flow -fn contract_self_spend_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - // create a single wallet and mine 4 blocks - let (wallets, chain, stopper, mut bh) = - create_wallets(vec![vec![("default", 4)]], test_dir).unwrap(); - let send_wallet = wallets[0].0.clone(); - let send_mask = wallets[0].1.as_ref(); - - let mut slate = Slate::blank(0, true); // this gets overriden below - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - // Send wallet inititates a standard transaction with --send=0 - let args = &ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - net_change: Some(0), - num_participants: 1, - ..Default::default() - }, - ..Default::default() - }; - slate = api.contract_new(m, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard1); - - // Send wallet finalizes and posts - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let args = &ContractSetupArgsAPI { - ..Default::default() - }; - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - // In the case of a self-spend, we just finish the slate when it's in the Standard2 state - assert_eq!(slate.state, SlateState::Standard2); - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - })?; - bh += 1; - - let _ = - test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false); - bh += 3; - - // Assert changes in send wallet - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(wallet_info.last_confirmed_height, bh); - assert!(refreshed); - assert_eq!(txs.len() as u64, bh + 1); // send wallet didn't mine 4 blocks and made 1 tx - let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4? - assert_eq!(tx_log.tx_type, TxLogEntryType::TxSelfSpend); - assert_eq!(tx_log.amount_credited, 0); - assert_eq!(tx_log.amount_debited, 0); - assert_eq!(tx_log.num_inputs, 1); - assert_eq!(tx_log.num_outputs, 1); - assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 1)?)); - Ok(()) - })?; - - // let logging finish - stopper.store(false, Ordering::Relaxed); - thread::sleep(Duration::from_millis(200)); - - Ok(()) -} - -#[test] -fn wallet_contract_self_spend_tx() -> Result<(), libwallet::Error> { - let test_dir = "test_output/contract_self_spend_tx"; - setup(test_dir); - contract_self_spend_tx_impl(test_dir)?; - clean_output_dir(test_dir); - Ok(()) -} diff --git a/controller/tests/contract_self_spend_cancel.rs b/controller/tests/contract_self_spend_cancel.rs deleted file mode 100644 index dc242dae8..000000000 --- a/controller/tests/contract_self_spend_cancel.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2022 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Test a wallet doing contract self-spend flow -// #[macro_use] -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; -extern crate log; - -use grin_wallet_libwallet as libwallet; - -use impls::test_framework::{self}; -use libwallet::contract::my_fee_contribution; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; -use libwallet::{Slate, SlateState, TxLogEntryType}; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -#[macro_use] -mod common; -use common::{clean_output_dir, create_wallets, setup}; - -/// contract self-spend flow -fn contract_self_spend_cancel_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - // create a single wallet and mine 4 blocks - let (wallets, chain, stopper, mut bh) = - create_wallets(vec![vec![("default", 4)]], test_dir).unwrap(); - let send_wallet = wallets[0].0.clone(); - let send_mask = wallets[0].1.as_ref(); - - let mut slate = Slate::blank(0, true); // this gets overriden below - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - // Send wallet initiates a standard transaction with --send=0 - let args = &ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - net_change: Some(0), - num_participants: 1, - ..Default::default() - }, - ..Default::default() - }; - slate = api.contract_new(m, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard1); - - // Send wallet finalizes and posts - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - api.cancel_tx(m, None, Some(slate.id))?; - Ok(()) - })?; - - // Assert tx log has been cancelled - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let query_args = libwallet::RetrieveTxQueryArgs { - exclude_cancelled: Some(false), - ..Default::default() - }; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, Some(query_args))?; - assert!(refreshed); - assert_eq!(txs.len() as u64, 5); // send wallet didn't mine 4 blocks and made 1 tx - let tx_log = txs[4].clone(); // TODO: why -5 and not -4? - assert_eq!(tx_log.tx_type, TxLogEntryType::TxSelfSpendCancelled); - Ok(()) - })?; - - // let logging finish - stopper.store(false, Ordering::Relaxed); - thread::sleep(Duration::from_millis(200)); - - Ok(()) -} - -#[test] -fn wallet_contract_self_spend_cancel() -> Result<(), libwallet::Error> { - let test_dir = "test_output/contract_self_spend_cancel"; - setup(test_dir); - contract_self_spend_cancel_impl(test_dir)?; - clean_output_dir(test_dir); - Ok(()) -} diff --git a/controller/tests/contract_self_spend_custom.rs b/controller/tests/contract_self_spend_custom.rs deleted file mode 100644 index 2bd51eed4..000000000 --- a/controller/tests/contract_self_spend_custom.rs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2022 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Test a wallet doing contract self-spend flow by using custom inputs and creating custom outputs -// #[macro_use] -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; -extern crate log; - -use grin_wallet_libwallet as libwallet; - -use grin_core::consensus; -use impls::test_framework::{self}; -use libwallet::contract::my_fee_contribution; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs}; -use libwallet::{OutputStatus, Slate, SlateState, TxLogEntryType}; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -#[macro_use] -mod common; -use common::{clean_output_dir, create_wallets, setup}; - -/// contract self-spend flow with custom picked inputs and outputs -fn contract_self_spend_custom_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - // create a single wallet and mine 4 blocks - let (wallets, chain, stopper, mut bh) = - create_wallets(vec![vec![("default", 10)]], test_dir).unwrap(); - let send_wallet = wallets[0].0.clone(); - let send_mask = wallets[0].1.as_ref(); - - let mut use_inputs = String::from(""); - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let (_, commits) = api.retrieve_outputs(m, true, false, None)?; - println!("OOOT: {:?}", commits[0].output); - use_inputs = format!( - "{},{}", - commits[0].output.commit.as_ref().unwrap(), - commits[1].output.commit.as_ref().unwrap() - ); - Ok(()) - })?; - - let mut slate = Slate::blank(0, true); // this gets overriden below - - let selection_args = OutputSelectionArgs { - min_input_confirmation: 0, - use_inputs: Some(use_inputs.clone()), // we will use two coinbase inputs - make_outputs: Some(String::from("88,35,3,0.2,15")), // the sum is such that it will need to pick another input making total of 3 inputs - }; - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - // Send wallet inititates a standard transaction with --send=0 - let args = &ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - net_change: Some(0), - num_participants: 1, - selection_args: selection_args.clone(), - ..Default::default() - }, - ..Default::default() - }; - slate = api.contract_new(m, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard1); - - // Send wallet finalizes and posts - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let args = &ContractSetupArgsAPI { - ..Default::default() - }; - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - // In the case of a self-spend, we just finish the slate when it's in the Standard2 state - assert_eq!(slate.state, SlateState::Standard2); - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - })?; - bh += 1; - - let _ = - test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false); - bh += 3; - - // Assert changes in send wallet - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(wallet_info.last_confirmed_height, bh); - assert!(refreshed); - assert_eq!(txs.len() as u64, bh + 1); // send wallet didn't mine 4 blocks and made 1 tx - let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4? - assert_eq!(tx_log.tx_type, TxLogEntryType::TxSelfSpend); - assert_eq!(tx_log.amount_credited, 0); - assert_eq!(tx_log.amount_debited, 0); - assert_eq!(tx_log.num_inputs, 3); - assert_eq!(tx_log.num_outputs, 6); - assert_eq!(tx_log.fee, Some(my_fee_contribution(3, 6, 1, 1)?)); - Ok(()) - })?; - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let (_, commits) = api.retrieve_outputs(m, true, false, None)?; - // Assert used inputs are the ones we specified - let used_inputs = use_inputs.split(",").collect::>(); - assert_eq!(commits[0].output.status, OutputStatus::Spent); - assert_eq!(commits[0].output.commit.as_ref().unwrap(), used_inputs[0]); - assert_eq!(commits[1].output.status, OutputStatus::Spent); - assert_eq!(commits[1].output.commit.as_ref().unwrap(), used_inputs[1]); - assert_eq!(commits[2].output.status, OutputStatus::Spent); - // Assert expected outputs were created - // 88, 35, 3, 0.2, 15 and a change output - assert_eq!(commits[10].output.value, 88 * consensus::GRIN_BASE); - assert_eq!(commits[11].output.value, 35 * consensus::GRIN_BASE); - assert_eq!(commits[12].output.value, 3 * consensus::GRIN_BASE); - assert_eq!( - commits[13].output.value, - (0.2 * consensus::GRIN_BASE as f64) as u64 - ); - assert_eq!(commits[14].output.value, 15 * consensus::GRIN_BASE); - // change output is 3*reward - (88-35-3-0.2-15) - my_fees - assert_eq!( - commits[15].output.value, - 3 * consensus::REWARD - - selection_args.sum_output_amounts() - - my_fee_contribution(3, 6, 1, 1)?.fee() - ); - Ok(()) - })?; - - // let logging finish - stopper.store(false, Ordering::Relaxed); - thread::sleep(Duration::from_millis(200)); - - Ok(()) -} - -#[test] -fn wallet_contract_self_spend_custom_tx() -> Result<(), libwallet::Error> { - let test_dir = "test_output/contract_self_spend_custom_tx"; - setup(test_dir); - contract_self_spend_custom_tx_impl(test_dir)?; - clean_output_dir(test_dir); - Ok(()) -} diff --git a/controller/tests/contract_srs.rs b/controller/tests/contract_srs.rs deleted file mode 100644 index 7fb9dcfaa..000000000 --- a/controller/tests/contract_srs.rs +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2022 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Test a wallet doing contract SRS flow -// #[macro_use] -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; -extern crate log; - -use grin_wallet_libwallet as libwallet; - -use impls::test_framework::{self}; -use libwallet::contract::my_fee_contribution; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; -use libwallet::{Slate, SlateState, TxLogEntryType}; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -#[macro_use] -mod common; -use common::{clean_output_dir, create_wallets, setup}; - -/// contract SRS flow -fn contract_srs_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - // create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin) - let (wallets, chain, stopper, mut bh) = - create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap(); - let send_wallet = wallets[0].0.clone(); - let send_mask = wallets[0].1.as_ref(); - let recv_wallet = wallets[1].0.clone(); - let recv_mask = wallets[1].1.as_ref(); - - let mut slate = Slate::blank(0, true); // this gets overriden below - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - // Send wallet inititates a standard transaction with --send=5 - let args = &mut ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - net_change: Some(-5_000_000_000), - ..Default::default() - }, - ..Default::default() - }; - slate = api.contract_new(m, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard1); - - wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| { - // Receive wallet calls --receive=5 - let args = &mut ContractSetupArgsAPI { - net_change: Some(5_000_000_000), - ..Default::default() - }; - args.proof_args.suppress_proof = true; - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard2); - - // Send wallet finalizes and posts - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let args = &mut ContractSetupArgsAPI { - ..Default::default() - }; - args.proof_args.suppress_proof = true; - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard3); - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - })?; - bh += 1; - - let _ = - test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false); - bh += 3; - - // Assert changes in receive wallet - wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| { - let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(wallet_info.last_confirmed_height, bh); - assert!(refreshed); - assert_eq!(txs.len(), 5); // 4 mined and 1 received - let tx_log = txs[4].clone(); - assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived); - assert_eq!(tx_log.amount_credited, 5_000_000_000); - assert_eq!(tx_log.amount_debited, 0); - assert_eq!(tx_log.num_inputs, 1); - assert_eq!(tx_log.num_outputs, 1); - let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?); - assert_eq!(tx_log.fee, expected_fees_paid); - assert_eq!( - wallet_info.amount_currently_spendable, - 4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid - ); - Ok(()) - })?; - - // Assert changes in send wallet - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(wallet_info.last_confirmed_height, bh); - assert!(refreshed); - assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx - let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4? - assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent); - assert_eq!(tx_log.amount_credited, 0); - assert_eq!(tx_log.amount_debited, 5_000_000_000); - assert_eq!(tx_log.num_inputs, 1); - assert_eq!(tx_log.num_outputs, 1); - assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?)); - Ok(()) - })?; - - // let logging finish - stopper.store(false, Ordering::Relaxed); - thread::sleep(Duration::from_millis(200)); - - Ok(()) -} - -#[test] -fn wallet_contract_srs_tx() -> Result<(), libwallet::Error> { - let test_dir = "test_output/contract_srs_tx"; - setup(test_dir); - contract_srs_tx_impl(test_dir)?; - clean_output_dir(test_dir); - Ok(()) -} diff --git a/controller/tests/contract_srs_mwmixnet.rs b/controller/tests/contract_srs_mwmixnet.rs deleted file mode 100644 index 8643eb106..000000000 --- a/controller/tests/contract_srs_mwmixnet.rs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2022 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Test a wallet doing contract SRS flow -// #[macro_use] -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; -extern crate log; - -use grin_wallet_libwallet as libwallet; - -use impls::test_framework::{self}; -use libwallet::contract::my_fee_contribution; -use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; -use libwallet::mwmixnet::onion::crypto::secp; -use libwallet::mwmixnet::types::MixnetReqCreationParams; -use libwallet::{Slate, SlateState, TxLogEntryType}; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -#[macro_use] -mod common; -use common::{clean_output_dir, create_wallets, setup}; - -/// contract SRS flow - just creating an mwmixnet tx at the moment -fn contract_srs_mwmixnet_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - // create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin) - let (wallets, chain, stopper, mut bh) = - create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap(); - let send_wallet = wallets[0].0.clone(); - let send_mask = wallets[0].1.as_ref(); - let recv_wallet = wallets[1].0.clone(); - let recv_mask = wallets[1].1.as_ref(); - - let mut slate = Slate::blank(0, true); // this gets overriden below - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - // Send wallet inititates a standard transaction with --send=5 - let args = &mut ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - net_change: Some(-5_000_000_000), - ..Default::default() - }, - ..Default::default() - }; - slate = api.contract_new(m, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard1); - - wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| { - // Receive wallet calls --receive=5 - let args = &mut ContractSetupArgsAPI { - net_change: Some(5_000_000_000), - ..Default::default() - }; - args.proof_args.suppress_proof = true; - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard2); - - // Send wallet finalizes and posts - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let args = &mut ContractSetupArgsAPI { - ..Default::default() - }; - args.proof_args.suppress_proof = true; - slate = api.contract_sign(m, &slate, args)?; - Ok(()) - })?; - assert_eq!(slate.state, SlateState::Standard3); - - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let server_key_1 = secp::random_secret(); - let server_key_2 = secp::random_secret(); - let params = MixnetReqCreationParams { - server_keys: vec![server_key_1, server_key_2], - fee_per_hop: 50_000_000, - }; - //api.create_mwmixnet_req(send_mask, ¶ms, &slate)?; - Ok(()) - })?; - - bh += 1; - - /* - let _ = - test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false); - bh += 3; - - // Assert changes in receive wallet - wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| { - let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(wallet_info.last_confirmed_height, bh); - assert!(refreshed); - assert_eq!(txs.len(), 5); // 4 mined and 1 received - let tx_log = txs[4].clone(); - assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived); - assert_eq!(tx_log.amount_credited, 5_000_000_000); - assert_eq!(tx_log.amount_debited, 0); - assert_eq!(tx_log.num_inputs, 1); - assert_eq!(tx_log.num_outputs, 1); - let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?); - assert_eq!(tx_log.fee, expected_fees_paid); - assert_eq!( - wallet_info.amount_currently_spendable, - 4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid - ); - Ok(()) - })?; - - // Assert changes in send wallet - wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| { - let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(wallet_info.last_confirmed_height, bh); - assert!(refreshed); - assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx - let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4? - assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent); - assert_eq!(tx_log.amount_credited, 0); - assert_eq!(tx_log.amount_debited, 5_000_000_000); - assert_eq!(tx_log.num_inputs, 1); - assert_eq!(tx_log.num_outputs, 1); - assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?)); - Ok(()) - })?;*/ - - // let logging finish - stopper.store(false, Ordering::Relaxed); - thread::sleep(Duration::from_millis(200)); - - Ok(()) -} - -#[test] -fn wallet_contract_srs_mwmixnet_tx() -> Result<(), libwallet::Error> { - let test_dir = "test_output/contract_srs_mwmixnet_tx"; - setup(test_dir); - contract_srs_mwmixnet_tx_impl(test_dir)?; - clean_output_dir(test_dir); - Ok(()) -} diff --git a/controller/tests/self_send_cancel.rs b/controller/tests/self_send_cancel.rs deleted file mode 100644 index 3947dda23..000000000 --- a/controller/tests/self_send_cancel.rs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2021 The Grin Developers -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Test a wallet sending to self -#[macro_use] -extern crate log; -extern crate grin_wallet_controller as wallet; -extern crate grin_wallet_impls as impls; - -use grin_core as core; - -use grin_wallet_libwallet as libwallet; -use impls::test_framework::{self, LocalWalletClient}; -use libwallet::{InitTxArgs, RetrieveTxQueryArgs}; -use std::sync::atomic::Ordering; -use std::thread; -use std::time::Duration; - -#[macro_use] -mod common; -use common::{clean_output_dir, create_wallet_proxy, setup}; - -/// self send impl -fn self_send_cancel_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - // Create a new proxy to simulate server and wallet responses - let mut wallet_proxy = create_wallet_proxy(test_dir); - let chain = wallet_proxy.chain.clone(); - let stopper = wallet_proxy.running.clone(); - - // Create a new wallet test client, and set its queues to communicate with the - // proxy - create_wallet_and_add!( - client1, - wallet1, - mask1_i, - test_dir, - "wallet1", - None, - &mut wallet_proxy, - true - ); - let mask1 = (&mask1_i).as_ref(); - - // Set the wallet proxy listener running - thread::spawn(move || { - if let Err(e) = wallet_proxy.run() { - error!("Wallet Proxy error: {}", e); - } - }); - - // few values to keep things shorter - let reward = core::consensus::REWARD; - - let mut bh = 10u64; - let _ = - test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); - - // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - // send to send - let args = InitTxArgs { - src_acct_name: None, - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let mut slate = api.init_send_tx(m, args)?; - api.tx_lock_outputs(m, &slate)?; - // Send directly to self - wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| { - slate = api.receive_tx(&slate, None, None)?; - Ok(()) - })?; - // Now cancel the transaction - api.cancel_tx(mask1, None, Some(slate.id.clone()))?; - bh += 1; - Ok(()) - })?; - - wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let query = RetrieveTxQueryArgs { - include_outstanding_only: Some(true), - exclude_cancelled: Some(true), - ..Default::default() - }; - let txs = api.retrieve_txs(mask1, true, None, None, Some(query))?; - for tx in txs.1.iter() { - println!("Tx: {:?}", tx); - } - assert!(txs.1.is_empty()); - Ok(()) - })?; - - // let logging finish - stopper.store(false, Ordering::Relaxed); - thread::sleep(Duration::from_millis(1000)); - Ok(()) -} - -#[test] -fn wallet_self_send_cancel() { - let test_dir = "test_output/self_send_cancel"; - setup(test_dir); - if let Err(e) = self_send_cancel_test_impl(test_dir) { - panic!("Libwallet Error: {}", e); - } - clean_output_dir(test_dir); -} diff --git a/default.nix b/default.nix deleted file mode 100644 index 6e40b0a76..000000000 --- a/default.nix +++ /dev/null @@ -1,18 +0,0 @@ -{ pkgs ? import {} }: - - pkgs.mkShell { - nativeBuildInputs = [ pkgs.clang ]; - buildInputs = with pkgs; [ - glibc - rustup - openssl - pkgconfig - llvmPackages.libclang - ncurses - glibcLocales - tor - ]; - shellHook = '' - export LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib"; - ''; - } \ No newline at end of file diff --git a/impls/Cargo.toml b/impls/Cargo.toml index b55abbbc0..28de4f920 100644 --- a/impls/Cargo.toml +++ b/impls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_impls" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" authors = ["Grin Developers "] description = "Concrete types derived from libwallet traits" license = "Apache-2.0" @@ -36,19 +36,19 @@ sysinfo = "0.29" base64 = "0.12.0" url = "2.1" -grin_wallet_util = { path = "../util", version = "5.4.0-contracts.0" } -grin_wallet_config = { path = "../config", version = "5.4.0-contracts.0" } -grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-contracts.0" } +grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" } +grin_wallet_config = { path = "../config", version = "5.4.0-alpha.1" } +grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-alpha.1" } ##### Grin Imports # For Release -# grin_core = "5.4.0-contracts.0" -# grin_keychain = "5.4.0-contracts.0" -# grin_chain = "5.4.0-contracts.0" -# grin_util = "5.4.0-contracts.0" -# grin_api = "5.4.0-contracts.0" -# grin_store = "5.4.0-contracts.0" +# grin_core = "5.4.0-alpha.1" +# grin_keychain = "5.4.0-alpha.1" +# grin_chain = "5.4.0-alpha.1" +# grin_util = "5.4.0-alpha.1" +# grin_api = "5.4.0-alpha.1" +# grin_store = "5.4.0-alpha.1" # For beta release diff --git a/impls/src/adapters/http.rs b/impls/src/adapters/http.rs index ac759157b..5b94dee86 100644 --- a/impls/src/adapters/http.rs +++ b/impls/src/adapters/http.rs @@ -179,10 +179,6 @@ impl HttpSlateSender { return Err(Error::ClientCallback(report)); } - if supported_slate_versions.contains(&"V5".to_owned()) { - return Ok(SlateVersion::V5); - } - if supported_slate_versions.contains(&"V4".to_owned()) { return Ok(SlateVersion::V4); } @@ -227,7 +223,6 @@ impl SlateSender for HttpSlateSender { self.launch_tor()?; let slate_send = match self.check_other_version(&url_str)? { - SlateVersion::V5 => VersionedSlate::into_version(slate.clone(), SlateVersion::V5)?, SlateVersion::V4 => VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?, }; // Note: not using easy-jsonrpc as don't want the dependencies in this crate diff --git a/impls/src/adapters/mod.rs b/impls/src/adapters/mod.rs index 44fe8bf8b..731568ee7 100644 --- a/impls/src/adapters/mod.rs +++ b/impls/src/adapters/mod.rs @@ -17,7 +17,7 @@ pub mod http; mod slatepack; pub use self::file::PathToSlate; -pub use self::http::HttpSlateSender; +pub use self::http::{HttpSlateSender, SchemeNotHttp}; pub use self::slatepack::PathToSlatepack; use crate::config::WalletConfig; diff --git a/impls/src/backends/lmdb.rs b/impls/src/backends/lmdb.rs index e50f2b2f3..6a94d057a 100644 --- a/impls/src/backends/lmdb.rs +++ b/impls/src/backends/lmdb.rs @@ -21,6 +21,8 @@ use std::io::{Read, Write}; use std::marker::PhantomData; use std::path::Path; +use uuid::Uuid; + use crate::blake2::blake2b::{Blake2b, Blake2bResult}; use crate::keychain::{ChildNumber, ExtKeychain, Identifier, Keychain, SwitchCommitmentType}; @@ -316,41 +318,9 @@ where Box::new(iter) } - // TODO: I think this can be deleted - // fn get_tx_log_entry(&self, u: &Uuid) -> Result, Error> { - // let key = to_key(TX_LOG_ENTRY_PREFIX, &mut u.as_bytes().to_vec()); - - // self.db.get_ser(&key, None).map_err(|e| e.into()) - // } - - fn get_tx_log_entry( - &self, - parent_id: Identifier, - log_id: u32, - ) -> Result, Error> { - let tx_log_key = to_key_u64( - TX_LOG_ENTRY_PREFIX, - &mut parent_id.to_bytes().to_vec(), - log_id as u64, - ); - self.db.get_ser(&tx_log_key, None).map_err(|e| e.into()) - /* - fn save_tx_log_entry( - &mut self, - tx_in: TxLogEntry, - parent_id: &Identifier, - ) -> Result<(), Error> { - let tx_log_key = to_key_u64( - TX_LOG_ENTRY_PREFIX, - &mut parent_id.to_bytes().to_vec(), - tx_in.id as u64, - ); - self.db - .borrow() - .as_ref() - .unwrap() - .put_ser(&tx_log_key, &tx_in)?; - */ + fn get_tx_log_entry(&self, u: &Uuid) -> Result, Error> { + let key = to_key(TX_LOG_ENTRY_PREFIX, &mut u.as_bytes().to_vec()); + self.db.get_ser(&key, None).map_err(|e| e.into()) } // TODO - fix this awkward conversion between PrefixIterator and our Box diff --git a/impls/src/node_clients/resp_types.rs b/impls/src/node_clients/resp_types.rs index 2689382a6..86c641465 100644 --- a/impls/src/node_clients/resp_types.rs +++ b/impls/src/node_clients/resp_types.rs @@ -14,7 +14,7 @@ // Derived from https://github.com/apoelstra/rust-jsonrpc //! JSON RPC Types for V2 node client -#[allow(dead_code)] + #[derive(Debug, Deserialize)] pub struct GetTipResp { pub height: u64, diff --git a/libwallet/Cargo.toml b/libwallet/Cargo.toml index b5251292a..584c46186 100644 --- a/libwallet/Cargo.toml +++ b/libwallet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_libwallet" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" authors = ["Grin Developers "] description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." license = "Apache-2.0" @@ -16,7 +16,6 @@ rand = "0.6" serde = "1" serde_derive = "1" serde_json = "1" -serde_with = { version = "1", features = ["chrono"] } log = "0.4" uuid = { version = "0.8", features = ["serde", "v4"] } chrono = { version = "0.4.11", features = ["serde"] } @@ -24,11 +23,11 @@ lazy_static = "1" strum = "0.18" strum_macros = "0.18" thiserror = "1" -ed25519-dalek = "1.0.1" +ed25519-dalek = "1.0.0-pre.4" x25519-dalek = "0.6" base64 = "0.9" regex = "1.3" -sha2 = "0.10.0" +sha2 = "0.8" bs58 = "0.3" age = "0.7" curve25519-dalek = "2.1" @@ -37,22 +36,16 @@ bech32 = "0.7" byteorder = "1.3" num-bigint = "0.2" -grin_wallet_util = { path = "../util", version = "5.4.0-contracts.0" } -grin_wallet_config = { path = "../config", version = "5.4.0-contracts.0" } - -grin_secp256k1zkp = { version = "0.7.12", features = ["bullet-proof-sizing"]} - -#mwmixnet onion -chacha20 = "0.8.1" -hmac = { version = "0.12.0", features = ["std"]} +grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" } +grin_wallet_config = { path = "../config", version = "5.4.0-alpha.1" } ##### Grin Imports # For Release -# grin_core = "5.4.0-contracts.0" -# grin_keychain = "5.4.0-contracts.0" -# grin_util = "5.4.0-contracts.0" -# grin_store = "5.4.0-contracts.0" +# grin_core = "5.4.0-alpha.1" +# grin_keychain = "5.4.0-alpha.1" +# grin_util = "5.4.0-alpha.1" +# grin_store = "5.4.0-alpha.1" # For beta release @@ -73,8 +66,4 @@ grin_store = { git = "https://github.com/mimblewimble/grin", branch = "master" } # grin_util = { path = "../../grin/util"} # grin_store = { path = "../../grin/store"} -# mw-mixnet - - - ##### diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index 67079c091..315091cd4 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -15,11 +15,7 @@ //! Generic implementation of owner API functions use strum::IntoEnumIterator; -use crate::api_impl::owner::contract_new as owner_contract_new; -use crate::api_impl::owner::contract_sign as owner_contract_sign; use crate::api_impl::owner::{check_ttl, post_tx}; -use crate::contract::proofs::InvoiceProof; -use crate::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI}; use crate::grin_core::core::FeeFields; use crate::grin_keychain::Keychain; use crate::grin_util::secp::key::SecretKey; @@ -29,7 +25,6 @@ use crate::{ address, BlockFees, CbData, Error, NodeClient, Slate, SlateState, TxLogEntryType, VersionInfo, WalletBackend, }; -use ed25519_dalek::PublicKey as DalekPublicKey; use super::owner::tx_lock_outputs; @@ -119,16 +114,14 @@ where let excess = ret_slate.calc_excess(keychain.secp())?; if let Some(ref mut p) = ret_slate.payment_proof { - if let Some(saddr) = p.sender_address { - let sig = tx::create_payment_proof_signature( - ret_slate.amount, - &excess, - saddr, - address::address_from_derivation_path(&keychain, &parent_key_id, 0)?, - )?; - - p.promise_signature = Some(sig); - } + let sig = tx::create_payment_proof_signature( + ret_slate.amount, + &excess, + p.sender_address, + address::address_from_derivation_path(&keychain, &parent_key_id, 0)?, + )?; + + p.receiver_signature = Some(sig); } ret_slate.amount = 0; @@ -238,94 +231,3 @@ where } Ok(sl) } - -/// Initialize a receive transaction contract -pub fn contract_new<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - args: &ContractNewArgsAPI, - // use_test_rng: bool, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - let net_change = args.setup_args.net_change.unwrap(); - if net_change <= 0 { - return Err(Error::GenericError( - "Can't create a non-receiving contract from a foreign API.".to_string(), - ) - .into()); - } - owner_contract_new(&mut *w, keychain_mask, args) -} - -/// Sign a receive transaction contract -pub fn contract_sign<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - args: &ContractSetupArgsAPI, - slate: &Slate, - // use_test_rng: bool, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - let net_change = args.net_change.unwrap(); - if net_change <= 0 { - return Err(Error::GenericError( - "Can't sign a non-receiving contract from a foreign API.".to_string(), - ) - .into()); - } - owner_contract_sign(&mut *w, keychain_mask, args, slate) -} - -/// Verify an invoice payment proof -pub fn verify_payment_proof_invoice<'a, T: ?Sized, C, K>( - w: &mut T, - recipient_address: &DalekPublicKey, - proof: &InvoiceProof, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - let mut client = w.w2n_client().clone(); - - let wd = match proof.witness_data.clone() { - Some(w) => w, - None => { - return Err(Error::PaymentProof(format!( - "Cannot verify invoice proof with no witness data", - ))) - } - }; - - let (retrieved_kernel, _) = match client.get_kernel(&wd.kernel_commitment, None, None) { - Err(e) => { - return Err(Error::PaymentProof(format!( - "Error retrieving kernel from chain: {}", - e - ))); - } - Ok(None) => { - return Err(Error::PaymentProof(format!( - "Transaction kernel with excess {:?} not found on chain", - wd.kernel_commitment - ))); - } - Ok(Some((k, _, index))) => (k, index), - }; - - // Now verify with retrieved data - proof.verify_witness( - recipient_address, - &retrieved_kernel.excess_sig, - &retrieved_kernel.msg_to_sign()?, - ) -} diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index baaf6bef6..6432656e4 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -17,35 +17,27 @@ use uuid::Uuid; use crate::api_impl::foreign::finalize_tx as foreign_finalize; -use crate::contract::proofs::{InvoiceProof, ProofWitness}; use crate::grin_core::core::hash::Hashed; use crate::grin_core::core::{Output, OutputFeatures, Transaction}; use crate::grin_core::libtx::proof; use crate::grin_keychain::ViewKey; use crate::grin_util::secp::key::SecretKey; -use crate::grin_util::secp::pedersen; -use crate::grin_util::{static_secp_instance, Mutex, ToHex}; +use crate::grin_util::Mutex; +use crate::grin_util::ToHex; use crate::util::{OnionV3Address, OnionV3AddressError}; use crate::api_impl::owner_updater::StatusMessage; -use crate::contract::types::{ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI}; use crate::grin_keychain::{BlindingFactor, Identifier, Keychain, SwitchCommitmentType}; -use crate::mwmixnet::onion::create_onion; -use crate::mwmixnet::types::{ - add_excess, new_hop, random_secret, ComSignature, Hop, MixnetReqCreationParams, SwapReq, -}; - use crate::internal::{keys, scan, selection, tx, updater}; use crate::slate::{PaymentInfo, Slate, SlateState}; use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletBackend, WalletInfo}; use crate::Error; use crate::{ - address, contract, wallet_lock, BuiltOutput, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, + address, wallet_lock, BuiltOutput, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping, PaymentProof, RetrieveTxQueryArgs, ScannedBlockInfo, Slatepack, SlatepackAddress, Slatepacker, SlatepackerArgs, TxLogEntryType, ViewWallet, WalletInitStatus, WalletInst, WalletLCProvider, }; -use chrono::prelude::{DateTime, NaiveDateTime, Utc}; use ed25519_dalek::PublicKey as DalekPublicKey; use ed25519_dalek::SecretKey as DalekSecretKey; use ed25519_dalek::Verifier; @@ -481,145 +473,6 @@ where }) } -/// Retrieve invoice payment proof -/// TODO: Need to unify with legacy above -pub fn retrieve_payment_proof_invoice<'a, L, C, K>( - wallet_inst: Arc>>>, - keychain_mask: Option<&SecretKey>, - status_send_channel: &Option>, - refresh_from_node: bool, - tx_id: Option, - tx_slate_id: Option, -) -> Result -where - L: WalletLCProvider<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - if tx_id.is_none() && tx_slate_id.is_none() { - return Err(Error::PaymentProofRetrieval( - "Transaction ID or Slate UUID must be specified".to_owned(), - )); - } - if refresh_from_node { - update_wallet_state( - wallet_inst.clone(), - keychain_mask, - status_send_channel, - false, - )? - } else { - false - }; - let txs = retrieve_txs( - wallet_inst.clone(), - keychain_mask, - status_send_channel, - refresh_from_node, - tx_id, - tx_slate_id, - None, - )?; - if txs.1.len() != 1 { - return Err(Error::PaymentProofRetrieval( - "Transaction doesn't exist".to_owned(), - )); - } - // Pull out all needed fields, returning an error if they're not present - let tx = txs.1[0].clone(); - let amount = if tx.amount_credited >= tx.amount_debited { - tx.amount_credited - tx.amount_debited - } else { - // TODO: Invoice proof not expecting fee included here - tx.amount_debited - tx.amount_credited - }; - - let (mut proof, sender_part_sig) = match tx.payment_proof { - Some(p) => { - if p.receiver_public_nonce.is_none() { - return Err(Error::PaymentProofRetrieval( - "Invoice Proof requires stored receiver public nonce".into(), - )); - }; - if p.receiver_public_excess.is_none() { - return Err(Error::PaymentProofRetrieval( - "Invoice Proof requires stored receiver public excess".into(), - )); - }; - if p.timestamp.is_none() { - return Err(Error::PaymentProofRetrieval( - "Invoice Proof requires stored timestamp".into(), - )); - }; - if p.sender_part_sig.is_none() { - return Err(Error::PaymentProofRetrieval( - "Invoice Proof requires stored sender partial signature".into(), - )); - }; - - ( - InvoiceProof { - proof_type: if let Some(t) = p.proof_type { t } else { 1u8 }, - amount, - receiver_public_nonce: p.receiver_public_nonce.unwrap(), - receiver_public_excess: p.receiver_public_excess.unwrap(), - sender_address: p.sender_address, - timestamp: p.timestamp.unwrap().timestamp(), - memo: p.memo, - promise_signature: p.promise_signature, - witness_data: None, - }, - p.sender_part_sig.unwrap(), - ) - } - None => { - return Err(Error::PaymentProofRetrieval( - "Transaction does not contain a payment proof".to_owned(), - )); - } - }; - - // Now to kernel lookup, to fill in the witness data - // Check kernel exists - let mut client = { - wallet_lock!(wallet_inst, w); - w.w2n_client().clone() - }; - - let kernel_excess = match tx.kernel_excess { - Some(k) => k, - None => { - return Err(Error::PaymentProofRetrieval(format!( - "Invoice proof transaction kernel excess missing", - ))) - } - }; - - let (retrieved_kernel, index) = match client.get_kernel(&kernel_excess, None, None) { - Err(e) => { - return Err(Error::PaymentProof(format!( - "Error retrieving kernel from chain: {}", - e - ))); - } - Ok(None) => { - return Err(Error::PaymentProof(format!( - "Transaction kernel with excess {:?} not found on chain", - kernel_excess - ))); - } - Ok(Some((k, _, index))) => (k, index), - }; - - proof.witness_data = Some(ProofWitness { - kernel_index: index, - kernel_commitment: retrieved_kernel.excess, - sender_partial_sig: sender_part_sig, - }); - - Ok(proof) -} - /// Initiate tx as sender pub fn init_send_tx<'a, T: ?Sized, C, K>( w: &mut T, @@ -715,14 +568,9 @@ where let sender_address = OnionV3Address::from_private(&sec_addr_key.0)?; slate.payment_proof = Some(PaymentInfo { - sender_address: Some(sender_address.to_ed25519()?), + sender_address: sender_address.to_ed25519()?, receiver_address: a.pub_key, - promise_signature: None, - timestamp: DateTime::::from_utc( - NaiveDateTime::from_timestamp_opt(0, 0).unwrap(), - Utc, - ), - memo: None, + receiver_signature: None, }); context.payment_proof_derivation_index = Some(deriv_path); @@ -1521,143 +1369,3 @@ where output: output, }) } - -// Contract implementation - -/// Initialize transaction contract -pub fn contract_new<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - args: &ContractNewArgsAPI, - // use_test_rng: bool, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - contract::new(&mut *w, keychain_mask, &args.setup_args) -} - -// /// Setup transaction contract -// pub fn contract_setup<'a, T: ?Sized, C, K>( -// w: &mut T, -// keychain_mask: Option<&SecretKey>, -// args: &ContractSetupArgsAPI, -// slate: &Slate, -// // use_test_rng: bool, -// ) -> Result -// where -// T: WalletBackend<'a, C, K>, -// C: NodeClient + 'a, -// K: Keychain + 'a, -// { -// contract::setup(&mut *w, keychain_mask, slate, &args) -// } - -/// Sign transaction contract -pub fn contract_sign<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - args: &ContractSetupArgsAPI, - slate: &Slate, - // use_test_rng: bool, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - contract::sign(&mut *w, keychain_mask, slate, &args) -} - -/// Revoke transaction contract -pub fn contract_revoke<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - args: &ContractRevokeArgsAPI, - // use_test_rng: bool, -) -> Result, Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - contract::revoke(&mut *w, keychain_mask, &args) -} - -/// Revoke transaction contract -pub fn get_slate_index_matching_my_context<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &Slate, - // use_test_rng: bool, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - let keychain = w.keychain(keychain_mask)?; - let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?; - slate.find_index_matching_context(&keychain, &context) -} - -/// Create MXMixnet request -pub fn create_mwmixnet_req<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - params: &MixnetReqCreationParams, - slate: &Slate, - // use_test_rng: bool, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?; - - let my_keys = context.get_private_keys(); - let kernel = slate.tx_or_err()?.kernels()[0]; - - let msg = kernel.msg_to_sign()?; - - let comsig = ComSignature::sign(slate.amount, &my_keys.0, &msg.to_hex().as_bytes().to_vec())?; - - let mut hops: Vec = Vec::new(); - let mut final_commit = kernel.excess.clone(); - let mut final_blind = my_keys.0.clone(); - - for i in 0..params.server_keys.len() { - let excess = params.server_keys[i].clone(); - - let secp = secp256k1zkp::Secp256k1::with_caps(secp256k1zkp::ContextFlag::Commit); - final_blind.add_assign(&secp, &excess).unwrap(); - final_commit = add_excess(&final_commit, &excess).unwrap(); - let proof = if i == params.server_keys.len() - 1 { - let n1 = random_secret(); - let rp = secp.bullet_proof( - slate.amount - (params.fee_per_hop * params.server_keys.len() as u32) as u64, - final_blind.clone(), - n1.clone(), - n1.clone(), - None, - None, - ); - assert!(secp.verify_bullet_proof(final_commit, rp, None).is_ok()); - Some(rp) - } else { - None - }; - - let hop = new_hop(¶ms.server_keys[i], &excess, params.fee_per_hop, proof); - hops.push(hop); - } - - let onion = create_onion(&kernel.excess, &hops)?; - - Ok(SwapReq { comsig, onion }) - - //slate.find_index_matching_context(&keychain, &context) -} diff --git a/libwallet/src/api_impl/types.rs b/libwallet/src/api_impl/types.rs index 61b7b810d..f50bfc48d 100644 --- a/libwallet/src/api_impl/types.rs +++ b/libwallet/src/api_impl/types.rs @@ -200,8 +200,6 @@ pub struct RetrieveTxQueryArgs { pub include_received_only: Option, /// whether to only consider coinbase transactions pub include_coinbase_only: Option, - /// whether to only consider self spend transactions - pub include_self_spend_only: Option, /// whether to only consider reverted transactions pub include_reverted_only: Option, /// lower bound on the total amount (amount_credited - amount_debited), inclusive @@ -239,7 +237,6 @@ impl Default for RetrieveTxQueryArgs { include_sent_only: Some(false), include_received_only: Some(false), include_coinbase_only: Some(false), - include_self_spend_only: Some(false), include_reverted_only: Some(false), min_amount: None, max_amount: None, diff --git a/libwallet/src/contract/actions/README.md b/libwallet/src/contract/actions/README.md deleted file mode 100644 index a49066900..000000000 --- a/libwallet/src/contract/actions/README.md +++ /dev/null @@ -1,116 +0,0 @@ -# Contract actions - -### API endpoints - -We introduce 3 new API endpoints `/new, /setup, /sign` each corresponding to a specific action on a contract. In the future we'll add the ability to also `view` a contract and to `revoke` it. - -### Rust implementation - -Every contract action on a slate is divided in 3 parts: -1. compute the new state -2. save the new state -3. return slate - -Putting this into code, it looks like the following: -```rust -// Compute the new state (both of the Slate and the Context) -let (slate, context) = compute(slate, args); -// Atomically commit the new state -contract_utils::save_step(slate, context, ...); -// Return the newly produced slate -return slate; -``` - -We only allow contribution of custom inputs/outputs when we're doing the setup phase. Once the setup phase is done, -we no longer allow any customization of inputs. This means that the customization can only happen at contract setup phase which is the first time we see the contract.Additionally, if we customize output selection, we immediately pick the inputs/outputs which means it's an early lock. These are however not added to the slate until we reach the 'sign' phase of the contract. Counterparties don't need to see our inputs/outputs before that. This means we always add inputs/outputs only when we have to and never before. - -Ideally we'd also separate side effects out of these functions e.g. computing the current_height -or refreshing the outputs with updater::refresh_outputs(...). The current_height could be -communicated through a &ChainState parameter which would collect these values before the call. -Additionally, we could fetch the existing Context before the call to avoid doing db fetch. -Separating side effects until the 'save_step' part would make these functions much easier to test. - -#### TODOs - - - for payjoins, instead of doing Some("any"), find an actual input and put the actual commitment in --use-inputs (only do that if none is added). Think about how to do that in a way that would be nice also for the API. Maybe the API should just take "any" which gets transformed into one of the random inputs. Maybe the "any" is ok. It seems to work fine, might be better if we selected the input though. - - make sure to forget the Context when we sign or at least forget the secret keys for it to avoid signing with the same nonce twice - - make_outputs api should receive nanogrins rather than grin. We have to make the conversion before calling the API - - sometimes the slatepack outputs with a \n for some reason which makes pasting it register \n as the end of paste and crashes? - - remove casting decimals, we should accept value in nanogrins (including --make-outputs option), never as 0.1 Grin through the interface. Casting should be left to the gui wallet logic - - Check casts to/from i64/u64 etc. consider using saturating methods. Make sure conversions are safe. - - separate side-effects out from the main computations - - is keys::next_available_key(..) safe from race-conditions? (do we lock?) - - function add_output_to_ctx has a comment around next_available_key - - add support for different accounts not just main (check parent_id, parent_key_id, etc. usage) - - ensure counterparty can't make you overpay fees through num_participants param - - make sure the stored transaction is saved correctly at each step (TxLogEntry has stored_tx field, check other fields as well) - - make sure the transaction log contains all the necessary data (check TODO comments on tx log entry functions) - - graceful error handling - - setup.rs# TODO: verify that the parent_key_id is consistent - - replace mutable objects with immutable when possible - - make sure we lock the wallet when needed (check wallet_lock!() macro that is used in api/owner.rs) - - add support for more than 2 parties (includes a new 'setup' API endpoint and command) - - do we avoid using "too recent" outputs? e.g. though with depth < 10 - - remove unneeded imports - - think if Context.setup_args.net_change type should be u64. If you make it i64, you divide it's size by 2. Perhaps it would be - better to have a u64 field and another field called 'positive' of type bool. - - add --no-setup to 'new' command - - add early-payment proofs. Make sure we have a symmetric variant of a payment proof to avoid having different proofs based on which position you are in the contract signing. Ideally, the position would be irrelevant. - - what happens if you call contract sign on some slate that was not initiated as a contract slate and has different context values? - - make sure you handle all the flows with coinbase outputs as well - - check if they can trick you by providing a slate with different inputs/outputs that are yours - - remove 'setup' API/CLI - - move the contract test utilities to a separate contract_utilities file instead of having it in 'common/mod.rs' - - we have --add-outputs, but we should also lock if we use the --use-inputs param - - check if contract_accounts_switch.rs is a legit scenario. It might need to return an error if the wallet is trying to sign with a different account - -#### Tests - - test contract_fee (various test around fee contribution with 1 or 2 parties) - - test save_step functionality (stored tx, context, logs,..) - - test different output selection in step1 and step3 - - test foreign API for contract new and sign - - test a case where the receiver doesn't have an input available (either not enough confirmations or no inputs) - - contract_rsr.rs asserts that you get amount_credited=5, should it subtract the fees? - - test using more than a single input - - test that sending then again the same slatepack doesn't produce a new signature (to avoid leaking key) - - test locking: - * test that outputs are locked after you sign - * test early locking when using --make-outputs or --use-inputs etc. - - test --no-payjoin - - test accounts - - test 0-value outputs - - test that if --no-payjoin is used, this doesn't mean that we early lock (we shouldn't). Same for --make-outputs - - test slate content through steps - - test negative cases (not enough funds, using input that doesn't exist, make outputs that go over the value, sign twice,...) - -#### DONE - - Always "late-add" inputs/outputs to the slate - - _Always_ add a change output, even if the change output ends up being a 0-value output - - -#### save_step - - // TODO: - // - is_signed should be derived from the slate - // - Check what happens if the batch fails. Also think about possible race conditions because - // of the time delay between the id was picked and saved. - // - Consider taking ownership of Context here. It should not be used after this is called. - - -### Side-effects - -#### Setup - // Side-effects: - // - height = w.w2n_client().get_chain_tip()?.0; - // - maybe_context = w.get_private_context(keychain_mask, sl.id.as_bytes()) - // - create_contract_ctx -> updater::refresh_outputs(wallet, keychain_mask, parent_key_id, false)?; - // - add_outputs -> let current_height = w.w2n_client().get_chain_tip()?.0; - // - add_outputs -> contribute_output -> let key_id = keys::next_available_key(wallet, keychain_mask).unwrap(); - // - TODO: would we need to compute keys::next_available_key for as many outputs as we plan to contribute and pass - // them as a param to keep this without side effects? - -#### Sign - // Side-effects: - // - contract_utils::check_already_signed -> tx_log_iter - // - contract_utils::get_net_change -> context and net_change - // - everything from 'setup' \ No newline at end of file diff --git a/libwallet/src/contract/actions/mod.rs b/libwallet/src/contract/actions/mod.rs deleted file mode 100644 index 5681e2ad0..000000000 --- a/libwallet/src/contract/actions/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! This module contains contract related actions. - -mod new; -mod revoke; -mod setup; -mod sign; -mod view; - -pub use self::new::new; -pub use self::revoke::revoke; -pub use self::setup::setup; -pub use self::sign::sign; -pub use self::view::view; diff --git a/libwallet/src/contract/actions/new.rs b/libwallet/src/contract/actions/new.rs deleted file mode 100644 index ce1e5a53e..000000000 --- a/libwallet/src/contract/actions/new.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Implementation of contract new - -use crate::contract; -use crate::contract::actions::setup; -use crate::contract::types::ContractSetupArgsAPI; -use crate::error::Error; -use crate::grin_keychain::Keychain; -use crate::grin_util::secp::key::SecretKey; -use crate::slate::Slate; -use crate::types::{Context, NodeClient, WalletBackend}; - -/// Create a new contract with initial setup done by the initiator -pub fn new<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - setup_args: &ContractSetupArgsAPI, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - // Compute state for 'new' - let (slate, mut context) = compute(w, keychain_mask, setup_args)?; - - // Atomically commit state - contract::utils::save_step( - w, - keychain_mask, - &slate, - &mut context, - setup_args.add_outputs, - false, - )?; - - Ok(slate) -} - -/// Compute logic for new -pub fn compute<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - setup_args: &ContractSetupArgsAPI, -) -> Result<(Slate, Context), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - let net_change = setup_args.net_change.unwrap(); - debug!("contract::new => net_change passed: {}", net_change); - - // Initialize a new contract (if net_change is positive, I'm the receiver meaning this is invoice flow) - let num_participants = setup_args.num_participants; - let mut slate = Slate::blank(num_participants, net_change > 0); - // We set slate.amount to contain the _positive_ net_change for the other party so they can derive expectations. - slate.amount = net_change.abs() as u64; - debug!("contract::new => slate amount: {}", slate.amount); - - // Perform setup for the slate - setup::compute(w, keychain_mask, &mut slate, setup_args) -} diff --git a/libwallet/src/contract/actions/revoke.rs b/libwallet/src/contract/actions/revoke.rs deleted file mode 100644 index 2cd510d59..000000000 --- a/libwallet/src/contract/actions/revoke.rs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Implementation of contract revoke -use crate::contract::types::{ContractRevokeArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs}; -use crate::contract::{new, sign}; -use crate::error::Error; -use crate::grin_keychain::Keychain; -use crate::grin_util::secp::key::SecretKey; -use crate::internal::tx; -use crate::slate::Slate; -use crate::types::{NodeClient, OutputData, OutputStatus, WalletBackend}; - -/// Contract revocation is done by double-spending the input -pub fn revoke<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - args: &ContractRevokeArgsAPI, -) -> Result, Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - // TODO: check the correctness of this. This is essentially old cancel + self-spend. - // FUTURE: we may want to boost fees in case we notice something in the mempool. There - // are also race conditions possible. We may not want to label txlogenry as Canceled - // until the new tx gets on the chain. - // NOTE: We should not care about deleting the context because as soon as we sign - // a contract, the context is deleted. - - // If we contributed inputs, we must have locked them at which point we also set the - // OutputData.tx_log_entry which is the tx_id. - let tx_id = args.tx_id; - - // Find my outputs that have been Locked and refer to the given tx_id - let my_contributed_inputs = w - .batch(keychain_mask)? - .iter() - .filter(|out| { - // Find an output that is Locked and is in the tx_input_commit - out.status == OutputStatus::Locked - && (out.tx_log_entry.is_some() && out.tx_log_entry.as_ref().unwrap() == &tx_id) - }) - .collect::>(); - - // 1. Unlock the input by calling cancel_tx - let parent_key_id = w.parent_key_id(); - tx::cancel_tx(&mut *w, keychain_mask, &parent_key_id, Some(tx_id), None)?; - - if my_contributed_inputs.len() == 0 { - return Ok(None); - } - let input_commit = my_contributed_inputs[0].commit.as_ref().unwrap(); - // 2. Create a 1-1 self-spend transaction using this input - let ct_slate = new( - w, - keychain_mask, - &ContractSetupArgsAPI { - // TODO: Check the src_acct_name below. This would use the currently active account - src_acct_name: None, - net_change: Some(0), // self-spend - num_participants: 1, - add_outputs: false, - selection_args: OutputSelectionArgs { - use_inputs: Some(String::from(input_commit)), - ..Default::default() - }, - proof_args: Default::default(), - }, - )?; - let finished_slate = sign( - w, - keychain_mask, - &ct_slate, - &ContractSetupArgsAPI { - // TODO: Check the src_acct_name below. This would use the currently active account - src_acct_name: None, - net_change: None, // we already have it in the context as 0 now - num_participants: 1, - add_outputs: false, - selection_args: OutputSelectionArgs { - use_inputs: Some(String::from(input_commit)), - ..Default::default() - }, - proof_args: Default::default(), - }, - )?; - // TODO: Think about what to do with transaction context of the cancelled slate. It should probably get deleted. - - Ok(Some(finished_slate)) -} diff --git a/libwallet/src/contract/actions/setup.rs b/libwallet/src/contract/actions/setup.rs deleted file mode 100644 index 3c9c059bc..000000000 --- a/libwallet/src/contract/actions/setup.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Implementation of contract setup - -use crate::api_impl::owner::check_ttl; -use crate::contract; -use crate::contract::types::ContractSetupArgsAPI; -use crate::error::Error; -use crate::grin_keychain::Keychain; -use crate::grin_util::secp::key::SecretKey; -use crate::slate::Slate; -use crate::types::{Context, NodeClient, WalletBackend}; - -/// Perform a contract setup -pub fn setup<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &Slate, - setup_args: &ContractSetupArgsAPI, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - // Compute state for 'setup' - let (slate, mut context) = compute(w, keychain_mask, slate, setup_args)?; - - // Atomically commit state - contract::utils::save_step( - w, - keychain_mask, - &slate, - &mut context, - setup_args.add_outputs, - false, - )?; - - Ok(slate) -} - -/// Compute logic for setup -pub fn compute<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &Slate, - setup_args: &ContractSetupArgsAPI, -) -> Result<(Slate, Context), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - let mut sl = slate.clone(); - check_ttl(w, &sl)?; - - // Get or create a transaction Context and verify consistency of setup arguments - let mut context = contract::context::get_or_create(w, keychain_mask, &mut sl, setup_args)?; - contract::utils::verify_setup_args_consistency( - &context.setup_args.as_ref().unwrap(), - &setup_args, - )?; - - // Add keys and payment proof to slate (both are idempotent operations) - contract::slate::add_keys(&mut sl, &w.keychain(keychain_mask)?, &mut context)?; - contract::slate::add_payment_proof( - w, - &mut sl, - keychain_mask, - &mut context, - &setup_args.net_change, - &setup_args.proof_args, - )?; // noop for the sender - - // Add inputs/outputs to the Context if needed. No locking is done here. This happens at save_step. - if setup_args.add_outputs { - contract::context::add_outputs(&mut *w, keychain_mask, &mut context)?; - } - - Ok((sl, context)) -} diff --git a/libwallet/src/contract/actions/sign.rs b/libwallet/src/contract/actions/sign.rs deleted file mode 100644 index aef428a16..000000000 --- a/libwallet/src/contract/actions/sign.rs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Implementation of contract sign - -use crate::contract; -use crate::contract::actions::setup; -use crate::contract::types::ContractSetupArgsAPI; -use crate::error::Error; -use crate::grin_keychain::Keychain; -use crate::grin_util::secp::key::SecretKey; -use crate::slate::Slate; -use crate::types::{Context, NodeClient, WalletBackend}; - -/// Sign a contract -pub fn sign<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &Slate, - setup_args: &ContractSetupArgsAPI, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - // Compute if we will add outputs at this step - let will_add_outputs = match w.get_private_context(keychain_mask, slate.id.as_bytes()) { - Ok(ctx) => ctx.get_inputs().len() + ctx.get_outputs().len() == 0, - Err(_) => true, - }; - // Compute state for 'sign' - let (sl, mut context) = compute(w, keychain_mask, slate, setup_args)?; - - // Atomically commit state - contract::utils::save_step(w, keychain_mask, &sl, &mut context, will_add_outputs, true)?; - - Ok(sl) -} - -/// Compute logic for sign -pub fn compute<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &Slate, - setup_args: &ContractSetupArgsAPI, -) -> Result<(Slate, Context), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - let mut sl = slate.clone(); - contract::utils::verify_not_signed(w, sl.id)?; - - // Ensure net_change has been provided - let expected_net_change = - contract::utils::get_net_change(w, keychain_mask, &sl, setup_args.net_change)?; - - // Define the values that must be provided in the setup phase at the sign step - let mut setup_args = setup_args.clone(); - setup_args.net_change = Some(expected_net_change); - setup_args.num_participants = sl.num_participants; - setup_args.add_outputs = true; // we add outputs to the Context in case we haven't done that yet - - // Ensure Setup phase is done and that inputs/outputs have been added to the Context - let (mut sl, mut context) = setup::compute(w, keychain_mask, &mut sl, &setup_args)?; - // Add outputs to the slate, verify the payment proof and sign the slate - contract::slate::add_outputs(w, keychain_mask, &mut sl, &context)?; - if let Some(ref p) = sl.payment_proof { - contract::slate::verify_payment_proof(&sl, expected_net_change, &p.receiver_address)?; - // noop for the receiver - } - - contract::slate::sign(w, keychain_mask, &mut sl, &mut context)?; - contract::slate::transition_state(&mut sl)?; - - // If we have all the partial signatures, finalize the tx - if contract::slate::can_finalize(&sl) { - contract::slate::finalize(w, keychain_mask, &mut sl)?; - } - - Ok((sl, context)) -} diff --git a/libwallet/src/contract/actions/view.rs b/libwallet/src/contract/actions/view.rs deleted file mode 100644 index fcbda670e..000000000 --- a/libwallet/src/contract/actions/view.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Implementation of contract view - -use crate::contract::types::ContractView; -use crate::error::Error; -use crate::grin_keychain::Keychain; -use crate::grin_util::secp::key::SecretKey; -use crate::slate::{Slate, SlateState}; -use crate::types::{NodeClient, WalletBackend}; - -/// View contract -pub fn view<'a, T: ?Sized, C, K>( - _w: &mut T, - _keychain_mask: Option<&SecretKey>, - slate: &mut Slate, - _encrypted_for: &str, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - // NOTE: This should only be run on slates that we received and were signed for us. - // Otherwise, you can't really predict who the party doing the next step should be. - - // TODO: Do we need to do any slate verification here? - let suggested_net_change: Option = match slate.state { - // TODO: Check bounds against overflow/underflow - SlateState::Invoice1 => Some(slate.amount as i64), - SlateState::Standard1 => Some(-(slate.amount as i64)), - _ => None, - }; - let is_executed = false; - let num_sigs = slate - .participant_data - .clone() - .into_iter() - .filter(|v| !v.is_complete()) - .count(); - - // TODO: Maybe we can know if the slate was meant for us if it was encrypted for us. - // A possible issue is that one can encrypt the same slate for 10 people. - let ct_view = ContractView { - num_participants: slate.num_participants, - suggested_net_change: suggested_net_change, - agreed_net_change: None, // TODO - num_sigs: num_sigs as u8, - is_executed: is_executed, - ..Default::default() - }; - Ok(ct_view) -} diff --git a/libwallet/src/contract/context.rs b/libwallet/src/contract/context.rs deleted file mode 100644 index 07764073e..000000000 --- a/libwallet/src/contract/context.rs +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Contract functions on the Context - -use crate::contract::selection::prepare_outputs; -use crate::contract::types::ContractSetupArgsAPI; -use crate::contract::utils as contract_utils; -use crate::grin_keychain::{Identifier, Keychain}; -use crate::grin_util::secp::key::SecretKey; -use crate::internal::{keys, updater}; -use crate::slate::Slate; -use crate::types::{Context, NodeClient, WalletBackend}; -use crate::{Error, OutputData}; -use grin_core::core::FeeFields; - -/// Get or create transaction Context for the given slate -pub fn get_or_create<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &mut Slate, - setup_args: &ContractSetupArgsAPI, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - debug!("contract::context::get_or_create => called"); - let maybe_context = w.get_private_context(keychain_mask, slate.id.as_bytes()); - - let context = match maybe_context { - Err(_) => { - // Get data required for creating a context - let height = w.w2n_client().get_chain_tip()?.0; - let parent_key_id = - contract_utils::parent_key_for(w, setup_args.src_acct_name.as_ref()); - self::create( - w, - keychain_mask, - slate, - height, - // &args, - setup_args, - &parent_key_id, - false, - )? - } - Ok(ctx) => ctx, - }; - Ok(context) -} - -/// Creates a context for a contract -fn create<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &mut Slate, - current_height: u64, - // TODO: compare with &InitTxArgs to see if any information is missing - setup_args: &ContractSetupArgsAPI, - parent_key_id: &Identifier, - use_test_rng: bool, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - debug!("Creating a new contract context"); - // sender should always refresh outputs - updater::refresh_outputs(w, keychain_mask, parent_key_id, false)?; - - // Fee contribution estimation - let net_change = setup_args.net_change.unwrap(); - // select inputs to estimate fee cost - let (inputs, _, my_fee) = - prepare_outputs(w, &parent_key_id, current_height, &setup_args, None)?; - // The number of outputs we expect is the number of custom outputs plus one change output - debug!( - "My fee contribution estimation: {} for n_inputs: {}, n_outputs: {}, n_kernels: {}, num_participants: {}", - my_fee.fee(), inputs.len(), setup_args.selection_args.num_custom_outputs() + 1, 1, setup_args.num_participants - ); - // Make sure `my_fee < net_change` holds for the receiver. This can't be true for a self-spend, because nobody - // has a net_change > 0 which makes a self-spend ok to be a net negative when fees are included. - if net_change > 0 && my_fee.fee() > net_change.abs() as u64 { - panic!( - "My contribution as a receiver would be net negative. my_fee: {}, net_change: {}", - my_fee.fee(), - net_change - ); - } - // Add my share of fee contribution to the slate fees - slate.fee_fields = FeeFields::new(0, slate.fee_fields.fee() + my_fee.fee())?; - debug!("Slate.fee: {}", slate.fee_fields.fee()); - - // Create a Context for this slate - let keychain = w.keychain(keychain_mask)?; - // TODO: it seems 'is_initiator: true' is only used in test_rng. Do we care about this? - let mut context = Context::new(keychain.secp(), &parent_key_id, use_test_rng, true); - // Context.fee will hold _our_ fee contribution and not the total slate fee - context.fee = my_fee.as_opt(); - // Context.amount is not used in contracts, but we set it anyway. - context.amount = slate.amount; - // TODO: looking at what uses Context.late_lock_args, it seems only the args in SelectionArgs are used except - // for args.ttl_blocks. Is this needed? Can we refactor this? - context.setup_args = Some(setup_args.clone()); - debug!( - "Setting Context.net_change as: {}", - context.get_net_change() - ); - - Ok(context) -} - -/// Add outputs to a contract context (including spent outputs which get locked) -pub fn add_outputs<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - context: &mut Context, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - debug!("contract::utils::add_outputs => called"); - // Do nothing if we have already contributed our outputs. The assumption is that if this was done, - // our output contribution is complete. - if context.output_ids.len() > 0 || context.input_ids.len() > 0 { - debug!("contract::utils::add_outputs => outputs have already been added, returning."); - return Ok(()); - } - let setup_args = context.setup_args.as_ref().unwrap(); - debug!("contract::utils::add_outputs => adding outputs"); - let current_height = w.w2n_client().get_chain_tip()?.0; - let parent_key_id = &context.parent_key_id; - - // Select inputs for which `Σmy_inputs >= Σmy_outputs + my_fee_cost` holds. Uses committed fee if present. - let (inputs, my_output_amounts, my_fee) = prepare_outputs( - &mut *w, - parent_key_id, - current_height, - &setup_args, - context.fee, - )?; - assert_eq!(my_fee.fee(), context.fee.unwrap().fee(), "my_fee!=ctx.fee"); - // Add selected/created inputs/outputs to the context - add_inputs_to_ctx(context, &inputs)?; - add_outputs_to_ctx(w, keychain_mask, context, my_output_amounts)?; - - Ok(()) -} - -/// Add inputs to Context -fn add_inputs_to_ctx(context: &mut Context, inputs: &Vec) -> Result<(), Error> { - debug!("contract::utils::add_inputs_to_ctx => adding inputs to context"); - for input in inputs { - context.add_input(&input.key_id, &input.mmr_index, input.value); - debug!( - "contract::utils::add_inputs_to_ctx => input id: {}, value:{}", - &input.key_id, input.value - ); - } - - Ok(()) -} - -/// Add outputs to Context -fn add_outputs_to_ctx<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - context: &mut Context, - amounts: Vec, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - for amount in amounts { - // TODO: it seems like next_available_key does not respect the parent_key_id. Check if it does, it probably should? - // A late-lock might have a different account set to active than the one that was set to the Context - let key_id = keys::next_available_key(w, keychain_mask).unwrap(); - context.add_output(&key_id, &None, amount); - debug!( - "contract::utils::add_output_to_ctx => added output to context. Output id: {}, amount: {}", - key_id.clone(), - amount - ); - } - Ok(()) -} diff --git a/libwallet/src/contract/mod.rs b/libwallet/src/contract/mod.rs deleted file mode 100644 index a7dbaf544..000000000 --- a/libwallet/src/contract/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! This module contains contract related actions. - -mod actions; -mod context; -pub mod proofs; -mod selection; -mod slate; -pub mod types; -mod utils; - -pub use self::actions::{new, revoke, setup, sign, view}; - -pub use self::slate::can_finalize; -pub use self::utils::my_fee_contribution; diff --git a/libwallet/src/contract/proofs.rs b/libwallet/src/contract/proofs.rs deleted file mode 100644 index d471e3148..000000000 --- a/libwallet/src/contract/proofs.rs +++ /dev/null @@ -1,452 +0,0 @@ -// Copyright 2023 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Experimental early payment proof functionality, currently only used -//! with contracts. Can move outside of this module if early proofs are adopted -//! by legacy transactions - -use crate::contract::types::ProofArgs; -use crate::grin_core::libtx::aggsig; -use crate::grin_core::libtx::secp_ser; -use crate::grin_core::ser as grin_ser; -use crate::grin_core::ser::{Readable, Reader, Writeable, Writer}; -use crate::grin_keychain::Keychain; -use crate::grin_util::secp::key::{PublicKey, SecretKey}; -use crate::grin_util::secp::pedersen::Commitment; -use crate::grin_util::secp::Signature; -use crate::grin_util::static_secp_instance; -use crate::slate::{PaymentInfo, PaymentMemo, Slate}; -use crate::slate_versions::ser as dalek_ser; -use crate::types::{Context, NodeClient, WalletBackend}; -use crate::{address, Error}; -use byteorder::{BigEndian, ByteOrder}; -use chrono::{DateTime, NaiveDateTime, Utc}; -use ed25519_dalek::Keypair as DalekKeypair; -use ed25519_dalek::PublicKey as DalekPublicKey; -use ed25519_dalek::SecretKey as DalekSecretKey; -use ed25519_dalek::Signature as DalekSignature; -use ed25519_dalek::{Signer, Verifier}; -use grin_util::secp::Message; -use std::convert::TryInto; - -/// All elements required to validate a proof within a single struct -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct ProofWitness { - /// Kernel index, supplied so verifiers can look up kernel - /// without an expensive lookup operation - #[serde(with = "secp_ser::string_or_u64")] - pub kernel_index: u64, - /// Kernel commitment, supplied so prover can recompute index - /// if required after a reorg - #[serde( - serialize_with = "secp_ser::as_hex", - deserialize_with = "secp_ser::commitment_from_hex" - )] - pub kernel_commitment: Commitment, - /// sender partial signature, used to recover receiver partial signature - #[serde(with = "secp_ser::sig_serde")] - pub sender_partial_sig: Signature, -} - -/// Payment proof, to be extracted from slates for -/// signing (when wrapped as PaymentProofBin) or json export from stored tx data -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct InvoiceProof { - /// Proof type, 0x00 legacy (though this will use StoredProofInfo above, 1 invoice, 2 Sender nonce) - pub proof_type: u8, - /// amount - #[serde(with = "secp_ser::string_or_u64")] - pub amount: u64, - /// receiver's public nonce from signing - #[serde(with = "secp_ser::pubkey_serde")] - pub receiver_public_nonce: PublicKey, - /// receiver's public excess from signing - #[serde(with = "secp_ser::pubkey_serde")] - pub receiver_public_excess: PublicKey, - /// Sender's address - #[serde(with = "dalek_ser::dalek_pubkey_serde")] - pub sender_address: DalekPublicKey, - /// Timestamp provided by recipient when signing - pub timestamp: i64, - /// Optional payment memo - #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, - /// Not serialized in binary format - #[serde(with = "dalek_ser::option_dalek_sig_serde")] - pub promise_signature: Option, - /// Not serialized in binary format, just a convenient place to insert - /// the witness kernel commitment index - #[serde(skip_serializing_if = "Option::is_none")] - pub witness_data: Option, -} - -struct InvoiceProofBin(InvoiceProof); - -impl Writeable for InvoiceProofBin { - fn write(&self, writer: &mut W) -> Result<(), grin_ser::Error> { - writer.write_u8(1)?; - - // Amount field is 7 bytes, throw error if value is greater - let mut amount_bytes = [0; 8]; - BigEndian::write_u64(&mut amount_bytes, self.0.amount); - - if amount_bytes[0] > 0 { - return Err(grin_ser::Error::UnexpectedData { - expected: [0u8].to_vec(), - received: [amount_bytes[0]].to_vec(), - }); - } - writer.write_fixed_bytes(amount_bytes[1..].to_vec())?; - { - let static_secp = static_secp_instance(); - let static_secp = static_secp.lock(); - writer.write_fixed_bytes( - self.0 - .receiver_public_nonce - .serialize_vec(&static_secp, true), - )?; - writer.write_fixed_bytes( - self.0 - .receiver_public_excess - .serialize_vec(&static_secp, true), - )?; - } - writer.write_fixed_bytes(self.0.sender_address.as_bytes())?; - writer.write_i64(self.0.timestamp)?; - match &self.0.memo { - Some(s) => { - writer.write_u8(s.memo_type)?; - writer.write_fixed_bytes(&s.memo.to_vec())?; - } - None => { - writer.write_u8(0)?; - writer.write_fixed_bytes([0u8; 32].to_vec())?; - } - } - Ok(()) - } -} - -/// Not strictly necessary, but useful for tests -impl Readable for InvoiceProofBin { - fn read(reader: &mut R) -> Result { - // first 8 bytes are proof type + 7 bytes worth of amount - let mut amount = reader.read_u64()?; - let proof_type: u8 = ((amount & 0xFF00_0000_0000_0000) >> 56).try_into().unwrap(); - amount &= 0x00FF_FFFF_FFFF_FFFF; - - let receiver_public_nonce; - let receiver_public_excess; - { - let static_secp = static_secp_instance(); - let static_secp = static_secp.lock(); - receiver_public_nonce = - PublicKey::from_slice(&static_secp, &reader.read_fixed_bytes(33)?).unwrap(); - receiver_public_excess = - PublicKey::from_slice(&static_secp, &reader.read_fixed_bytes(33)?).unwrap(); - } - - let sender_address_vec = reader.read_fixed_bytes(32)?; - let sender_address = DalekPublicKey::from_bytes(&sender_address_vec).unwrap(); - - let timestamp = reader.read_i64()?; - - let memo_type = reader.read_u8()?; - let memo = reader.read_fixed_bytes(32)?; - let mut memo_bytes: [u8; 32] = [0u8; 32]; - memo_bytes.copy_from_slice(&memo); - - let res = InvoiceProof { - proof_type, - amount, - receiver_public_nonce, - receiver_public_excess, - sender_address, - timestamp, - memo: match memo_type { - 0 => None, - _ => Some(PaymentMemo { - memo_type, - memo: memo_bytes, - }), - }, - promise_signature: None, - witness_data: None, - }; - - Ok(InvoiceProofBin(res)) - } -} - -impl InvoiceProof { - /// Extracts as much data as possible from the slate to create an invoice proof - pub fn from_slate( - slate: &Slate, - participant_index: usize, - sender_address: Option, - ) -> Result { - // Sender address is either provided or in slate (or error) - let sender_address = match sender_address { - Some(a) => a, - None => { - if let Some(ref p) = slate.payment_proof { - if let Some(a) = p.sender_address { - a - } else { - return Err(Error::NoSenderAddressProvided); - } - } else { - return Err(Error::NoSenderAddressProvided); - } - } - }; - - let timestamp = match slate.payment_proof.as_ref() { - Some(p) => NaiveDateTime::from_timestamp_opt(p.timestamp.timestamp(), 0) - .unwrap() - .timestamp(), - None => 0, - }; - - let memo = match slate.payment_proof.as_ref() { - Some(p) => p.memo.clone(), - None => None, - }; - - let promise_signature = match slate.payment_proof.as_ref() { - Some(p) => p.promise_signature.clone(), - None => None, - }; - - Ok(Self { - proof_type: 1u8, - amount: slate.amount, - receiver_public_nonce: slate.participant_data[participant_index].public_nonce, - receiver_public_excess: slate.participant_data[participant_index].public_blind_excess, - sender_address, - timestamp, - memo, - promise_signature, - witness_data: None, - }) - } - - /// Sign the invoice proof, provided all fields are populated - pub fn sign(&self, sec_key: &SecretKey) -> Result<(DalekSignature, DalekPublicKey), Error> { - let d_skey = match DalekSecretKey::from_bytes(&sec_key.0) { - Ok(k) => k, - Err(e) => { - return Err(Error::ED25519Key(format!("{}", e))); - } - }; - let pub_key: DalekPublicKey = (&d_skey).into(); - let keypair = DalekKeypair { - public: pub_key, - secret: d_skey, - }; - let mut sig_data_bin = Vec::new(); - let _ = grin_ser::serialize_default(&mut sig_data_bin, &InvoiceProofBin(self.clone())) - .expect("serialization failed"); - - Ok((keypair.sign(&sig_data_bin), pub_key)) - } - - /// Verify the signature of the invoice proof - pub fn verify_promise_signature( - &self, - recipient_address: &DalekPublicKey, - ) -> Result<(), Error> { - if self.promise_signature.is_none() { - return Err(Error::PaymentProofValidation( - "Missing promise signature".into(), - )); - } - - // Rebuild message - let mut sig_data_bin = Vec::new(); - let _ = grin_ser::serialize_default(&mut sig_data_bin, &InvoiceProofBin(self.clone())) - .expect("serialization failed"); - - if recipient_address - .verify(&sig_data_bin, self.promise_signature.as_ref().unwrap()) - .is_err() - { - return Err(Error::PaymentProof( - "Invalid recipient signature".to_owned(), - )); - }; - Ok(()) - } - - /// Verify signature and proof against a given kernel message (kernel lookup is beyond the scope - /// of this module) - pub fn verify_witness( - &self, - recipient_address: &DalekPublicKey, - excess_sig: &Signature, - msg: &Message, - ) -> Result<(), Error> { - if self.witness_data.is_none() { - return Err(Error::PaymentProofValidation("Missing witness data".into())); - } - - self.verify_promise_signature(recipient_address)?; - - let wd = self.witness_data.as_ref().unwrap().clone(); - { - let static_secp = static_secp_instance(); - let static_secp = static_secp.lock(); - - let receiver_part_sig = - aggsig::subtract_signature(&static_secp, &excess_sig, &wd.sender_partial_sig)?; - - // Retrieve the public nonce sum from the kernel excess signature - let mut pub_nonce_sum_bytes = [3u8; 33]; - pub_nonce_sum_bytes[1..33].copy_from_slice(&excess_sig[0..32]); - let pub_nonce_sum = PublicKey::from_slice(&static_secp, &pub_nonce_sum_bytes)?; - - // Retrieve the public key sum from the kernel excess - let pub_blind_sum = wd.kernel_commitment.to_pubkey(&static_secp)?; - - if let Err(_) = aggsig::verify_partial_sig( - &static_secp, - &receiver_part_sig.0, - &pub_nonce_sum, - &self.receiver_public_excess, - Some(&pub_blind_sum), - &msg, - ) { - // Try other possibility - if let Some(s) = receiver_part_sig.1 { - aggsig::verify_partial_sig( - &static_secp, - &s, - &pub_nonce_sum, - &self.receiver_public_excess, - Some(&pub_blind_sum), - &msg, - )?; - } else { - return Err(Error::PaymentProofValidation( - "Signature subtraction failed".into(), - )); - } - } - } - Ok(()) - } -} - -impl serde::Serialize for InvoiceProofBin { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut vec = vec![]; - grin_ser::serialize(&mut vec, grin_ser::ProtocolVersion(4), self) - .map_err(|err| serde::ser::Error::custom(err.to_string()))?; - serializer.serialize_bytes(&vec) - } -} - -/// Adds all info needed for a payment proof to a slate, complete with signed recipient data -pub fn add_payment_proof<'a, T: ?Sized, C, K>( - wallet: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &mut Slate, - context: &Context, - proof_args: &ProofArgs, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - // TODO: Just generating invoice (type 1) for now - let (invoice_proof, promise_signature, receiver_address) = - generate_invoice_signature(wallet, keychain_mask, slate, context, proof_args)?; - let timestamp = NaiveDateTime::from_timestamp_opt(Utc::now().timestamp(), 0).unwrap(); - let timestamp = DateTime::::from_utc(timestamp, Utc); - - let proof = PaymentInfo { - sender_address: proof_args.sender_address.clone(), - receiver_address, - timestamp, - promise_signature: Some(promise_signature), - memo: invoice_proof.memo, - }; - slate.payment_proof = Some(proof); - Ok(()) -} - -/// Generates a signature for proof type 'Invoice' -fn generate_invoice_signature<'a, T: ?Sized, C, K>( - wallet: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &mut Slate, - context: &Context, - proof_args: &ProofArgs, -) -> Result<(InvoiceProof, DalekSignature, DalekPublicKey), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - let keychain = wallet.keychain(keychain_mask)?; - let index = slate.find_index_matching_context(&keychain, context)?; - let mut invoice_proof = InvoiceProof::from_slate(&slate, index, proof_args.sender_address)?; - let derivation_index = match context.payment_proof_derivation_index { - Some(i) => i, - None => 0, - }; - let parent_key_id = wallet.parent_key_id(); - let recp_key = - address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?; - - invoice_proof.timestamp = NaiveDateTime::from_timestamp_opt(Utc::now().timestamp(), 0) - .unwrap() - .timestamp(); - let (sig, addr) = invoice_proof.sign(&recp_key)?; - Ok((invoice_proof, sig, addr)) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::slate_versions::tests::populate_test_slate; - - #[test] - fn ser_invoice_proof_bin() -> Result<(), Error> { - let mut slate = populate_test_slate()?; - slate.amount |= 0xFF00_0000_0000_0000; - // Bin serialization doesn't include promise sig as it's used to create signature data - slate.payment_proof.as_mut().unwrap().promise_signature = None; - - // Should fail, amount too big - let invoice_proof = InvoiceProof::from_slate(&slate, 1, None)?; - let mut vec = Vec::new(); - assert!(grin_ser::serialize_default(&mut vec, &InvoiceProofBin(invoice_proof)).is_err()); - - // Should be okay now - slate.amount = 1234; - let invoice_proof = InvoiceProof::from_slate(&slate, 1, None)?; - let mut vec = Vec::new(); - grin_ser::serialize_default(&mut vec, &InvoiceProofBin(invoice_proof.clone())) - .expect("Serialization Failed"); - - let proof_deser: InvoiceProofBin = grin_ser::deserialize_default(&mut &vec[..]).unwrap(); - assert_eq!(invoice_proof, proof_deser.0); - Ok(()) - } -} diff --git a/libwallet/src/contract/selection.rs b/libwallet/src/contract/selection.rs deleted file mode 100644 index 6b98afe18..000000000 --- a/libwallet/src/contract/selection.rs +++ /dev/null @@ -1,596 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Contract coin selection functions - -use crate::contract::types::{ContractSetupArgsAPI, OutputSelectionArgs}; -use crate::contract::utils::my_fee_contribution; -use crate::grin_core::core::amount_to_hr_string; -use crate::grin_keychain::{Identifier, Keychain}; -use crate::types::{NodeClient, WalletBackend}; -use crate::{Error, OutputData}; -use grin_core::core::FeeFields; - -/// Prepares inputs & outputs that satisfy `Σmy_inputs >= Σmy_outputs + my_fee_cost` taking into account selection args -pub fn prepare_outputs<'a, T: ?Sized, C, K>( - w: &mut T, - parent_key_id: &Identifier, - current_height: u64, - setup_args: &ContractSetupArgsAPI, - committed_fee: Option, -) -> Result<(Vec, Vec, FeeFields), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - // Find available inputs - let mut eligible_inputs = find_eligible(w, parent_key_id, current_height)?; - // Select which inputs to use to satisfy the equation - compute(setup_args, committed_fee, &mut eligible_inputs) -} - -/// Find all inputs eligible to spend -pub fn find_eligible<'a, T: ?Sized, C, K>( - w: &mut T, - parent_key_id: &Identifier, - current_height: u64, -) -> Result, Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - // Find eligible inputs in the wallet - let eligible_inputs = w - .iter() - .filter(|out| out.root_key_id == *parent_key_id && out.eligible_to_spend(current_height, 1)) - .collect::>(); - Ok(eligible_inputs) -} -// Given a list of inputs, an optional committed fee and setup args, compute which inputs to use, what amount outputs to make and fee cost -pub fn compute( - setup_args: &ContractSetupArgsAPI, - committed_fee: Option, - inputs: &mut Vec, -) -> Result<(Vec, Vec, FeeFields), Error> -where -{ - let (inputs, fee) = select_inputs(setup_args, committed_fee, inputs)?; - let output_amounts = build_output_amount_list( - inputs.clone().iter().map(|out| out.value).sum::(), - fee.fee(), - setup_args, - ); - Ok((inputs, output_amounts, fee)) -} - -// Given a list of inputs, an optional committed fee and setup args, compute which inputs to use -fn select_inputs( - setup_args: &ContractSetupArgsAPI, - committed_fee: Option, - inputs: &mut Vec, -) -> Result<(Vec, FeeFields), Error> -where -{ - // We use 'lhs' and 'rhs' to denote the amounts on the left/right-hand side of the equation. - // To simulate receive/payment value we: - // - add positive net_change to the 'lhs' for the receiver (to simulate sender's input) - // - add positive net_change to the 'rhs' for the sender (to simulate receiver's output) - // For either party, the following MUST hold for the inputs the function returns: - // Σmy_inputs >= Σmy_outputs + my_fee_cost - // Each party later balances the equation by adding an additional output (change output or receiver output) - let net_change = setup_args.net_change.unwrap(); - let custom_outputs_amount_sum = setup_args.selection_args.sum_output_amounts(); - let pay_amount = if net_change < 0 { - net_change.abs() as u64 - } else { - 0 - }; - // Add the amount we pay and the custom outputs to rhs of the equation - let rhs = pay_amount + custom_outputs_amount_sum; - let required_inputs = setup_args.selection_args.required_inputs(); - let is_payjoin = setup_args.selection_args.is_payjoin(); - let is_self_spend = setup_args.num_participants == 1; - debug!( - "contract::selection::selecting inputs: num_participants: {}, min_input_amount: {}, is_payjoin: {}", - setup_args.num_participants, rhs, is_payjoin - ); - // We don't try to contribute an input only in the case where we have multiple participants - // where we are on the receiving end and we don't want to do a payjoin - if !is_self_spend && (pay_amount == 0 && !is_payjoin) { - return Ok(( - vec![], - my_fee_contribution( - 0, - setup_args.selection_args.num_custom_outputs() + 1, - 1, - setup_args.num_participants, - )?, - )); - } - // NOTE: that these are inputs that MUST be selected. We should lock the inputs if they're - // required to minimize any potential race conditions. - let must_use_list = required_inputs.unwrap_or(vec![]); - if must_use_list.len() > 0 { - // Sort the inputs first by the ones listed in the use_inputs and then by value - inputs.sort_by_key(|out| { - ( - // We have to negate the boolean to prioritize truthy values because - // false is 0 and hence would be sorted before truthy entries - !(out.commit.is_some() - && must_use_list.contains(&&out.commit.as_ref().unwrap()[..])), - out.value, - ) - }); - } else { - // Sort the inputs only by value - inputs.sort_by_key(|out| out.value); - } - - // NOTE: Since we sort by value increasingly, if we hold any 0-value inputs, they will all be used if we're the sender - // or a single one if we're the receiver doing a payjoin. - // If we are the receiver, we pretend we have a virtual input from the sender (for which we don't pay the fees) so we can easily - // test that lhs >= rhs and see if we will be able to satisfy equation. We simulate this by starting with lhs = net_change. - let mut lhs = 0; - if net_change > 0 { - lhs = net_change as u64; // TODO: check bounds - } - // We want to count how many inputs we've picked _so far_. This is used to prevent picking - // all 0*H +r*G outputs when we call with min_input_amount=0 and want just a payjoin. - let mut n_inputs = 0; - let mut must_use_list_cnt: u32 = 0; - let my_num_outputs = setup_args.selection_args.num_custom_outputs() + 1; - // If we have already committed to a fee (context.fee) then set this as our "minimum" fee. The reason we have to - // do this is to avoid solving the equation for less than the committed fee. We have to guarantee the inputs we take - // are enough to cover the committed fee. At the end of selection, we check that the fees for the selection were not - // higher than the fee value we committed to. - let mut my_fee = if committed_fee.is_some() { - committed_fee.unwrap() - } else { - // We start with a fee of 1 output and a shared kernel which is minimum for both parties - my_fee_contribution(0, 1, 1, setup_args.num_participants).unwrap() - // FeeFields::zero() - }; - - // NOTE: This always takes at least one input if it is available. We take the inputs we must take and then we take - // inputs until we fulfill Σmy_inputs >= Σmy_outputs + my_fee_cost - let selected_inputs = inputs - .iter() - .take_while(|out| { - // Take the commitment if it is listed as one of those we MUST take - let must_take = - out.commit.is_some() && must_use_list.contains(&&out.commit.as_ref().unwrap()[..]); - // Compute the fee without this input - let fee_without = - my_fee_contribution(n_inputs, my_num_outputs, 1, setup_args.num_participants) - .unwrap(); - // Compute the total fee cost if we took this input - let mut fee_with = - my_fee_contribution(n_inputs + 1, my_num_outputs, 1, setup_args.num_participants) - .unwrap(); - // If the current fee is lower than the committed fee (my_fee) then set it to committed fee - if my_fee.fee() > fee_with.fee() { - fee_with = my_fee; - } - // If we don't have a "must take" input, have contributed an input and have enough to balance the equation, we can stop - let can_finish = lhs >= (rhs + fee_without.fee()) && n_inputs > 0 && !must_take; - if can_finish { - return false; - } - // Take the commitment if `lhs < rhs+fees_with` (or if we have not yet taken an input - payjoins) - let should_take = lhs < (rhs + fee_with.fee()) || n_inputs == 0; - let res = must_take || should_take; - if res { - lhs += out.value; - n_inputs += 1; - // Update the fee cost if we decided to take the input - my_fee = fee_with; - if must_take { - must_use_list_cnt += 1; - } - } - debug!( - "contract::selection::select_inputs => out_value:{}, new my_inputs_sum:{}", - out.value, lhs - ); - res - }) - .cloned() - .collect::>(); - - // Return an error if the fee computed is larger than the committed fee - if committed_fee.is_some() && my_fee.fee() > committed_fee.unwrap().fee() { - // TODO: Return a specific Fee estimation error and suggest the user to cancel the transaction - let msg = format!( - "Fee computed ({}) is larger than the committed fee ({})", - my_fee.fee(), - committed_fee.unwrap().fee() - ); - return Err(Error::GenericError(msg.into()).into()); - } - - // Check that the inputs we picked are enough to cover all our output amounts and fees - // asserts that Σmy_inputs >= Σmy_outputs + my_fee_cost - if lhs < rhs + my_fee.fee() { - let total = inputs.iter().fold(0, |acc, x| acc + x.value); - debug!("Not enough funds. Total funds eligible to spend: {}, needed: {}. Fee cost for this transaction: {}", total, rhs+my_fee.fee(), my_fee.fee()); - return Err(Error::NotEnoughFunds { - available: total, - available_disp: amount_to_hr_string(total, false), - needed: rhs + my_fee.fee(), - needed_disp: amount_to_hr_string(rhs + my_fee.fee(), false), - } - .into()); - // return Err(ErrorKind::GenericError(msg.into()).into()); - } - - // Assert that all the use_inputs have been selected - if must_use_list.len() != must_use_list_cnt as usize { - let msg = format!( - "We have not found all the inputs that have been requested. {}, found only: {}", - setup_args.selection_args.use_inputs.as_ref().unwrap(), - must_use_list_cnt - ); - return Err(Error::GenericError(msg.into()).into()); - } - - debug!( - "contract::selection::select_inputs => selected_inputs: {:#?}", - selected_inputs - ); - // We are returning a set of inputs for which `Σmy_inputs >= Σmy_outputs + my_fee_cost` holds - Ok((selected_inputs, my_fee)) -} - -fn build_output_amount_list( - my_input_sum: u64, - my_fee_cost: u64, - setup_args: &ContractSetupArgsAPI, -) -> Vec { - let expected_net_change = setup_args.net_change.unwrap(); - let mut my_output_amounts = setup_args.selection_args.output_amounts(); - let custom_outputs_sum = my_output_amounts.iter().sum::(); - // We know that `Σmy_inputs >= Σmy_outputs + my_fee_cost` holds so we balance the equation by adding - // an additional output holding the missing amount (change output or receiver output) - // TODO: check bounds when casting. - let my_change_output_amount = - (my_input_sum - custom_outputs_sum) as i64 + expected_net_change - my_fee_cost as i64; - // TODO: Check if it's even possible for change output to be negative (it shouldn't be if the equation is correct) - if my_change_output_amount < 0 { - panic!( - "contract::selection::build_output_amount_list => ERROR: This should never happen!!! Values: my_input_sum: {}, expected_net_change: {}, my_fee_cost: {}", - my_input_sum as i64, expected_net_change, my_fee_cost as i64 - ); - } - // Add our change/receiver output (which can be a zero-value output) to the list of outputs - my_output_amounts.push(my_change_output_amount as u64); - debug!( - "contract::selection::build_output_amount_list => inputs sum: {}, my_output_amounts:{:#?}", - my_input_sum, my_output_amounts - ); - my_output_amounts -} - -/// Compares the output selection args provided at call with those from Context and checks whether they conflict -pub fn verify_selection_consistency( - ctx_args: &OutputSelectionArgs, - cur_args: &OutputSelectionArgs, -) -> Result<(), Error> { - // We can't define a selection strategy if we've already done the setup phase. We only allow to pass either the - // default or exactly the same strategy we defined when doing the setup phase. - // TODO: Test that this works. Perhaps we'd have to define how to compare the two? - if cur_args != ctx_args && cur_args != &OutputSelectionArgs::default() { - panic!("Can't define selection args now because we've already done the setup phase. ctx_selection_args:{:#?}, cur_selection_args:{:#?}", ctx_args, cur_args); - } - // NOTE: The logic above isn't perfect. This is because the user could define arguments that are the default. In this case - // we'd simply silently use the arguments provided in the setup phase. This could be confusing for the user. - Ok(()) -} - -// Tests -#[cfg(test)] -mod tests { - - use super::*; - use crate::grin_keychain::{Identifier, IDENTIFIER_SIZE}; - use crate::OutputStatus; - - fn _create_output_data_for(amounts: Vec) -> Vec { - let mut rv: Vec = vec![]; - for (idx, amount) in amounts.iter().enumerate() { - let identifier = [0u8; IDENTIFIER_SIZE]; - let key_id = Identifier::from_bytes(&identifier); - rv.push(OutputData { - // The identifiers here don't make sense, but they're not needed for testing - root_key_id: key_id.clone(), - key_id: key_id.clone(), - n_child: key_id.clone().to_path().last_path_index(), - mmr_index: None, - commit: Some(format!("{}{}", "abc", idx.to_string())), - value: *amount, - status: OutputStatus::Unspent, - height: 1, - lock_height: 0, - is_coinbase: false, - tx_log_entry: None, - }); - } - rv - } - - #[test] - fn sender_no_inputs() { - // net_change=-1, no inputs, no fee committed => NotEnoughFunds - let setup_args = ContractSetupArgsAPI { - net_change: Some(-1_000_000_000), - ..Default::default() - }; - let expected = Error::NotEnoughFunds { - available: 0, - available_disp: amount_to_hr_string(0, false), - needed: 1_000_000_000 + my_fee_contribution(0, 1, 1, 2).unwrap().fee(), - needed_disp: amount_to_hr_string( - 1_000_000_000 + my_fee_contribution(0, 1, 1, 2).unwrap().fee(), - false, - ), - }; - let result = compute(&setup_args, None, &mut vec![]); - assert_eq!(result.err().unwrap(), expected); - } - - #[test] - fn sender_not_enough_funds_for_fee() { - // net_change=-3, inputs=[2, 1], no fee committed => NotEnoughFunds because we can't pay for fees - let setup_args = ContractSetupArgsAPI { - net_change: Some(-3_000_000_000), - ..Default::default() - }; - let expected = Error::NotEnoughFunds { - available: 3_000_000_000, - available_disp: amount_to_hr_string(3_000_000_000, false), - needed: 3_000_000_000 + my_fee_contribution(2, 1, 1, 2).unwrap().fee(), - needed_disp: amount_to_hr_string( - 3_000_000_000 + my_fee_contribution(2, 1, 1, 2).unwrap().fee(), - false, - ), - }; - let mut inputs = _create_output_data_for(vec![2_000_000_000, 1_000_000_000]); - let result = compute(&setup_args, None, &mut inputs); - assert_eq!(result.err().unwrap(), expected); - } - - #[test] - fn sender_happy_path() { - // net_change=-3, inputs=[3, 2, 1, 2], no fee committed => Ok([1, 2, 2], fees) - let setup_args = ContractSetupArgsAPI { - net_change: Some(-3_000_000_000), - ..Default::default() - }; - let inputs = _create_output_data_for(vec![ - 3_000_000_000, - 2_000_000_000, - 1_000_000_000, - 2_000_000_000, - ]); - // We expect 3 inputs with amounts 1, 2, 2 - let expected_inputs = vec![&inputs[2], &inputs[1], &inputs[3]]; - let expected_fee = my_fee_contribution(3, 1, 1, 2).unwrap(); - let expected_output_amounts = vec![ - // we expect a single output with change holding 5 - 3 - fees - (5_000_000_000 as i64 + (setup_args.net_change.unwrap())) as u64 - expected_fee.fee(), - ]; - let result = compute(&setup_args, None, &mut inputs.clone()).unwrap(); - - let result_ref = ( - result.0.iter().collect::>(), - result.1, - result.2, - ); - assert_eq!( - result_ref, - (expected_inputs, expected_output_amounts, expected_fee) - ); - } - - #[test] - fn sender_exact() { - // net_change=-3, inputs=[3, my_fees(2, 1)], no fee committed => Ok([3, my_fees(2, 1)], fees) - let setup_args = ContractSetupArgsAPI { - net_change: Some(-3_000_000_000), - ..Default::default() - }; - let inputs = _create_output_data_for(vec![ - my_fee_contribution(2, 1, 1, 2).unwrap().fee(), - 3_000_000_000, - ]); - let expected_inputs = inputs.clone(); // we expect both inputs in the same order - let expected_fee = my_fee_contribution(2, 1, 1, 2).unwrap(); - let expected_output_amounts = vec![0]; // we expect a change output of 0-value - let result = compute(&setup_args, None, &mut inputs.clone()).unwrap(); - - assert_eq!( - result, - (expected_inputs, expected_output_amounts, expected_fee) - ); - } - - #[test] - fn receiver_payjoin_exact() { - // net_change=-my_fees(1, 1), inputs=[my_fees(1, 0)], no fee committed => Ok([3, my_fees(2, 1)], fees) - let setup_args = ContractSetupArgsAPI { - // we expect to receive exactly our fee contribution my_fees(1, 1) - net_change: Some(my_fee_contribution(1, 1, 1, 2).unwrap().fee() as i64), - ..Default::default() - }; - let inputs = _create_output_data_for(vec![0, 1_000_000_000]); // we have a 0-value and 1 grin input - let expected_inputs = vec![&inputs[0]]; // we expect to use the 0-value input - let expected_fee = my_fee_contribution(1, 1, 1, 2).unwrap(); - let expected_output_amounts = vec![0]; // we expect a change output of 0-value - let result = compute(&setup_args, None, &mut inputs.clone()).unwrap(); - - let result_ref = ( - result.0.iter().collect::>(), - result.1, - result.2, - ); - assert_eq!( - result_ref, - (expected_inputs, expected_output_amounts, expected_fee) - ); - } - - #[test] - fn receiver_no_payjoin() { - let setup_args = ContractSetupArgsAPI { - // we expect to receive exactly our fee contribution my_fees(1, 1) - net_change: Some(3_000_000_000), - selection_args: OutputSelectionArgs { - use_inputs: None, - ..Default::default() - }, - ..Default::default() - }; - let inputs = _create_output_data_for(vec![1_000_000_000]); - let expected_inputs = vec![]; - let expected_fee = my_fee_contribution(0, 1, 1, 2).unwrap(); - let expected_output_amounts = vec![ - // we expect a single output with change holding 3 - fees - (setup_args.net_change.unwrap() as u64) - expected_fee.fee(), - ]; - let result = compute(&setup_args, None, &mut inputs.clone()).unwrap(); - - assert_eq!( - result, - (expected_inputs, expected_output_amounts, expected_fee) - ); - } - - #[test] - fn sender_use_inputs_ok() { - let setup_args = ContractSetupArgsAPI { - // we expect to receive exactly our fee contribution my_fees(1, 1) - net_change: Some(-2_000_000_000), - selection_args: OutputSelectionArgs { - use_inputs: Some(String::from("abc0,abc2,abc3")), - ..Default::default() - }, - ..Default::default() - }; - let inputs = _create_output_data_for(vec![ - 1_000_000_000, // abc0 - 2_000_000_000, - 3_000_000_000, // abc2 - 4_000_000_000, // abc3 - ]); - let expected_inputs = vec![&inputs[0], &inputs[2], &inputs[3]]; - let expected_fee = my_fee_contribution(3, 1, 1, 2).unwrap(); - let expected_output_amounts = vec![ - // we expect a single output with change holding 3 - fees - (8_000_000_000 as i64 + setup_args.net_change.unwrap()) as u64 - expected_fee.fee(), - ]; - let result = compute(&setup_args, None, &mut inputs.clone()).unwrap(); - - let result_ref = ( - result.0.iter().collect::>(), - result.1, - result.2, - ); - assert_eq!( - result_ref, - (expected_inputs, expected_output_amounts, expected_fee) - ); - } - - #[test] - fn sender_use_inputs_happy_err() { - let setup_args = ContractSetupArgsAPI { - net_change: Some(-2_000_000_000), - selection_args: OutputSelectionArgs { - // there is no abc5 input - use_inputs: Some(String::from("abc0,abc2,abc5")), - ..Default::default() - }, - ..Default::default() - }; - let inputs = _create_output_data_for(vec![ - 1_000_000_000, // abc0 - 2_000_000_000, - 3_000_000_000, // abc2 - 4_000_000_000, - ]); - let msg = format!( - "We have not found all the inputs that have been requested. abc0,abc2,abc5, found only: 2" - ); - let expected_err = Error::GenericError(msg.into()).into(); - let result = compute(&setup_args, None, &mut inputs.clone()); - - assert_eq!(result.err().unwrap(), expected_err); - } - - #[test] - fn sender_make_outputs_ok() { - let setup_args = ContractSetupArgsAPI { - net_change: Some(-2_000_000_000), - selection_args: OutputSelectionArgs { - // there is no abc5 input - make_outputs: Some(String::from("1,3")), - ..Default::default() - }, - ..Default::default() - }; - let inputs = _create_output_data_for(vec![ - 1_000_000_000, - 2_000_000_000, - 3_000_000_000, - 4_000_000_000, - ]); - // we expect all to be used (2+1+3+fees) - let expected_inputs = inputs.clone(); - let expected_fee = my_fee_contribution(4, 3, 1, 2).unwrap(); - let expected_output_amounts = vec![ - 1_000_000_000u64, - 3_000_000_000u64, - // change output - ((10_000_000_000u64 - 4_000_000_000u64) as i64 + setup_args.net_change.unwrap()) as u64 - - expected_fee.fee(), - ]; - let result = compute(&setup_args, None, &mut inputs.clone()).unwrap(); - - assert_eq!( - result, - (expected_inputs, expected_output_amounts, expected_fee) - ); - } - - /* - - Tests to add: - - compute_receiver_invariant - test that receiving_amount - my_fees >= 0 (to prevent going into negative accidentally) - - compute_sender_invariant - test that -send_amount - my_fees < 0 (do you need this one and is it correct?) - - compute_receiver_payjoin_negative_fee - could the receiver receive a negative amount through fees? but the thing - would go through because they made a payjoin so they could pay for the fees? - - compute_receiver_omit_payjoin - we can't contribute an input, but have enough for other fees - - compute_make_outputs_fee_err - fail due to not enough funds for fees - - compute_make_outputs_sum_err - is this even possible? - - compute_zero_value_outputs_sender - sender uses all 0-value outputs when sending - - compute_zero_value_inputs_receiver - receiver uses 0-value inputs in payjoin - - test_fee_committed_err - we have already committed to a certain fee which we no longer satisfy - - think if we should have sender/receiver separate testing - - sender_use_all_features - - coinbase output cases - - validate --make-outputs has all positive u64 numbers - - */ -} diff --git a/libwallet/src/contract/slate.rs b/libwallet/src/contract/slate.rs deleted file mode 100644 index 4cc65fe7b..000000000 --- a/libwallet/src/contract/slate.rs +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Contract functions on the Slate - -use crate::grin_core::libtx::build; -use crate::grin_core::libtx::proof::ProofBuilder; -use crate::grin_keychain::Keychain; -use crate::grin_util::secp::key::SecretKey; -use crate::slate::{Slate, SlateState}; -use crate::types::{Context, NodeClient, WalletBackend}; -use crate::Error; - -use super::types::ProofArgs; -use crate::contract::proofs::InvoiceProof; -use ed25519_dalek::PublicKey as DalekPublicKey; - -/// TODO: Removed for now, consider secp error in sign function -/// The secret key we replace the actual key with after we have signed with the Context keys. This is -/// to prevent possibility of signing with the same key twice. -/// pub const SEC_KEY_FAKE: [u8; 32] = [0; 32]; - -/// Add payment proof data to slate, noop for sender -pub fn add_payment_proof<'a, T: ?Sized, C, K>( - w: &mut T, - slate: &mut Slate, - keychain_mask: Option<&SecretKey>, - context: &Context, - net_change: &Option, - proof_args: &ProofArgs, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - // TODO: Implement. Consider adding this function to the Slate itself so they can easily be versioned - // e.g. slate.add_payment_proof_data() - debug!("contract::slate::add_payment_proof => called"); - // If we're a recipient, generate proof unless explicity told not to - if let Some(ref c) = net_change { - if *c > 0 && !proof_args.suppress_proof && slate.payment_proof.is_none() { - super::proofs::add_payment_proof(w, keychain_mask, slate, &context, proof_args)?; - } - } - - Ok(()) -} - -/// Verify payment proof signature -pub fn verify_payment_proof( - slate: &Slate, - net_change: i64, - recipient_address: &DalekPublicKey, -) -> Result<(), Error> { - // TODO: Implement. Consider adding this function to the Slate itself so they can easily be versioned - // e.g. slate.verify_payment_proof_sig() - debug!("contract::slate::verify_payment_proof => called"); - if net_change > 0 && slate.payment_proof.is_some() { - let invoice_proof = InvoiceProof::from_slate(&slate, 1, None)?; - invoice_proof.verify_promise_signature(&recipient_address)?; - } - Ok(()) -} - -/// Adds inputs and outputs to slate -pub fn add_outputs<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &mut Slate, - context: &Context, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - add_inputs_to_slate(w, keychain_mask, slate, context)?; - add_outputs_to_slate(w, keychain_mask, slate, context)?; - // Adjust the offset for the added input and outputs - let keychain = &w.keychain(keychain_mask)?; - slate.adjust_offset(keychain, &context)?; - - Ok(()) -} - -/// Contribute inputs to slate -fn add_inputs_to_slate<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &mut Slate, - context: &Context, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - debug!("contract::slate::add_inputs_to_slate => adding inputs to slate"); - let keychain = w.keychain(keychain_mask)?; - let batch = w.batch(keychain_mask)?; - for (key_id, mmr_index, _) in context.get_inputs() { - // We have no information if the input is a coinbase or not, so we fetch the data from DB - let coin = batch.get(&key_id, &mmr_index).unwrap(); - if coin.is_coinbase { - slate.add_transaction_elements( - &keychain, - &ProofBuilder::new(&keychain), - vec![build::coinbase_input(coin.value, coin.key_id.clone())], - )?; - debug!( - "contract::slate::add_inputs_to_slate => added coinbase input id: {}, value: {}", - coin.key_id.clone(), - coin.value - ); - } else { - slate.add_transaction_elements( - &keychain, - &ProofBuilder::new(&keychain), - vec![build::input(coin.value, coin.key_id.clone())], - )?; - debug!( - "contract::slate::add_inputs_to_slate => added regular input id: {}, value: {}", - coin.key_id.clone(), - coin.value - ); - } - } - - Ok(()) -} - -/// Contribute outputs to slate -fn add_outputs_to_slate<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &mut Slate, - context: &Context, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - debug!("contract::slate::add_outputs_to_slate => start"); - let keychain = w.keychain(keychain_mask)?; - // Iterate over outputs in the Context and add the same output to the slate - for (key_id, _, amount) in context.get_outputs() { - slate.add_transaction_elements( - &keychain, - &ProofBuilder::new(&keychain), - vec![build::output(amount, key_id.clone())], - )?; - debug!( - "contract::slate::add_outputs_to_slate => added output to slate. Output id: {}, amount: {}", - key_id.clone(), - amount - ); - } - - Ok(()) -} - -/// Transition the slate state to the next one -pub fn transition_state(slate: &mut Slate) -> Result<(), Error> { - // We don't really use these states right now apart from leaving it to derive expected net_change. - // This suggests these can't be used for manipulation. It doesn't hurt to think a bit more if that's the case. - let new_state = match slate.state { - SlateState::Invoice1 => SlateState::Invoice2, - SlateState::Invoice2 => SlateState::Invoice3, - SlateState::Standard1 => SlateState::Standard2, - SlateState::Standard2 => SlateState::Standard3, - _ => { - debug!("Slate.state: {}", slate.state); - SlateState::Standard3 - } - }; - slate.state = new_state; - // NOTE: It's possible to never reach the step3. A self-spend has only 2 steps: new -> sign. - Ok(()) -} - -/// Add partial signature to the slate. -// TODO: Should be a sign & forget pubkey+nonce implementation. -pub fn sign<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &mut Slate, - context: &mut Context, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - debug!("contract::slate::sign => called"); - let keychain = w.keychain(keychain_mask)?; - slate.fill_round_2(&keychain, &context.sec_key, &context.sec_nonce)?; - debug!( - "contract::sign => signed for slate fees: {}", - slate.fee_fields - ); - debug!("contract::slate::sign => done"); - - // TODO: This produces a secp error, probably need a valid key. Verify that this is what we want to do. - // let fake_key = SecretKey::from_slice(keychain.secp(), &SEC_KEY_FAKE)?; - // context.sec_key = fake_key.clone(); - // context.sec_nonce = fake_key.clone(); - // context.initial_sec_key = fake_key.clone(); - // context.initial_sec_nonce = fake_key.clone(); - - Ok(()) -} - -/// We can finalize if all partial sigs are present -pub fn can_finalize(slate: &Slate) -> bool { - let res = slate - .participant_data - .clone() - .into_iter() - .filter(|v| !v.is_complete()) - .count(); - - // We can finalize if the number of partial sigs is the same as the number of participants - res == 0 && slate.participant_data.len() == slate.num_participants as usize -} - -/// Finalize slate -pub fn finalize<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &mut Slate, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - debug!("contract::slate::finalize => called"); - // Final transaction can be built by anyone at this stage - trace!("Slate to finalize is: {}", slate); - // At this point, everyone adjusted their offset, so we update the offset on the tx - slate.tx_or_err_mut()?.offset = slate.offset.clone(); - slate.finalize(&w.keychain(keychain_mask)?)?; - - Ok(()) -} - -/// Perform 'setup' step for a contract. This adds our public key and nonce to the slate -/// The operation should be idempotent. -pub fn add_keys(slate: &mut Slate, keychain: &K, context: &mut Context) -> Result<(), Error> -where - K: Keychain, -{ - debug!("contract::slate::add_keys => called"); - // TODO: Is this safe from manipulation? - slate.add_participant_info(keychain, context, None) -} diff --git a/libwallet/src/contract/types.rs b/libwallet/src/contract/types.rs deleted file mode 100644 index 10f6aa3bc..000000000 --- a/libwallet/src/contract/types.rs +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Types related to a contract - -use crate::grin_core::consensus; -use crate::slate_versions::ser as dalek_ser; -use ed25519_dalek::PublicKey as DalekPublicKey; - -/// Output selection args -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct OutputSelectionArgs { - /// Constraint on how many confirmations used inputs must have - pub min_input_confirmation: u64, - /// Which inputs we want to use - default to payjoin if available with Some("any") - pub use_inputs: Option, - /// Change output specification (comma separated amounts which don't include fee subtraction) - /// e.g. "3,1,4,0,0" describes 5 outputs two of which hold 0 value - pub make_outputs: Option, -} - -impl OutputSelectionArgs { - /// We try to make a payjoin if use_inputs has a value (either commitments or Some("any")) - pub fn is_payjoin(&self) -> bool { - self.use_inputs.is_some() - } - /// Return a list of commitments we must use - pub fn required_inputs(&self) -> Option> { - if self.use_inputs.is_some() { - Some( - self.use_inputs.as_ref().unwrap()[..] - .split(",") - .filter(|x| *x != "any") - .collect(), - ) - } else { - None - } - } - /// Returns the outputs we have to create - pub fn output_amounts(&self) -> Vec { - if self.make_outputs.is_some() { - let output_amounts: Vec = self.make_outputs.as_ref().unwrap()[..] - .split(",") - // TODO: move consensus code outside of here. Consider turning make_outputs to Vec - .map(|amt| (amt.parse::().unwrap() * consensus::GRIN_BASE as f64) as u64) - .collect(); - output_amounts - } else { - vec![] - } - } - /// Returns the sum of our output amounts - pub fn sum_output_amounts(&self) -> u64 { - self.output_amounts().iter().sum() - } - /// Returns the number of custom outputs - pub fn num_custom_outputs(&self) -> usize { - self.output_amounts().len() - } - - // TODO: make sure to validate this: if custom outputs are specified, it has to be a payjoin. -} - -impl Default for OutputSelectionArgs { - fn default() -> OutputSelectionArgs { - OutputSelectionArgs { - min_input_confirmation: 10, - use_inputs: Some(String::from("any")), - make_outputs: None, - } - } -} - -/// Types of proof that can be generated -/// as per https://github.com/tromp/grin-rfcs/blob/early-payment-proofs/text/0000-early-payment-proofs.md -/// TODO: Update when RFC is merged - -#[derive(Clone, Serialize, Deserialize, Debug)] -pub enum ProofType { - /// Legacy (0x00) - Legacy, - /// Invoice (0x01, Default) - Invoice, - /// Sender Nonce (0x02) - SenderNonce, -} - -/// Proof generation parameters that can be provided during new or sign phases -#[derive(Clone, Serialize, Deserialize, Debug)] -pub struct ProofArgs { - /// If net change is positive during this step, whether to suppress the creation of payment proof - pub suppress_proof: bool, - /// Type of proof (Default 'Invoice') - pub proof_type: ProofType, - /// Sender address (required at some stage, may not necessarily be in slate so can be provided explicitly) - #[serde(with = "dalek_ser::option_dalek_pubkey_serde")] - pub sender_address: Option, -} - -impl Default for ProofArgs { - fn default() -> ProofArgs { - ProofArgs { - suppress_proof: false, - proof_type: ProofType::Legacy, - sender_address: None, - } - } -} - -/// Contract Setup - defines how we pick inputs/outputs and what we expect from a contract. Both -/// 'new' and 'sign' actions perform a setup phase which is why their endpoints take these parameters. -#[derive(Clone, Serialize, Deserialize, Debug)] -pub struct ContractSetupArgsAPI { - /// The human readable account name from which to draw outputs - /// for the transaction, overriding whatever the active account is as set via the - /// [`set_active_account`](../grin_wallet_api/owner/struct.Owner.html#method.set_active_account) method. - pub src_acct_name: Option, - /// The net change we will agree on. The amount is in nanogrins (`1 G = 1_000_000_000nG`). - /// The value is positive when we are on the receiving end and negative when we are the sender. - /// It is optional because we could have agreed on it before we reach the sign e.g. when we create new contract - pub net_change: Option, - /// The number of participants in a contract. Used for computing our kernel fee contribution - pub num_participants: u8, - /// Should we perform an early lock of outputs - pub add_outputs: bool, - /// Output selection arguments - pub selection_args: OutputSelectionArgs, - /// Proof arguments - pub proof_args: ProofArgs, -} - -impl Default for ContractSetupArgsAPI { - fn default() -> ContractSetupArgsAPI { - ContractSetupArgsAPI { - src_acct_name: None, - net_change: None, - num_participants: 2, - add_outputs: false, - selection_args: OutputSelectionArgs { - ..Default::default() - }, - proof_args: ProofArgs::default(), - } - } -} - -/// Contract New -#[derive(Clone, Serialize, Deserialize)] -pub struct ContractNewArgsAPI { - /// TODO: do we need the target_slate_version? - /// Optionally set the output target slate version (acceptable - /// down to the minimum slate version compatible with the current. If `None` the slate - /// is generated with the latest version. - pub target_slate_version: Option, - /// Setup args - contract new also initiates the setup by default - pub setup_args: ContractSetupArgsAPI, -} - -impl Default for ContractNewArgsAPI { - fn default() -> ContractNewArgsAPI { - ContractNewArgsAPI { - target_slate_version: None, - setup_args: ContractSetupArgsAPI { - src_acct_name: None, - net_change: None, - num_participants: 2, - add_outputs: false, - selection_args: OutputSelectionArgs { - ..Default::default() - }, - proof_args: ProofArgs::default(), - }, - } - } -} - -/// ContractView -#[derive(Clone, Serialize, Deserialize)] -pub struct ContractView { - /// TODO: do we need the target_slate_version? - pub target_slate_version: Option, - /// Every slatepack has a number of participants - pub num_participants: u8, - /// Suggested value for the party at step2 (only provided if slatepack is at step1) - pub suggested_net_change: Option, - /// Agreed net_change if we've agreed on it (the context must exist for this) - // NOTE: we drop the Context once we've signed. Perhaps we should think about dropping - // only the private keys associated with it to prevent double-signing with the same - // (pubkey, nonce) pair. This way, we'd retain the history on that wallet instance. - // There might also be value in forgetting the whole context. - pub agreed_net_change: Option, - /// Number of singatures on the contract - pub num_sigs: u8, - /// Has the contract been executed on chain - pub is_executed: bool, -} - -impl Default for ContractView { - fn default() -> ContractView { - ContractView { - target_slate_version: None, - num_participants: 2, - suggested_net_change: None, - agreed_net_change: None, - num_sigs: 0, - is_executed: false, - } - } -} - -/// Arguments for contract revoke function -#[derive(Clone, Serialize, Deserialize)] -pub struct ContractRevokeArgsAPI { - /// Tx id to cancel - pub tx_id: u32, -} diff --git a/libwallet/src/contract/utils.rs b/libwallet/src/contract/utils.rs deleted file mode 100644 index 6e2dcbc7d..000000000 --- a/libwallet/src/contract/utils.rs +++ /dev/null @@ -1,398 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Contract building utility functions - -use crate::contract::selection::verify_selection_consistency; -use crate::contract::types::ContractSetupArgsAPI; -use crate::grin_core::libtx::tx_fee; -use crate::grin_keychain::{Identifier, Keychain}; -use crate::grin_util::secp::key::SecretKey; -use crate::slate::Slate; -use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType, WalletBackend}; -use crate::util::OnionV3Address; -use crate::{address, Error, OutputData, OutputStatus, TxLogEntry}; -use grin_core::core::FeeFields; -use uuid::Uuid; - -/// Creates an initial TxLogEntry without input/output or kernel information -pub fn create_tx_log_entry( - slate: &Slate, - net_change: i64, - parent_key_id: Identifier, - log_id: u32, -) -> Result { - let log_type = if slate.num_participants == 1 { - TxLogEntryType::TxSelfSpend - } else { - if net_change > 0 { - TxLogEntryType::TxReceived - } else { - TxLogEntryType::TxSent - } - }; - let mut t = TxLogEntry::new(parent_key_id.clone(), log_type, log_id); - // TODO: TxLogEntry has stored_tx field. Check what this needs to be set to and check other fields as well - - t.tx_slate_id = Some(slate.id); - if net_change > 0 { - t.amount_credited = net_change as u64; - } else { - t.amount_debited = -net_change as u64; - } - t.ttl_cutoff_height = match slate.ttl_cutoff_height { - 0 => None, - n => Some(n), - }; - - Ok(t) -} - -/// Updates TxLogEntry for a contract with information available in the 'sign' step -pub fn update_tx_log_entry<'a, T: ?Sized, C, K>( - wallet: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &Slate, - context: &Context, - tx_log_entry: &mut TxLogEntry, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - // This is expected to be called when we are signing the contract and have already contributed inputs & outputs - let keychain = wallet.keychain(keychain_mask)?; - let parent_key_id = context.parent_key_id.clone(); - let current_height = wallet.w2n_client().get_chain_tip()?.0; - // We have already contributed inputs and outputs so we know how much of each we contribute - tx_log_entry.num_outputs = context.output_ids.len(); - tx_log_entry.num_inputs = context.input_ids.len(); - tx_log_entry.fee = context.fee; - // Set kernel information - match slate.calc_excess(keychain.secp()) { - Ok(e) => tx_log_entry.kernel_excess = Some(e), - Err(_) => panic!("We can't update tx log entry. Excess could not be computed."), - }; - tx_log_entry.kernel_lookup_min_height = Some(current_height); - - // If we're sending and there's payment proof info in the slate added by recipient, store as well - if let Some(ref p) = slate.payment_proof { - if tx_log_entry.amount_debited > 0 { - // note we only use a single path for now - let sender_address_path = 0u32; - let sender_key = address::address_from_derivation_path( - &keychain, - &parent_key_id, - sender_address_path, - )?; - let sender_address = OnionV3Address::from_private(&sender_key.0)?; - - // We're looking for the OTHER party here, the recipient - let sender_index = slate.find_index_matching_context(&keychain, context)?; - let recipient_index = sender_index ^ 1; - - tx_log_entry.payment_proof = Some(StoredProofInfo { - receiver_address: p.receiver_address, - receiver_signature: p.promise_signature, - sender_address: sender_address.to_ed25519()?, - sender_address_path, - sender_signature: None, - /// TODO: Will fill these as separate steps for now, check whether this - /// can be merged in a general case (which means knowing which nonces here belong to - /// the recipient) - proof_type: Some(1u8), - receiver_public_nonce: Some(slate.participant_data[recipient_index].public_nonce), - receiver_public_excess: Some( - slate.participant_data[recipient_index].public_blind_excess, - ), - timestamp: Some(p.timestamp), - memo: p.memo.clone(), - promise_signature: p.promise_signature, - sender_part_sig: slate.participant_data[sender_index].part_sig, - }); - } - } - - Ok(()) -} - -/// Get net_change value. This is obtained either from the Context.net_change or the setup_args.net_change -pub fn get_net_change<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - // TODO: make this receive only slate.id instead of passing the whole slate - slate: &Slate, - setup_args_net_change: Option, -) -> Result -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - let mut expected_net_change: Option = setup_args_net_change; - match w.get_private_context(keychain_mask, slate.id.as_bytes()) { - Ok(ctx) => { - debug!("contract::sign => context found"); - // We have a context so we must have agreed on a certain net_change value in Context.net_change. - // If we have both Context.net_change and setup_args.net_change, then they must be equal. - match expected_net_change { - Some(args_net_change) => { - if ctx.get_net_change() != args_net_change { - panic!( - "Expected net change mismatch! Context.net_change: {}, setup_args.net_change: {}", - ctx.get_net_change(), args_net_change - ); - } - } - None => (), - } - expected_net_change = Some(ctx.get_net_change()); - } - Err(_) => debug!("contract::utils::get_net_change => context not found"), - }; - - // Fail if net_change was not passed to setup_args and was also not present in the context. - // This means it has not been explicitly agreed on and we require the user to pass it. - if expected_net_change.is_none() { - return Err(Error::GenericError( - "You did not agree on the expected net difference.".into(), - ) - .into()); - } - debug!( - "contract::utils::get_net_change => expected_net_change: {}", - expected_net_change.unwrap() - ); - - Ok(expected_net_change.unwrap()) -} - -/// Atomically locks the inputs and saves the changes of Context, TxLogEntry and OutputData. -/// Additionally, the transaction is saved in a file in case we signed it. -pub fn save_step<'a, T: ?Sized, C, K>( - w: &mut T, - keychain_mask: Option<&SecretKey>, - slate: &Slate, - context: &mut Context, - step_added_outputs: bool, - is_signed: bool, -) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - debug!( - "contract::utils::save_step => performing atomic update for slate_id: {}", - slate.id - ); - // Phase 1 - precompute the data needed for atomic update - let parent_key_id = &context.parent_key_id; - let current_height = w.w2n_client().get_chain_tip()?.0; - // We are at step2 if we don't have context.log_id and we have signed the slate - let is_step2 = !context.log_id.is_some() && is_signed; - - let mut tx_log_entry = { - if !context.log_id.is_some() { - // We create a new entry with log_id=0 and but replace it with the real id before committing - create_tx_log_entry(slate, context.get_net_change(), parent_key_id.clone(), 0)? - } else { - w.get_tx_log_entry(parent_key_id.clone(), context.log_id.unwrap())? - .unwrap() - } - }; - - // Update TxLogEntry if we have signed the contract (we have data about the kernel) - if is_signed { - update_tx_log_entry(w, keychain_mask, &slate, &context, &mut tx_log_entry)?; - // TODO: It's possible to store the transaction in a file while and the atomic commit below fails - // In this case, we should revert to the previous stored tx to avoid having discrepancy - w.store_tx(&format!("{}", slate.id), slate.tx_or_err()?)?; - } - // If we added outputs in this step, we have to create OutputData here because 'batch' - // takes the mutable ref and we can no longer call calc_commit_for_cache for output - let added_outputs = if !step_added_outputs { - vec![] - } else { - let mut output_data_xs: Vec = vec![]; - // Create an OutputData entry for every created output - for (key_id, _, amount) in context.get_outputs() { - let commit = w.calc_commit_for_cache(keychain_mask, amount, &key_id)?; - let output_data = OutputData { - root_key_id: parent_key_id.clone(), - key_id: key_id.clone(), - mmr_index: None, - n_child: key_id.to_path().last_path_index(), - commit: commit, - value: amount, - status: OutputStatus::Unconfirmed, - height: current_height, - lock_height: 0, - is_coinbase: false, - tx_log_entry: None, - }; - output_data_xs.push(output_data); - } - output_data_xs - }; - - // Phase 2 - atomically update Context, OutputData and TxLogEntry - let mut batch = w.batch(keychain_mask)?; - - // Update TxLogEntry - if !context.log_id.is_some() { - // If we just created the TxLogEntry, we have to assign it an id - let log_id = batch.next_tx_log_id(&parent_key_id)?; - tx_log_entry.id = log_id; - context.log_id = Some(log_id); - } - batch.save_tx_log_entry(tx_log_entry.clone(), &parent_key_id)?; - // Create OutputData entries and lock inputs if we added outputs at this step - if step_added_outputs { - // Create an OutputData entry for every created output - for mut output_data in added_outputs { - output_data.tx_log_entry = context.log_id; - batch.save(output_data)?; - } - // Lock inputs - for id in context.get_inputs() { - let mut coin = batch.get(&id.0, &id.1).unwrap(); - // At this point we already have context.log_id set - coin.tx_log_entry = context.log_id; - batch.lock_output(&mut coin)?; - } - } - - // Update context - if is_signed && !is_step2 { - // NOTE: We MUST forget the context when we sign. Ideally, these two would be atomic or perhaps - // when we call slate::sigadd_partial_signaturen we could swap the secret key with a temporary one just to be safe. - // The reason we don't delete if we are at step2 is because in case we want to do safe cancel, - // we need to know which inputs are in the context to know which input we have to double-spend. - batch.delete_private_context(slate.id.as_bytes())?; - } else { - batch.save_private_context(slate.id.as_bytes(), &context)?; - } - - batch.commit()?; - - // TODO: Assert we don't have the context to avoid potentially leaking it! Also write tests around this. - debug!("contract::utils::save_step => Atomic updated done"); - - Ok(()) -} - -/// Computes fees contribution for a participant -pub fn my_fee_contribution( - n_inputs: usize, - n_outputs: usize, - n_kernels: usize, - num_participants: u8, -) -> Result { - // Add our fee costs for our inputs and a single output - let mut fee = tx_fee(n_inputs, n_outputs, 0); - // Add out fee costs for kernel. We pay 1/num_participants of a kernel cost - let kernel_cost = tx_fee(0, 0, n_kernels); - // TODO: we slightly overpay. Make sure to cover all the cases - let my_kernel_cost = (kernel_cost as f64 / (num_participants as f64)).ceil(); - fee += my_kernel_cost as u64; - - // Add my fee contribution to the slate total fee. - // TODO: Does this break compatibility with existing slates? - let my_fee_fields = FeeFields::new(0, fee)?; - Ok(my_fee_fields) -} - -/// Returns an error if the slate has already been signed (in our local database). Even if the -/// result is Ok, it's still possible it was signed but we don't have the data about it locally. -pub fn verify_not_signed<'a, T: ?Sized, C, K>(w: &mut T, slate_id: Uuid) -> Result<(), Error> -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - // If we have a transaction log entry for that slatepack that has a kernel value, then - // we have already signed this slate. - let tx = w - .tx_log_iter() - .find(|t| t.tx_slate_id.is_some() && t.tx_slate_id.unwrap() == slate_id); - let already_signed = tx.is_some() && tx.unwrap().kernel_excess.is_some(); - if already_signed { - debug!("contract::utils::verify_not_signed => The slate has already been signed."); - return Err(Error::GenericError( - format!("Slate with id:{} has already been signed.", slate_id).into(), - ) - .into()); - } - - Ok(()) -} - -/// Compares the setup args provided at call with those in the Context and checks whether they conflict. -/// This is relevant to see if there's any conflict in the arguments provided at step1 with step3. -pub fn verify_setup_args_consistency( - ctx_setup_args: &ContractSetupArgsAPI, - cur_setup_args: &ContractSetupArgsAPI, -) -> Result<(), Error> { - // Compare net_change - if ctx_setup_args.net_change.unwrap() != cur_setup_args.net_change.unwrap() { - panic!( - "Inconsistent net change. Ctx net_change:{}, Current net_change: {}", - ctx_setup_args.net_change.unwrap(), - cur_setup_args.net_change.unwrap() - ); - } - // Compare num_participants - if ctx_setup_args.num_participants != cur_setup_args.num_participants { - panic!( - "Inconsistent num_participants. Ctx num_participants:{}, Current num_participants: {}", - ctx_setup_args.num_participants, cur_setup_args.num_participants - ); - } - // TODO: Should we verify add_outputs? - // TODO: verify that the parent_key_id is consistent, perhaps even with the active_account set? - - // Compare OutputSelectionArgs - verify_selection_consistency( - &ctx_setup_args.selection_args, - &cur_setup_args.selection_args, - )?; - Ok(()) -} - -/// Get the parent_key_id for a given wallet instance and src_acct_name -pub fn parent_key_for<'a, T: ?Sized, C, K>(w: &mut T, src_acct_name: Option<&String>) -> Identifier -where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, -{ - // TODO: does it matter what api.set_active_account is set? also check LMDB set_parent_key_id etc. methods - // - Does it matter what api.set_active_account is set? I think w.parent_key_id() already takes the active one - // but the verify_consistency may need to verify this or perhaps give a warning that active is different than - // the one that was set at the first setup phase. - let parent_key_id = match src_acct_name { - Some(d) => { - let pm = w.get_acct_path(d.clone()).unwrap(); - match pm { - Some(p) => p.path, - // TODO: should we error if the path is not found? - None => w.parent_key_id(), - } - } - None => w.parent_key_id(), - }; - parent_key_id -} diff --git a/libwallet/src/error.rs b/libwallet/src/error.rs index 4c33c0562..fe4bbd8cc 100644 --- a/libwallet/src/error.rs +++ b/libwallet/src/error.rs @@ -65,14 +65,6 @@ pub enum Error { #[error("Onion V3 Address Error: {0}")] OnionV3Address(#[from] util::OnionV3AddressError), - /// Comsig error - #[error("Comsig error: {0}")] - ComSig(#[from] crate::mwmixnet::onion::crypto::comsig::ComSigError), - - /// MwMixnet Onion error - #[error("Onion error: {0}")] - Onion(#[from] crate::mwmixnet::onion::onion::OnionError), - /// Callback implementation error conversion #[error("Trait Implementation error")] CallbackImpl(&'static str), @@ -265,18 +257,10 @@ pub enum Error { #[error("Payment Proof parsing error: {0}")] PaymentProofParsing(String), - /// Retrieving Payment Proof - #[error("Unable to verify payment proof: {0}")] - PaymentProofValidation(String), - /// Decoding OnionV3 addresses to payment proof addresses #[error("Proof Address decoding: {0}")] AddressDecoding(String), - /// Payment proof - no sender address provided or found in slate - #[error("Sender address has not been provided")] - NoSenderAddressProvided, - /// Transaction has expired it's TTL #[error("Transaction Expired")] TransactionExpired, @@ -325,10 +309,6 @@ pub enum Error { #[error("Stored Tx error: {0}")] StoredTx(String), - /// Trying to match index to context - #[error("Cannot match transaction context to slate index")] - ContextToIndex, - /// Other #[error("Generic error: {0}")] GenericError(String), diff --git a/libwallet/src/internal/selection.rs b/libwallet/src/internal/selection.rs index 4170b07a8..5bc45005f 100644 --- a/libwallet/src/internal/selection.rs +++ b/libwallet/src/internal/selection.rs @@ -203,23 +203,12 @@ where sender_address_path, )?; let sender_address = OnionV3Address::from_private(&sender_key.0)?; - t.payment_proof = Some(StoredProofInfo { receiver_address: p.receiver_address, - receiver_signature: p.promise_signature, + receiver_signature: p.receiver_signature, sender_address: sender_address.to_ed25519()?, sender_address_path, sender_signature: None, - /// TODO: Will fill these as separate steps for now, check whether this - /// can be merged in a general case (which means knowing which nonces here belong to - /// the recipient) - proof_type: None, - receiver_public_nonce: None, - receiver_public_excess: None, - timestamp: None, - memo: None, - promise_signature: None, - sender_part_sig: None, }); }; diff --git a/libwallet/src/internal/tx.rs b/libwallet/src/internal/tx.rs index 2ecd215f4..a5dde1aa9 100644 --- a/libwallet/src/internal/tx.rs +++ b/libwallet/src/internal/tx.rs @@ -359,32 +359,27 @@ where Some(&parent_key_id), false, )?; - if tx_vec.len() == 0 { + if tx_vec.len() != 1 { return Err(Error::TransactionDoesntExist(tx_id_string)); } - for tx in tx_vec { - debug!("cancel_tx: tx: {}", tx.tx_type); - match tx.tx_type { - TxLogEntryType::TxSent - | TxLogEntryType::TxReceived - | TxLogEntryType::TxReverted - | TxLogEntryType::TxSelfSpend => {} - _ => return Err(Error::TransactionNotCancellable(tx_id_string)), - } - if tx.confirmed { - return Err(Error::TransactionNotCancellable(tx_id_string)); - } - // get outputs associated with tx - let res = updater::retrieve_outputs( - wallet, - keychain_mask, - false, - Some(tx.id), - Some(&parent_key_id), - )?; - let outputs = res.iter().map(|m| m.output.clone()).collect(); - updater::cancel_tx_and_outputs(wallet, keychain_mask, tx, outputs, parent_key_id)?; + let tx = tx_vec[0].clone(); + match tx.tx_type { + TxLogEntryType::TxSent | TxLogEntryType::TxReceived | TxLogEntryType::TxReverted => {} + _ => return Err(Error::TransactionNotCancellable(tx_id_string)), } + if tx.confirmed { + return Err(Error::TransactionNotCancellable(tx_id_string)); + } + // get outputs associated with tx + let res = updater::retrieve_outputs( + wallet, + keychain_mask, + false, + Some(tx.id), + Some(&parent_key_id), + )?; + let outputs = res.iter().map(|m| m.output.clone()).collect(); + updater::cancel_tx_and_outputs(wallet, keychain_mask, tx, outputs, parent_key_id)?; Ok(()) } @@ -426,36 +421,25 @@ where } if let Some(ref p) = slate.clone().payment_proof { - if let Some(saddr) = p.sender_address { - let derivation_index = match context.payment_proof_derivation_index { - Some(i) => i, - None => 0, - }; - let keychain = wallet.keychain(keychain_mask)?; - let parent_key_id = wallet.parent_key_id(); - let excess = slate.calc_excess(keychain.secp())?; - let sender_key = - address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?; - let sender_address = OnionV3Address::from_private(&sender_key.0)?; - let sig = create_payment_proof_signature(slate.amount, &excess, saddr, sender_key)?; - - tx.payment_proof = Some(StoredProofInfo { - receiver_address: p.receiver_address, - receiver_signature: p.promise_signature, - sender_address_path: derivation_index, - sender_address: sender_address.to_ed25519()?, - sender_signature: Some(sig), - // Filled in during contract flow proofs for now - proof_type: None, - receiver_public_nonce: None, - receiver_public_excess: None, - timestamp: None, - memo: None, - promise_signature: None, - sender_part_sig: None, - }) - } else { - } + let derivation_index = match context.payment_proof_derivation_index { + Some(i) => i, + None => 0, + }; + let keychain = wallet.keychain(keychain_mask)?; + let parent_key_id = wallet.parent_key_id(); + let excess = slate.calc_excess(keychain.secp())?; + let sender_key = + address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?; + let sender_address = OnionV3Address::from_private(&sender_key.0)?; + let sig = + create_payment_proof_signature(slate.amount, &excess, p.sender_address, sender_key)?; + tx.payment_proof = Some(StoredProofInfo { + receiver_address: p.receiver_address, + receiver_signature: p.receiver_signature, + sender_address_path: derivation_index, + sender_address: sender_address.to_ed25519()?, + sender_signature: Some(sig), + }) } wallet.store_tx(&format!("{}", tx.tx_slate_id.unwrap()), slate.tx_or_err()?)?; @@ -577,15 +561,9 @@ where let orig_sender_sk = address::address_from_derivation_path(&keychain, parent_key_id, index)?; let orig_sender_address = OnionV3Address::from_private(&orig_sender_sk.0)?; - if let Some(saddr) = p.sender_address { - if saddr != orig_sender_address.to_ed25519()? { - return Err(Error::PaymentProof( - "Sender address on slate does not match original sender address".to_owned(), - )); - } - } else { + if p.sender_address != orig_sender_address.to_ed25519()? { return Err(Error::PaymentProof( - "Sender address on slate is not provided".to_owned(), + "Sender address on slate does not match original sender address".to_owned(), )); } @@ -599,7 +577,7 @@ where &slate.calc_excess(&keychain.secp())?, orig_sender_address.to_ed25519()?, )?; - let sig = match p.promise_signature { + let sig = match p.receiver_signature { Some(s) => s, None => { return Err(Error::PaymentProof( diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index 3d5db4362..a9958dec0 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -112,7 +112,6 @@ where if v { tx_entry.tx_type != TxLogEntryType::TxReceivedCancelled && tx_entry.tx_type != TxLogEntryType::TxSentCancelled - && tx_entry.tx_type != TxLogEntryType::TxSelfSpendCancelled } else { true } @@ -147,7 +146,6 @@ where if v { tx_entry.tx_type == TxLogEntryType::TxSent || tx_entry.tx_type == TxLogEntryType::TxSentCancelled - || tx_entry.tx_type == TxLogEntryType::TxSelfSpend } else { true } @@ -160,7 +158,6 @@ where if v { tx_entry.tx_type == TxLogEntryType::TxReceived || tx_entry.tx_type == TxLogEntryType::TxReceivedCancelled - || tx_entry.tx_type == TxLogEntryType::TxSelfSpend } else { true } @@ -179,17 +176,6 @@ where true } }) - .filter(|tx_entry| { - if let Some(v) = query_args.include_self_spend_only { - if v { - tx_entry.tx_type == TxLogEntryType::TxSelfSpend - } else { - true - } - } else { - true - } - }) .filter(|tx_entry| { if let Some(v) = query_args.include_reverted_only { if v { @@ -379,8 +365,7 @@ where !tx_entry.confirmed && (tx_entry.tx_type == TxLogEntryType::TxReceived || tx_entry.tx_type == TxLogEntryType::TxSent - || tx_entry.tx_type == TxLogEntryType::TxReverted - || tx_entry.tx_type == TxLogEntryType::TxSelfSpend) + || tx_entry.tx_type == TxLogEntryType::TxReverted) } false => true, }; @@ -492,7 +477,6 @@ where TxLogEntryType::TxReceived | TxLogEntryType::TxReverted => { tx.tx_type = TxLogEntryType::TxReceivedCancelled } - TxLogEntryType::TxSelfSpend => tx.tx_type = TxLogEntryType::TxSelfSpendCancelled, _ => {} } batch.save_tx_log_entry(tx, parent_key_id)?; diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index de17e05f0..3c663a9c1 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -33,7 +33,7 @@ use grin_wallet_util as util; use blake2_rfc as blake2; #[macro_use] -extern crate serde_with; +extern crate serde_derive; #[macro_use] extern crate log; #[macro_use] @@ -45,14 +45,11 @@ extern crate strum_macros; pub mod address; pub mod api_impl; -pub mod contract; mod error; mod internal; mod slate; pub mod slate_versions; pub mod slatepack; - -pub mod mwmixnet; mod types; pub use crate::error::Error; @@ -80,8 +77,6 @@ pub use types::{ WalletOutputBatch, }; -pub use contract::can_finalize; - /// Helper for taking a lock on the wallet instance #[macro_export] macro_rules! wallet_lock { diff --git a/libwallet/src/mwmixnet/mod.rs b/libwallet/src/mwmixnet/mod.rs deleted file mode 100644 index 74597a32f..000000000 --- a/libwallet/src/mwmixnet/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2023 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Onion modules for mxmixnet -pub mod onion; -pub mod types; diff --git a/libwallet/src/mwmixnet/onion/crypto/comsig.rs b/libwallet/src/mwmixnet/onion/crypto/comsig.rs deleted file mode 100644 index 69b3d31d6..000000000 --- a/libwallet/src/mwmixnet/onion/crypto/comsig.rs +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2023 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Comsig modules for mxmixnet - -use secp256k1zkp::{self, pedersen::Commitment, ContextFlag, Secp256k1, SecretKey}; - -use blake2_rfc::blake2b::Blake2b; -use byteorder::{BigEndian, ByteOrder}; -use grin_core::ser::{self, Readable, Reader, Writeable, Writer}; -use secp256k1zkp::rand::thread_rng; -use thiserror::Error; - -/// A generalized Schnorr signature with a pedersen commitment value & blinding factors as the keys -#[derive(Clone, Debug)] -pub struct ComSignature { - pub_nonce: Commitment, - s: SecretKey, - t: SecretKey, -} - -/// Error types for Commitment Signatures -#[derive(Error, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] -pub enum ComSigError { - /// Invalid com sig - #[error("Commitment signature is invalid")] - InvalidSig, - /// SECP Error Wrapper - #[error("Secp256k1zkp error: {0:?}")] - Secp256k1zkp(secp256k1zkp::Error), -} - -impl From for ComSigError { - fn from(err: secp256k1zkp::Error) -> ComSigError { - ComSigError::Secp256k1zkp(err) - } -} - -impl ComSignature { - /// Create new Com signature from commit and keys - pub fn new(pub_nonce: &Commitment, s: &SecretKey, t: &SecretKey) -> ComSignature { - ComSignature { - pub_nonce: pub_nonce.to_owned(), - s: s.to_owned(), - t: t.to_owned(), - } - } - - #[allow(dead_code)] - /// Sign com signature with kernel values - pub fn sign( - amount: u64, - blind: &SecretKey, - msg: &Vec, - ) -> Result { - let secp = Secp256k1::with_caps(ContextFlag::Commit); - - let mut amt_bytes = [0; 32]; - BigEndian::write_u64(&mut amt_bytes[24..32], amount); - let k_amt = SecretKey::from_slice(&secp, &amt_bytes)?; - - let k_1 = SecretKey::new(&secp, &mut thread_rng()); - let k_2 = SecretKey::new(&secp, &mut thread_rng()); - - let commitment = secp.commit(amount, blind.clone())?; - let nonce_commitment = secp.commit_blind(k_1.clone(), k_2.clone())?; - - let e = ComSignature::calc_challenge(&secp, &commitment, &nonce_commitment, &msg)?; - - // s = k_1 + (e * amount) - let mut s = k_amt.clone(); - s.mul_assign(&secp, &e)?; - s.add_assign(&secp, &k_1)?; - - // t = k_2 + (e * blind) - let mut t = blind.clone(); - t.mul_assign(&secp, &e)?; - t.add_assign(&secp, &k_2)?; - - Ok(ComSignature::new(&nonce_commitment, &s, &t)) - } - - #[allow(non_snake_case)] - /// Verify a com sig - pub fn verify(&self, commit: &Commitment, msg: &Vec) -> Result<(), ComSigError> { - let secp = Secp256k1::with_caps(ContextFlag::Commit); - - let S1 = secp.commit_blind(self.s.clone(), self.t.clone())?; - - let mut Ce = commit.to_pubkey(&secp)?; - let e = ComSignature::calc_challenge(&secp, &commit, &self.pub_nonce, &msg)?; - Ce.mul_assign(&secp, &e)?; - - let commits = vec![Commitment::from_pubkey(&secp, &Ce)?, self.pub_nonce.clone()]; - let S2 = secp.commit_sum(commits, Vec::new())?; - - if S1 != S2 { - return Err(ComSigError::InvalidSig); - } - - Ok(()) - } - - fn calc_challenge( - secp: &Secp256k1, - commit: &Commitment, - nonce_commit: &Commitment, - msg: &Vec, - ) -> Result { - let mut challenge_hasher = Blake2b::new(32); - challenge_hasher.update(&commit.0); - challenge_hasher.update(&nonce_commit.0); - challenge_hasher.update(msg); - - let mut challenge = [0; 32]; - challenge.copy_from_slice(challenge_hasher.finalize().as_bytes()); - - Ok(SecretKey::from_slice(&secp, &challenge)?) - } -} - -/// Serializes a ComSignature to and from hex -pub mod comsig_serde { - use super::ComSignature; - use grin_core::ser::{self, ProtocolVersion}; - use grin_util::ToHex; - use serde::{Deserialize, Serializer}; - - /// Serializes a ComSignature as a hex string - pub fn serialize(comsig: &ComSignature, serializer: S) -> Result - where - S: Serializer, - { - use serde::ser::Error; - let bytes = ser::ser_vec(&comsig, ProtocolVersion::local()).map_err(Error::custom)?; - serializer.serialize_str(&bytes.to_hex()) - } - - /// Creates a ComSignature from a hex string - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de::Error; - let bytes = String::deserialize(deserializer) - .and_then(|string| grin_util::from_hex(&string).map_err(Error::custom))?; - let sig: ComSignature = ser::deserialize_default(&mut &bytes[..]).map_err(Error::custom)?; - Ok(sig) - } -} - -#[allow(non_snake_case)] -impl Readable for ComSignature { - fn read(reader: &mut R) -> Result { - let R = Commitment::read(reader)?; - let s = super::secp::read_secret_key(reader)?; - let t = super::secp::read_secret_key(reader)?; - Ok(ComSignature::new(&R, &s, &t)) - } -} - -impl Writeable for ComSignature { - fn write(&self, writer: &mut W) -> Result<(), ser::Error> { - writer.write_fixed_bytes(self.pub_nonce.0)?; - writer.write_fixed_bytes(self.s.0)?; - writer.write_fixed_bytes(self.t.0)?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::{ComSigError, ComSignature, ContextFlag, Secp256k1, SecretKey}; - - use rand::Rng; - use secp256k1zkp::rand::{thread_rng, RngCore}; - - /// Test signing and verification of ComSignatures - #[test] - fn verify_comsig() -> Result<(), ComSigError> { - let secp = Secp256k1::with_caps(ContextFlag::Commit); - - let amount = thread_rng().next_u64(); - let blind = SecretKey::new(&secp, &mut thread_rng()); - let msg: [u8; 16] = rand::thread_rng().gen(); - let comsig = ComSignature::sign(amount, &blind, &msg.to_vec())?; - - let commit = secp.commit(amount, blind.clone())?; - assert!(comsig.verify(&commit, &msg.to_vec()).is_ok()); - - let wrong_msg: [u8; 16] = rand::thread_rng().gen(); - assert!(comsig.verify(&commit, &wrong_msg.to_vec()).is_err()); - - let wrong_commit = secp.commit(amount, SecretKey::new(&secp, &mut thread_rng()))?; - assert!(comsig.verify(&wrong_commit, &msg.to_vec()).is_err()); - - Ok(()) - } -} diff --git a/libwallet/src/mwmixnet/onion/crypto/dalek.rs b/libwallet/src/mwmixnet/onion/crypto/dalek.rs deleted file mode 100644 index a8b4f5887..000000000 --- a/libwallet/src/mwmixnet/onion/crypto/dalek.rs +++ /dev/null @@ -1,293 +0,0 @@ -// Copyright 2023 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Dalek key wrapper for mwmixnet primitives - -use super::secp::SecretKey; - -use ed25519_dalek::{Keypair, PublicKey, Signature, Signer, Verifier}; -use grin_core::ser::{self, Readable, Reader, Writeable, Writer}; -use grin_util::ToHex; -use thiserror::Error; - -/// Error types for Dalek structures and logic -#[derive(Clone, Error, Debug, PartialEq)] -pub enum DalekError { - /// Hex deser error - #[error("Hex error {0:?}")] - HexError(String), - /// Key parsing error - #[error("Failed to parse secret key")] - KeyParseError, - /// Error validating signature - #[error("Failed to verify signature")] - SigVerifyFailed, -} - -/// Encapsulates an ed25519_dalek::PublicKey and provides (de-)serialization -#[derive(Clone, Debug, PartialEq)] -pub struct DalekPublicKey(PublicKey); - -impl DalekPublicKey { - /// Convert DalekPublicKey to hex string - pub fn to_hex(&self) -> String { - self.0.to_hex() - } - - /// Convert hex string to DalekPublicKey. - pub fn from_hex(hex: &str) -> Result { - let bytes = grin_util::from_hex(hex) - .map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?; - let pk = PublicKey::from_bytes(bytes.as_ref()) - .map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?; - Ok(DalekPublicKey(pk)) - } - - /// Compute DalekPublicKey from a SecretKey - pub fn from_secret(key: &SecretKey) -> Self { - let secret = ed25519_dalek::SecretKey::from_bytes(&key.0).unwrap(); - let pk: PublicKey = (&secret).into(); - DalekPublicKey(pk) - } -} - -impl AsRef for DalekPublicKey { - fn as_ref(&self) -> &PublicKey { - &self.0 - } -} - -/// Serializes an Option to and from hex -pub mod option_dalek_pubkey_serde { - use super::DalekPublicKey; - use grin_util::ToHex; - use serde::de::Error; - use serde::{Deserialize, Deserializer, Serializer}; - - /// - pub fn serialize(pk: &Option, serializer: S) -> Result - where - S: Serializer, - { - match pk { - Some(pk) => serializer.serialize_str(&pk.0.to_hex()), - None => serializer.serialize_none(), - } - } - - /// - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - Option::::deserialize(deserializer).and_then(|res| match res { - Some(string) => DalekPublicKey::from_hex(&string) - .map_err(|e| Error::custom(e.to_string())) - .and_then(|pk: DalekPublicKey| Ok(Some(pk))), - None => Ok(None), - }) - } -} - -impl Readable for DalekPublicKey { - fn read(reader: &mut R) -> Result { - let pk = PublicKey::from_bytes(&reader.read_fixed_bytes(32)?) - .map_err(|_| ser::Error::CorruptedData)?; - Ok(DalekPublicKey(pk)) - } -} - -impl Writeable for DalekPublicKey { - fn write(&self, writer: &mut W) -> Result<(), ser::Error> { - writer.write_fixed_bytes(self.0.to_bytes())?; - Ok(()) - } -} - -/// Encapsulates an ed25519_dalek::Signature and provides (de-)serialization -#[derive(Clone, Debug, PartialEq)] -pub struct DalekSignature(Signature); - -impl DalekSignature { - /// Convert hex string to DalekSignature. - pub fn from_hex(hex: &str) -> Result { - let bytes = grin_util::from_hex(hex) - .map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?; - let sig = Signature::from_bytes(bytes.as_ref()) - .map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?; - Ok(DalekSignature(sig)) - } - - /// Verifies DalekSignature - pub fn verify(&self, pk: &DalekPublicKey, msg: &[u8]) -> Result<(), DalekError> { - pk.as_ref() - .verify(&msg, &self.0) - .map_err(|_| DalekError::SigVerifyFailed) - } -} - -impl AsRef for DalekSignature { - fn as_ref(&self) -> &Signature { - &self.0 - } -} - -/// Serializes a DalekSignature to and from hex -pub mod dalek_sig_serde { - use super::DalekSignature; - use grin_util::ToHex; - use serde::de::Error; - use serde::{Deserialize, Deserializer, Serializer}; - - /// - pub fn serialize(sig: &DalekSignature, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&sig.0.to_hex()) - } - - /// - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let str = String::deserialize(deserializer)?; - let sig = DalekSignature::from_hex(&str).map_err(|e| Error::custom(e.to_string()))?; - Ok(sig) - } -} - -/// Dalek signature sign wrapper -// TODO: This is likely duplicated throughout crate, check -pub fn sign(sk: &SecretKey, message: &[u8]) -> Result { - let secret = - ed25519_dalek::SecretKey::from_bytes(&sk.0).map_err(|_| DalekError::KeyParseError)?; - let public: PublicKey = (&secret).into(); - let keypair = Keypair { secret, public }; - let sig = keypair.sign(&message); - Ok(DalekSignature(sig)) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mwmixnet::onion::test_util::rand_keypair; - use grin_core::ser::{self, ProtocolVersion}; - use grin_util::ToHex; - use rand::Rng; - use serde::{Deserialize, Serialize}; - use serde_json::Value; - - #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] - struct TestPubKeySerde { - #[serde(with = "option_dalek_pubkey_serde", default)] - pk: Option, - } - - #[test] - fn pubkey_test() -> Result<(), Box> { - // Test from_hex - let rand_pk = rand_keypair().1; - let pk_from_hex = DalekPublicKey::from_hex(rand_pk.0.to_hex().as_str()).unwrap(); - assert_eq!(rand_pk.0, pk_from_hex.0); - - // Test ser (de-)serialization - let bytes = ser::ser_vec(&rand_pk, ProtocolVersion::local()).unwrap(); - assert_eq!(bytes.len(), 32); - let pk_from_deser: DalekPublicKey = ser::deserialize_default(&mut &bytes[..]).unwrap(); - assert_eq!(rand_pk.0, pk_from_deser.0); - - // Test serde with Some(rand_pk) - let some = TestPubKeySerde { - pk: Some(rand_pk.clone()), - }; - let val = serde_json::to_value(some.clone()).unwrap(); - if let Value::Object(o) = &val { - if let Value::String(s) = o.get("pk").unwrap() { - assert_eq!(s, &rand_pk.0.to_hex()); - } else { - panic!("Invalid type"); - } - } else { - panic!("Invalid type") - } - assert_eq!(some, serde_json::from_value(val).unwrap()); - - // Test serde with empty pk field - let none = TestPubKeySerde { pk: None }; - let val = serde_json::to_value(none.clone()).unwrap(); - if let Value::Object(o) = &val { - if let Value::Null = o.get("pk").unwrap() { - // ok - } else { - panic!("Invalid type"); - } - } else { - panic!("Invalid type") - } - assert_eq!(none, serde_json::from_value(val).unwrap()); - - // Test serde with no pk field - let none2 = serde_json::from_str::("{}").unwrap(); - assert_eq!(none, none2); - - Ok(()) - } - - #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] - struct TestSigSerde { - #[serde(with = "dalek_sig_serde")] - sig: DalekSignature, - } - - #[test] - fn sig_test() -> Result<(), Box> { - // Sign a message - let (sk, pk) = rand_keypair(); - let msg: [u8; 16] = rand::thread_rng().gen(); - let sig = sign(&sk, &msg).unwrap(); - - // Verify signature - assert!(sig.verify(&pk, &msg).is_ok()); - - // Wrong message - let wrong_msg: [u8; 16] = rand::thread_rng().gen(); - assert!(sig.verify(&pk, &wrong_msg).is_err()); - - // Wrong pubkey - let wrong_pk = rand_keypair().1; - assert!(sig.verify(&wrong_pk, &msg).is_err()); - - // Test from_hex - let sig_from_hex = DalekSignature::from_hex(sig.0.to_hex().as_str()).unwrap(); - assert_eq!(sig.0, sig_from_hex.0); - - // Test serde (de-)serialization - let serde_test = TestSigSerde { sig: sig.clone() }; - let val = serde_json::to_value(serde_test.clone()).unwrap(); - if let Value::Object(o) = &val { - if let Value::String(s) = o.get("sig").unwrap() { - assert_eq!(s, &sig.0.to_hex()); - } else { - panic!("Invalid type"); - } - } else { - panic!("Invalid type") - } - assert_eq!(serde_test, serde_json::from_value(val).unwrap()); - - Ok(()) - } -} diff --git a/libwallet/src/mwmixnet/onion/crypto/mod.rs b/libwallet/src/mwmixnet/onion/crypto/mod.rs deleted file mode 100644 index 8ae01a97c..000000000 --- a/libwallet/src/mwmixnet/onion/crypto/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2023 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Onion and comsig modules for mxmixnet - -pub mod comsig; -pub mod dalek; -pub mod secp; diff --git a/libwallet/src/mwmixnet/onion/crypto/secp.rs b/libwallet/src/mwmixnet/onion/crypto/secp.rs deleted file mode 100644 index 28872ee24..000000000 --- a/libwallet/src/mwmixnet/onion/crypto/secp.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2023 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! SECP wrapper functions for onion/comsig -//! TODO: Likely redundant stuff in here, trim - -pub use secp256k1zkp::aggsig; -pub use secp256k1zkp::constants::{ - AGG_SIGNATURE_SIZE, COMPRESSED_PUBLIC_KEY_SIZE, MAX_PROOF_SIZE, PEDERSEN_COMMITMENT_SIZE, - SECRET_KEY_SIZE, -}; -pub use secp256k1zkp::ecdh::SharedSecret; -pub use secp256k1zkp::key::{PublicKey, SecretKey, ZERO_KEY}; -pub use secp256k1zkp::pedersen::{Commitment, RangeProof}; -pub use secp256k1zkp::{ContextFlag, Message, Secp256k1, Signature}; - -use grin_core::ser::{self, Reader}; -use secp256k1zkp::rand::thread_rng; - -/// Generate a random SecretKey. -pub fn random_secret() -> SecretKey { - let secp = Secp256k1::new(); - SecretKey::new(&secp, &mut thread_rng()) -} - -/// Deserialize a SecretKey from a Reader -pub fn read_secret_key(reader: &mut R) -> Result { - let buf = reader.read_fixed_bytes(SECRET_KEY_SIZE)?; - let secp = Secp256k1::with_caps(ContextFlag::None); - let pk = SecretKey::from_slice(&secp, &buf).map_err(|_| ser::Error::CorruptedData)?; - Ok(pk) -} - -/// Build a Pedersen Commitment using the provided value and blinding factor -pub fn commit(value: u64, blind: &SecretKey) -> Result { - let secp = Secp256k1::with_caps(ContextFlag::Commit); - let commit = secp.commit(value, blind.clone())?; - Ok(commit) -} - -/// Add a blinding factor to an existing Commitment -pub fn add_excess( - commitment: &Commitment, - excess: &SecretKey, -) -> Result { - let secp = Secp256k1::with_caps(ContextFlag::Commit); - let excess_commit: Commitment = secp.commit(0, excess.clone())?; - - let commits = vec![commitment.clone(), excess_commit.clone()]; - let sum = secp.commit_sum(commits, Vec::new())?; - Ok(sum) -} - -/// Subtracts a value (v*H) from an existing commitment -pub fn sub_value(commitment: &Commitment, value: u64) -> Result { - let secp = Secp256k1::with_caps(ContextFlag::Commit); - let neg_commit: Commitment = secp.commit(value, ZERO_KEY)?; - let sum = secp.commit_sum(vec![commitment.clone()], vec![neg_commit.clone()])?; - Ok(sum) -} - -/// Signs the message with the provided SecretKey -pub fn sign(sk: &SecretKey, msg: &Message) -> Result { - let secp = Secp256k1::with_caps(ContextFlag::Full); - let pubkey = PublicKey::from_secret_key(&secp, &sk)?; - let sig = aggsig::sign_single(&secp, &msg, &sk, None, None, None, Some(&pubkey), None)?; - Ok(sig) -} diff --git a/libwallet/src/mwmixnet/onion/mod.rs b/libwallet/src/mwmixnet/onion/mod.rs deleted file mode 100644 index bcabf8ec5..000000000 --- a/libwallet/src/mwmixnet/onion/mod.rs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2023 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Onion module definition - -pub mod crypto; -pub mod onion; -pub mod util; - -use crypto::secp::{random_secret, Commitment, SecretKey}; -use onion::{new_stream_cipher, Onion, OnionError, Payload, RawBytes}; - -use chacha20::cipher::StreamCipher; -use grin_core::core::FeeFields; -use secp256k1zkp::pedersen::RangeProof; -use x25519_dalek::PublicKey as xPublicKey; -use x25519_dalek::{SharedSecret, StaticSecret}; - -/// Onion hop struct -#[derive(Clone)] -pub struct Hop { - /// Comsig server public key - pub server_pubkey: xPublicKey, - /// Kernel excess - pub excess: SecretKey, - /// Fee - pub fee: FeeFields, - /// Rangeproof - pub rangeproof: Option, -} - -/// Crate a new hop -pub fn new_hop( - server_key: &SecretKey, - hop_excess: &SecretKey, - fee: u32, - proof: Option, -) -> Hop { - Hop { - server_pubkey: xPublicKey::from(&StaticSecret::from(server_key.0.clone())), - excess: hop_excess.clone(), - fee: FeeFields::from(fee as u32), - rangeproof: proof, - } -} - -/// Create an Onion for the Commitment, encrypting the payload for each hop -pub fn create_onion(commitment: &Commitment, hops: &Vec) -> Result { - if hops.is_empty() { - return Ok(Onion { - ephemeral_pubkey: xPublicKey::from([0u8; 32]), - commit: commitment.clone(), - enc_payloads: vec![], - }); - } - - let mut shared_secrets: Vec = Vec::new(); - let mut enc_payloads: Vec = Vec::new(); - let mut ephemeral_sk = StaticSecret::from(random_secret().0); - let onion_ephemeral_pk = xPublicKey::from(&ephemeral_sk); - for i in 0..hops.len() { - let hop = &hops[i]; - let shared_secret = ephemeral_sk.diffie_hellman(&hop.server_pubkey); - shared_secrets.push(shared_secret); - - ephemeral_sk = StaticSecret::from(random_secret().0); - let next_ephemeral_pk = if i < (hops.len() - 1) { - xPublicKey::from(&ephemeral_sk) - } else { - xPublicKey::from([0u8; 32]) - }; - - let payload = Payload { - next_ephemeral_pk, - excess: hop.excess.clone(), - fee: hop.fee.clone(), - rangeproof: hop.rangeproof.clone(), - }; - enc_payloads.push(payload.serialize()?); - } - - for i in (0..shared_secrets.len()).rev() { - let mut cipher = new_stream_cipher(&shared_secrets[i])?; - for j in i..shared_secrets.len() { - cipher.apply_keystream(&mut enc_payloads[j]); - } - } - - let onion = Onion { - ephemeral_pubkey: onion_ephemeral_pk, - commit: commitment.clone(), - enc_payloads, - }; - Ok(onion) -} - -/// Internal tests -#[allow(missing_docs)] -pub mod test_util { - use super::*; - use crypto::dalek::DalekPublicKey; - use crypto::secp; - - use grin_core::core::hash::Hash; - use grin_util::ToHex; - use rand::{thread_rng, RngCore}; - use secp256k1zkp::Secp256k1; - - pub fn rand_onion() -> Onion { - let commit = rand_commit(); - let mut hops = Vec::new(); - let k = (thread_rng().next_u64() % 5) + 1; - for i in 0..k { - let rangeproof = if i == (k - 1) { - Some(rand_proof()) - } else { - None - }; - let hop = new_hop( - &random_secret(), - &random_secret(), - thread_rng().next_u32(), - rangeproof, - ); - hops.push(hop); - } - - create_onion(&commit, &hops).unwrap() - } - - pub fn rand_commit() -> Commitment { - secp::commit(rand::thread_rng().next_u64(), &secp::random_secret()).unwrap() - } - - pub fn rand_hash() -> Hash { - Hash::from_hex(secp::random_secret().to_hex().as_str()).unwrap() - } - - pub fn rand_proof() -> RangeProof { - let secp = Secp256k1::new(); - secp.bullet_proof( - rand::thread_rng().next_u64(), - secp::random_secret(), - secp::random_secret(), - secp::random_secret(), - None, - None, - ) - } - - pub fn proof( - value: u64, - fee: u32, - input_blind: &SecretKey, - hop_excesses: &Vec<&SecretKey>, - ) -> (Commitment, RangeProof) { - let secp = Secp256k1::new(); - - let mut blind = input_blind.clone(); - for hop_excess in hop_excesses { - blind.add_assign(&secp, &hop_excess).unwrap(); - } - - let out_value = value - (fee as u64); - - let rp = secp.bullet_proof( - out_value, - blind.clone(), - secp::random_secret(), - secp::random_secret(), - None, - None, - ); - - (secp::commit(out_value, &blind).unwrap(), rp) - } - - pub fn rand_keypair() -> (SecretKey, DalekPublicKey) { - let sk = random_secret(); - let pk = DalekPublicKey::from_secret(&sk); - (sk, pk) - } -} diff --git a/libwallet/src/mwmixnet/onion/onion.rs b/libwallet/src/mwmixnet/onion/onion.rs deleted file mode 100644 index 425605934..000000000 --- a/libwallet/src/mwmixnet/onion/onion.rs +++ /dev/null @@ -1,430 +0,0 @@ -// Copyright 2023 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Onion defn for mwmixnet - -use super::crypto::secp::{self, Commitment, RangeProof, SecretKey}; -use super::util::{read_optional, vec_to_array, write_optional}; - -use chacha20::cipher::{NewCipher, StreamCipher}; -use chacha20::{ChaCha20, Key, Nonce}; -use grin_core::core::FeeFields; -use grin_core::ser::{self, Readable, Reader, Writeable, Writer}; -use grin_util::{self, ToHex}; -use hmac::digest::InvalidLength; -use hmac::{Hmac, Mac}; -use serde::ser::SerializeStruct; -use serde::Deserialize; -use sha2::Sha256; -use std::convert::TryFrom; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::result::Result; -use thiserror::Error; -use x25519_dalek::{PublicKey as xPublicKey, SharedSecret, StaticSecret}; - -type HmacSha256 = Hmac; -/// Wrap u8 vec -pub type RawBytes = Vec; - -const CURRENT_ONION_VERSION: u8 = 0; - -/// A data packet with layers of encryption -#[derive(Clone, Debug)] -pub struct Onion { - /// The onion originator's portion of the shared secret - pub ephemeral_pubkey: xPublicKey, - /// The pedersen commitment before adjusting the excess and subtracting the fee - pub commit: Commitment, - /// The encrypted payloads which represent the layers of the onion - pub enc_payloads: Vec, -} - -impl PartialEq for Onion { - fn eq(&self, other: &Onion) -> bool { - *self.ephemeral_pubkey.as_bytes() == *other.ephemeral_pubkey.as_bytes() - && self.commit == other.commit - && self.enc_payloads == other.enc_payloads - } -} - -impl Eq for Onion {} - -impl Hash for Onion { - fn hash(&self, state: &mut H) { - state.write(self.ephemeral_pubkey.as_bytes()); - state.write(self.commit.as_ref()); - state.write_usize(self.enc_payloads.len()); - for p in &self.enc_payloads { - state.write(p.as_slice()); - } - } -} - -/// A single, decrypted/peeled layer of an Onion. -#[derive(Debug, Clone)] -pub struct Payload { - /// next ephemeral pk - pub next_ephemeral_pk: xPublicKey, - /// excess - pub excess: SecretKey, - /// fee - pub fee: FeeFields, - /// proof - pub rangeproof: Option, -} - -impl Payload { - /// Deser a payload - pub fn deserialize(bytes: &Vec) -> Result { - let payload: Payload = ser::deserialize_default(&mut &bytes[..])?; - Ok(payload) - } - - /// Serialize a payload - pub fn serialize(&self) -> Result, ser::Error> { - let mut vec = vec![]; - ser::serialize_default(&mut vec, &self)?; - Ok(vec) - } -} - -impl Readable for Payload { - fn read(reader: &mut R) -> Result { - let version = reader.read_u8()?; - if version != CURRENT_ONION_VERSION { - return Err(ser::Error::UnsupportedProtocolVersion); - } - - let next_ephemeral_pk = - xPublicKey::from(vec_to_array::<32>(&reader.read_fixed_bytes(32)?)?); - let excess = secp::read_secret_key(reader)?; - let fee = FeeFields::try_from(reader.read_u64()?).map_err(|_| ser::Error::CorruptedData)?; - let rangeproof = read_optional(reader)?; - Ok(Payload { - next_ephemeral_pk, - excess, - fee, - rangeproof, - }) - } -} - -impl Writeable for Payload { - fn write(&self, writer: &mut W) -> Result<(), ser::Error> { - writer.write_u8(CURRENT_ONION_VERSION)?; - writer.write_fixed_bytes(&self.next_ephemeral_pk.as_bytes())?; - writer.write_fixed_bytes(&self.excess)?; - writer.write_u64(self.fee.into())?; - write_optional(writer, &self.rangeproof)?; - Ok(()) - } -} - -/// An onion with a layer decrypted -#[derive(Clone, Debug)] -pub struct PeeledOnion { - /// The payload from the peeled layer - pub payload: Payload, - /// The onion remaining after a layer was peeled - pub onion: Onion, -} - -impl Onion { - /// Serialize onion - pub fn serialize(&self) -> Result, ser::Error> { - let mut vec = vec![]; - ser::serialize_default(&mut vec, &self)?; - Ok(vec) - } - - /// Peel a single layer off of the Onion, returning the peeled Onion and decrypted Payload - pub fn peel_layer(&self, server_key: &SecretKey) -> Result { - let shared_secret = StaticSecret::from(server_key.0).diffie_hellman(&self.ephemeral_pubkey); - let mut cipher = new_stream_cipher(&shared_secret)?; - - let mut decrypted_bytes = self.enc_payloads[0].clone(); - cipher.apply_keystream(&mut decrypted_bytes); - let decrypted_payload = Payload::deserialize(&decrypted_bytes) - .map_err(|e| OnionError::DeserializationError(e))?; - - let enc_payloads: Vec = self - .enc_payloads - .iter() - .enumerate() - .filter(|&(i, _)| i != 0) - .map(|(_, enc_payload)| { - let mut p = enc_payload.clone(); - cipher.apply_keystream(&mut p); - p - }) - .collect(); - - let mut commitment = self.commit.clone(); - commitment = secp::add_excess(&commitment, &decrypted_payload.excess) - .map_err(|e| OnionError::CalcCommitError(e))?; - commitment = secp::sub_value(&commitment, decrypted_payload.fee.into()) - .map_err(|e| OnionError::CalcCommitError(e))?; - - let peeled_onion = Onion { - ephemeral_pubkey: decrypted_payload.next_ephemeral_pk, - commit: commitment.clone(), - enc_payloads, - }; - Ok(PeeledOnion { - payload: decrypted_payload, - onion: peeled_onion, - }) - } -} - -/// Create new stream cypher from shared secret -pub fn new_stream_cipher(shared_secret: &SharedSecret) -> Result { - let mut mu_hmac = HmacSha256::new_from_slice(b"MWIXNET")?; - mu_hmac.update(shared_secret.as_bytes()); - let mukey = mu_hmac.finalize().into_bytes(); - - let key = Key::from_slice(&mukey[0..32]); - let nonce = Nonce::from_slice(b"NONCE1234567"); - - Ok(ChaCha20::new(&key, &nonce)) -} - -impl Writeable for Onion { - fn write(&self, writer: &mut W) -> Result<(), ser::Error> { - writer.write_fixed_bytes(self.ephemeral_pubkey.as_bytes())?; - writer.write_fixed_bytes(&self.commit)?; - writer.write_u64(self.enc_payloads.len() as u64)?; - for p in &self.enc_payloads { - writer.write_u64(p.len() as u64)?; - p.write(writer)?; - } - Ok(()) - } -} - -impl Readable for Onion { - fn read(reader: &mut R) -> Result { - let pubkey_bytes: [u8; 32] = vec_to_array(&reader.read_fixed_bytes(32)?)?; - let ephemeral_pubkey = xPublicKey::from(pubkey_bytes); - let commit = Commitment::read(reader)?; - let mut enc_payloads: Vec = Vec::new(); - let len = reader.read_u64()?; - for _ in 0..len { - let size = reader.read_u64()?; - let bytes = reader.read_fixed_bytes(size as usize)?; - enc_payloads.push(bytes); - } - Ok(Onion { - ephemeral_pubkey, - commit, - enc_payloads, - }) - } -} - -impl serde::ser::Serialize for Onion { - fn serialize(&self, serializer: S) -> Result - where - S: serde::ser::Serializer, - { - let mut state = serializer.serialize_struct("Onion", 3)?; - - state.serialize_field("pubkey", &self.ephemeral_pubkey.as_bytes().to_hex())?; - state.serialize_field("commit", &self.commit.to_hex())?; - - let hex_payloads: Vec = self.enc_payloads.iter().map(|v| v.to_hex()).collect(); - state.serialize_field("data", &hex_payloads)?; - state.end() - } -} - -impl<'de> serde::de::Deserialize<'de> for Onion { - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(field_identifier, rename_all = "snake_case")] - enum Field { - Pubkey, - Commit, - Data, - } - - struct OnionVisitor; - - impl<'de> serde::de::Visitor<'de> for OnionVisitor { - type Value = Onion; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("an Onion") - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - let mut pubkey = None; - let mut commit = None; - let mut data = None; - - while let Some(key) = map.next_key()? { - match key { - Field::Pubkey => { - let val: String = map.next_value()?; - let vec = - grin_util::from_hex(&val).map_err(serde::de::Error::custom)?; - pubkey = - Some(xPublicKey::from(vec_to_array::<32>(&vec).map_err( - |_| serde::de::Error::custom("Invalid length pubkey"), - )?)); - } - Field::Commit => { - let val: String = map.next_value()?; - let vec = - grin_util::from_hex(&val).map_err(serde::de::Error::custom)?; - commit = Some(Commitment::from_vec(vec)); - } - Field::Data => { - let val: Vec = map.next_value()?; - let mut vec: Vec> = Vec::new(); - for hex in val { - vec.push( - grin_util::from_hex(&hex).map_err(serde::de::Error::custom)?, - ); - } - data = Some(vec); - } - } - } - - Ok(Onion { - ephemeral_pubkey: pubkey.unwrap(), - commit: commit.unwrap(), - enc_payloads: data.unwrap(), - }) - } - } - - const FIELDS: &[&str] = &["pubkey", "commit", "data"]; - deserializer.deserialize_struct("Onion", &FIELDS, OnionVisitor) - } -} - -/// Error types for creating and peeling Onions -#[derive(Clone, Error, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub enum OnionError { - /// Invalid Key Length - #[error("Invalid key length for MAC initialization")] - InvalidKeyLength, - /// Serialization error - #[error("Serialization error occurred: {0:?}")] - SerializationError(ser::Error), - /// Deserialization error - #[error("Deserialization error occurred: {0:?}")] - DeserializationError(ser::Error), - /// Error calculating blinding factor - #[error("Error calculating blinding factor: {0:?}")] - CalcBlindError(secp256k1zkp::Error), - /// Error calculating ephemeral key - #[error("Error calculating ephemeral pubkey: {0:?}")] - CalcPubKeyError(secp256k1zkp::Error), - /// Error calculating commitment - #[error("Error calculating commitment: {0:?}")] - CalcCommitError(secp256k1zkp::Error), -} - -impl From for OnionError { - fn from(_err: InvalidLength) -> OnionError { - OnionError::InvalidKeyLength - } -} - -impl From for OnionError { - fn from(err: ser::Error) -> OnionError { - OnionError::SerializationError(err) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use crate::mwmixnet::onion::crypto::secp::random_secret; - use crate::mwmixnet::onion::{new_hop, Hop}; - - use grin_core::core::FeeFields; - - /// Test end-to-end Onion creation and unwrapping logic. - #[test] - fn onion() { - let total_fee: u64 = 10; - let fee_per_hop: u32 = 2; - let in_value: u64 = 1000; - let out_value: u64 = in_value - total_fee; - let blind = random_secret(); - let commitment = secp::commit(in_value, &blind).unwrap(); - - let mut hops: Vec = Vec::new(); - let mut keys: Vec = Vec::new(); - let mut final_commit = secp::commit(out_value, &blind).unwrap(); - let mut final_blind = blind.clone(); - for i in 0..5 { - keys.push(random_secret()); - - let excess = random_secret(); - - let secp = secp256k1zkp::Secp256k1::with_caps(secp256k1zkp::ContextFlag::Commit); - final_blind.add_assign(&secp, &excess).unwrap(); - final_commit = secp::add_excess(&final_commit, &excess).unwrap(); - let proof = if i == 4 { - let n1 = random_secret(); - let rp = secp.bullet_proof( - out_value, - final_blind.clone(), - n1.clone(), - n1.clone(), - None, - None, - ); - assert!(secp.verify_bullet_proof(final_commit, rp, None).is_ok()); - Some(rp) - } else { - None - }; - - let hop = new_hop(&keys[i], &excess, fee_per_hop, proof); - hops.push(hop); - } - - let mut onion_packet = crate::mwmixnet::onion::create_onion(&commitment, &hops).unwrap(); - - let mut payload = Payload { - next_ephemeral_pk: onion_packet.ephemeral_pubkey.clone(), - excess: random_secret(), - fee: FeeFields::from(fee_per_hop as u32), - rangeproof: None, - }; - for i in 0..5 { - let peeled = onion_packet.peel_layer(&keys[i]).unwrap(); - payload = peeled.payload; - onion_packet = peeled.onion; - } - - assert!(payload.rangeproof.is_some()); - assert_eq!(payload.rangeproof.unwrap(), hops[4].rangeproof.unwrap()); - assert_eq!(secp::commit(out_value, &final_blind).unwrap(), final_commit); - assert_eq!(payload.fee, FeeFields::from(fee_per_hop as u32)); - } -} diff --git a/libwallet/src/mwmixnet/onion/util.rs b/libwallet/src/mwmixnet/onion/util.rs deleted file mode 100644 index 31495dd9a..000000000 --- a/libwallet/src/mwmixnet/onion/util.rs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2023 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Util fns for mwmixnet -//! TODO: possibly redundant, check or move elsewhere - -use grin_core::ser::{self, Readable, Reader, Writeable, Writer}; -use std::convert::TryInto; - -/// Writes an optional value as '1' + value if Some, or '0' if None -/// -/// This function is used to serialize an optional value into a Writer. If the option -/// contains Some value, it writes '1' followed by the serialized value. If the option -/// is None, it just writes '0'. -/// -/// # Arguments -/// -/// * `writer` - A Writer instance where the data will be written. -/// * `o` - The Optional value that will be written. -/// -/// # Returns -/// -/// * If successful, returns Ok with nothing. -/// * If an error occurs during writing, returns Err wrapping the error. -/// -/// # Example -/// -/// ``` -/// use grin_wallet_libwallet::mwmixnet::onion::util::write_optional; -/// let mut writer:Vec = vec![]; -/// let optional_value: Option = Some(10); -/// //write_optional(&mut writer, &optional_value); -/// ``` -pub fn write_optional( - writer: &mut W, - o: &Option, -) -> Result<(), ser::Error> { - match &o { - Some(o) => { - writer.write_u8(1)?; - o.write(writer)?; - } - None => writer.write_u8(0)?, - }; - Ok(()) -} - -/// Reads an optional value as '1' + value if Some, or '0' if None -/// -/// This function is used to deserialize an optional value from a Reader. If the first byte -/// read is '0', it returns None. If the first byte is '1', it reads the next value and -/// returns Some(value). -/// -/// # Arguments -/// -/// * `reader` - A Reader instance from where the data will be read. -/// -/// # Returns -/// -/// * If successful, returns Ok wrapping an optional value. If the first byte read was '0', -/// returns None. If it was '1', returns Some(value). -/// * If an error occurs during reading, returns Err wrapping the error. -/// -/// # Example -/// -/// ``` -/// use grin_wallet_libwallet::mwmixnet::onion::util::read_optional; -/// use grin_core::ser::{BinReader, ProtocolVersion, DeserializationMode}; -/// let mut buf: &[u8] = &[1, 0, 0, 0, 10]; -/// let mut reader = BinReader::new(&mut buf, ProtocolVersion::local(), DeserializationMode::default()); -/// let optional_value: Option = read_optional(&mut reader).unwrap(); -/// assert_eq!(optional_value, Some(10)); -/// ``` -pub fn read_optional(reader: &mut R) -> Result, ser::Error> { - let o = if reader.read_u8()? == 0 { - None - } else { - Some(O::read(reader)?) - }; - Ok(o) -} - -/// Convert a vector to an array of size `S`. -/// -/// # Arguments -/// -/// * `vec` - The input vector. -/// -/// # Returns -/// -/// * If successful, returns an `Ok` wrapping an array of size `S` containing -/// the first `S` bytes of `vec`. -/// * If `vec` is smaller than `S`, returns an `Err` indicating a count error. -/// -/// # Example -/// -/// ``` -/// use grin_wallet_libwallet::mwmixnet::onion::util::vec_to_array; -/// let v = vec![0, 1, 2, 3, 4, 5]; -/// let a = vec_to_array::<4>(&v).unwrap(); -/// assert_eq!(a, [0, 1, 2, 3]); -/// ``` -pub fn vec_to_array(vec: &Vec) -> Result<[u8; S], ser::Error> { - if vec.len() < S { - return Err(ser::Error::CountError); - } - let arr: [u8; S] = vec[0..S].try_into().unwrap(); - Ok(arr) -} - -#[cfg(test)] -mod tests { - use super::*; - use grin_core::ser::{BinReader, BinWriter, DeserializationMode, ProtocolVersion}; - - #[test] - fn test_write_optional() { - // Test with Some value - let mut buf: Vec = vec![]; - let val: Option = Some(10); - write_optional(&mut BinWriter::default(&mut buf), &val).unwrap(); - assert_eq!(buf, &[1, 0, 0, 0, 10]); // 1 for Some, then 10 as a little-endian u32 - - // Test with None value - buf.clear(); - let val: Option = None; - write_optional(&mut BinWriter::default(&mut buf), &val).unwrap(); - assert_eq!(buf, &[0]); // 0 for None - } - - #[test] - fn test_read_optional() { - // Test with Some value - let mut buf: &[u8] = &[1, 0, 0, 0, 10]; // 1 for Some, then 10 as a little-endian u32 - let val: Option = read_optional(&mut BinReader::new( - &mut buf, - ProtocolVersion::local(), - DeserializationMode::default(), - )) - .unwrap(); - assert_eq!(val, Some(10)); - - // Test with None value - buf = &[0]; // 0 for None - let val: Option = read_optional(&mut BinReader::new( - &mut buf, - ProtocolVersion::local(), - DeserializationMode::default(), - )) - .unwrap(); - assert_eq!(val, None); - } - - #[test] - fn test_vec_to_array_success() { - let v = vec![1, 2, 3, 4, 5, 6, 7, 8]; - let a = vec_to_array::<4>(&v).unwrap(); - assert_eq!(a, [1, 2, 3, 4]); - } - - #[test] - fn test_vec_to_array_too_small() { - let v = vec![1, 2, 3]; - let res = vec_to_array::<4>(&v); - assert!(res.is_err()); - } - - #[test] - fn test_vec_to_array_empty() { - let v = vec![]; - let res = vec_to_array::<4>(&v); - assert!(res.is_err()); - } -} diff --git a/libwallet/src/mwmixnet/types.rs b/libwallet/src/mwmixnet/types.rs deleted file mode 100644 index 7e62ed883..000000000 --- a/libwallet/src/mwmixnet/types.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2022 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Types related to mwmixnet requests required by rest of lib crate apis -//! Should rexport all needed types here - -pub use super::onion::crypto::comsig::{self, ComSignature}; -pub use super::onion::crypto::secp::{add_excess, random_secret}; -pub use super::onion::onion::Onion; -pub use super::onion::{new_hop, Hop}; -use crate::grin_util::secp::key::SecretKey; -use serde::{Deserialize, Serialize}; - -/// A Swap request -#[derive(Serialize, Deserialize)] -pub struct SwapReq { - /// Com signature - #[serde(with = "comsig::comsig_serde")] - pub comsig: ComSignature, - /// Onion - pub onion: Onion, -} - -/// MWMixnetRequest Creation Params - -pub struct MixnetReqCreationParams { - /// List of all the server keys - pub server_keys: Vec, - /// Fees per hop - pub fee_per_hop: u32, -} diff --git a/libwallet/src/slate.rs b/libwallet/src/slate.rs index 4c0d9772f..43a7ba906 100644 --- a/libwallet/src/slate.rs +++ b/libwallet/src/slate.rs @@ -28,7 +28,6 @@ use crate::grin_util::secp::key::{PublicKey, SecretKey}; use crate::grin_util::secp::pedersen::Commitment; use crate::grin_util::secp::Signature; use crate::grin_util::{secp, static_secp_instance}; -use chrono::prelude::{DateTime, NaiveDateTime, Utc}; use ed25519_dalek::PublicKey as DalekPublicKey; use ed25519_dalek::Signature as DalekSignature; use serde::ser::{Serialize, Serializer}; @@ -40,37 +39,18 @@ use crate::slate_versions::v4::{ CommitsV4, KernelFeaturesArgsV4, OutputFeaturesV4, ParticipantDataV4, PaymentInfoV4, SlateStateV4, SlateV4, VersionCompatInfoV4, }; -use crate::slate_versions::v5::{ - CommitsV5, KernelFeaturesArgsV5, OutputFeaturesV5, ParticipantDataV5, PaymentInfoV5, - PaymentMemoV5, SlateStateV5, SlateV5, VersionCompatInfoV5, -}; use crate::slate_versions::VersionedSlate; use crate::slate_versions::{CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION}; use crate::Context; -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct PaymentMemo { - // The type of memo - // 0x00 is the absence of any specific memo data - // 0x01 is directly embedded additional payment details - // 0x02 represents the blake2b hash of an arbitrary invoice document - pub memo_type: u8, - // memo data itself - pub memo: [u8; 32], -} - -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone)] pub struct PaymentInfo { /// Sender address - pub sender_address: Option, + pub sender_address: DalekPublicKey, /// Receiver address pub receiver_address: DalekPublicKey, - /// Promise signature - pub promise_signature: Option, - /// Timestamp (seconds) - pub timestamp: DateTime, - /// Memo - pub memo: Option, + /// Receiver signature + pub receiver_signature: Option, } /// Public data for each participant in the slate @@ -246,11 +226,10 @@ impl Slate { /// Upgrade a versioned slate pub fn upgrade(v_slate: VersionedSlate) -> Result { - let internal: Slate = match v_slate { - VersionedSlate::V4(s) => s.into(), - VersionedSlate::V5(s) => s.into(), + let v4: SlateV4 = match v_slate { + VersionedSlate::V4(s) => s, }; - Ok(internal.into()) + Ok(v4.into()) } /// Compact the slate for initial sending, storing the excess + offset explicit /// and removing my input/output data @@ -297,12 +276,8 @@ impl Slate { kernel_features_args: None, } } - /// Removes any signature data that isn't mine, for compacting /// slates for a return journey - // TODO: Check if this is a noop when we have only 2 parties. The first sig appears at - // step2 and removing everything except your sig means you remove nothing. For more than - // 2 parties, we should probably never remove the part_sigs so that everyone can verify them. pub fn remove_other_sigdata( &mut self, keychain: &K, @@ -335,22 +310,13 @@ impl Slate { K: Keychain, B: ProofBuild, { - debug!("slate::add_transaction_elements => start"); self.update_kernel()?; - - debug!("slate::add_transaction_elements => kernel updated"); if elems.is_empty() { - debug!("slate::add_transaction_elements => elems is empty, returning"); return Ok(BlindingFactor::zero()); } - let (tx, blind) = build::partial_transaction(self.tx_or_err()?.clone(), &elems, keychain, builder)?; - - debug!("slate::add_transaction_elements => built partial transaction"); self.tx = Some(tx); - - debug!("slate::add_transaction_elements => slate.tx is set"); Ok(blind) } @@ -411,28 +377,6 @@ impl Slate { Ok(msg) } - /// Matches a participant index on the slate with the stored context - pub fn find_index_matching_context( - &self, - keychain: &K, - context: &Context, - ) -> Result - where - K: Keychain, - { - for i in 0..self.num_participants() as usize { - let calc_pub_excess = PublicKey::from_secret_key(keychain.secp(), &context.sec_key)?; - let calc_pub_nonce = PublicKey::from_secret_key(keychain.secp(), &context.sec_nonce)?; - - // find my entry - if self.participant_data[i].public_blind_excess == calc_pub_excess - || self.participant_data[i].public_nonce == calc_pub_nonce - { - return Ok(i); - } - } - return Err(Error::ContextToIndex); - } /// Completes caller's part of round 2, completing signatures pub fn fill_round_2( &mut self, @@ -742,458 +686,13 @@ impl Serialize for Slate { where S: Serializer, { - let v5 = SlateV5::from(self); - v5.serialize(serializer) + let v4 = SlateV4::from(self); + v4.serialize(serializer) } } // Current slate version to versioned conversions -////// V5 -impl From for SlateV5 { - fn from(slate: Slate) -> SlateV5 { - let Slate { - num_participants: num_parts, - id, - state, - tx: _, - amount, - fee_fields, - kernel_features, - ttl_cutoff_height: ttl, - offset: off, - participant_data, - version_info, - payment_proof, - kernel_features_args, - } = slate.clone(); - let participant_data = map_vec!(participant_data, |data| ParticipantDataV5::from(data)); - let ver = VersionCompatInfoV5::from(&version_info); - let payment_proof = match payment_proof { - Some(p) => Some(PaymentInfoV5::from(&p)), - None => None, - }; - let feat_args = match kernel_features_args { - Some(a) => Some(KernelFeaturesArgsV5::from(&a)), - None => None, - }; - let sta = SlateStateV5::from(&state); - SlateV5 { - num_parts, - id, - sta, - coms: (&slate).into(), - amt: amount, - fee: fee_fields, - feat: kernel_features, - ttl, - off, - sigs: participant_data, - ver, - proof: payment_proof, - feat_args, - } - } -} - -impl From<&Slate> for SlateV5 { - fn from(slate: &Slate) -> SlateV5 { - let Slate { - num_participants: num_parts, - id, - state, - tx: _, - amount, - fee_fields, - kernel_features, - ttl_cutoff_height: ttl, - offset, - participant_data, - version_info, - payment_proof, - kernel_features_args, - } = slate; - let num_parts = *num_parts; - let id = *id; - let amount = *amount; - let fee_fields = *fee_fields; - let feat = *kernel_features; - let ttl = *ttl; - let off = offset.clone(); - let participant_data = map_vec!(participant_data, |data| ParticipantDataV5::from(data)); - let ver = VersionCompatInfoV5::from(version_info); - let payment_proof = match payment_proof { - Some(p) => Some(PaymentInfoV5::from(p)), - None => None, - }; - let sta = SlateStateV5::from(state); - let feat_args = match kernel_features_args { - Some(a) => Some(KernelFeaturesArgsV5::from(a)), - None => None, - }; - SlateV5 { - num_parts, - id, - sta, - coms: slate.into(), - amt: amount, - fee: fee_fields, - feat, - ttl, - off, - sigs: participant_data, - ver, - proof: payment_proof, - feat_args, - } - } -} - -impl From<&Slate> for Option> { - fn from(slate: &Slate) -> Self { - match slate.tx { - None => None, - Some(ref tx) => { - let mut ret_vec = vec![]; - match tx.inputs() { - Inputs::CommitOnly(_) => panic!("commit only inputs unsupported"), - Inputs::FeaturesAndCommit(ref inputs) => { - for input in inputs { - ret_vec.push(input.into()); - } - } - } - for output in tx.outputs() { - ret_vec.push(output.into()); - } - Some(ret_vec) - } - } - } -} - -impl From<&ParticipantData> for ParticipantDataV5 { - fn from(data: &ParticipantData) -> ParticipantDataV5 { - let ParticipantData { - public_blind_excess, - public_nonce, - part_sig, - } = data; - let public_blind_excess = *public_blind_excess; - let public_nonce = *public_nonce; - let part_sig = *part_sig; - ParticipantDataV5 { - xs: public_blind_excess, - nonce: public_nonce, - part: part_sig, - } - } -} - -impl From<&SlateState> for SlateStateV5 { - fn from(data: &SlateState) -> SlateStateV5 { - match data { - SlateState::Unknown => SlateStateV5::Unknown, - SlateState::Standard1 => SlateStateV5::Standard1, - SlateState::Standard2 => SlateStateV5::Standard2, - SlateState::Standard3 => SlateStateV5::Standard3, - SlateState::Invoice1 => SlateStateV5::Invoice1, - SlateState::Invoice2 => SlateStateV5::Invoice2, - SlateState::Invoice3 => SlateStateV5::Invoice3, - } - } -} - -impl From<&KernelFeaturesArgs> for KernelFeaturesArgsV5 { - fn from(data: &KernelFeaturesArgs) -> KernelFeaturesArgsV5 { - let KernelFeaturesArgs { lock_height } = data; - let lock_hgt = *lock_height; - KernelFeaturesArgsV5 { lock_hgt } - } -} - -impl From<&VersionCompatInfo> for VersionCompatInfoV5 { - fn from(data: &VersionCompatInfo) -> VersionCompatInfoV5 { - let VersionCompatInfo { - version, - block_header_version, - } = data; - let version = *version; - let block_header_version = *block_header_version; - VersionCompatInfoV5 { - version, - block_header_version, - } - } -} - -impl From<&PaymentInfo> for PaymentInfoV5 { - fn from(data: &PaymentInfo) -> PaymentInfoV5 { - let PaymentInfo { - sender_address, - receiver_address, - promise_signature, - timestamp, - memo, - } = data; - let sender_address = *sender_address; - // TODO: If not provided and we need to downgrade to V5, - // Provide a blank key insted. Consider whether this should fail - // instead, noting that `try_from`isn't currently used in any versioning - // logic - // Also note the zeroized ed25519 public key has a known private key, check if - // this could ever possibly become an issue - let saddr = match sender_address { - Some(a) => a, - None => DalekPublicKey::from_bytes(&[0u8; 32]).unwrap(), - }; - let receiver_address = *receiver_address; - let promise_signature = *promise_signature; - let timestamp = *timestamp; - let memo = match memo { - Some(m) => Some(PaymentMemoV5 { - memo_type: m.memo_type, - memo: m.memo, - }), - None => None, - }; - PaymentInfoV5 { - saddr, - raddr: receiver_address, - psig: promise_signature, - ts: timestamp, - memo: memo, - } - } -} - -impl From for OutputFeaturesV5 { - fn from(of: OutputFeatures) -> OutputFeaturesV5 { - let index = match of { - OutputFeatures::Plain => 0, - OutputFeatures::Coinbase => 1, - }; - OutputFeaturesV5(index) - } -} - -///// V5 -impl From for Slate { - fn from(slate: SlateV5) -> Slate { - let SlateV5 { - num_parts: num_participants, - id, - sta, - coms: _, - amt: amount, - fee: fee_fields, - feat: kernel_features, - ttl: ttl_cutoff_height, - off: offset, - sigs: participant_data, - ver, - proof: payment_proof, - feat_args, - } = slate.clone(); - let participant_data = map_vec!(participant_data, |data| ParticipantData::from(data)); - let version_info = VersionCompatInfo::from(&ver); - let payment_proof = match &payment_proof { - Some(p) => Some(PaymentInfo::from(p)), - None => None, - }; - let kernel_features_args = match &feat_args { - Some(a) => Some(KernelFeaturesArgs::from(a)), - None => None, - }; - let state = SlateState::from(&sta); - Slate { - num_participants, - id, - state, - tx: (&slate).into(), - amount, - fee_fields, - kernel_features, - ttl_cutoff_height, - offset, - participant_data, - version_info, - payment_proof, - kernel_features_args, - } - } -} - -pub fn tx_from_slate_v5(slate: &SlateV5) -> Option { - let coms = match slate.coms.as_ref() { - Some(c) => c, - None => return None, - }; - let secp = static_secp_instance(); - let secp = secp.lock(); - let mut calc_slate = Slate::blank(2, false); - calc_slate.fee_fields = slate.fee; - for d in slate.sigs.iter() { - calc_slate.participant_data.push(ParticipantData { - public_blind_excess: d.xs, - public_nonce: d.nonce, - part_sig: d.part, - }); - } - let excess = match calc_slate.calc_excess(&secp) { - Ok(e) => e, - Err(_) => Commitment::from_vec(vec![0]), - }; - let excess_sig = match calc_slate.finalize_signature(&secp) { - Ok(s) => s, - Err(_) => Signature::from_raw_data(&[0; 64]).unwrap(), - }; - let kernel = TxKernel { - features: match slate.feat { - 0 => KernelFeatures::Plain { fee: slate.fee }, - 1 => KernelFeatures::HeightLocked { - fee: slate.fee, - lock_height: match slate.feat_args.as_ref() { - Some(a) => a.lock_hgt, - None => 0, - }, - }, - _ => KernelFeatures::Plain { fee: slate.fee }, - }, - excess, - excess_sig, - }; - let mut tx = Slate::empty_transaction().with_kernel(kernel); - - let mut outputs = vec![]; - let mut inputs = vec![]; - - for c in coms.iter() { - match &c.p { - Some(p) => { - outputs.push(Output::new(c.f.into(), c.c, p.clone())); - } - None => { - inputs.push(Input { - features: c.f.into(), - commit: c.c, - }); - } - } - } - - tx.body = tx - .body - .replace_inputs(inputs.as_slice().into()) - .replace_outputs(outputs.as_slice()); - tx.offset = slate.off.clone(); - Some(tx) -} - -// Node's Transaction object and lock height to SlateV5 `coms` -impl From<&SlateV5> for Option { - fn from(slate: &SlateV5) -> Option { - tx_from_slate_v5(slate) - } -} - -impl From<&ParticipantDataV5> for ParticipantData { - fn from(data: &ParticipantDataV5) -> ParticipantData { - let ParticipantDataV5 { - xs: public_blind_excess, - nonce: public_nonce, - part: part_sig, - } = data; - let public_blind_excess = *public_blind_excess; - let public_nonce = *public_nonce; - let part_sig = *part_sig; - ParticipantData { - public_blind_excess, - public_nonce, - part_sig, - } - } -} - -impl From<&KernelFeaturesArgsV5> for KernelFeaturesArgs { - fn from(data: &KernelFeaturesArgsV5) -> KernelFeaturesArgs { - let KernelFeaturesArgsV5 { lock_hgt } = data; - let lock_height = *lock_hgt; - KernelFeaturesArgs { lock_height } - } -} - -impl From<&SlateStateV5> for SlateState { - fn from(data: &SlateStateV5) -> SlateState { - match data { - SlateStateV5::Unknown => SlateState::Unknown, - SlateStateV5::Standard1 => SlateState::Standard1, - SlateStateV5::Standard2 => SlateState::Standard2, - SlateStateV5::Standard3 => SlateState::Standard3, - SlateStateV5::Invoice1 => SlateState::Invoice1, - SlateStateV5::Invoice2 => SlateState::Invoice2, - SlateStateV5::Invoice3 => SlateState::Invoice3, - } - } -} - -impl From<&VersionCompatInfoV5> for VersionCompatInfo { - fn from(data: &VersionCompatInfoV5) -> VersionCompatInfo { - let VersionCompatInfoV5 { - version, - block_header_version, - } = data; - let version = *version; - let block_header_version = *block_header_version; - VersionCompatInfo { - version, - block_header_version, - } - } -} - -impl From<&PaymentInfoV5> for PaymentInfo { - fn from(data: &PaymentInfoV5) -> PaymentInfo { - let PaymentInfoV5 { - saddr: sender_address, - raddr: receiver_address, - psig: promise_signature, - ts: timestamp, - memo, - } = data; - let sender_address = *sender_address; - let receiver_address = *receiver_address; - let promise_signature = *promise_signature; - let timestamp = *timestamp; - let memo: Option = match memo { - Some(m) => { - //memo_ret.copy_from_slice(&grin_util::from_hex(m.memo).unwrap_or_default()[0..32]); - Some(PaymentMemo { - memo_type: m.memo_type, - memo: m.memo, - }) - } - None => None, - }; - PaymentInfo { - sender_address: Some(sender_address), - receiver_address, - promise_signature: promise_signature, - timestamp, - memo, - } - } -} - -impl From for OutputFeatures { - fn from(of: OutputFeaturesV5) -> OutputFeatures { - match of.0 { - 1 => OutputFeatures::Coinbase, - 0 | _ => OutputFeatures::Plain, - } - } -} - -///////// V4 +// Slate to versioned impl From for SlateV4 { fn from(slate: Slate) -> SlateV4 { let Slate { @@ -1376,25 +875,13 @@ impl From<&PaymentInfo> for PaymentInfoV4 { let PaymentInfo { sender_address, receiver_address, - promise_signature: receiver_signature, - timestamp: _, - memo: _, + receiver_signature, } = data; let sender_address = *sender_address; - // TODO: If not provided and we need to downgrade to V4, - // Provide a blank key insted. Consider whether this should fail - // instead, noting that `try_from`isn't currently used in any versioning - // logic - // Also note the zeroized ed25519 public key has a known private key, check if - // this could ever possibly become an issue - let saddr = match sender_address { - Some(a) => a, - None => DalekPublicKey::from_bytes(&[0u8; 32]).unwrap(), - }; let receiver_address = *receiver_address; let receiver_signature = *receiver_signature; PaymentInfoV4 { - saddr, + saddr: sender_address, raddr: receiver_address, rsig: receiver_signature, } @@ -1597,14 +1084,9 @@ impl From<&PaymentInfoV4> for PaymentInfo { let receiver_address = *receiver_address; let receiver_signature = *receiver_signature; PaymentInfo { - sender_address: Some(sender_address), + sender_address, receiver_address, - promise_signature: receiver_signature, - timestamp: DateTime::::from_utc( - NaiveDateTime::from_timestamp_opt(0, 0).unwrap(), - Utc, - ), - memo: None, + receiver_signature, } } } diff --git a/libwallet/src/slate_versions/mod.rs b/libwallet/src/slate_versions/mod.rs index acb2f92c0..ca2e749f8 100644 --- a/libwallet/src/slate_versions/mod.rs +++ b/libwallet/src/slate_versions/mod.rs @@ -20,8 +20,6 @@ use crate::slate::Slate; use crate::slate_versions::v4::{CoinbaseV4, SlateV4}; use crate::slate_versions::v4_bin::SlateV4Bin; -use crate::slate_versions::v5::{CoinbaseV5, SlateV5}; -use crate::slate_versions::v5_bin::SlateV5Bin; use crate::types::CbData; use crate::Error; use std::convert::TryFrom; @@ -32,13 +30,9 @@ pub mod ser; pub mod v4; #[allow(missing_docs)] pub mod v4_bin; -#[allow(missing_docs)] -pub mod v5; -#[allow(missing_docs)] -pub mod v5_bin; /// The most recent version of the slate -pub const CURRENT_SLATE_VERSION: u16 = 5; +pub const CURRENT_SLATE_VERSION: u16 = 4; /// The grin block header this slate is intended to be compatible with pub const GRIN_BLOCK_HEADER_VERSION: u16 = 3; @@ -46,9 +40,7 @@ pub const GRIN_BLOCK_HEADER_VERSION: u16 = 3; /// Existing versions of the slate #[derive(EnumIter, Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] pub enum SlateVersion { - /// V5 (Most Current) - V5, - /// V4 + /// V4 (most current) V4, } @@ -57,9 +49,7 @@ pub enum SlateVersion { /// Versions are ordered newest to oldest so serde attempts to /// deserialize newer versions first, then falls back to older versions. pub enum VersionedSlate { - /// Current (5.0.0 Onwards?) - V5(SlateV5), - /// Current (4.0.0) + /// Current (4.0.0 Onwards ) V4(SlateV4), } @@ -67,7 +57,6 @@ impl VersionedSlate { /// Return slate version pub fn version(&self) -> SlateVersion { match *self { - VersionedSlate::V5(_) => SlateVersion::V4, VersionedSlate::V4(_) => SlateVersion::V4, } } @@ -75,7 +64,6 @@ impl VersionedSlate { /// convert this slate type to a specified older version pub fn into_version(slate: Slate, version: SlateVersion) -> Result { match version { - SlateVersion::V5 => Ok(VersionedSlate::V5(slate.into())), SlateVersion::V4 => Ok(VersionedSlate::V4(slate.into())), } } @@ -84,7 +72,6 @@ impl VersionedSlate { impl From for Slate { fn from(slate: VersionedSlate) -> Slate { match slate { - VersionedSlate::V5(s) => Slate::from(s), VersionedSlate::V4(s) => Slate::from(s), } } @@ -94,11 +81,7 @@ impl From for Slate { #[serde(untagged)] /// Binary versions, can only be parsed 1:1 into the appropriate /// version, and VersionedSlate can up/downgrade from there -/// NB (IMPORTANT): Ensure the slates are listed in reverse chronological -/// order (latest first) pub enum VersionedBinSlate { - /// Version 5, binary - V5(SlateV5Bin), /// Version 4, binary V4(SlateV4Bin), } @@ -107,7 +90,6 @@ impl TryFrom for VersionedBinSlate { type Error = Error; fn try_from(slate: VersionedSlate) -> Result { match slate { - VersionedSlate::V5(s) => Ok(VersionedBinSlate::V5(SlateV5Bin(s))), VersionedSlate::V4(s) => Ok(VersionedBinSlate::V4(SlateV4Bin(s))), } } @@ -116,7 +98,6 @@ impl TryFrom for VersionedBinSlate { impl From for VersionedSlate { fn from(slate: VersionedBinSlate) -> VersionedSlate { match slate { - VersionedBinSlate::V5(s) => VersionedSlate::V5(s.0), VersionedBinSlate::V4(s) => VersionedSlate::V4(s.0), } } @@ -128,8 +109,6 @@ impl From for VersionedSlate { /// deserialize newer versions first, then falls back to older versions. pub enum VersionedCoinbase { /// Current supported coinbase version. - V5(CoinbaseV5), - /// Previous version (no difference) V4(CoinbaseV4), } @@ -137,196 +116,7 @@ impl VersionedCoinbase { /// convert this coinbase data to a specific versioned representation for the json api. pub fn into_version(cb: CbData, version: SlateVersion) -> VersionedCoinbase { match version { - SlateVersion::V5 => VersionedCoinbase::V5(cb.into()), SlateVersion::V4 => VersionedCoinbase::V4(cb.into()), } } } -#[cfg(test)] -pub mod tests { - use crate::grin_core::core::transaction::OutputFeatures; - use crate::grin_util::from_hex; - use crate::grin_util::secp::key::PublicKey; - use crate::grin_util::secp::pedersen::{Commitment, RangeProof}; - use crate::grin_util::secp::Signature; - use crate::slate::{KernelFeaturesArgs, ParticipantData, PaymentInfo, PaymentMemo}; - use crate::slate_versions::v5::{CommitsV5, SlateV5}; - use crate::{ - slate, Error, Slate, Slatepacker, SlatepackerArgs, VersionedBinSlate, VersionedSlate, - }; - use chrono::{DateTime, NaiveDateTime, Utc}; - use ed25519_dalek::PublicKey as DalekPublicKey; - use ed25519_dalek::Signature as DalekSignature; - use grin_core::global::{set_local_chain_type, ChainTypes}; - use grin_keychain::{ExtKeychain, Keychain, SwitchCommitmentType}; - use std::convert::TryInto; - - // Populate a test internal slate with all fields to test conversions - pub fn populate_test_slate() -> Result { - let keychain = ExtKeychain::from_random_seed(true).unwrap(); - let switch = SwitchCommitmentType::Regular; - - let mut slate_internal = Slate::blank(2, false); - let id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let id2 = ExtKeychain::derive_key_id(1, 1, 1, 0, 0); - let skey1 = keychain.derive_key(0, &id1, switch).unwrap(); - let skey2 = keychain.derive_key(0, &id2, switch).unwrap(); - let xs = PublicKey::from_secret_key(keychain.secp(), &skey1).unwrap(); - let nonce = PublicKey::from_secret_key(keychain.secp(), &skey2).unwrap(); - - let part = ParticipantData { - public_blind_excess: xs, - public_nonce: nonce, - part_sig: None, - }; - let part2 = ParticipantData { - public_blind_excess: xs, - public_nonce: nonce, - part_sig: Some(Signature::from_raw_data(&[11; 64]).unwrap()), - }; - slate_internal.participant_data.push(part.clone()); - slate_internal.participant_data.push(part2); - slate_internal.participant_data.push(part); - - // Another temp slate to convert commit data into internal 'transaction' like data - // add some random commit data - let slate_tmp = Slate::blank(1, false); - let mut v5 = SlateV5::from(slate_tmp); - - let com1 = CommitsV5 { - f: OutputFeatures::Plain.into(), - c: Commitment::from_vec([3u8; 1].to_vec()), - p: None, - }; - let com2 = CommitsV5 { - f: OutputFeatures::Plain.into(), - c: Commitment::from_vec([4u8; 1].to_vec()), - p: Some(RangeProof::zero()), - }; - - let mut coms = vec![]; - coms.push(com1.clone()); - coms.push(com1.clone()); - coms.push(com1.clone()); - coms.push(com2); - - v5.coms = Some(coms); - - slate_internal.tx = slate::tx_from_slate_v5(&v5); - - // basic fields - slate_internal.amount = 23820323; - slate_internal.kernel_features = 1; - slate_internal.num_participants = 2; - slate_internal.kernel_features_args = Some(KernelFeaturesArgs { - lock_height: 2323223, - }); - - // current style payment proof - let raw_pubkey_str = "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb"; - let b = from_hex(raw_pubkey_str).unwrap(); - let d_pkey = DalekPublicKey::from_bytes(&b).unwrap(); - // Need to remove milliseconds component for comparison. Won't be serialized - let ts = NaiveDateTime::from_timestamp_opt(Utc::now().timestamp(), 0).unwrap(); - let ts = DateTime::::from_utc(ts, Utc); - let pm = PaymentMemo { - memo_type: 1, - memo: [9; 32], - }; - - let psig = DalekSignature::from_bytes(&[0u8; 64]).unwrap(); - slate_internal.payment_proof = Some(PaymentInfo { - sender_address: Some(d_pkey.clone()), - receiver_address: d_pkey.clone(), - timestamp: ts.clone(), - promise_signature: Some(psig), - memo: Some(pm), - }); - - Ok(slate_internal) - } - - #[test] - fn ser_deser_current_slate() -> Result<(), Error> { - let slate_internal = populate_test_slate()?; - // Serialize slate into slatepack - let slatepacker_args = SlatepackerArgs { - sender: None, - recipients: vec![], - dec_key: None, - }; - - let slate_packer = Slatepacker::new(slatepacker_args); - let slate_packed = slate_packer.create_slatepack(&slate_internal).unwrap(); - - let slate_unpacked = slate_packer.get_slate(&slate_packed).unwrap(); - - // Just verifying payment proof for now, extend later to cover EQ for full slate if needs - // be - assert_eq!(slate_internal.payment_proof, slate_unpacked.payment_proof); - Ok(()) - } - - #[test] - fn slatepack_version_v4_v5() -> Result<(), Error> { - set_local_chain_type(ChainTypes::Mainnet); - - // Convert V5 slate into V4 slate, check result - let slate_internal = populate_test_slate()?; - let v5 = VersionedSlate::V5(slate_internal.clone().into()); - let v4 = VersionedSlate::V4(slate_internal.into()); - - let v5_converted: Slate = v5.into(); - let v4_converted: Slate = v4.into(); - - assert!(v5_converted.payment_proof.as_ref().unwrap().memo.is_some()); - - // Converted from v4 will not have memos and ts will be zeroed out - assert!(v4_converted.payment_proof.as_ref().unwrap().memo.is_none()); - assert_eq!( - v4_converted - .payment_proof - .as_ref() - .unwrap() - .timestamp - .timestamp(), - 0 - ); - - Ok(()) - } - - #[test] - fn slatepack_version_v4_v5_bin() -> Result<(), Error> { - set_local_chain_type(ChainTypes::Mainnet); - - // Convert V5 slate into V4 slate, check result - let slate_internal = populate_test_slate()?; - let v5 = VersionedSlate::V5(slate_internal.clone().into()); - let v5_bin: VersionedBinSlate = v5.try_into().unwrap(); - - let v4 = VersionedSlate::V4(slate_internal.into()); - let v4_bin: VersionedBinSlate = v4.try_into().unwrap(); - - let v5_versioned: VersionedSlate = v5_bin.into(); - let v4_versioned: VersionedSlate = v4_bin.into(); - - let v5_converted: Slate = v5_versioned.into(); - let v4_converted: Slate = v4_versioned.into(); - - assert!(v5_converted.payment_proof.as_ref().unwrap().memo.is_some()); - // Converted from v4 will not have memos and ts will be zeroed out - assert!(v4_converted.payment_proof.as_ref().unwrap().memo.is_none()); - assert_eq!( - v4_converted - .payment_proof - .as_ref() - .unwrap() - .timestamp - .timestamp(), - 0 - ); - - Ok(()) - } -} diff --git a/libwallet/src/slate_versions/ser.rs b/libwallet/src/slate_versions/ser.rs index 28c67d009..58f866a08 100644 --- a/libwallet/src/slate_versions/ser.rs +++ b/libwallet/src/slate_versions/ser.rs @@ -472,7 +472,7 @@ pub mod option_dalek_sig_base64 { } } -/// Serializes slates 'version_info' field - V4 +/// Serializes slates 'version_info' field pub mod version_info_v4 { use serde::de::Error; use serde::{Deserialize, Deserializer, Serializer}; @@ -514,49 +514,7 @@ pub mod version_info_v4 { } } -/// Serializes slates 'version_info' field - V5 -pub mod version_info_v5 { - use serde::de::Error; - use serde::{Deserialize, Deserializer, Serializer}; - - use crate::slate_versions::v5::VersionCompatInfoV5; - - /// - pub fn serialize(v: &VersionCompatInfoV5, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&format!("{}:{}", v.version, v.block_header_version)) - } - - /// - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - String::deserialize(deserializer).and_then(|s| { - let mut retval = VersionCompatInfoV5 { - version: 0, - block_header_version: 0, - }; - let v: Vec<&str> = s.split(':').collect(); - if v.len() != 2 { - return Err(Error::custom("Cannot parse version")); - } - match u16::from_str_radix(v[0], 10) { - Ok(u) => retval.version = u, - Err(e) => return Err(Error::custom(format!("Cannot parse version: {}", e))), - } - match u16::from_str_radix(v[1], 10) { - Ok(u) => retval.block_header_version = u, - Err(e) => return Err(Error::custom(format!("Cannot parse version: {}", e))), - } - Ok(retval) - }) - } -} - -/// Serializes slates 'state' field - V4 +/// Serializes slates 'state' field pub mod slate_state_v4 { use serde::de::Error; use serde::{Deserialize, Deserializer, Serializer}; @@ -601,51 +559,6 @@ pub mod slate_state_v4 { } } -/// Serializes slates 'state' field - V5 -pub mod slate_state_v5 { - use serde::de::Error; - use serde::{Deserialize, Deserializer, Serializer}; - - use crate::slate_versions::v5::SlateStateV5; - - /// - pub fn serialize(st: &SlateStateV5, serializer: S) -> Result - where - S: Serializer, - { - let label = match st { - SlateStateV5::Unknown => "NA", - SlateStateV5::Standard1 => "S1", - SlateStateV5::Standard2 => "S2", - SlateStateV5::Standard3 => "S3", - SlateStateV5::Invoice1 => "I1", - SlateStateV5::Invoice2 => "I2", - SlateStateV5::Invoice3 => "I3", - }; - serializer.serialize_str(label) - } - - /// - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - String::deserialize(deserializer).and_then(|s| { - let retval = match s.as_str() { - "NA" => SlateStateV5::Unknown, - "S1" => SlateStateV5::Standard1, - "S2" => SlateStateV5::Standard2, - "S3" => SlateStateV5::Standard3, - "I1" => SlateStateV5::Invoice1, - "I2" => SlateStateV5::Invoice2, - "I3" => SlateStateV5::Invoice3, - _ => return Err(Error::custom("Invalid Slate state")), - }; - Ok(retval) - }) - } -} - /// Serializes an secp256k1 pubkey to base64 pub mod uuid_base64 { use base64; diff --git a/libwallet/src/slate_versions/v4_bin.rs b/libwallet/src/slate_versions/v4_bin.rs index e7a8fc018..68f5190b2 100644 --- a/libwallet/src/slate_versions/v4_bin.rs +++ b/libwallet/src/slate_versions/v4_bin.rs @@ -64,7 +64,7 @@ impl Readable for SlateStateV4 { } /// Allow serializing of Uuids not defined in crate -pub struct UuidWrap(pub Uuid); +struct UuidWrap(Uuid); impl Writeable for UuidWrap { fn write(&self, writer: &mut W) -> Result<(), grin_ser::Error> { @@ -82,7 +82,7 @@ impl Readable for UuidWrap { } /// Helper struct to serialize optional fields efficiently -pub struct SlateOptFields { +struct SlateOptFields { /// num parts, default 2 pub num_parts: u8, /// amt, default 0 diff --git a/libwallet/src/slate_versions/v5.rs b/libwallet/src/slate_versions/v5.rs deleted file mode 100644 index 5bb403971..000000000 --- a/libwallet/src/slate_versions/v5.rs +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright 2023 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Contains V5 of the slate (version as yet undetermined) -//! -//! TODO: Should be considered experimental and remain in an experimental branch -//! until such time as the relevant RFCs are accepted -//! -//! Changes from V4: -//! #### Top-Level Slate Struct -//! * -//! #### PaymentInfoV5 -//! * `saddr`, i.e. `sender_address` in main Slate becomes optional -//! * `rsig` is renamed to `psig`, corresponding to rename of `receiver_signature` to `promise_signature` in main Slate -//! -//! * `ts` added (`timestamp` in main slate). Serialized as i64 representing epoch time in seconds -//! * `memo` added as optional MemoV5 Struct, which contains: -//! * `memo_type`: u8 -//! * 0x00 = payment details directly embedded -//! * 0x01 = Blake2b hash of an arbitrary invoice document -//! * `memo`: [u8;32] the memo data itself - -use crate::grin_core::core::FeeFields; -use crate::grin_core::core::{Input, Output, TxKernel}; -use crate::grin_core::libtx::secp_ser; -use crate::grin_keychain::{BlindingFactor, Identifier}; -use crate::grin_util::secp; -use crate::grin_util::secp::key::PublicKey; -use crate::grin_util::secp::pedersen::{Commitment, RangeProof}; -use crate::grin_util::secp::Signature; -use crate::{slate_versions::ser, CbData}; -use chrono::prelude::{DateTime, Utc}; -use ed25519_dalek::PublicKey as DalekPublicKey; -use ed25519_dalek::Signature as DalekSignature; -use serde_with::TimestampSeconds; -use uuid::Uuid; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct SlateV5 { - // Required Fields - /// Versioning info - #[serde(with = "ser::version_info_v5")] - pub ver: VersionCompatInfoV5, - /// Unique transaction ID, selected by sender - pub id: Uuid, - /// Slate state - #[serde(with = "ser::slate_state_v5")] - pub sta: SlateStateV5, - /// Offset, modified by each participant inserting inputs - /// as the transaction progresses - #[serde( - serialize_with = "secp_ser::as_hex", - deserialize_with = "secp_ser::blind_from_hex" - )] - #[serde(default = "default_offset_zero")] - #[serde(skip_serializing_if = "offset_is_zero")] - pub off: BlindingFactor, - // Optional fields depending on state - /// The number of participants intended to take part in this transaction - #[serde(default = "default_num_participants_2")] - #[serde(skip_serializing_if = "num_parts_is_2")] - pub num_parts: u8, - /// base amount (excluding fee) - #[serde(with = "secp_ser::string_or_u64")] - #[serde(skip_serializing_if = "u64_is_blank")] - #[serde(default = "default_u64_0")] - pub amt: u64, - /// fee - #[serde(skip_serializing_if = "fee_is_zero")] - #[serde(default = "default_fee")] - pub fee: FeeFields, - /// kernel features, if any - #[serde(skip_serializing_if = "u8_is_blank")] - #[serde(default = "default_u8_0")] - pub feat: u8, - /// TTL, the block height at which wallets - /// should refuse to process the transaction and unlock all - #[serde(with = "secp_ser::string_or_u64")] - #[serde(skip_serializing_if = "u64_is_blank")] - #[serde(default = "default_u64_0")] - pub ttl: u64, - // Structs always required - /// Participant data, each participant in the transaction will - /// insert their public data here. For now, 0 is sender and 1 - /// is receiver, though this will change for multi-party - pub sigs: Vec, - // Situational, but required at some point in the tx - /// Inputs/Output commits added to slate - #[serde(default = "default_coms_none")] - #[serde(skip_serializing_if = "Option::is_none")] - pub coms: Option>, - // Optional Structs - /// Payment Proof - #[serde(default = "default_payment_none")] - #[serde(skip_serializing_if = "Option::is_none")] - pub proof: Option, - /// Kernel features arguments - #[serde(default = "default_kernel_features_none")] - #[serde(skip_serializing_if = "Option::is_none")] - pub feat_args: Option, -} - -fn default_payment_none() -> Option { - None -} - -fn default_offset_zero() -> BlindingFactor { - BlindingFactor::zero() -} - -fn offset_is_zero(o: &BlindingFactor) -> bool { - *o == BlindingFactor::zero() -} - -fn default_coms_none() -> Option> { - None -} - -fn default_u64_0() -> u64 { - 0 -} - -fn num_parts_is_2(n: &u8) -> bool { - *n == 2 -} - -fn default_num_participants_2() -> u8 { - 2 -} - -fn default_kernel_features_none() -> Option { - None -} - -/// Slate state definition -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum SlateStateV5 { - /// Unknown, coming from earlier versions of the slate - Unknown, - /// Standard flow, freshly init - Standard1, - /// Standard flow, return journey - Standard2, - /// Standard flow, ready for transaction posting - Standard3, - /// Invoice flow, freshly init - Invoice1, - ///Invoice flow, return journey - Invoice2, - /// Invoice flow, ready for tranasction posting - Invoice3, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -/// Kernel features arguments definition -pub struct KernelFeaturesArgsV5 { - /// Lock height, for HeightLocked - pub lock_hgt: u64, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct VersionCompatInfoV5 { - /// The current version of the slate format - pub version: u16, - /// Version of grin block header this slate is compatible with - pub block_header_version: u16, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct ParticipantDataV5 { - /// Public key corresponding to private blinding factor - #[serde(with = "secp_ser::pubkey_serde")] - pub xs: PublicKey, - /// Public key corresponding to private nonce - #[serde(with = "secp_ser::pubkey_serde")] - pub nonce: PublicKey, - /// Public partial signature - #[serde(default = "default_part_sig_none")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(with = "secp_ser::option_sig_serde")] - pub part: Option, -} - -fn default_part_sig_none() -> Option { - None -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct PaymentMemoV5 { - // The type of memo - // 0x00 is directly embedded additional payment details - // 0x01 represents the blake2b hash of an arbitrary invoice document - pub memo_type: u8, - // memo data itself - pub memo: [u8; 32], -} - -#[serde_as] -#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] -pub struct PaymentInfoV5 { - #[serde(with = "ser::dalek_pubkey_serde")] - pub saddr: DalekPublicKey, - #[serde(with = "ser::dalek_pubkey_serde")] - pub raddr: DalekPublicKey, - #[serde_as(as = "TimestampSeconds")] - pub ts: DateTime, - #[serde(default = "default_promise_signature_none")] - #[serde(with = "ser::option_dalek_sig_serde")] - #[serde(skip_serializing_if = "Option::is_none")] - pub psig: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, -} - -fn default_promise_signature_none() -> Option { - None -} - -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -pub struct CommitsV5 { - /// Options for an output's structure or use - #[serde(default = "default_output_feature")] - #[serde(skip_serializing_if = "output_feature_is_plain")] - pub f: OutputFeaturesV5, - /// The homomorphic commitment representing the output amount - #[serde( - serialize_with = "secp_ser::as_hex", - deserialize_with = "secp_ser::commitment_from_hex" - )] - pub c: Commitment, - /// A proof that the commitment is in the right range - /// Only applies for transaction outputs - #[serde(with = "ser::option_rangeproof_hex")] - #[serde(default = "default_range_proof")] - #[serde(skip_serializing_if = "Option::is_none")] - pub p: Option, -} - -impl From<&Output> for CommitsV5 { - fn from(out: &Output) -> CommitsV5 { - CommitsV5 { - f: out.features().into(), - c: out.commitment(), - p: Some(out.proof()), - } - } -} - -// This will need to be reworked once we no longer support input features with "commit only" inputs. -impl From<&Input> for CommitsV5 { - fn from(input: &Input) -> CommitsV5 { - CommitsV5 { - f: input.features.into(), - c: input.commitment(), - p: None, - } - } -} - -fn default_output_feature() -> OutputFeaturesV5 { - OutputFeaturesV5(0) -} - -fn output_feature_is_plain(o: &OutputFeaturesV5) -> bool { - o.0 == 0 -} - -#[derive(Serialize, Deserialize, Copy, Debug, Clone, PartialEq, Eq)] -pub struct OutputFeaturesV5(pub u8); - -pub fn sig_is_blank(s: &secp::Signature) -> bool { - for b in s.to_raw_data().iter() { - if *b != 0 { - return false; - } - } - true -} - -fn default_range_proof() -> Option { - None -} - -fn u64_is_blank(u: &u64) -> bool { - *u == 0 -} - -fn default_u8_0() -> u8 { - 0 -} - -fn u8_is_blank(u: &u8) -> bool { - *u == 0 -} - -fn fee_is_zero(f: &FeeFields) -> bool { - f.is_zero() -} - -fn default_fee() -> FeeFields { - FeeFields::zero() -} - -/// A mining node requests new coinbase via the foreign api every time a new candidate block is built. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct CoinbaseV5 { - /// Output - output: CbOutputV5, - /// Kernel - kernel: CbKernelV5, - /// Key Id - key_id: Option, -} - -impl From for CoinbaseV5 { - fn from(cb: CbData) -> CoinbaseV5 { - CoinbaseV5 { - output: CbOutputV5::from(&cb.output), - kernel: CbKernelV5::from(&cb.kernel), - key_id: cb.key_id, - } - } -} - -impl From<&Output> for CbOutputV5 { - fn from(output: &Output) -> CbOutputV5 { - CbOutputV5 { - features: CbOutputFeatures::Coinbase, - commit: output.commitment(), - proof: output.proof(), - } - } -} - -impl From<&TxKernel> for CbKernelV5 { - fn from(kernel: &TxKernel) -> CbKernelV5 { - CbKernelV5 { - features: CbKernelFeatures::Coinbase, - excess: kernel.excess, - excess_sig: kernel.excess_sig, - } - } -} - -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -enum CbOutputFeatures { - Coinbase, -} - -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -enum CbKernelFeatures { - Coinbase, -} - -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -struct CbOutputV5 { - features: CbOutputFeatures, - #[serde(serialize_with = "secp_ser::as_hex")] - commit: Commitment, - #[serde(serialize_with = "secp_ser::as_hex")] - proof: RangeProof, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct CbKernelV5 { - features: CbKernelFeatures, - #[serde(serialize_with = "secp_ser::as_hex")] - excess: Commitment, - #[serde(with = "secp_ser::sig_serde")] - excess_sig: secp::Signature, -} diff --git a/libwallet/src/slate_versions/v5_bin.rs b/libwallet/src/slate_versions/v5_bin.rs deleted file mode 100644 index d03302975..000000000 --- a/libwallet/src/slate_versions/v5_bin.rs +++ /dev/null @@ -1,524 +0,0 @@ -// Copyright 2023 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Wraps a V5 Slate into a V5 Binary slate - -use crate::grin_core::core::transaction::OutputFeatures; -use crate::grin_core::ser as grin_ser; -use crate::grin_core::ser::{Readable, Reader, Writeable, Writer}; -use crate::grin_keychain::BlindingFactor; -use crate::grin_util::secp::key::PublicKey; -use crate::grin_util::secp::pedersen::{Commitment, RangeProof}; -use crate::grin_util::secp::Signature; -use chrono::{DateTime, NaiveDateTime, Utc}; -use ed25519_dalek::PublicKey as DalekPublicKey; -use ed25519_dalek::Signature as DalekSignature; -use std::convert::{TryFrom, TryInto}; -use std::f64::consts::E; - -use crate::slate_versions::v5::{ - CommitsV5, KernelFeaturesArgsV5, ParticipantDataV5, PaymentInfoV5, PaymentMemoV5, SlateStateV5, - SlateV5, VersionCompatInfoV5, -}; - -use crate::slate_versions::v4_bin::{SlateOptFields, UuidWrap}; - -impl Writeable for SlateStateV5 { - fn write(&self, writer: &mut W) -> Result<(), grin_ser::Error> { - let b = match self { - SlateStateV5::Unknown => 0, - SlateStateV5::Standard1 => 1, - SlateStateV5::Standard2 => 2, - SlateStateV5::Standard3 => 3, - SlateStateV5::Invoice1 => 4, - SlateStateV5::Invoice2 => 5, - SlateStateV5::Invoice3 => 6, - }; - writer.write_u8(b) - } -} - -impl Readable for SlateStateV5 { - fn read(reader: &mut R) -> Result { - let b = reader.read_u8()?; - let sta = match b { - 0 => SlateStateV5::Unknown, - 1 => SlateStateV5::Standard1, - 2 => SlateStateV5::Standard2, - 3 => SlateStateV5::Standard3, - 4 => SlateStateV5::Invoice1, - 5 => SlateStateV5::Invoice2, - 6 => SlateStateV5::Invoice3, - _ => SlateStateV5::Unknown, - }; - Ok(sta) - } -} - -struct SigsWrap(Vec); -struct SigsWrapRef<'a>(&'a Vec); - -impl<'a> Writeable for SigsWrapRef<'a> { - fn write(&self, writer: &mut W) -> Result<(), grin_ser::Error> { - writer.write_u8(self.0.len() as u8)?; - for s in self.0.iter() { - //0 means part sig is not yet included - //1 means part sig included - if s.part.is_some() { - writer.write_u8(1)?; - } else { - writer.write_u8(0)?; - } - s.xs.write(writer)?; - s.nonce.write(writer)?; - if let Some(s) = s.part { - s.write(writer)?; - } - } - Ok(()) - } -} - -impl Readable for SigsWrap { - fn read(reader: &mut R) -> Result { - let sigs_len = reader.read_u8()?; - let sigs = { - let mut ret = vec![]; - for _ in 0..sigs_len as usize { - let has_partial = reader.read_u8()?; - let c = ParticipantDataV5 { - xs: PublicKey::read(reader)?, - nonce: PublicKey::read(reader)?, - part: match has_partial { - 1 => Some(Signature::read(reader)?), - 0 | _ => None, - }, - }; - ret.push(c); - } - ret - }; - Ok(SigsWrap(sigs)) - } -} - -/// Serialization of optional structs -struct SlateOptStructsRef<'a> { - /// coms, default none - pub coms: &'a Option>, - ///// proof, default none - pub proof: &'a Option, -} - -/// Serialization of optional structs -struct SlateOptStructs { - /// coms, default none - pub coms: Option>, - /// proof, default none - pub proof: Option, -} - -impl<'a> Writeable for SlateOptStructsRef<'a> { - fn write(&self, writer: &mut W) -> Result<(), grin_ser::Error> { - // Status byte, bits determing which optional structs are serialized - // 0 0 0 0 0 0 1 1 - // p c - let mut status = 0u8; - if self.coms.is_some() { - status |= 0x01 - }; - if self.proof.is_some() { - status |= 0x02 - }; - writer.write_u8(status)?; - if let Some(c) = self.coms { - ComsWrapRef(&c).write(writer)?; - } - if let Some(p) = self.proof { - ProofWrapRef(&p).write(writer)?; - } - Ok(()) - } -} - -impl Readable for SlateOptStructs { - fn read(reader: &mut R) -> Result { - let status = reader.read_u8()?; - let coms = if status & 0x01 > 0 { - Some(ComsWrap::read(reader)?.0) - } else { - None - }; - let proof = if status & 0x02 > 0 { - Some(ProofWrap::read(reader)?.0) - } else { - None - }; - Ok(SlateOptStructs { coms, proof }) - } -} - -struct ComsWrap(Vec); -struct ComsWrapRef<'a>(&'a Vec); - -impl<'a> Writeable for ComsWrapRef<'a> { - fn write(&self, writer: &mut W) -> Result<(), grin_ser::Error> { - writer.write_u16(self.0.len() as u16)?; - for o in self.0.iter() { - //0 means input - //1 means output with proof - if o.p.is_some() { - writer.write_u8(1)?; - } else { - writer.write_u8(0)?; - } - OutputFeatures::from(o.f).write(writer)?; - o.c.write(writer)?; - if let Some(p) = o.p { - p.write(writer)?; - } - } - Ok(()) - } -} - -impl Readable for ComsWrap { - fn read(reader: &mut R) -> Result { - let coms_len = reader.read_u16()?; - let coms = { - let mut ret = vec![]; - for _ in 0..coms_len as usize { - let is_output = reader.read_u8()?; - let c = CommitsV5 { - f: OutputFeatures::read(reader)?.into(), - c: Commitment::read(reader)?, - p: match is_output { - 1 => Some(RangeProof::read(reader)?), - 0 | _ => None, - }, - }; - ret.push(c); - } - ret - }; - Ok(ComsWrap(coms)) - } -} - -struct ProofWrap(PaymentInfoV5); -struct ProofWrapRef<'a>(&'a PaymentInfoV5); - -impl<'a> Writeable for ProofWrapRef<'a> { - fn write(&self, writer: &mut W) -> Result<(), grin_ser::Error> { - writer.write_fixed_bytes(self.0.saddr.to_bytes())?; - writer.write_fixed_bytes(self.0.raddr.to_bytes())?; - writer.write_i64(self.0.ts.timestamp())?; - match self.0.psig { - Some(s) => { - writer.write_u8(1)?; - writer.write_fixed_bytes(&s.to_bytes().to_vec())?; - } - None => writer.write_u8(0)?, - } - match &self.0.memo { - Some(s) => { - writer.write_u8(1)?; - writer.write_u8(s.memo_type)?; - writer.write_fixed_bytes(&s.memo)?; - } - None => writer.write_u8(0)?, - } - Ok(()) - } -} - -impl Readable for ProofWrap { - fn read(reader: &mut R) -> Result { - let saddr = DalekPublicKey::from_bytes(&reader.read_fixed_bytes(32)?).unwrap(); - let raddr = DalekPublicKey::from_bytes(&reader.read_fixed_bytes(32)?).unwrap(); - let ts_raw: i64 = match reader.read_i64() { - Ok(v) => v, - Err(_) => return Err(grin_ser::Error::CorruptedData), - }; - let ts_opt = match NaiveDateTime::from_timestamp_opt(ts_raw, 0) { - Some(o) => o, - None => return Err(grin_ser::Error::CorruptedData), - }; - let ts = DateTime::::from_utc(ts_opt, Utc); - let psig = match reader.read_u8()? { - 0 => None, - 1 | _ => Some(DalekSignature::try_from(&reader.read_fixed_bytes(64)?[..]).unwrap()), - }; - let memo = match reader.read_u8()? { - 0 => None, - 1 | _ => Some(PaymentMemoV5 { - memo_type: reader.read_u8().unwrap(), - memo: reader.read_fixed_bytes(32)?.try_into().unwrap_or_default(), - }), - }; - Ok(ProofWrap(PaymentInfoV5 { - saddr, - raddr, - ts, - psig, - memo, - })) - } -} - -#[derive(Debug, Clone)] -pub struct SlateV5Bin(pub SlateV5); - -impl From for SlateV5Bin { - fn from(slate: SlateV5) -> SlateV5Bin { - SlateV5Bin(slate) - } -} - -impl From for SlateV5 { - fn from(slate: SlateV5Bin) -> SlateV5 { - slate.0 - } -} - -impl serde::Serialize for SlateV5Bin { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut vec = vec![]; - grin_ser::serialize(&mut vec, grin_ser::ProtocolVersion(4), self) - .map_err(|err| serde::ser::Error::custom(err.to_string()))?; - serializer.serialize_bytes(&vec) - } -} - -impl<'de> serde::Deserialize<'de> for SlateV5Bin { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct SlateV5BinVisitor; - - impl<'de> serde::de::Visitor<'de> for SlateV5BinVisitor { - type Value = SlateV5Bin; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "a serialised binary V5 slate") - } - - fn visit_bytes(self, value: &[u8]) -> Result - where - E: serde::de::Error, - { - let mut reader = std::io::Cursor::new(value.to_vec()); - let s = grin_ser::deserialize( - &mut reader, - grin_ser::ProtocolVersion(4), - grin_ser::DeserializationMode::default(), - ) - .map_err(|err| serde::de::Error::custom(err.to_string()))?; - Ok(s) - } - } - deserializer.deserialize_bytes(SlateV5BinVisitor) - } -} - -impl Writeable for SlateV5Bin { - fn write(&self, writer: &mut W) -> Result<(), grin_ser::Error> { - let v5 = &self.0; - writer.write_u16(v5.ver.version)?; - writer.write_u16(v5.ver.block_header_version)?; - (UuidWrap(v5.id)).write(writer)?; - v5.sta.write(writer)?; - v5.off.write(writer)?; - SlateOptFields { - num_parts: v5.num_parts, - amt: v5.amt, - fee: v5.fee, - feat: v5.feat, - ttl: v5.ttl, - } - .write(writer)?; - (SigsWrapRef(&v5.sigs)).write(writer)?; - SlateOptStructsRef { - coms: &v5.coms, - proof: &v5.proof, - } - .write(writer)?; - // Write lock height for height locked kernels - if v5.feat == 2 { - let lock_hgt = match &v5.feat_args { - Some(l) => l.lock_hgt, - None => 0, - }; - writer.write_u64(lock_hgt)?; - } - Ok(()) - } -} - -impl Readable for SlateV5Bin { - fn read(reader: &mut R) -> Result { - let ver = VersionCompatInfoV5 { - version: reader.read_u16()?, - block_header_version: reader.read_u16()?, - }; - let id = UuidWrap::read(reader)?.0; - let sta = SlateStateV5::read(reader)?; - let off = BlindingFactor::read(reader)?; - - let opts = SlateOptFields::read(reader)?; - let sigs = SigsWrap::read(reader)?.0; - let opt_structs = SlateOptStructs::read(reader)?; - - let feat_args = if opts.feat == 2 { - Some(KernelFeaturesArgsV5 { - lock_hgt: reader.read_u64()?, - }) - } else { - None - }; - - Ok(SlateV5Bin(SlateV5 { - ver, - id, - sta, - off, - num_parts: opts.num_parts, - amt: opts.amt, - fee: opts.fee, - feat: opts.feat, - ttl: opts.ttl, - sigs, - coms: opt_structs.coms, - proof: opt_structs.proof, - feat_args, - })) - } -} - -#[test] -fn slate_v5_serialize_deserialize() { - use crate::grin_util::from_hex; - use crate::grin_util::secp::key::PublicKey; - use crate::Slate; - use grin_core::global::{set_local_chain_type, ChainTypes}; - use grin_keychain::{ExtKeychain, Keychain, SwitchCommitmentType}; - set_local_chain_type(ChainTypes::Mainnet); - let slate = Slate::blank(1, false); - let mut v5 = SlateV5::from(slate); - use chrono::prelude::*; - - let keychain = ExtKeychain::from_random_seed(true).unwrap(); - let switch = SwitchCommitmentType::Regular; - // add some sig data - let id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let id2 = ExtKeychain::derive_key_id(1, 1, 1, 0, 0); - let skey1 = keychain.derive_key(0, &id1, switch).unwrap(); - let skey2 = keychain.derive_key(0, &id2, switch).unwrap(); - let xs = PublicKey::from_secret_key(keychain.secp(), &skey1).unwrap(); - let nonce = PublicKey::from_secret_key(keychain.secp(), &skey2).unwrap(); - let part = ParticipantDataV5 { - xs, - nonce, - part: None, - }; - let part2 = ParticipantDataV5 { - xs, - nonce, - part: Some(Signature::from_raw_data(&[11; 64]).unwrap()), - }; - v5.sigs.push(part.clone()); - v5.sigs.push(part2); - v5.sigs.push(part); - - // add some random commit data - let com1 = CommitsV5 { - f: OutputFeatures::Plain.into(), - c: Commitment::from_vec([3u8; 1].to_vec()), - p: None, - }; - let com2 = CommitsV5 { - f: OutputFeatures::Plain.into(), - c: Commitment::from_vec([4u8; 1].to_vec()), - p: Some(RangeProof::zero()), - }; - let mut coms = vec![]; - coms.push(com1.clone()); - coms.push(com1.clone()); - coms.push(com1.clone()); - coms.push(com2); - - v5.coms = Some(coms); - v5.amt = 234324899824; - v5.feat = 1; - v5.num_parts = 2; - v5.feat_args = Some(KernelFeaturesArgsV5 { lock_hgt: 23092039 }); - let v4_1 = v5.clone(); - let v4_1_copy = v5.clone(); - - let v4_bin = SlateV5Bin(v5); - let mut vec = Vec::new(); - let _ = grin_ser::serialize_default(&mut vec, &v4_bin).expect("serialization failed"); - let b4_bin_2: SlateV5Bin = grin_ser::deserialize_default(&mut &vec[..]).unwrap(); - let v4_2 = b4_bin_2.0.clone(); - assert_eq!(v4_1.ver, v4_2.ver); - assert_eq!(v4_1.id, v4_2.id); - assert_eq!(v4_1.amt, v4_2.amt); - assert_eq!(v4_1.fee, v4_2.fee); - let v4_2_coms = v4_2.coms.as_ref().unwrap().clone(); - for (i, c) in v4_1.coms.unwrap().iter().enumerate() { - assert_eq!(c.f, v4_2_coms[i].f); - assert_eq!(c.c, v4_2_coms[i].c); - assert_eq!(c.p, v4_2_coms[i].p); - } - assert_eq!(v4_1.sigs, v4_2.sigs); - assert_eq!(v4_1.proof, v4_2.proof); - - // Include Payment proof, remove coms to mix it up a bit - let mut v5 = v4_1_copy; - let raw_pubkey_str = "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb"; - let b = from_hex(raw_pubkey_str).unwrap(); - let d_pkey = DalekPublicKey::from_bytes(&b).unwrap(); - // Need to remove milliseconds component for comparison. Won't be serialized - let ts = NaiveDateTime::from_timestamp_opt(Utc::now().timestamp(), 0).unwrap(); - let ts = DateTime::::from_utc(ts, Utc); - let pm = PaymentMemoV5 { - memo_type: 0, - memo: [9; 32], - }; - v5.proof = Some(PaymentInfoV5 { - raddr: d_pkey.clone(), - saddr: d_pkey.clone(), - ts: ts.clone(), - psig: None, - memo: Some(pm), - }); - v5.coms = None; - let v5_1 = v5.clone(); - let v5_bin = SlateV5Bin(v5); - let mut vec = Vec::new(); - let _ = grin_ser::serialize_default(&mut vec, &v5_bin).expect("serialization failed"); - let b4_bin_2: SlateV5Bin = grin_ser::deserialize_default(&mut &vec[..]).unwrap(); - let v5_2 = b4_bin_2.0.clone(); - assert_eq!(v5_1.ver, v5_2.ver); - assert_eq!(v5_1.id, v5_2.id); - assert_eq!(v5_1.amt, v5_2.amt); - assert_eq!(v5_1.fee, v5_2.fee); - assert!(v5_1.coms.is_none()); - assert_eq!(v5_1.sigs, v5_2.sigs); - assert_eq!(v5_1.proof, v5_2.proof); -} diff --git a/libwallet/src/slatepack/armor.rs b/libwallet/src/slatepack/armor.rs index f35554916..12a0ced28 100644 --- a/libwallet/src/slatepack/armor.rs +++ b/libwallet/src/slatepack/armor.rs @@ -189,10 +189,10 @@ fn format_slatepack(slatepack: &str) -> Result { // Returns the first four bytes of a double sha256 hash of some bytes fn generate_check(payload: &[u8]) -> Result, Error> { let mut first_hash = Sha256::new(); - first_hash.update(payload); + first_hash.input(payload); let mut second_hash = Sha256::new(); - second_hash.update(first_hash.finalize()); - let checksum = second_hash.finalize(); + second_hash.input(first_hash.result()); + let checksum = second_hash.result(); let check_bytes: Vec = checksum[0..4].to_vec(); Ok(check_bytes) } diff --git a/libwallet/src/slatepack/packer.rs b/libwallet/src/slatepack/packer.rs index d44f5c6cf..f1c54e92c 100644 --- a/libwallet/src/slatepack/packer.rs +++ b/libwallet/src/slatepack/packer.rs @@ -94,7 +94,7 @@ impl<'a> Slatepacker<'a> { /// Create slatepack from slate and args pub fn create_slatepack(&self, slate: &Slate) -> Result { - let out_slate = VersionedSlate::into_version(slate.clone(), SlateVersion::V5)?; + let out_slate = VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?; let bin_slate = VersionedBinSlate::try_from(out_slate).map_err(|_| Error::SlatepackSer)?; let mut slatepack = Slatepack::default(); slatepack.payload = byte_ser::to_bytes(&bin_slate).map_err(|_| Error::SlatepackSer)?; diff --git a/libwallet/src/slatepack/types.rs b/libwallet/src/slatepack/types.rs index fe08c933c..90c200312 100644 --- a/libwallet/src/slatepack/types.rs +++ b/libwallet/src/slatepack/types.rs @@ -190,8 +190,8 @@ impl Slatepack { let mut b = [0u8; 32]; b.copy_from_slice(&dec_key.as_bytes()[0..32]); let mut hasher = Sha512::new(); - hasher.update(b); - let result = hasher.finalize(); + hasher.input(b); + let result = hasher.result(); b.copy_from_slice(&result[0..32]); let x_dec_secret = StaticSecret::from(b); diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index 8b3962598..3a63b7aa1 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -16,7 +16,6 @@ //! implementation use crate::config::{TorConfig, WalletConfig}; -use crate::contract::types::ContractSetupArgsAPI; use crate::error::Error; use crate::grin_core::core::hash::Hash; use crate::grin_core::core::FeeFields; @@ -26,9 +25,8 @@ use crate::grin_core::{global, ser}; use crate::grin_keychain::{Identifier, Keychain}; use crate::grin_util::logger::LoggingConfig; use crate::grin_util::secp::key::{PublicKey, SecretKey}; -use crate::grin_util::secp::{self, pedersen, Secp256k1, Signature}; +use crate::grin_util::secp::{self, pedersen, Secp256k1}; use crate::grin_util::{ToHex, ZeroingString}; -use crate::slate::PaymentMemo; use crate::slate_versions::ser as dalek_ser; use crate::InitTxArgs; use chrono::prelude::*; @@ -191,15 +189,7 @@ where fn get(&self, id: &Identifier, mmr_index: &Option) -> Result; /// Get an (Optional) tx log entry by uuid - // TODO: I think this can be deleted - // fn get_tx_log_entry(&self, uuid: &Uuid) -> Result, Error>; - - /// Get an (Optional) tx log entry by uuid - fn get_tx_log_entry( - &self, - parent_id: Identifier, - log_id: u32, - ) -> Result, Error>; + fn get_tx_log_entry(&self, uuid: &Uuid) -> Result, Error>; /// Retrieves the private context associated with a given slate id fn get_private_context( @@ -573,13 +563,6 @@ pub struct Context { /// for invoice I2 Only, store the tx excess so we can /// remove it from the slate on return pub calculated_excess: Option, - /// Arguments that define which outputs to pick for a contract - pub setup_args: Option, - /// TxLogEntry id (needed to avoid a linear scan) - // Services that keep a long history might need to search - // through a list when they need to update a txlogentry. - // This is why we keep the id in the context. - pub log_id: Option, } impl Context { @@ -629,8 +612,6 @@ impl Context { payment_proof_derivation_index: None, late_lock_args: None, calculated_excess: None, - setup_args: None, - log_id: None, } } } @@ -671,11 +652,6 @@ impl Context { PublicKey::from_secret_key(secp, &self.sec_nonce).unwrap(), ) } - - /// Returns net_change for the contract - pub fn get_net_change(&self) -> i64 { - self.setup_args.as_ref().unwrap().net_change.unwrap() - } } impl ser::Writeable for Context { @@ -791,10 +767,6 @@ pub enum TxLogEntryType { TxReceivedCancelled, /// Sent transaction that was rolled back by user TxSentCancelled, - /// Self spend, as per contracts and mwmixnet - TxSelfSpend, - /// Self Spend Cancelled (has to happen before sent to chain, flag rather than delete) - TxSelfSpendCancelled, /// Received transaction that was reverted on-chain TxReverted, } @@ -808,8 +780,6 @@ impl fmt::Display for TxLogEntryType { TxLogEntryType::TxReceivedCancelled => write!(f, "Received Tx\n- Cancelled"), TxLogEntryType::TxSentCancelled => write!(f, "Sent Tx\n- Cancelled"), TxLogEntryType::TxReverted => write!(f, "Received Tx\n- Reverted"), - TxLogEntryType::TxSelfSpend => write!(f, "Self Spend"), - TxLogEntryType::TxSelfSpendCancelled => write!(f, "Self Spend\n- Cancelled"), } } } @@ -941,23 +911,6 @@ pub struct StoredProofInfo { /// sender signature #[serde(with = "dalek_ser::option_dalek_sig_serde")] pub sender_signature: Option, - // Fields beyond here are specific to early payment proofs, - // invoice and sender nonce - /// Assumed to be 0x00 (Legacy) if missing - pub proof_type: Option, - /// receiver's public nonce from signing - pub receiver_public_nonce: Option, - /// receiver's public excess from signing - pub receiver_public_excess: Option, - /// Timestamp provided by recipient when signing - pub timestamp: Option>, - /// Optional payment memo - pub memo: Option, - /// recipient promise signature - #[serde(with = "dalek_ser::option_dalek_sig_serde")] - pub promise_signature: Option, - /// Original Sender partial key - pub sender_part_sig: Option, } impl ser::Writeable for StoredProofInfo { diff --git a/src/bin/grin-wallet.yml b/src/bin/grin-wallet.yml index a88efb4b0..39a8962ca 100644 --- a/src/bin/grin-wallet.yml +++ b/src/bin/grin-wallet.yml @@ -446,149 +446,3 @@ subcommands: - input: help: Filename of a proof file index: 1 - - contract: - subcommands: - - new: - about: Create a new contract with initial setup - args: - - encrypt-for: - help: The counter party grin address - short: e - long: encrypt-for - takes_value: true - # index: 1 - - receive: - help: How much you want to receive - short: r - long: receive - takes_value: true - - send: - help: How much you want to send - short: s - long: send - takes_value: true - - num-participants: - help: How many participants are involved? (can be 1 or 2) - short: n - long: num-participants - takes_value: true - default_value: "2" - - as-json: - help: Show result as JSON - short: j - long: as-json - takes_value: false - - no-payjoin: - help: Don't make it a payjoin (if receiver) - long: no-payjoin - takes_value: false - - add-outputs: - help: Defines whether we should pick inputs/outputs in the first step. - short: a - long: add-outputs - takes_value: false - - use-inputs: - help: Which inputs you want to use (provide comma separated commitments) - short: i - long: use-inputs - takes_value: true - - make-outputs: - help: Which outputs should we create? (provide comma separated amounts) - short: o - long: make-outputs - takes_value: true - # - setup: - # about: Perform a key setup on a contract - # args: - # - encrypt-for: - # help: The counter party grin address - # short: e - # long: encrypt-for - # takes_value: true - # # index: 1 - # - receive: - # help: How much you want to receive - # short: r - # long: receive - # takes_value: true - # - send: - # help: How much you want to send - # short: s - # long: send - # takes_value: true - # - as-json: - # help: Show result as JSON - # short: j - # long: as-json - # takes_value: false - # - no-payjoin: - # help: Don't make it a payjoin (if receiver) - # long: no-payjoin - # takes_value: false - # - add-outputs: - # help: Defines whether we should pick inputs/outputs in the setup step. - # short: a - # long: add-outputs - # takes_value: false - # - use-inputs: - # help: Which inputs you want to use (provide comma separated commitments) - # short: i - # long: use-inputs - # takes_value: true - # - make-outputs: - # help: Which outputs should we create? (provide comma separated amounts) - # short: o - # long: make-outputs - # takes_value: true - - sign: - about: Sign a contract - args: - - encrypt-for: - help: The counter party grin address - short: e - long: encrypt-for - takes_value: true - # index: 1 - - receive: - help: How much you want to receive - short: r - long: receive - takes_value: true - - send: - help: How much you want to send - short: s - long: send - takes_value: true - - as-json: - help: Show result as JSON - short: j - long: as-json - takes_value: false - - no-payjoin: - help: Don't make it a payjoin (if receiver) - long: no-payjoin - takes_value: false - - use-inputs: - help: Which inputs you want to use (provide comma separated commitments) - short: i - long: use-inputs - takes_value: true - - make-outputs: - help: Which outputs should we create? (provide comma separated amounts) - short: o - long: make-outputs - takes_value: true - - no-broadcast: - help: Don't broadcast the transaction if it was finalized - long: no-broadcast - takes_value: false - - view: - about: View a contract - - revoke: - about: Attempt to revoke a contract - args: - - tx-id: - help: Id of a transaction we want to cancel - short: i - long: tx-id - takes_value: true diff --git a/src/build/build.rs b/src/build/build.rs index 1d3d9bb14..4f7904d86 100644 --- a/src/build/build.rs +++ b/src/build/build.rs @@ -39,10 +39,13 @@ fn main() { } // build and versioning information + let mut opts = built::Options::default(); + opts.set_dependencies(true); let out_dir_path = format!("{}{}", env::var("OUT_DIR").unwrap(), "/built.rs"); // don't fail the build if something's missing, may just be cargo release let _ = built::write_built_file_with_opts( - Some(Path::new(env!("CARGO_MANIFEST_DIR"))), + &opts, + Path::new(env!("CARGO_MANIFEST_DIR")), Path::new(&out_dir_path), ); } diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index a0f48d2b4..866db6cac 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// Argument parsing and error handling for wallet commands -use self::core::consensus; use crate::api::TLSConfig; use crate::cli::command_loop; use crate::config::GRIN_WALLET_DIR; use crate::util::file::get_first_line; use crate::util::secp::key::SecretKey; use crate::util::{Mutex, ZeroingString}; +/// Argument parsing and error handling for wallet commands use clap::ArgMatches; use grin_core as core; use grin_core::core::amount_to_hr_string; @@ -966,148 +965,6 @@ pub fn parse_verify_proof_args(args: &ArgMatches) -> Result Result { - let counterparty_addr = args.value_of("encrypt-for").unwrap(); - - // TODO: Make sure the values are in some expected bounds. - // TODO: How to deal with decimals and precision? we probably want users to express the value in Grin. - // Parse receive and send params and convert them to nano grin - let receive = match args.value_of("receive") { - Some(g) => Some((g.parse::().unwrap() * consensus::GRIN_BASE as f64) as u64), - None => None, - }; - let send = match args.value_of("send") { - Some(g) => Some((g.parse::().unwrap() * consensus::GRIN_BASE as f64) as u64), - None => None, - }; - if receive.is_some() && send.is_some() { - return Err(ParseError::ArgumentError(String::from( - "You can only specify receive or send, not both.", - ))); - }; - // TODO: verify this is correct e.g. which values are passed here by default etc. - let src_acct_name = Some(String::from(account)); - let add_outputs = args.is_present("add-outputs"); - let as_json = args.is_present("as-json"); - let no_payjoin = args.is_present("no-payjoin"); - let use_inputs = match args.value_of("use-inputs") { - Some(v) => { - if no_payjoin { - panic!("Can't use --no-payjoin with --use-inputs."); - } - Some(String::from(v)) - } - None => { - if no_payjoin { - None - } else { - // Some("any") means pick 1 random input to contribute (payjoin) - Some(String::from("any")) - } - } - }; - let make_outputs = match args.value_of("make-outputs") { - Some(v) => Some(String::from(v)), - None => None, - }; - - let num_participants = args - .value_of("num-participants") - .unwrap() - .parse::() - .unwrap(); - - Ok(command::ContractNewArgs { - counterparty_addr: String::from(counterparty_addr), - receive: receive, - send: send, - src_acct_name: src_acct_name, - num_participants: num_participants, - as_json: as_json, - add_outputs: add_outputs, - use_inputs: use_inputs, - make_outputs: make_outputs, - // TODO: Future features below - fee_rate: None, - outfile: None, - }) -} - -// TODO: parse args -pub fn parse_contract_setup_args( - args: &ArgMatches, -) -> Result { - // TODO: Make sure the values are in some expected bounds. - // TODO: How to deal with decimals and precision? we probably want users to express the value in Grin. - let counterparty_addr = match args.value_of("encrypt-for") { - Some(v) => Some(String::from(v)), - None => None, - }; - // Parse receive and send params and convert them to nano grin - let receive = match args.value_of("receive") { - Some(g) => Some((g.parse::().unwrap() * consensus::GRIN_BASE as f64) as u64), - None => None, - }; - let send = match args.value_of("send") { - Some(g) => Some((g.parse::().unwrap() * consensus::GRIN_BASE as f64) as u64), - None => None, - }; - if receive.is_some() && send.is_some() { - return Err(ParseError::ArgumentError(String::from( - "You can only specify receive or send, not both.", - ))); - }; - let as_json = args.is_present("as-json"); - let no_payjoin = args.is_present("no-payjoin"); - let use_inputs = match args.value_of("use-inputs") { - Some(v) => { - if no_payjoin { - panic!("Can't use --no-payjoin with --use-inputs."); - } - Some(String::from(v)) - } - None => { - if no_payjoin { - None - } else { - // Some("any") means pick 1 random input to contribute (payjoin) - Some(String::from("any")) - } - } - }; - let make_outputs = match args.value_of("make-outputs") { - Some(v) => Some(String::from(v)), - None => None, - }; - // TODO: should we catch if the person calls "--receive=5" when it should be "--send=5"? - // Perhaps we could detect this from the slate state e.g. S1 -> receive, I1 -> send? - - Ok(command::ContractSetupArgs { - counterparty_addr: counterparty_addr, - receive: receive, - send: send, - as_json: as_json, - add_outputs: false, - use_inputs: use_inputs, - make_outputs: make_outputs, - // TODO: Future features below - fee_rate: None, - outfile: None, - }) -} - -pub fn parse_contract_revoke_args( - args: &ArgMatches, -) -> Result { - let tx_id = args.value_of("tx-id").unwrap().parse::().unwrap(); - - Ok(command::ContractRevokeArgs { tx_id: tx_id }) -} - pub fn wallet_command( wallet_args: &ArgMatches, mut wallet_config: WalletConfig, @@ -1438,31 +1295,6 @@ where // for CLI mode only, should be handled externally Ok(()) } - ("contract", Some(args)) => match args.subcommand() { - ("new", Some(new_args)) => { - let account = &global_wallet_args.account; - let a = arg_parse!(parse_contract_new_args(&new_args, account)); - command::contract_new(owner_api, km, a) - } - // ("setup", Some(setup_args)) => { - // let a = arg_parse!(parse_contract_setup_args(&setup_args)); - // command::contract_setup(owner_api, km, a) - // } - ("sign", Some(sign_args)) => { - // Sign command takes setup_args so we use the same parser - let setup_args = arg_parse!(parse_contract_setup_args(&sign_args)); - let broadcast_tx = !sign_args.is_present("no-broadcast"); - command::contract_sign(owner_api, km, setup_args, broadcast_tx) - } - ("view", Some(view_args)) => { - Err(Error::ArgumentError(String::from("Not implemented")).into()) - } - ("revoke", Some(revoke_args)) => { - let a = arg_parse!(parse_contract_revoke_args(&revoke_args)); - command::contract_revoke(owner_api, km, a) - } - _ => Err(Error::ArgumentError(String::from("Unknown contract subcommand.")).into()), - }, _ => { let msg = format!("Unknown wallet command, use 'grin-wallet help' for details"); return Err(Error::ArgumentError(msg)); diff --git a/util/Cargo.toml b/util/Cargo.toml index be663aede..c4f911e64 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_util" -version = "5.4.0-contracts.0" +version = "5.4.0-alpha.1" authors = ["Grin Developers "] description = "Util, for generic utilities and to re-export grin crates" license = "Apache-2.0" @@ -21,7 +21,7 @@ thiserror = "1" ##### Grin Imports # For Release -# grin_util = "5.4.0-contracts.0" +# grin_util = "5.4.0-alpha.1" # For beta release