diff --git a/Cargo.lock b/Cargo.lock index f168e4c9d..251be5e15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,7 +99,7 @@ dependencies = [ "sha2 0.8.2", "subtle 2.2.3", "x25519-dalek", - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -238,7 +238,7 @@ dependencies = [ "crypto-mac 0.7.0", "pbkdf2 0.3.0", "sha2 0.8.2", - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -500,7 +500,7 @@ dependencies = [ "aead", "poly1305", "stream-cipher", - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -747,7 +747,7 @@ dependencies = [ "digest 0.8.1", "rand_core 0.5.1", "subtle 2.2.3", - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -890,7 +890,7 @@ dependencies = [ "rand 0.7.3", "serde", "sha2 0.8.2", - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -955,7 +955,7 @@ dependencies = [ "proc-macro2 1.0.19", "quote 1.0.7", "syn 1.0.38", - "synstructure 0.12.4", + "synstructure", ] [[package]] @@ -1183,7 +1183,7 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "grin_api" version = "5.2.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin?branch=master#f51b6e13761ac4c3c8e57904618ef431c14c6227" +source = "git+https://github.com/geneferneau/grin?branch=atomic#b6b9a037513d331af62bc25e86a72ebf0b6f9023" dependencies = [ "bytes 0.5.6", "easy-jsonrpc-mw", @@ -1216,7 +1216,7 @@ dependencies = [ [[package]] name = "grin_chain" version = "5.2.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin?branch=master#f51b6e13761ac4c3c8e57904618ef431c14c6227" +source = "git+https://github.com/geneferneau/grin?branch=atomic#b6b9a037513d331af62bc25e86a72ebf0b6f9023" dependencies = [ "bit-vec", "bitflags 1.2.1", @@ -1240,7 +1240,7 @@ dependencies = [ [[package]] name = "grin_core" version = "5.2.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin?branch=master#f51b6e13761ac4c3c8e57904618ef431c14c6227" +source = "git+https://github.com/geneferneau/grin?branch=atomic#b6b9a037513d331af62bc25e86a72ebf0b6f9023" dependencies = [ "blake2-rfc", "byteorder", @@ -1261,13 +1261,13 @@ dependencies = [ "serde", "serde_derive", "siphasher", - "zeroize 1.1.0", + "zeroize", ] [[package]] name = "grin_keychain" version = "5.2.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin?branch=master#f51b6e13761ac4c3c8e57904618ef431c14c6227" +source = "git+https://github.com/geneferneau/grin?branch=atomic#b6b9a037513d331af62bc25e86a72ebf0b6f9023" dependencies = [ "blake2-rfc", "byteorder", @@ -1283,13 +1283,13 @@ dependencies = [ "serde_derive", "serde_json", "sha2 0.7.1", - "zeroize 1.1.0", + "zeroize", ] [[package]] name = "grin_p2p" version = "5.2.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin?branch=master#f51b6e13761ac4c3c8e57904618ef431c14c6227" +source = "git+https://github.com/geneferneau/grin?branch=atomic#b6b9a037513d331af62bc25e86a72ebf0b6f9023" dependencies = [ "bitflags 1.2.1", "bytes 0.5.6", @@ -1311,7 +1311,7 @@ dependencies = [ [[package]] name = "grin_pool" version = "5.2.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin?branch=master#f51b6e13761ac4c3c8e57904618ef431c14c6227" +source = "git+https://github.com/geneferneau/grin?branch=atomic#b6b9a037513d331af62bc25e86a72ebf0b6f9023" dependencies = [ "blake2-rfc", "chrono", @@ -1328,9 +1328,8 @@ dependencies = [ [[package]] name = "grin_secp256k1zkp" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca5852d12dbd4df8b7b760eaad791d191ea546bf80c46b033bbcc650b35a5c" +version = "0.7.11" +source = "git+https://github.com/geneferneau/rust-secp256k1-zkp?branch=atomic#45c27af7ac91d8c4323a19730760922348ee9a08" dependencies = [ "arrayvec 0.3.25", "cc", @@ -1339,13 +1338,13 @@ dependencies = [ "rustc-serialize", "serde", "serde_json", - "zeroize 0.9.3", + "zeroize", ] [[package]] name = "grin_store" version = "5.2.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin?branch=master#f51b6e13761ac4c3c8e57904618ef431c14c6227" +source = "git+https://github.com/geneferneau/grin?branch=atomic#b6b9a037513d331af62bc25e86a72ebf0b6f9023" dependencies = [ "byteorder", "croaring", @@ -1365,7 +1364,7 @@ dependencies = [ [[package]] name = "grin_util" version = "5.2.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin?branch=master#f51b6e13761ac4c3c8e57904618ef431c14c6227" +source = "git+https://github.com/geneferneau/grin?branch=atomic#b6b9a037513d331af62bc25e86a72ebf0b6f9023" dependencies = [ "backtrace", "base64 0.12.3", @@ -1379,7 +1378,7 @@ dependencies = [ "serde", "serde_derive", "walkdir", - "zeroize 1.1.0", + "zeroize", "zip", ] @@ -3133,7 +3132,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9182278ed645df3477a9c27bfee0621c621aa16f6972635f7f795dae3d81070f" dependencies = [ - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -3432,18 +3431,6 @@ dependencies = [ "unicode-xid 0.2.1", ] -[[package]] -name = "synstructure" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "syn 0.15.44", - "unicode-xid 0.1.0", -] - [[package]] name = "synstructure" version = "0.12.4" @@ -4100,7 +4087,7 @@ checksum = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217" dependencies = [ "curve25519-dalek", "rand_core 0.5.1", - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -4118,34 +4105,13 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "zeroize" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45af6a010d13e4cf5b54c94ba5a2b2eba5596b9e46bf5875612d332a1f2b3f86" -dependencies = [ - "zeroize_derive 0.9.3", -] - [[package]] name = "zeroize" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" dependencies = [ - "zeroize_derive 1.0.0", -] - -[[package]] -name = "zeroize_derive" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080616bd0e31f36095288bb0acdf1f78ef02c2fa15527d7e993f2a6c7591643e" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "syn 0.15.44", - "synstructure 0.10.2", + "zeroize_derive", ] [[package]] @@ -4157,7 +4123,7 @@ dependencies = [ "proc-macro2 1.0.19", "quote 1.0.7", "syn 1.0.38", - "synstructure 0.12.4", + "synstructure", ] [[package]] diff --git a/api/src/foreign.rs b/api/src/foreign.rs index 813f35dc6..63c41aa94 100644 --- a/api/src/foreign.rs +++ b/api/src/foreign.rs @@ -334,7 +334,7 @@ where /// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config); /// /// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false); - /// # let slate = Slate::blank(2, false); + /// # let slate = Slate::blank(2, TxFlow::Standard); /// /// // . . . /// // Obtain a sent slate somehow @@ -388,6 +388,49 @@ where } } + /// Receive an atomic swap transaction + pub fn receive_atomic_tx( + &self, + slate: &Slate, + dest_acct_name: Option<&str>, + r_addr: Option, + ) -> Result { + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + if let Some(m) = self.middleware.as_ref() { + m( + ForeignCheckMiddlewareFn::ReceiveTx, + w.w2n_client().get_version_info(), + Some(slate), + )?; + } + let ret_slate = foreign::receive_atomic_tx( + &mut **w, + (&self.keychain_mask).as_ref(), + slate, + dest_acct_name, + self.doctest_mode, + )?; + match r_addr { + Some(a) => { + let tor_config_lock = self.tor_config.lock(); + let res = try_slatepack_sync_workflow( + &ret_slate, + &a, + tor_config_lock.clone(), + None, + false, + self.doctest_mode, + ); + match res { + Ok(s) => return Ok(s.unwrap()), + Err(_) => return Ok(ret_slate), + } + } + None => Ok(ret_slate), + } + } + /// Finalizes a (standard or invoice) transaction initiated by this wallet's Owner api. /// This step assumes the paying party has completed round 1 and 2 of slate /// creation, and added their partial signatures. This wallet will verify @@ -430,7 +473,7 @@ where /// // If result okay, send to payer, who will apply the transaction via their /// // owner API, then send back the slate /// // ... - /// # let slate = Slate::blank(2, true); + /// # let slate = Slate::blank(2, TxFlow::Invoice); /// /// let slate = api_foreign.finalize_tx(&slate, true); /// // if okay, then post via the owner API @@ -474,7 +517,7 @@ macro_rules! doctest_helper_setup_doc_env_foreign { use api::{Foreign, Owner}; use config::WalletConfig; use impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient}; - use libwallet::{BlockFees, IssueInvoiceTxArgs, Slate, WalletInst}; + use libwallet::{BlockFees, IssueInvoiceTxArgs, Slate, TxFlow, WalletInst}; // don't run on windows CI, which gives very inconsistent results if cfg!(windows) { diff --git a/api/src/foreign_rpc.rs b/api/src/foreign_rpc.rs index af7db48c9..03e4db56d 100644 --- a/api/src/foreign_rpc.rs +++ b/api/src/foreign_rpc.rs @@ -17,7 +17,7 @@ use crate::keychain::Keychain; use crate::libwallet::{ self, BlockFees, CbData, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, - NodeVersionInfo, Slate, SlateVersion, VersionInfo, VersionedCoinbase, VersionedSlate, + NodeVersionInfo, Slate, SlateVersion, TxFlow, VersionInfo, VersionedCoinbase, VersionedSlate, WalletLCProvider, }; use crate::{Foreign, ForeignCheckMiddlewareFn}; @@ -53,13 +53,14 @@ pub trait ForeignRpc { "Ok": { "foreign_api_version": 2, "supported_slate_versions": [ + "V5", "V4" ] } } } # "# - # ,false, 0, false, false); + # ,false, 0, false, TxFlow::Standard); ``` */ fn check_version(&self) -> Result; @@ -107,7 +108,7 @@ pub trait ForeignRpc { } } # "# - # ,false, 4, false, false); + # ,false, 4, false, TxFlow::Standard); ``` */ @@ -142,7 +143,7 @@ pub trait ForeignRpc { } ], "sta": "S1", - "ver": "4:2" + "ver": "5:2" }, null, null @@ -177,12 +178,12 @@ pub trait ForeignRpc { } ], "sta": "S2", - "ver": "4:2" + "ver": "5:2" } } } # "# - # ,false, 5, true, false); + # ,false, 5, true, TxFlow::Standard); ``` */ fn receive_tx( @@ -192,6 +193,85 @@ pub trait ForeignRpc { dest: Option, ) -> Result; + /** + ;Networked version of [Foreign::receive_atomic_tx](struct.Foreign.html#method.receive_atomic_tx). + + # Json rpc example + + ``` + # grin_wallet_api::doctest_helper_json_rpc_foreign_assert_response!( + # r#" + { + "jsonrpc": "2.0", + "method": "receive_atomic_tx", + "id": 1, + "params": [ + { + "amt": "6000000000", + "atomic_id": "046d7761746f6d69630000000000000000", + "fee": "23500000", + "id": "0436430c-2b02-624c-2032-570501212b00", + "off": "d202964900000000d302964900000000d402964900000000d502964900000000", + "sigs": [ + { + "nonce": "02b57c1f4fea69a3ee070309cf8f06082022fe06f25a9be1851b56ef0fa18f25d6", + "xs": "023878ce845727f3a4ec76ca3f3db4b38a2d05d636b8c3632108b857fed63c96de" + } + ], + "multisig_path": "m/1018305059/3401844484/447546778/208817231", + "sta": "A1", + "ver": "5:2" + }, + null, + null + ] + } + # "# + # , + # r#" + { + "id": 1, + "jsonrpc": "2.0", + "result": { + "Ok": { + "amt": "6000000000", + "coms": [ + { + "c": "091582c92b99943b57955e52b5ccf1223780c2a2e55995c00c86fca2bcb46b6b9f", + "p": "49972a8d5b7c088e7813c3988ebe0982f8f0b12b849b1788df7da07b549408b0d6c99f80c0e2335370c104225ef5d282d79966e9044c959bedc3be03af6246fa07fc13eb3c60c90213c9f3a7a5ecf9a34c8fbaddc1a72e49e12dba9495e5aaa53bb6ac6ed63d8774707c57ab604d6bdc46de18da57a731fe336c3ccef92b4dae967417ffdae2c7d75864d46d30e287dd9cc15882e15f296b9bab0040e4432f4024be33924f112dd26c90cc800ac09a327b0ac3a661f63da9945fb1bcc82a7777d61d97cbe657675e22d035d2cf9ea03a89cfa410960ebc18a0a18b1909f4c5bef20b0fd13ffcf5a818ad8768d354b1c0f2e9b16dd7a9cf0641546f57d1945a98b8684d067dd085b90b40457e4c14665fb1b94feecf30a90f508ded16ba1bba8080a6866dffd0b1f01738fff8c62ce5e38e677835752a1b4072124dd9ff14ba8ff92126baebbb5f6e14fbb052f5d5b09aec11bfd880d7d4640a295aa83f184034d26f00cbdbabf9b89fddd7a7c9cc8c5d4b53fc39971e4495a8d984ac9607be89780fde528ee3f2d6b912908b4caf04f5c93f64431517af6b32d0b9c18255959f6903c6696ec71f615a0c877630a2d871f3f8a107fc80f306a94b6ad5790070f7d2535163bad7feae9263a9d3558ea1acecc4e61ff4e05b0162f6aba1a3b299ff1c3bb85e4109e550ad870c328bedc45fed8b504f679bc3c1a25b2b65ede44602f21fac123ba7c5f132e7c786bf9420a27bae4d2559cf7779e77f96b747b6d3ad5c13b5e8c9b49a7083001b2f98bcf242d4644537bb5a3b5b41764812a93395b7ab372c18be575e02c3763b4170234e5fddeb43420aadb71cb80f75cc681c1e7ffee3e6a8868c6076fd1da539ab9a12fef1c8cbe271b6de60100c9f82d826dc97b47b57ee9804e60112f556c1dce4f12ecc91ef34d69090b8c9d2ae9cbae38994a955cb" + } + ], + "fee": "23500000", + "id": "0436430c-2b02-624c-2032-570501212b00", + "off": "d202964900000000d302964900000000d402964900000000d502964900000000", + "sigs": [ + { + "nonce": "02b57c1f4fea69a3ee070309cf8f06082022fe06f25a9be1851b56ef0fa18f25d6", + "xs": "023878ce845727f3a4ec76ca3f3db4b38a2d05d636b8c3632108b857fed63c96de" + }, + { + "atomic": "03e1d14f7b440af4944193b0559452651720ecf1847ef0f7092ef7e68414b8d732", + "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841bf9df0f2c4d96d6bb0b2e6e0b94ad6cbbc0e3f9e5582b085b31b5c8bb0e15bd36", + "xs": "02e3c128e436510500616fef3f9a22b15ca015f407c8c5cf96c9059163c873828f" + } + ], + "sta": "A2", + "ver": "5:2" + } + } + } + # "# + # ,false, 5, true, TxFlow::Atomic); + ``` + */ + fn receive_atomic_tx( + &self, + slate: VersionedSlate, + dest_acct_name: Option, + dest: Option, + ) -> Result; + /** Networked version of [Foreign::finalize_tx](struct.Foreign.html#method.finalize_tx). @@ -206,7 +286,7 @@ pub trait ForeignRpc { "method": "finalize_tx", "id": 1, "params": [{ - "ver": "4:2", + "ver": "5:2", "id": "0436430c-2b02-624c-2032-570501212b00", "sta": "I2", "off": "383bc9df0dd332629520a0a72f8dd7f0e97d579dccb4dbdc8592aa3d424c846c", @@ -276,12 +356,12 @@ pub trait ForeignRpc { } ], "sta": "I3", - "ver": "4:2" + "ver": "5:2" } } } # "# - # ,false, 5, false, true); + # ,false, 5, true, TxFlow::Invoice); ``` */ fn finalize_tx(&self, slate: VersionedSlate) -> Result; @@ -299,7 +379,7 @@ where fn build_coinbase(&self, block_fees: &BlockFees) -> Result { let cb: CbData = Foreign::build_coinbase(self, block_fees).map_err(|e| e.kind())?; - Ok(VersionedCoinbase::into_version(cb, SlateVersion::V4)) + Ok(VersionedCoinbase::into_version(cb, SlateVersion::V5)) } fn receive_tx( @@ -308,7 +388,7 @@ where dest_acct_name: Option, dest: Option, ) -> Result { - let version = in_slate.version(); + let v = in_slate.version(); let slate_from = Slate::from(in_slate); let out_slate = Foreign::receive_tx( self, @@ -317,14 +397,32 @@ where dest, ) .map_err(|e| e.kind())?; - Ok(VersionedSlate::into_version(out_slate, version).map_err(|e| e.kind())?) + Ok(VersionedSlate::into_version(out_slate, v).map_err(|e| e.kind())?) + } + + fn receive_atomic_tx( + &self, + in_slate: VersionedSlate, + dest_acct_name: Option, + dest: Option, + ) -> Result { + let v = in_slate.version(); + let slate_from = Slate::from(in_slate); + let out_slate = Foreign::receive_atomic_tx( + self, + &slate_from, + dest_acct_name.as_ref().map(String::as_str), + dest, + ) + .map_err(|e| e.kind())?; + Ok(VersionedSlate::into_version(out_slate, v).map_err(|e| e.kind())?) } fn finalize_tx(&self, in_slate: VersionedSlate) -> Result { - let version = in_slate.version(); + let v = in_slate.version(); let out_slate = Foreign::finalize_tx(self, &Slate::from(in_slate), true).map_err(|e| e.kind())?; - Ok(VersionedSlate::into_version(out_slate, version).map_err(|e| e.kind())?) + Ok(VersionedSlate::into_version(out_slate, v).map_err(|e| e.kind())?) } } @@ -344,8 +442,8 @@ pub fn run_doctest_foreign( test_dir: &str, use_token: bool, blocks_to_mine: u64, - init_tx: bool, - init_invoice_tx: bool, + do_tx: bool, + tx_flow: TxFlow, ) -> Result, String> { use easy_jsonrpc_mw::Handler; use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy}; @@ -467,59 +565,129 @@ pub fn run_doctest_foreign( assert!(wallet_refreshed); } - if init_invoice_tx { - let amount = 60_000_000_000; - let mut slate = { - let mut w_lock = wallet2.lock(); - let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); - let args = IssueInvoiceTxArgs { - amount, - ..Default::default() - }; - api_impl::owner::issue_invoice_tx(&mut **w, (&mask2).as_ref(), args, true).unwrap() - }; - slate = { - let mut w_lock = wallet1.lock(); - let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - api_impl::owner::process_invoice_tx(&mut **w, (&mask1).as_ref(), &slate, args, true) - .unwrap() - }; - println!("INIT INVOICE SLATE"); - // Spit out slate for input to finalize_tx - println!("{}", serde_json::to_string_pretty(&slate).unwrap()); - } - - if init_tx { - let amount = 60_000_000_000; + if tx_flow == TxFlow::Atomic { + let amount = 60_012_500_000; let mut w_lock = wallet1.lock(); let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); let args = InitTxArgs { src_acct_name: None, amount, - minimum_confirmations: 2, + minimum_confirmations: 0, max_outputs: 500, num_change_outputs: 1, selection_strategy_is_use_all: true, + is_multisig: Some(true), ..Default::default() }; - let slate = api_impl::owner::init_send_tx(&mut **w, (&mask1).as_ref(), args, true).unwrap(); - println!("INIT SLATE"); - // Spit out slate for input to finalize_tx - println!("{}", serde_json::to_string_pretty(&slate).unwrap()); + let mut sl = api_impl::owner::init_send_tx(&mut **w, mask1.as_ref(), args, true).unwrap(); + { + let mut w_lock = wallet2.lock(); + let w2 = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + sl = + api_impl::foreign::receive_tx(&mut **w2, mask2.as_ref(), &sl, None, false).unwrap(); + + // Spit out slate for input to finalize_tx + println!("LOCKING TX"); + api_impl::owner::tx_lock_outputs(&mut **w, mask1.as_ref(), &sl).unwrap(); + + sl = + api_impl::owner::process_multisig_tx(&mut **w, mask1.as_ref(), &sl, false).unwrap(); + sl = api_impl::foreign::finalize_tx(&mut **w2, mask2.as_ref(), &sl, false).unwrap(); + } + + let _ = api_impl::owner::finalize_tx(&mut **w, mask1.as_ref(), &sl).unwrap(); + } + + if do_tx { + match tx_flow { + TxFlow::Invoice => { + let amount = 60_000_000_000; + let mut slate = { + let mut w_lock = wallet2.lock(); + let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + let args = IssueInvoiceTxArgs { + amount, + ..Default::default() + }; + api_impl::owner::issue_invoice_tx(&mut **w, (&mask2).as_ref(), args, true) + .unwrap() + }; + slate = { + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + api_impl::owner::process_invoice_tx( + &mut **w, + (&mask1).as_ref(), + &slate, + args, + true, + ) + .unwrap() + }; + println!("INIT INVOICE SLATE"); + // Spit out slate for input to finalize_tx + println!("{}", serde_json::to_string_pretty(&slate).unwrap()); + } + TxFlow::Standard => { + let amount = 60_000_000_000; + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate = + api_impl::owner::init_send_tx(&mut **w, (&mask1).as_ref(), args, true).unwrap(); + println!("INIT SLATE"); + // Spit out slate for input to finalize_tx + println!("{}", serde_json::to_string_pretty(&slate).unwrap()); + } + TxFlow::Atomic => { + let amount = 60_000_000_000; + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 0, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + multisig_path: Some("m/3489303641/867659429/4142508075/4116169848".into()), + ..Default::default() + }; + let slate = + api_impl::owner::init_atomic_swap(&mut **w, (&mask1).as_ref(), args, true) + .unwrap(); + println!("INIT SLATE"); + // Spit out slate for input to finalize_tx + println!("{}", serde_json::to_string_pretty(&slate).unwrap()); + } + TxFlow::Multisig => { + unimplemented!("multisig"); + } + } } - let mut api_foreign = match init_invoice_tx { - false => Foreign::new(wallet1, mask1, Some(test_check_middleware), true), - true => Foreign::new(wallet2, mask2, Some(test_check_middleware), true), + let mut api_foreign = match tx_flow { + TxFlow::Standard | TxFlow::Atomic | TxFlow::Multisig => { + Foreign::new(wallet1, mask1, Some(test_check_middleware), true) + } + TxFlow::Invoice => Foreign::new(wallet2, mask2, Some(test_check_middleware), true), }; api_foreign.doctest_mode = true; let foreign_api = &api_foreign as &dyn ForeignRpc; @@ -531,11 +699,12 @@ pub fn run_doctest_foreign( #[doc(hidden)] #[macro_export] macro_rules! doctest_helper_json_rpc_foreign_assert_response { - ($request:expr, $expected_response:expr, $use_token:expr, $blocks_to_mine:expr, $init_tx:expr, $init_invoice_tx:expr) => { + ($request:expr, $expected_response:expr, $use_token:expr, $blocks_to_mine:expr, $do_tx:expr, $tx_flow:expr) => { // create temporary wallet, run jsonrpc request on owner api of wallet, delete wallet, return // json response. // In order to prevent leaking tempdirs, This function should not panic. use grin_wallet_api::run_doctest_foreign; + use grin_wallet_libwallet::TxFlow; use serde_json; use serde_json::Value; use tempfile::tempdir; @@ -555,8 +724,8 @@ macro_rules! doctest_helper_json_rpc_foreign_assert_response { dir, $use_token, $blocks_to_mine, - $init_tx, - $init_invoice_tx, + $do_tx, + $tx_flow, ) .unwrap() .unwrap(); diff --git a/api/src/owner.rs b/api/src/owner.rs index bf1710dd1..c6bdb639d 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -22,17 +22,17 @@ use crate::config::{TorConfig, WalletConfig}; use crate::core::global; use crate::impls::HttpSlateSender; use crate::impls::SlateSender as _; -use crate::keychain::{Identifier, Keychain}; +use crate::keychain::{Identifier, Keychain, SwitchCommitmentType}; use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage}; use crate::libwallet::api_impl::{owner, owner_updater}; use crate::libwallet::{ AcctPathMapping, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult, - OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress, TxLogEntry, WalletInfo, - WalletInst, WalletLCProvider, + OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress, TxFlow, TxLogEntry, + WalletInfo, WalletInst, WalletLCProvider, }; use crate::util::logger::LoggingConfig; use crate::util::secp::key::SecretKey; -use crate::util::{from_hex, static_secp_instance, Mutex, ZeroingString}; +use crate::util::{from_hex, static_secp_instance, Mutex, ToHex, ZeroingString}; use grin_wallet_util::OnionV3Address; use std::convert::TryFrom; use std::sync::atomic::{AtomicBool, Ordering}; @@ -801,7 +801,8 @@ where /// /// // . . . /// // The slate has been recieved from the invoicer, somehow - /// # let slate = Slate::blank(2, true); + /// # use grin_wallet_libwallet::TxFlow; + /// # let slate = Slate::blank(2, TxFlow::Invoice); /// let args = InitTxArgs { /// src_acct_name: None, /// amount: slate.amount, @@ -860,6 +861,124 @@ where } } + /// Process a multisig transaction to perform step 1 + 2 in the multisig bulletproof creation + /// process. + /// + /// # Arguments + /// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if + /// being used. + /// * `slate` - The transaction [`Slate`](../grin_wallet_libwallet/slate/struct.Slate.html). The + /// receiver should have filled in round 0 and 1 of the multisig bulletproof. + /// + /// # Returns + /// * a result containing: + /// * The transaction [Slate](../grin_wallet_libwallet/slate/struct.Slate.html), + /// which can be forwarded to the receiving party by any means. Once the caller receives the final partial + /// bulletproof from the receiver, the associated wallet transaction outputs should be locked via a call to + /// [`tx_lock_outputs`](struct.Owner.html#method.tx_lock_outputs). This must be called before calling + /// [`finalize_tx`](struct.Owner.html#method.finalize_tx). + /// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered. + /// + /// # Remarks + /// + /// * This method will store a partially completed transaction in the wallet's transaction log, + /// which will be updated on the corresponding call to [`finalize_tx`](struct.Owner.html#method.finalize_tx). + /// + /// # Example + /// Set up as in [new](struct.Owner.html#method.new) method above. + /// ```rust,no_run + /// # use grin_wallet_libwallet::TxFlow; + /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); + /// + /// let mut api_owner = Owner::new(wallet.clone(), None); + /// // Normally, slate is the output of multisig `receive_tx` + /// # let slate = Slate::blank(2, TxFlow::Multisig); + /// // Attempt to create a transaction using the 'default' account + /// let result = api_owner.process_multisig_tx( + /// None, + /// &slate, + /// ); + /// + /// if let Ok(slate) = result { + /// // Send slate somehow + /// // ... + /// // Lock our outputs if we're happy the slate was (or is being) sent + /// api_owner.tx_lock_outputs(None, &slate); + /// } + /// ``` + pub fn process_multisig_tx( + &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::process_multisig_tx(&mut **w, keychain_mask, slate, self.doctest_mode) + } + + /// Initializes an atomic swap transaction. The transaction can either be + /// the main or refund. To create a refund transaction, set `args.late_lock = Some(true)`. + pub fn init_atomic_swap( + &self, + keychain_mask: Option<&SecretKey>, + args: InitTxArgs, + ) -> Result { + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let send_args = args.send_args.clone(); + let slate = owner::init_atomic_swap(&mut **w, keychain_mask, args, self.doctest_mode)?; + if let Some(sa) = send_args { + let tor_config_lock = self.tor_config.lock(); + let tc = tor_config_lock.clone(); + let tc = match tc { + Some(mut c) => { + c.skip_send_attempt = Some(sa.skip_tor); + Some(c) + } + None => None, + }; + let res = + try_slatepack_sync_workflow(&slate, &sa.dest, tc, None, false, self.doctest_mode); + match res { + Ok(s) => Ok(s.unwrap()), + Err(_) => Ok(slate), + } + } else { + Ok(slate) + } + } + + /// Countersign the atomic swap transaction. Creates the first partial signature + /// over the transaction that contributes to the kernel excess signature. + pub fn countersign_atomic_swap( + &self, + slate: &Slate, + keychain_mask: Option<&SecretKey>, + r_addr: Option, + ) -> Result { + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let slate = owner::countersign_atomic_swap(&mut **w, &slate, keychain_mask)?; + match r_addr { + Some(a) => { + let tor_config_lock = self.tor_config.lock(); + let res = try_slatepack_sync_workflow( + &slate, + &a, + tor_config_lock.clone(), + None, + false, + self.doctest_mode, + ); + match res { + Ok(s) => return Ok(s.unwrap()), + Err(_) => return Ok(slate), + } + } + None => Ok(slate), + } + } + /// Locks the outputs associated with the inputs to the transaction in the given /// [`Slate`](../grin_wallet_libwallet/slate/struct.Slate.html), /// making them unavailable for use in further transactions. This function is called @@ -990,6 +1109,125 @@ where owner::finalize_tx(&mut **w, keychain_mask, slate) } + /// Finalizes an atomic swap transaction, after all parties + /// have filled in all rounds of Slate generation. This step adds + /// all participants partial signatures to create the final signature, + /// resulting in a final transaction that is ready to post to a node. + /// + /// Note that this function DOES NOT POST the transaction to a node + /// for validation. This is done in separately via the + /// [`post_tx`](struct.Owner.html#method.post_tx) function. + /// + /// This function also stores the final transaction in the user's wallet files for retrieval + /// via the [`get_stored_tx`](struct.Owner.html#method.get_stored_tx) function. + /// + /// Combined with the partial signature from the second round, the kernel signature is used + /// to recover the atomic secret for unlocking funds on the other chain. + /// + /// # Arguments + /// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if + /// being used. + /// * `slate` - The transaction [`Slate`](../grin_wallet_libwallet/slate/struct.Slate.html). All + /// participants must have filled in both rounds, and the sender should have locked their + /// outputs (via the [`tx_lock_outputs`](struct.Owner.html#method.tx_lock_outputs) function). + /// + /// # Returns + /// * ``Ok([`slate`](../grin_wallet_libwallet/slate/struct.Slate.html))` if successful, + /// containing the new finalized slate. + /// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered. + /// + /// # Example + /// Set up as in [`new`](struct.Owner.html#method.new) method above. + /// ``` + /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); + /// + /// let mut api_owner = Owner::new(wallet.clone(), None); + /// let args = InitTxArgs { + /// src_acct_name: None, + /// amount: 2_000_000_000, + /// minimum_confirmations: 10, + /// max_outputs: 500, + /// num_change_outputs: 1, + /// selection_strategy_is_use_all: false, + /// ..Default::default() + /// }; + /// let result = api_owner.init_send_tx( + /// None, + /// args, + /// ); + /// + /// if let Ok(slate) = result { + /// // Send slate somehow + /// // ... + /// // Lock our outputs if we're happy the slate was (or is being) sent + /// let res = api_owner.tx_lock_outputs(None, &slate); + /// // + /// // Retrieve slate back from recipient + /// // + /// let res = api_owner.finalize_atomic_swap(None, &slate); + /// } + /// ``` + pub fn finalize_atomic_swap( + &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::finalize_atomic_swap(&mut **w, keychain_mask, slate) + } + + /// Recover the atomic secret from the second round adaptor signature, and the finalized kernel + /// excess signature. Use the atomic secret to recover funds on the other chain + /// # Arguments + /// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using + /// * `slate` - Third round atomic swap transaction slate with the initiator's partial signature + /// + /// # Returns + /// * `Ok(())` if successful + /// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered. + pub fn recover_atomic_secret( + &self, + keychain_mask: Option<&SecretKey>, + slate: &Slate, + ) -> Result<(), Error> { + let atomic_id = + owner::recover_atomic_secret(&mut self.wallet_inst.clone(), keychain_mask, slate)?; + self.get_atomic_secrets( + keychain_mask, + Slate::atomic_id_to_int(&atomic_id)?, + slate.amount, + ) + } + + /// Recover the atomic secret from the second round adaptor signature, and the finalized kernel + /// excess signature. Use the atomic secret to recover funds on the other chain + /// # Arguments + /// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using + /// * `id` - Unique atomic swap identifier to recover stored atomic secrets + /// + /// # Returns + /// * `Ok(())` if successful + /// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered. + pub fn get_atomic_secrets( + &self, + keychain_mask: Option<&SecretKey>, + id: u32, + amount: u64, + ) -> Result<(), Error> { + let mut w_lock = self.wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let atomic_id = Slate::create_atomic_id(id); + let keychain = w.keychain(keychain_mask)?; + let our_nonce = keychain.derive_key(amount, &atomic_id, SwitchCommitmentType::Regular)?; + let rec_nonce = w.get_recovered_atomic_secret(keychain_mask, &atomic_id)?; + info!("Your atomic secret:"); + info!("{}\n", our_nonce.to_hex()); + info!("Recovered atomic secret:"); + info!("{}\n", rec_nonce.to_hex()); + Ok(()) + } + /// Posts a completed transaction to the listening node for validation and inclusion in a block /// for mining. /// @@ -2338,7 +2576,7 @@ pub fn try_slatepack_sync_workflow( return Ok(None); } } - let mut ret_slate = Slate::blank(2, false); + let mut ret_slate = Slate::blank(2, TxFlow::Standard); let mut send_sync = |mut sender: HttpSlateSender, method_str: &str| match sender .send_tx(&slate, send_to_finalize) { diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index 6e61195e3..c305b74b9 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -21,7 +21,7 @@ use crate::keychain::{Identifier, Keychain}; use crate::libwallet::{ AcctPathMapping, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult, OutputCommitMapping, PaymentProof, Slate, SlateVersion, Slatepack, SlatepackAddress, - StatusMessage, TxLogEntry, VersionedSlate, WalletInfo, WalletLCProvider, + StatusMessage, TxFlow, TxLogEntry, VersionedSlate, WalletInfo, WalletLCProvider, }; use crate::util::logger::LoggingConfig; use crate::util::secp::key::{PublicKey, SecretKey}; @@ -73,7 +73,7 @@ pub trait OwnerRpc { "id": 1 } # "# - # , 4, false, false, false, false); + # , 4, false, false, false, false, false, false); ``` */ fn accounts(&self, token: Token) -> Result, ErrorKind>; @@ -106,7 +106,7 @@ pub trait OwnerRpc { "id": 1 } # "# - # , 4, false, false, false, false); + # , 4, false, false, false, false, false, false); ``` */ fn create_account_path(&self, token: Token, label: &String) -> Result; @@ -139,7 +139,7 @@ pub trait OwnerRpc { "id": 1 } # "# - # , 4, false, false, false, false); + # , 4, false, false, false, false, false, false); ``` */ fn set_active_account(&self, token: Token, label: &String) -> Result<(), ErrorKind>; @@ -179,6 +179,7 @@ pub trait OwnerRpc { "commit": "08e1da9e6dc4d6e808a718b2f110a991dd775d65ce5ae408a4e1f002a4961aa9e7", "height": "1", "is_coinbase": true, + "is_multisig": false, "key_id": "0300000000000000000000000000000000", "lock_height": "4", "mmr_index": null, @@ -195,6 +196,7 @@ pub trait OwnerRpc { "commit": "087df32304c5d4ae8b2af0bc31e700019d722910ef87dd4eec3197b80b207e3045", "height": "2", "is_coinbase": true, + "is_multisig": false, "key_id": "0300000000000000000000000100000000", "lock_height": "5", "mmr_index": null, @@ -210,7 +212,7 @@ pub trait OwnerRpc { } } # "# - # , 2, false, false, false, false); + # , 2, false, false, false, false, false, false); ``` */ fn retrieve_outputs( @@ -295,7 +297,7 @@ pub trait OwnerRpc { } } # "# - # , 2, false, false, false, false); + # , 2, false, false, false, false, false, false); ``` */ @@ -347,7 +349,7 @@ pub trait OwnerRpc { } } # "# - # , 4, false, false, false, false); + # , 4, false, false, false, false, false, false); ``` */ @@ -406,12 +408,12 @@ pub trait OwnerRpc { } ], "sta": "S1", - "ver": "4:2" + "ver": "5:2" } } } # "# - # , 4, false, false, false, false); + # , 4, false, false, false, false, false, false); ``` */ @@ -453,12 +455,12 @@ pub trait OwnerRpc { } ], "sta": "I1", - "ver": "4:2" + "ver": "5:2" } } } # "# - # , 4, false, false, false, false); + # , 4, false, false, false, false, false, false); ``` */ @@ -469,7 +471,7 @@ pub trait OwnerRpc { ) -> Result; /** - ;Networked version of [Owner::process_invoice_tx](struct.Owner.html#method.process_invoice_tx). + Networked version of [Owner::process_invoice_tx](struct.Owner.html#method.process_invoice_tx). ``` # grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!( @@ -490,7 +492,7 @@ pub trait OwnerRpc { } ], "sta": "I1", - "ver": "4:2" + "ver": "5:2" }, "args": { "src_acct_name": null, @@ -536,12 +538,12 @@ pub trait OwnerRpc { } ], "sta": "I2", - "ver": "4:2" + "ver": "5:2" } } } # "# - # , 4, false, false, false, false); + # , 4, false, false, false, false, false, false); ``` */ @@ -552,6 +554,76 @@ pub trait OwnerRpc { args: InitTxArgs, ) -> Result; + /** + Networked version of [Owner::process_multisig_tx](struct.Owner.html#method.process_multisig_tx). + + # Json rpc example + + ``` + # grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!( + # r#" + { + "jsonrpc": "2.0", + "method": "process_multisig_tx", + "params": { + "token": "d202964900000000d302964900000000d402964900000000d502964900000000", + "slate": { + "amt": "5001250000", + "id": "0436430c-2b02-624c-2032-570501212b00", + "off": "d202964900000000d302964900000000d402964900000000d502964900000000", + "sigs": [ + { + "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "xs": "028e95921cc0d5be5922362265d352c9bdabe51a9e1502a3f0d4a10387f1893f40", + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841be7bf31d80494f5e4a3d656649b1610c61a268f9cafcfc604b5d9f25efb2aa3c5", + "part_commit": "091c34a190f7352149a343e3652d3f9e37972d822440e0d8ee03c658f003e178b4", + "tau_one": "02799bbf36460d56c1999bedd02acfefde8fd608f8b9ecc9bf02559a6d203cf308", + "tau_two": "020052d5842dc9a75859384b5136573545dc60553d766cad01f0665dbca937fb9a" + } + ], + "sta": "M3", + "ver": "5:2" + } + }, + "id": 1 + } + # "# + # , + # r#" + { + "id": 1, + "jsonrpc": "2.0", + "result": { + "Ok": { + "amt": "5001250000", + "id": "0436430c-2b02-624c-2032-570501212b00", + "off": "d202964900000000d302964900000000d402964900000000d502964900000000", + "sigs": [ + { + "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841be7bf31d80494f5e4a3d656649b1610c61a268f9cafcfc604b5d9f25efb2aa3c5", + "part_commit": "091c34a190f7352149a343e3652d3f9e37972d822440e0d8ee03c658f003e178b4", + "tau_one": "0262ce501b38d17d3b378a198c94ab8dbbc7fbc6482543bea6d9f526350f874bd9", + "tau_two": "0204504dd81a04fbb104f3c5980dcbdc58f0b7084a721c760bbb47bc639b2e144d", + "tau_x": "3b77bcde9039b5219eabfd4b386981107659174bc1bf33283ee0ef1a2063456f", + "xs": "028e95921cc0d5be5922362265d352c9bdabe51a9e1502a3f0d4a10387f1893f40" + } + ], + "sta": "M3", + "ver": "5:2" + } + } + } + # "# + # , 5, false, true, false, false, false, true); + ``` + */ + fn process_multisig_tx( + &self, + token: Token, + slate: VersionedSlate, + ) -> Result; + /** Networked version of [Owner::tx_lock_outputs](struct.Owner.html#method.tx_lock_outputs). @@ -565,7 +637,7 @@ pub trait OwnerRpc { "params": { "token": "d202964900000000d302964900000000d402964900000000d502964900000000", "slate": { - "ver": "4:2", + "ver": "5:2", "id": "0436430c-2b02-624c-2032-570501212b00", "sta": "S1", "off": "d202964900000000d302964900000000d402964900000000d502964900000000", @@ -591,12 +663,163 @@ pub trait OwnerRpc { } } # "# - # , 5 ,true, false, false, false); + # , 5 ,true, false, false, false, false, false); ``` */ fn tx_lock_outputs(&self, token: Token, slate: VersionedSlate) -> Result<(), ErrorKind>; + /** + ;Networked version of [Owner::init_atomic_swap](struct.Owner.html#method.init_atomic_swap). + + ``` + # grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!( + # r#" + { + "jsonrpc": "2.0", + "method": "init_atomic_swap", + "params": { + "token": "d202964900000000d302964900000000d402964900000000d502964900000000", + "args": { + "src_acct_name": null, + "amount": "5000000000", + "minimum_confirmations": 0, + "max_outputs": 500, + "num_change_outputs": 1, + "selection_strategy_is_use_all": true, + "target_slate_version": null, + "payment_proof_recipient_address": "tgrin1xtxavwfgs48ckf3gk8wwgcndmn0nt4tvkl8a7ltyejjcy2mc6nfs9gm2lp", + "multisig_path": "m/860601635/303558731/534026549/1571534207", + "ttl_blocks": null, + "multisig_path": "m/1018305059/3401844484/447546778/208817231", + "send_args": null + } + }, + "id": 1 + } + # "# + # , + # r#" + { + "id": 1, + "jsonrpc": "2.0", + "result": { + "Ok": { + "amt": "5000000000", + "fee": "23000000", + "id": "0436430c-2b02-624c-2032-570501212b01", + "multisig_key_id": "043cb21a23cac407041aad059a0c724c4f", + "sigs": [ + { + "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "xs": "02e89cce4499ac1e9bb498dab9e3fab93cc40cd3d26c04a0292e00f4bf272499ec" + } + ], + "sta": "A1", + "ver": "5:2" + } + } + } + # "# + # , 4, false, true, false, false, true, false); + ``` + */ + fn init_atomic_swap(&self, token: Token, args: InitTxArgs) + -> Result; + + /** + Networked version of [Owner::countersign_atomic_swap](struct.Owner.html#method.countersign_atomic_swap). + + ``` + # grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!( + # r#" + { + "jsonrpc": "2.0", + "method": "countersign_atomic_swap", + "id": 1, + "params": { + "token": "d202964900000000d302964900000000d402964900000000d502964900000000", + "slate": { + "ver": "5:2", + "id": "0436430c-2b02-624c-2032-570501212b01", + "sta": "A2", + "off": "a5a632f26f27a9b71e98c1c8b8098bb41204ffcfd206d995f9c16d10764ad95a", + "amt": "50000000000", + "fee": "23500000", + "sigs": [ + { + "xs": "02e89cce4499ac1e9bb498dab9e3fab93cc40cd3d26c04a0292e00f4bf272499ec", + "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f" + }, + { + "xs": "02e3c128e436510500616fef3f9a22b15ca015f407c8c5cf96c9059163c873828f", + "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "atomic": "03f94457808f7e1e68e07fe7dd0c43414220c1656573428c1b616175714f9d7c85", + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b5205d0410c300529f342722369713adb64819dcbebee633f023e97cf3116a1a8" + } + ], + "coms": [ + { + "c": "099b48cfb1f80a2347dc89818449e68e76a3c6817a532a8e9ef2b4a5ccf4363850", + "p": "29701ceae262cac77b79b868c883a292e61e6de8192b868edcd1300b0973d91396b156ace6bd673402a303de10ddd8a5e6b7f17ba6557a574a672bd04cc273ab04ed8e2ca80bac483345c0ec843f521814ce1301ec9adc38956a12b4d948acce71295a4f52bcdeb8a1c9f2d6b2da5d731262a5e9c0276ef904df9ef8d48001420cd59f75a2f1ae5c7a1c7c6b9f140e7613e52ef9e249f29f9340b7efb80699e460164324616f98fd4cde3db52497c919e95222fffeacb7e65deca7e368a80ce713c19de7da5369726228ee336f5bd494538c12ccbffeb1b9bfd5fc8906d1c64245b516f103fa96d9c56975837652c1e0fa5803d7ccf1147d8f927e36da717f7ad79471dbe192f5f50f87a79fc3fe030dba569b634b92d2cf307993cce545633af263897cd7e6ebf4dcafb176d07358bdc38d03e45a49dfa9c8c6517cd68d167ffbf6c3b4de0e2dd21909cbad4c467b84e5700be473a39ac59c669d7c155c4bcab9b8026eea3431c779cd277e4922d2b9742e1f6678cbe869ec3b5b7ef4132ddb6cdd06cf27dbeb28be72b949fa897610e48e3a0d789fd2eea75abc97b3dc7e00e5c8b3d24e40c6f24112adb72352b89a2bef0599345338e9e76202a3c46efa6370952b2aca41aadbae0ea32531acafcdab6dd066d769ebf50cf4f3c0a59d2d5fa79600a207b9417c623f76ad05e8cccfcd4038f9448bc40f127ca7c0d372e46074e334fe49f5a956ec0056f4da601e6af80eb1a6c4951054869e665b296d8c14f344ca2dc5fdd5df4a3652536365a1615ad9b422165c77bf8fe65a835c8e0c41e070014eb66ef8c525204e990b3a3d663c1e42221b496895c37a2f0c1bf05e91235409c3fe3d89a9a79d6c78609ab18a463311911f71fa37bb73b15fcd38143d1404fd2ce81004dc7ff89cf1115dcc0c35ce1c1bf9941586fb959770f2618ccb7118a7" + } + ], + "atomic_id": "046d7761746f6d69630000000000000000" + }, + "r_addr": null + } + } + # "# + # , + # r#" + { + "id": 1, + "jsonrpc": "2.0", + "result": { + "Ok": { + "amt": "50000000000", + "coms": [ + { + "c": "09632f4e0fdec7e7113aee155113e591c5e55e0e000a879eca63e44835057a4d13", + "f": 2 + }, + { + "c": "099b48cfb1f80a2347dc89818449e68e76a3c6817a532a8e9ef2b4a5ccf4363850", + "p": "29701ceae262cac77b79b868c883a292e61e6de8192b868edcd1300b0973d91396b156ace6bd673402a303de10ddd8a5e6b7f17ba6557a574a672bd04cc273ab04ed8e2ca80bac483345c0ec843f521814ce1301ec9adc38956a12b4d948acce71295a4f52bcdeb8a1c9f2d6b2da5d731262a5e9c0276ef904df9ef8d48001420cd59f75a2f1ae5c7a1c7c6b9f140e7613e52ef9e249f29f9340b7efb80699e460164324616f98fd4cde3db52497c919e95222fffeacb7e65deca7e368a80ce713c19de7da5369726228ee336f5bd494538c12ccbffeb1b9bfd5fc8906d1c64245b516f103fa96d9c56975837652c1e0fa5803d7ccf1147d8f927e36da717f7ad79471dbe192f5f50f87a79fc3fe030dba569b634b92d2cf307993cce545633af263897cd7e6ebf4dcafb176d07358bdc38d03e45a49dfa9c8c6517cd68d167ffbf6c3b4de0e2dd21909cbad4c467b84e5700be473a39ac59c669d7c155c4bcab9b8026eea3431c779cd277e4922d2b9742e1f6678cbe869ec3b5b7ef4132ddb6cdd06cf27dbeb28be72b949fa897610e48e3a0d789fd2eea75abc97b3dc7e00e5c8b3d24e40c6f24112adb72352b89a2bef0599345338e9e76202a3c46efa6370952b2aca41aadbae0ea32531acafcdab6dd066d769ebf50cf4f3c0a59d2d5fa79600a207b9417c623f76ad05e8cccfcd4038f9448bc40f127ca7c0d372e46074e334fe49f5a956ec0056f4da601e6af80eb1a6c4951054869e665b296d8c14f344ca2dc5fdd5df4a3652536365a1615ad9b422165c77bf8fe65a835c8e0c41e070014eb66ef8c525204e990b3a3d663c1e42221b496895c37a2f0c1bf05e91235409c3fe3d89a9a79d6c78609ab18a463311911f71fa37bb73b15fcd38143d1404fd2ce81004dc7ff89cf1115dcc0c35ce1c1bf9941586fb959770f2618ccb7118a7" + } + ], + "fee": "12500000", + "id": "0436430c-2b02-624c-2032-570501212b01", + "off": "98b0ec495f87dabacee15e3af88b316179bbd26e0481ba49bc5c1d5a73c97a08", + "sigs": [ + { + "atomic": "03f94457808f7e1e68e07fe7dd0c43414220c1656573428c1b616175714f9d7c85", + "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b5205d0410c300529f342722369713adb64819dcbebee633f023e97cf3116a1a8", + "xs": "02e3c128e436510500616fef3f9a22b15ca015f407c8c5cf96c9059163c873828f" + }, + { + "nonce": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "part": "8f07ddd5e9f5179cff19486034181ed76505baaad53e5d994064127b56c5841b04e1e15ceb1b5dbab8baf7750d7bd4aad6cfe97b83e4dc080dae328eb75881fd", + "xs": "02e89cce4499ac1e9bb498dab9e3fab93cc40cd3d26c04a0292e00f4bf272499ec" + } + ], + "sta": "A3", + "ver": "5:2" + } + } + } + # "# + # , 5 , true, true, false, false, true, false); + ``` + */ + fn countersign_atomic_swap( + &self, + token: Token, + slate: VersionedSlate, + r_addr: Option, + ) -> Result; + /** Networked version of [Owner::finalize_tx](struct.Owner.html#method.finalize_tx). @@ -611,7 +834,7 @@ pub trait OwnerRpc { "token": "d202964900000000d302964900000000d402964900000000d502964900000000", "slate": { - "ver": "4:2", + "ver": "5:2", "id": "0436430c-2b02-624c-2032-570501212b00", "sta": "S2", "off": "6c6a69136154775488782121887bb3c32787a8320551fdb9732ec2d333fe54ee", @@ -673,12 +896,12 @@ pub trait OwnerRpc { } ], "sta": "S3", - "ver": "4:2" + "ver": "5:2" } } } # "# - # , 5, true, true, false, false); + # , 5, true, true, false, false, false, false); ``` */ fn finalize_tx(&self, token: Token, slate: VersionedSlate) @@ -697,7 +920,7 @@ pub trait OwnerRpc { "params": { "token": "d202964900000000d302964900000000d402964900000000d502964900000000", "slate": { - "ver": "4:2", + "ver": "5:2", "id": "0436430c-2b02-624c-2032-570501212b00", "sta": "S3", "off": "750dbf4fd43b7f4cfd68d2698a522f3ff6e6a00ad9895b33f1ec46493b837b49", @@ -747,7 +970,7 @@ pub trait OwnerRpc { } } # "# - # , 5, true, true, true, false); + # , 5, true, true, true, false, false, false); ``` */ @@ -781,7 +1004,7 @@ pub trait OwnerRpc { } } # "# - # , 5, true, true, false, false); + # , 5, true, true, false, false, false, false); ``` */ fn cancel_tx( @@ -825,12 +1048,12 @@ pub trait OwnerRpc { "id": "0436430c-2b02-624c-2032-570501212b00", "sigs": [], "sta": "S3", - "ver": "4:3" + "ver": "5:3" } } } # "# - # , 5, true, true, false, false); + # , 5, true, true, false, false, false, false); ``` */ fn get_stored_tx( @@ -868,7 +1091,7 @@ pub trait OwnerRpc { } } # "# - # , 1, false, false, false, false); + # , 1, false, false, false, false, false, false); ``` */ fn scan( @@ -907,7 +1130,7 @@ pub trait OwnerRpc { } } # "# - # , 5, false, false, false, false); + # , 5, false, false, false, false, false, false); ``` */ fn node_height(&self, token: Token) -> Result; @@ -990,7 +1213,7 @@ pub trait OwnerRpc { } } # "# - # , 5, false, false, false, false); + # , 5, false, false, false, false, false, false); ``` */ @@ -1020,7 +1243,7 @@ pub trait OwnerRpc { } } # "# - # , 5, false, false, false, false); + # , 5, false, false, false, false, false, false); ``` */ @@ -1086,7 +1309,7 @@ pub trait OwnerRpc { } } # "# - # , 5, false, false, false, false); + # , 5, false, false, false, false, false, false); ``` */ fn create_config( @@ -1124,7 +1347,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ @@ -1161,7 +1384,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ @@ -1191,7 +1414,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ @@ -1222,7 +1445,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ @@ -1254,7 +1477,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ fn change_password( @@ -1288,7 +1511,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ fn delete_wallet(&self, name: Option) -> Result<(), ErrorKind>; @@ -1318,7 +1541,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ @@ -1346,7 +1569,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ fn stop_updater(&self) -> Result<(), ErrorKind>; @@ -1375,7 +1598,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ @@ -1406,7 +1629,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ @@ -1441,7 +1664,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ @@ -1464,7 +1687,7 @@ pub trait OwnerRpc { "sender_index": 0, "recipients": [], "slate": { - "ver": "4:2", + "ver": "5:2", "id": "0436430c-2b02-624c-2032-570501212b00", "sta": "S1", "off": "d202964900000000d302964900000000d402964900000000d502964900000000", @@ -1487,11 +1710,11 @@ pub trait OwnerRpc { "id": 1, "jsonrpc": "2.0", "result": { - "Ok": "BEGINSLATEPACK. xyfzdULuUuM5r3R kS68aywyCuYssPs Jf1JbvnBcK6NDDo ajiGAgh2SPx4t49 xtKuJE3BZCcSEue ksecMmbSoV2DQbX gGcmJniP9UadcmR N1KSc5FBhwAaUjy LXeYDP7EV7Cmsj4 pLaJdZTJTQbccUH 2zG8QTgoEiEWP5V T6rKst1TibmDAFm RRVHYDtskdYJb5G krqfpgN7RjvPfpm Z5ZFyz6ipAt5q9T 2HCjrTxkHdVi9js 22tr2Lx6iXT5vm8 JL6HhjwyFrSaEmN AjsBE8jgiaAABA6 GGZKwcXeXToMfRt nL9DeX1. ENDSLATEPACK." + "Ok": "BEGINSLATEPACK. 6PrWJrSxHAaYXif KSiSbTCJwgsconT yuny3iuyTxN6fd9 rYdUc3x5mTB3vRY sRFxNroQbChWXP4 5AG5JXPvkQezHGU wTLdoPdn5J1TfDe y5SwPMjbDN9Mm6a EraP2XYkcabg28z bic2YZfdNmJGWNa skPVRP3PtatQ4dk MpJ4qnn76w4uby3 EGzUDCJGtJRdbJK CByHc6B97qZLgL2 sKQ5fso4jiHc1xY YskK5J6FpoaDBK8 XRJyGTL9kQ79QUt WcVGDCYHiKpntkw BUmCJqE5iaVZEii zWN7hFh4G8yVfp9 WtdmYNxmM. ENDSLATEPACK." } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ @@ -1546,7 +1769,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ @@ -1588,7 +1811,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ @@ -1633,7 +1856,7 @@ pub trait OwnerRpc { } } # "# - # , 5, true, true, true, true); + # , 5, true, true, true, true, false, false); ``` */ @@ -1680,7 +1903,7 @@ pub trait OwnerRpc { } } # "# - # , 5, true, true, true, true); + # , 5, true, true, true, true, false, false); ``` */ @@ -1718,7 +1941,7 @@ pub trait OwnerRpc { } } # "# - # , 0, false, false, false, false); + # , 0, false, false, false, false, false, false); ``` */ fn set_tor_config(&self, tor_config: Option) -> Result<(), ErrorKind>; @@ -1796,8 +2019,8 @@ where fn init_send_tx(&self, token: Token, args: InitTxArgs) -> Result { let slate = Owner::init_send_tx(self, (&token.keychain_mask).as_ref(), args) .map_err(|e| e.kind())?; - let version = SlateVersion::V4; - Ok(VersionedSlate::into_version(slate, version).map_err(|e| e.kind())?) + let v = slate.version(); + Ok(VersionedSlate::into_version(slate, v).map_err(|e| e.kind())?) } fn issue_invoice_tx( @@ -1807,8 +2030,8 @@ where ) -> Result { let slate = Owner::issue_invoice_tx(self, (&token.keychain_mask).as_ref(), args) .map_err(|e| e.kind())?; - let version = SlateVersion::V4; - Ok(VersionedSlate::into_version(slate, version).map_err(|e| e.kind())?) + let v = slate.version(); + Ok(VersionedSlate::into_version(slate, v).map_err(|e| e.kind())?) } fn process_invoice_tx( @@ -1824,8 +2047,47 @@ where args, ) .map_err(|e| e.kind())?; - let version = SlateVersion::V4; - Ok(VersionedSlate::into_version(out_slate, version).map_err(|e| e.kind())?) + let v = out_slate.version(); + Ok(VersionedSlate::into_version(out_slate, v).map_err(|e| e.kind())?) + } + + fn process_multisig_tx( + &self, + token: Token, + in_slate: VersionedSlate, + ) -> Result { + let out_slate = Owner::process_multisig_tx( + self, + (&token.keychain_mask).as_ref(), + &Slate::from(in_slate), + ) + .map_err(|e| e.kind())?; + Ok(VersionedSlate::into_version(out_slate, SlateVersion::V5).map_err(|e| e.kind())?) + } + + fn init_atomic_swap( + &self, + token: Token, + args: InitTxArgs, + ) -> Result { + let out_slate = Owner::init_atomic_swap(self, (&token.keychain_mask).as_ref(), args) + .map_err(|e| e.kind())?; + let v = out_slate.version(); + Ok(VersionedSlate::into_version(out_slate, v).map_err(|e| e.kind())?) + } + + fn countersign_atomic_swap( + &self, + token: Token, + in_slate: VersionedSlate, + r_addr: Option, + ) -> Result { + let slate = Slate::from(in_slate); + let out_slate = + Owner::countersign_atomic_swap(self, &slate, (&token.keychain_mask).as_ref(), r_addr) + .map_err(|e| e.kind())?; + let v = out_slate.version(); + Ok(VersionedSlate::into_version(out_slate, v).map_err(|e| e.kind())?) } fn finalize_tx( @@ -1839,8 +2101,8 @@ where &Slate::from(in_slate), ) .map_err(|e| e.kind())?; - let version = SlateVersion::V4; - Ok(VersionedSlate::into_version(out_slate, version).map_err(|e| e.kind())?) + let v = out_slate.version(); + Ok(VersionedSlate::into_version(out_slate, v).map_err(|e| e.kind())?) } fn tx_lock_outputs(&self, token: Token, in_slate: VersionedSlate) -> Result<(), ErrorKind> { @@ -1876,12 +2138,10 @@ where ) .map_err(|e| e.kind())?; match out_slate { - Some(s) => { - let version = SlateVersion::V4; - Ok(Some( - VersionedSlate::into_version(s, version).map_err(|e| e.kind())?, - )) - } + Some(s) => Ok({ + let v = s.version(); + Some(VersionedSlate::into_version(s, v).map_err(|e| e.kind())?) + }), None => Ok(None), } } @@ -2082,8 +2342,8 @@ where secret_indices, ) .map_err(|e| e.kind())?; - let version = SlateVersion::V4; - Ok(VersionedSlate::into_version(slate, version).map_err(|e| e.kind())?) + let v = slate.version(); + Ok(VersionedSlate::into_version(slate, v).map_err(|e| e.kind())?) } fn decode_slatepack_message( @@ -2142,6 +2402,8 @@ pub fn run_doctest_owner( lock_tx: bool, finalize_tx: bool, payment_proof: bool, + is_atomic: bool, + is_multisig: bool, ) -> Result, String> { use easy_jsonrpc_mw::Handler; use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy}; @@ -2202,7 +2464,7 @@ pub fn run_doctest_owner( mask1.clone(), ); - let mut slate_outer = Slate::blank(2, false); + let mut slate_outer = Slate::blank(2, TxFlow::Standard); let rec_phrase_2 = util::ZeroingString::from( "hour kingdom ripple lunch razor inquiry coyote clay stamp mean \ @@ -2265,8 +2527,74 @@ pub fn run_doctest_owner( assert!(wallet_refreshed); } + if is_atomic && !perform_tx { + // Calculate the multisig output for the atomic swap + let amount = 50_012_500_000; + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 0, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + payment_proof_recipient_address: None, + is_multisig: Some(true), + ..Default::default() + }; + let mut sl = api_impl::owner::init_send_tx(&mut **w, mask1.as_ref(), args, true).unwrap(); + { + let mut w_lock = wallet2.lock(); + let w2 = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + sl = + api_impl::foreign::receive_tx(&mut **w2, mask2.as_ref(), &sl, None, false).unwrap(); + + // Spit out slate for input to finalize_tx + if lock_tx { + println!("LOCKING TX"); + api_impl::owner::tx_lock_outputs(&mut **w, (&mask1).as_ref(), &sl).unwrap(); + } + + sl = + api_impl::owner::process_multisig_tx(&mut **w, mask1.as_ref(), &sl, false).unwrap(); + sl = api_impl::foreign::finalize_tx(&mut **w2, mask2.as_ref(), &sl, false).unwrap(); + } + + let _ = api_impl::owner::finalize_tx(&mut **w, mask1.as_ref(), &sl).unwrap(); + } + + if is_multisig { + let amount = 50_012_500_000; + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 0, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + payment_proof_recipient_address: None, + is_multisig: Some(true), + ..Default::default() + }; + let mut sl = api_impl::owner::init_send_tx(&mut **w, mask1.as_ref(), args, true).unwrap(); + let mut w_lock = wallet2.lock(); + let w2 = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + sl = api_impl::foreign::receive_tx(&mut **w2, mask2.as_ref(), &sl, None, false).unwrap(); + + // Spit out slate for input to finalize_tx + println!("LOCKING TX"); + api_impl::owner::tx_lock_outputs(&mut **w, (&mask1).as_ref(), &sl).unwrap(); + } + if perform_tx { - let amount = 60_000_000_000; + let amount = if is_atomic { + 50_012_500_000 + } else { + 60_000_000_000 + }; let mut w_lock = wallet1.lock(); let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); let proof_address = match payment_proof { @@ -2285,17 +2613,67 @@ pub fn run_doctest_owner( num_change_outputs: 1, selection_strategy_is_use_all: true, payment_proof_recipient_address: proof_address, + is_multisig: Some(is_multisig), ..Default::default() }; - let mut slate = - api_impl::owner::init_send_tx(&mut **w, (&mask1).as_ref(), args, true).unwrap(); + // Calculate the multisig output for the atomic swap + let sl = if is_atomic { + let mut pre_args = args.clone(); + pre_args.is_multisig = Some(true); + let mut sl = + api_impl::owner::init_send_tx(&mut **w, mask1.as_ref(), pre_args, true).unwrap(); + { + let mut w_lock = wallet2.lock(); + let w2 = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + sl = api_impl::foreign::receive_tx(&mut **w2, mask2.as_ref(), &sl, None, false) + .unwrap(); + + // Spit out slate for input to finalize_tx + println!("LOCKING TX"); + api_impl::owner::tx_lock_outputs(&mut **w, mask1.as_ref(), &sl).unwrap(); + + sl = api_impl::owner::process_multisig_tx(&mut **w, mask1.as_ref(), &sl, false) + .unwrap(); + sl = api_impl::foreign::finalize_tx(&mut **w2, mask2.as_ref(), &sl, false).unwrap(); + } + + api_impl::owner::finalize_tx(&mut **w, mask1.as_ref(), &sl).unwrap() + } else { + Slate::blank(2, TxFlow::Standard) + }; + let mut slate = if is_atomic { + let mut atom_args = args.clone(); + atom_args.amount = 50_000_000_000; + atom_args.minimum_confirmations = 0; + atom_args.multisig_path = Some(sl.create_multisig_id().to_bip_32_string()); + api_impl::owner::init_atomic_swap(&mut **w, (&mask1).as_ref(), atom_args, true).unwrap() + } else { + api_impl::owner::init_send_tx(&mut **w, (&mask1).as_ref(), args, true).unwrap() + }; println!("INITIAL SLATE"); println!("{}", serde_json::to_string_pretty(&slate).unwrap()); { let mut w_lock = wallet2.lock(); let w2 = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); - slate = api_impl::foreign::receive_tx(&mut **w2, (&mask2).as_ref(), &slate, None, true) - .unwrap(); + slate = if is_atomic { + api_impl::foreign::receive_atomic_tx( + &mut **w2, + (&mask2).as_ref(), + &slate, + None, + true, + ) + .unwrap() + } else { + api_impl::foreign::receive_tx( + &mut **w2, + (&mask2).as_ref(), + &slate, + None, + !is_multisig, + ) + .unwrap() + }; w2.close().unwrap(); } // Spit out slate for input to finalize_tx @@ -2306,7 +2684,33 @@ pub fn run_doctest_owner( println!("RECEIPIENT SLATE"); println!("{}", serde_json::to_string_pretty(&slate).unwrap()); if finalize_tx { - slate = api_impl::owner::finalize_tx(&mut **w, (&mask1).as_ref(), &slate).unwrap(); + slate = if is_atomic { + let sl = + api_impl::owner::countersign_atomic_swap(&mut **w, &slate, (&mask1).as_ref()) + .unwrap(); + let mut w_lock = wallet2.lock(); + let w2 = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + let sl = api_impl::foreign::finalize_tx(&mut **w2, (&mask2).as_ref(), &sl, false) + .unwrap(); + w2.close().unwrap(); + sl + } else if is_multisig { + let sl = api_impl::owner::process_multisig_tx( + &mut **w, + (&mask1).as_ref(), + &slate, + false, + ) + .unwrap(); + let mut w_lock = wallet2.lock(); + let w2 = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); + let sl = api_impl::foreign::finalize_tx(&mut **w2, (&mask2).as_ref(), &sl, false) + .unwrap(); + w2.close().unwrap(); + sl + } else { + api_impl::owner::finalize_tx(&mut **w, (&mask1).as_ref(), &slate).unwrap() + }; error!("FINALIZED TX SLATE"); println!("{}", serde_json::to_string_pretty(&slate).unwrap()); } @@ -2339,7 +2743,7 @@ pub fn run_doctest_owner( #[doc(hidden)] #[macro_export] macro_rules! doctest_helper_json_rpc_owner_assert_response { - ($request:expr, $expected_response:expr, $blocks_to_mine:expr, $perform_tx:expr, $lock_tx:expr, $finalize_tx:expr, $payment_proof:expr) => { + ($request:expr, $expected_response:expr, $blocks_to_mine:expr, $perform_tx:expr, $lock_tx:expr, $finalize_tx:expr, $payment_proof:expr, $is_atomic:expr, $is_multisig:expr) => { // create temporary wallet, run jsonrpc request on owner api of wallet, delete wallet, return // json response. // In order to prevent leaking tempdirs, This function should not panic. @@ -2372,6 +2776,8 @@ macro_rules! doctest_helper_json_rpc_owner_assert_response { $lock_tx, $finalize_tx, $payment_proof, + $is_atomic, + $is_multisig, ) .unwrap() .unwrap(); diff --git a/api/tests/slate_versioning.rs b/api/tests/slate_versioning.rs index ede8453b7..7bf69aaea 100644 --- a/api/tests/slate_versioning.rs +++ b/api/tests/slate_versioning.rs @@ -12,9 +12,10 @@ // limitations under the License. //! core::libtx specific tests -//use grin_wallet_api::foreign_rpc_client; +use grin_wallet_api::foreign_rpc_client; use grin_wallet_api::run_doctest_foreign; -//use grin_wallet_libwallet::VersionedSlate; +use grin_wallet_libwallet::TxFlow; +//use grin_wallet_libwallet::{Slate, SlateVersion, VersionedSlate}; use serde_json; use serde_json::Value; use tempfile::tempdir; @@ -52,7 +53,7 @@ fn _receive_versioned_slate() { let request_val: Value = serde_json::from_str(v1_req).unwrap(); let expected_response: Value = serde_json::from_str(v1_resp).unwrap(); - let response = run_doctest_foreign(request_val, dir, false, 5, true, false) + let response = run_doctest_foreign(request_val, dir, false, 5, false, TxFlow::Standard) .unwrap() .unwrap(); @@ -83,52 +84,45 @@ fn receive_tx(vs: VersionedSlate) -> VersionedSlate { ) .unwrap(); let (call, tracker) = bound_method.call(); - let json_response = run_doctest_foreign(call.as_request(), dir, 5, false, false) + let json_response = run_doctest_foreign(call.as_request(), dir, 5, TxFlow::Standard, false) .unwrap() .unwrap(); - let mut response = easy_jsonrpc::Response::from_json_response(json_response).unwrap(); + let mut response = easy_jsonrpc_mw::Response::from_json_response(json_response).unwrap(); tracker.get_return(&mut response).unwrap().unwrap() } #[test] fn version_unchanged() { - let req: Value = serde_json::from_str(include_str!("slates/v1_req.slate")).unwrap(); - let slate: VersionedSlate = serde_json::from_value(req["params"][0].clone()).unwrap(); - let slate_req: Slate = slate.into(); + let req_v4: Value = serde_json::from_str(include_str!("slates/v4_req.slate")).unwrap(); + let slate: VersionedSlate = serde_json::from_value(req_v4["params"][0].clone()).unwrap(); + let mut slate_req: Slate = slate.into(); assert_eq!( receive_tx(VersionedSlate::into_version( - slate_req.clone(), - SlateVersion::V0 - )) + slate_req.clone() + ).unwrap()) .version(), - SlateVersion::V0 + SlateVersion::V4 ); - assert_eq!( - receive_tx(VersionedSlate::into_version( - slate_req.clone(), - SlateVersion::V1 - )) - .version(), - SlateVersion::V1 - ); + let req_v5: Value = serde_json::from_str(include_str!("slates/v5_req.slate")).unwrap(); + let slate: VersionedSlate = serde_json::from_value(req_v5["params"][0].clone()).unwrap(); + slate_req = slate.into(); assert_eq!( receive_tx(VersionedSlate::into_version( - slate_req.clone(), - SlateVersion::V2 - )) + slate_req.clone() + ).unwrap()) .version(), - SlateVersion::V2 + SlateVersion::V5 ); // compile time test will remind us to update these tests when updating slate format fn _all_versions_tested(vs: VersionedSlate) { match vs { - VersionedSlate::V0(_) => (), - VersionedSlate::V1(_) => (), - VersionedSlate::V2(_) => (), + VersionedSlate::V4(_) => (), + VersionedSlate::V5(_) => (), } } -}*/ +} +*/ diff --git a/api/tests/slates/v4_req.slate b/api/tests/slates/v4_req.slate new file mode 100644 index 000000000..b4b63cf07 --- /dev/null +++ b/api/tests/slates/v4_req.slate @@ -0,0 +1,27 @@ +{ + "jsonrpc": "2.0", + "method": "receive_tx", + "id": 1, + "params": [ + { + "amt": "6000000000", + "fee": "23500000", + "id": "0436430c-2b02-624c-2032-570501212b00", + "off": "d202964900000000d302964900000000d402964900000000d502964900000000", + "proof": { + "raddr": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3", + "saddr": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3" + }, + "sigs": [ + { + "nonce": "02b57c1f4fea69a3ee070309cf8f06082022fe06f25a9be1851b56ef0fa18f25d6", + "xs": "023878ce845727f3a4ec76ca3f3db4b38a2d05d636b8c3632108b857fed63c96de" + } + ], + "sta": "S1", + "ver": "4:2" + }, + null, + null + ] +} diff --git a/api/tests/slates/v5_req.slate b/api/tests/slates/v5_req.slate new file mode 100644 index 000000000..bf39108db --- /dev/null +++ b/api/tests/slates/v5_req.slate @@ -0,0 +1,27 @@ +{ + "jsonrpc": "2.0", + "method": "receive_tx", + "id": 1, + "params": [ + { + "amt": "6000000000", + "fee": "23500000", + "id": "0436430c-2b02-624c-2032-570501212b00", + "off": "d202964900000000d302964900000000d402964900000000d502964900000000", + "proof": { + "raddr": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3", + "saddr": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3" + }, + "sigs": [ + { + "nonce": "02b57c1f4fea69a3ee070309cf8f06082022fe06f25a9be1851b56ef0fa18f25d6", + "xs": "023878ce845727f3a4ec76ca3f3db4b38a2d05d636b8c3632108b857fed63c96de" + } + ], + "sta": "S1", + "ver": "5:2" + }, + null, + null + ] +} diff --git a/controller/src/command.rs b/controller/src/command.rs index 722e344e7..e75bef92b 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -24,7 +24,7 @@ use crate::impls::SlateGetter as _; use crate::keychain; use crate::libwallet::{ self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState, Slatepack, - SlatepackAddress, Slatepacker, SlatepackerArgs, WalletLCProvider, + SlatepackAddress, Slatepacker, SlatepackerArgs, TxFlow, WalletLCProvider, }; use crate::util::secp::key::SecretKey; use crate::util::{Mutex, ZeroingString}; @@ -259,6 +259,9 @@ pub struct SendArgs { pub ttl_blocks: Option, pub skip_tor: bool, pub outfile: Option, + pub is_multisig: Option, + pub derive_path: Option, + pub multisig_path: Option, } pub fn send( @@ -268,13 +271,14 @@ pub fn send( args: SendArgs, dark_scheme: bool, test_mode: bool, + tx_flow: TxFlow, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: keychain::Keychain + 'static, { - let mut slate = Slate::blank(2, false); + let mut slate = Slate::blank(2, tx_flow.clone()); controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { if args.estimate_selection_strategies { let strategies = vec!["smallest", "all"] @@ -288,9 +292,16 @@ where num_change_outputs: args.change_outputs as u32, selection_strategy_is_use_all: strategy == "all", estimate_only: Some(true), + is_multisig: args.is_multisig, + multisig_path: args.multisig_path.clone(), ..Default::default() }; - let slate = api.init_send_tx(m, init_args)?; + let slate = match tx_flow { + TxFlow::Standard => api.init_send_tx(m, init_args)?, + TxFlow::Atomic => api.init_atomic_swap(m, init_args)?, + _ => api.init_send_tx(m, init_args)?, + }; + Ok((strategy, slate.amount, slate.fee_fields)) }) .collect::, grin_wallet_libwallet::Error>>()?; @@ -309,9 +320,15 @@ where ttl_blocks: args.ttl_blocks, send_args: None, late_lock: Some(args.late_lock), + is_multisig: args.is_multisig, + multisig_path: args.multisig_path.clone(), ..Default::default() }; - let result = api.init_send_tx(m, init_args); + let result = match tx_flow { + TxFlow::Standard => api.init_send_tx(m, init_args), + TxFlow::Atomic => api.init_atomic_swap(m, init_args), + _ => api.init_send_tx(m, init_args), + }; slate = match result { Ok(s) => { info!( @@ -495,7 +512,7 @@ where Some(s) => s, None => { // try and parse directly from input_slatepack_message - let mut slate = Slate::blank(2, false); + let mut slate = Slate::blank(2, TxFlow::Standard); match message { Some(message) => { controller::owner_single_use( @@ -539,6 +556,7 @@ pub fn receive( args: ReceiveArgs, tor_config: Option, test_mode: bool, + tx_flow: TxFlow, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K>, @@ -566,7 +584,15 @@ where }; controller::foreign_single_use(owner_api.wallet_inst.clone(), km, |api| { - slate = api.receive_tx(&slate, Some(&g_args.account), None)?; + slate = match tx_flow { + TxFlow::Standard => api.receive_tx(&slate, Some(&g_args.account), None)?, + TxFlow::Atomic => api.receive_atomic_tx(&slate, Some(&g_args.account), None)?, + _ => { + return Err( + libwallet::ErrorKind::GenericError("Invalid transaction flow".into()).into(), + ) + } + }; Ok(()) })?; @@ -602,6 +628,149 @@ where Err(e) => Err(e.into()), } } +pub fn countersign_atomic( + owner_api: &mut Owner, + keychain_mask: Option<&SecretKey>, + args: ReceiveArgs, + tor_config: Option, + test_mode: bool, +) -> Result<(), Error> +where + L: WalletLCProvider<'static, C, K>, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ + let (mut slate, ret_address) = parse_slatepack( + owner_api, + keychain_mask, + args.input_file, + args.input_slatepack_message, + )?; + + let tor_config = match tor_config { + Some(mut c) => { + c.skip_send_attempt = Some(args.skip_tor); + Some(c) + } + None => None, + }; + + controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { + slate = api.countersign_atomic_swap(&slate, m, None)?; + Ok(()) + })?; + + let dest = match ret_address { + Some(a) => String::try_from(&a).unwrap(), + None => String::from(""), + }; + + let res = try_slatepack_sync_workflow(&slate, &dest, tor_config, None, true, test_mode); + + match res { + Ok(Some(_)) => { + println!(); + println!( + "Transaction countersigned and sent back to {} for finalization.", + dest + ); + println!(); + Ok(()) + } + Ok(None) => { + output_slatepack( + owner_api, + keychain_mask, + &slate, + &dest, + args.outfile, + false, + false, + )?; + Ok(()) + } + Err(e) => Err(e.into()), + } +} + +/// Process Multisig command arguments +#[derive(Clone)] +pub struct MultisigArgs { + pub input_file: Option, + pub input_slatepack_message: Option, + pub skip_tor: bool, + pub outfile: Option, +} + +pub fn process_multisig( + owner_api: &mut Owner, + keychain_mask: Option<&SecretKey>, + args: MultisigArgs, + tor_config: Option, + test_mode: bool, +) -> Result<(), Error> +where + L: WalletLCProvider<'static, C, K>, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ + let (mut slate, ret_address) = parse_slatepack( + owner_api, + keychain_mask, + args.input_file, + args.input_slatepack_message, + )?; + + let tor_config = match tor_config { + Some(mut c) => { + c.skip_send_attempt = Some(args.skip_tor); + Some(c) + } + None => None, + }; + + controller::owner_single_use( + Some(owner_api.wallet_inst.clone()), + keychain_mask, + Some(owner_api), + |api, m| { + slate = api.process_multisig_tx(m, &slate)?; + Ok(()) + }, + )?; + + let dest = match ret_address { + Some(a) => String::try_from(&a).unwrap(), + None => String::from(""), + }; + + let res = try_slatepack_sync_workflow(&slate, &dest, tor_config, None, true, test_mode); + + match res { + Ok(Some(_)) => { + println!(); + println!( + "Transaction multisig bulletproof processed and sent back to recipient at {} for first round of finalization.", + dest + ); + println!(); + Ok(()) + } + Ok(None) => { + output_slatepack( + owner_api, + keychain_mask, + &slate, + &dest, + args.outfile, + false, + false, + )?; + Ok(()) + } + Err(e) => Err(e.into()), + } +} pub fn unpack( owner_api: &mut Owner, @@ -695,6 +864,7 @@ pub fn finalize( owner_api: &mut Owner, keychain_mask: Option<&SecretKey>, args: FinalizeArgs, + tx_flow: TxFlow, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, @@ -724,7 +894,16 @@ where })?; } else { controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - slate = api.finalize_tx(m, &slate)?; + slate = match tx_flow { + TxFlow::Standard => api.finalize_tx(m, &slate)?, + TxFlow::Atomic => api.finalize_atomic_swap(m, &slate)?, + _ => { + return Err(libwallet::ErrorKind::GenericError( + "invalid transaction flow".into(), + ) + .into()) + } + }; Ok(()) })?; } @@ -750,6 +929,14 @@ where println!("Transaction finalized successfully"); + if slate.is_multisig() { + info!( + "Transaction multisig identifier: {}", + slate.create_multisig_id().to_bip_32_string() + ); + info!("Use the identifier to select the multisig output in future transactions"); + } + output_slatepack( owner_api, keychain_mask, @@ -763,6 +950,84 @@ where Ok(()) } +/// Recover atomic secret args +#[derive(Clone)] +pub struct RecoverAtomicArgs { + pub input_file: Option, + pub input_slatepack_message: Option, + pub outfile: Option, +} + +/// Recover the atomic secret from an adaptor signature and kernel excess signature +pub fn recover_atomic_secret( + owner_api: &mut Owner, + keychain_mask: Option<&SecretKey>, + args: RecoverAtomicArgs, +) -> Result<(), Error> +where + L: WalletLCProvider<'static, C, K> + 'static, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ + let (slate, _ret_address) = parse_slatepack( + owner_api, + keychain_mask, + args.input_file.clone(), + args.input_slatepack_message.clone(), + )?; + controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { + let result = api.recover_atomic_secret(m, &slate); + match result { + Ok(_) => { + info!("Atomic nonce recovered successfully."); + Ok(()) + } + Err(e) => { + error!("Tx not sent: {}", e); + Err(e) + } + } + })?; + + println!("Transaction finalized successfully"); + Ok(()) +} + +/// Get atomic secrets args +#[derive(Clone)] +pub struct GetAtomicSecretsArgs { + pub id: u32, + pub amount: f64, +} + +/// Get atomic secrets from storage to recover funds on the other chain +pub fn get_atomic_secrets( + owner_api: &mut Owner, + keychain_mask: Option<&SecretKey>, + args: GetAtomicSecretsArgs, +) -> Result<(), Error> +where + L: WalletLCProvider<'static, C, K> + 'static, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ + controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { + let result = + api.get_atomic_secrets(m, args.id, (args.amount * (10_u64.pow(9) as f64)) as u64); + match result { + Ok(_) => { + info!("Atomic nonces recovered successfully."); + Ok(()) + } + Err(e) => { + error!("Tx not sent: {}", e); + Err(e) + } + } + })?; + Ok(()) +} + /// Issue Invoice Args pub struct IssueInvoiceArgs { /// Slatepack address @@ -785,7 +1050,7 @@ where { let issue_args = args.issue_args.clone(); - let mut slate = Slate::blank(2, false); + let mut slate = Slate::blank(2, TxFlow::Standard); controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { slate = api.issue_invoice_tx(m, issue_args)?; Ok(()) diff --git a/controller/tests/atomic.rs b/controller/tests/atomic.rs new file mode 100644 index 000000000..95f92cdbc --- /dev/null +++ b/controller/tests/atomic.rs @@ -0,0 +1,613 @@ +// 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 wallets performing an atomic swap +#[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::{Keychain, SwitchCommitmentType}; + +use impls::test_framework::{self, LocalWalletClient}; +use libwallet::{InitTxArgs, Slate, SlateState, TxFlow}; +use std::{sync::atomic::Ordering, thread, time::Duration}; + +#[macro_use] +mod common; +use common::{clean_output_dir, create_wallet_proxy, setup}; + +/// atomic swap impl +fn atomic_tx_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_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 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 mut slate = Slate::blank(2, TxFlow::Atomic); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + // Wallet 1 inititates the main atomic swap transaction + let args = InitTxArgs { + amount: 5012500000, + is_multisig: Some(true), + ..Default::default() + }; + slate = api.init_send_tx(m, args)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig1); + + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + slate = api.receive_tx(&slate, None, None)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig2); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1.clone(), None, |api, m| { + slate = api.process_multisig_tx(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig3); + + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + slate = api.finalize_tx(&slate, false)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig4); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1.clone(), None, |api, m| { + slate = api.finalize_tx(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig4); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + // Wallet 1 inititates the main atomic swap transaction + let args = InitTxArgs { + amount: 5000000000, + minimum_confirmations: 0, + multisig_path: Some(slate.create_multisig_id().to_bip_32_string()), + ..Default::default() + }; + slate = api.init_atomic_swap(m, args)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic1); + + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + slate = api.receive_atomic_tx(&slate, None, None)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic2); + + // Get the receiver's atomic secret created in `receive_atomic_tx` + // This is one of the keys locking the multisig transaction on the other chain + // Only revealed if the refund transaction is fully signed + posted + let atomic_secret = { + let mut w_lock = wallet2.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let atomic_id = w.get_used_atomic_id(&slate.id)?; + w.keychain(mask2).unwrap().derive_key( + slate.amount, + &atomic_id, + SwitchCommitmentType::Regular, + )? + }; + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1.clone(), None, |api, m| { + // wallet 1 creates the first partial signature on the atomic swap + slate = api.countersign_atomic_swap(&slate, m, None)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic3); + + // wallet 2 finalizes and posts the atomic swap + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + slate = api.finalize_tx(&slate, false)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic4); + + let rec_atomic_secret = { + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let tx = slate.tx_or_err()?; + libwallet::recover_atomic_secret(&mut **w, mask1, &slate, &tx.kernels()[0])? + }; + + assert_eq!(rec_atomic_secret, atomic_secret); + + stopper.store(false, Ordering::Relaxed); + thread::sleep(Duration::from_millis(200)); + + Ok(()) +} + +/// atomic swap refund impl +fn atomic_refund_tx_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_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 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 mut slate = Slate::blank(2, TxFlow::Atomic); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + // Wallet 1 inititates the main atomic swap transaction + let args = InitTxArgs { + amount: 5012500000, + is_multisig: Some(true), + ..Default::default() + }; + slate = api.init_send_tx(m, args)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig1); + + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + slate = api.receive_tx(&slate, None, None)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig2); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1.clone(), None, |api, m| { + slate = api.process_multisig_tx(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig3); + + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + slate = api.finalize_tx(&slate, false)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig4); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1.clone(), None, |api, m| { + slate = api.finalize_tx(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig4); + + let _ = + test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); + + wallet::controller::owner_single_use(Some(wallet2.clone()), mask2.clone(), None, |api, m| { + // Wallet 2 inititates the refund atomic swap transaction + let args = InitTxArgs { + amount: 5000000000, + late_lock: Some(true), + minimum_confirmations: 0, + multisig_path: Some(slate.create_multisig_id().to_bip_32_string()), + ..Default::default() + }; + slate = api.init_atomic_swap(m, args)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic1); + + wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| { + api.doctest_mode = true; + slate = api.receive_atomic_tx(&slate, None, None)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic2); + + // Get the sender's atomic secret created in `receive_atomic_tx` + // This is one of the keys locking the multisig transaction on the other chain + // Only revealed if the refund transaction is fully signed + posted + let atomic_secret = { + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let atomic_id = w.get_used_atomic_id(&slate.id)?; + w.keychain(mask1)? + .derive_key(slate.amount, &atomic_id, SwitchCommitmentType::Regular)? + }; + + wallet::controller::owner_single_use(Some(wallet2.clone()), mask2.clone(), None, |api, m| { + // wallet 1 creates the first partial signature on the atomic swap + slate = api.countersign_atomic_swap(&slate, m, None)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic3); + + // wallet 2 finalizes and posts the atomic swap + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1.clone(), None, |api, m| { + api.tx_lock_outputs(m, &slate)?; + slate = api.finalize_atomic_swap(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic4); + + let rec_atomic_secret = { + let mut w_lock = wallet2.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let tx = slate.tx_or_err()?; + libwallet::recover_atomic_secret(&mut **w, mask2, &slate, &tx.kernels()[0])? + }; + + assert_eq!(rec_atomic_secret, atomic_secret); + + stopper.store(false, Ordering::Relaxed); + thread::sleep(Duration::from_millis(200)); + + Ok(()) +} + +/// atomic swap end-to-end impl +fn atomic_end_to_end_tx_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_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 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 mut slate = Slate::blank(2, TxFlow::Atomic); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + // Wallet 1 inititates the main atomic swap transaction + let args = InitTxArgs { + amount: 5012500000, + is_multisig: Some(true), + ..Default::default() + }; + slate = api.init_send_tx(m, args)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig1); + + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + slate = api.receive_tx(&slate, None, None)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig2); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1.clone(), None, |api, m| { + slate = api.process_multisig_tx(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig3); + + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + slate = api.finalize_tx(&slate, false)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig4); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1.clone(), None, |api, m| { + slate = api.finalize_tx(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Multisig4); + let multisig_path = slate.create_multisig_id().to_bip_32_string(); + wallet::controller::owner_single_use(Some(wallet2.clone()), mask2.clone(), None, |api, m| { + // Wallet 2 inititates the refund atomic swap transaction + let args = InitTxArgs { + amount: 5000000000, + late_lock: Some(true), + multisig_path: Some(multisig_path.clone()), + ..Default::default() + }; + slate = api.init_atomic_swap(m, args)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic1); + + wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| { + api.doctest_mode = true; + slate = api.receive_atomic_tx(&slate, None, None)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic2); + + // Get the sender's atomic secret created in `receive_atomic_tx` + // This is one of the keys locking the multisig transaction on the other chain + // Only revealed if the refund transaction is fully signed + posted + let _atomic_secret = { + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let id = w.get_used_atomic_id(&slate.id)?; + w.keychain(mask1)? + .derive_key(slate.amount, &id, SwitchCommitmentType::Regular)? + }; + + wallet::controller::owner_single_use(Some(wallet2.clone()), mask2.clone(), None, |api, m| { + // wallet 1 creates the first partial signature on the atomic swap + slate = api.countersign_atomic_swap(&slate, m, None)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic3); + + /* Don't finalize and lock funds, since this locks the outputs used for the main transaction + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1.clone(), None, |api, m| { + api.tx_lock_outputs(m, &slate)?; + slate = api.finalize_atomic_swap(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic4); + + let rec_atomic_secret = { + let mut w_lock = wallet2.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let tx = slate.tx_or_err()?; + libwallet::recover_atomic_secret(&mut **w, mask2, &slate, &tx.kernels()[0])? + }; + + assert_eq!(rec_atomic_secret, atomic_secret); + */ + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + // Wallet 1 inititates the main atomic swap transaction + let args = InitTxArgs { + amount: 500000000, + minimum_confirmations: 0, + multisig_path: Some(multisig_path), + ..Default::default() + }; + slate = api.init_atomic_swap(m, args)?; + api.tx_lock_outputs(m, &slate)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic1); + + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + slate = api.receive_atomic_tx(&slate, None, None)?; + Ok(()) + })?; + + // Create atomic secret, this is created during the refund transaction + // This is one of the keys locking the multisig transaction on the other chain + let atomic_secret = { + let mut w_lock = wallet2.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let atomic_id = w.get_used_atomic_id(&slate.id)?; + w.keychain(mask2)? + .derive_key(slate.amount, &atomic_id, SwitchCommitmentType::Regular)? + }; + + assert_eq!(slate.state, SlateState::Atomic2); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1.clone(), None, |api, m| { + // wallet 1 creates the first partial signature on the atomic swap + slate = api.countersign_atomic_swap(&slate, m, None)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic3); + + // wallet 2 finalizes and posts the atomic swap + wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { + slate = api.finalize_tx(&slate, false)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Atomic4); + + let rec_atomic_secret = { + let mut w_lock = wallet1.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let tx = slate.tx_or_err()?; + libwallet::recover_atomic_secret(&mut **w, mask1, &slate, &tx.kernels()[0])? + }; + + assert_eq!(rec_atomic_secret, atomic_secret); + + stopper.store(false, Ordering::Relaxed); + thread::sleep(Duration::from_millis(200)); + + Ok(()) +} + +#[test] +fn wallet_atomic_tx() -> Result<(), libwallet::Error> { + let test_dir = "test_output/atomic_tx"; + setup(test_dir); + atomic_tx_impl(test_dir)?; + clean_output_dir(test_dir); + Ok(()) +} + +#[test] +fn wallet_atomic_refund_tx() -> Result<(), libwallet::Error> { + let test_dir = "test_output/atomic_refund_tx"; + setup(test_dir); + atomic_refund_tx_impl(test_dir)?; + clean_output_dir(test_dir); + Ok(()) +} + +#[test] +fn wallet_atomic_end_to_end_tx() -> Result<(), libwallet::Error> { + let test_dir = "test_output/atomic_end_to_end_tx"; + setup(test_dir); + atomic_end_to_end_tx_impl(test_dir)?; + clean_output_dir(test_dir); + Ok(()) +} diff --git a/controller/tests/file.rs b/controller/tests/file.rs index 155ffd523..bc71306d9 100644 --- a/controller/tests/file.rs +++ b/controller/tests/file.rs @@ -26,7 +26,7 @@ use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; -use grin_wallet_libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate}; +use grin_wallet_libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate, TxFlow}; #[macro_use] mod common; @@ -195,7 +195,7 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), ), }; - let mut slate = Slate::blank(2, true); + let mut slate = Slate::blank(2, TxFlow::Invoice); wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { let args = IssueInvoiceTxArgs { @@ -249,7 +249,7 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), format!("{}/standard_pp_S3.txbin", test_dir), ), }; - let mut slate = Slate::blank(2, true); + let mut slate = Slate::blank(2, TxFlow::Invoice); let mut address = None; wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { address = Some(api.get_slatepack_address(m, 0)?); diff --git a/controller/tests/invoice.rs b/controller/tests/invoice.rs index 0972ad5ae..307eda21b 100644 --- a/controller/tests/invoice.rs +++ b/controller/tests/invoice.rs @@ -21,7 +21,7 @@ use grin_wallet_libwallet as libwallet; use grin_wallet_util::grin_core as core; use impls::test_framework::{self, LocalWalletClient}; -use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate, SlateState}; +use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate, SlateState, TxFlow}; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -95,7 +95,7 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { Ok(()) })?; - let mut slate = Slate::blank(2, true); + let mut slate = Slate::blank(2, TxFlow::Invoice); wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { // Wallet 2 inititates an invoice transaction, requesting payment diff --git a/controller/tests/late_lock.rs b/controller/tests/late_lock.rs index b038c6f88..f11d701e9 100644 --- a/controller/tests/late_lock.rs +++ b/controller/tests/late_lock.rs @@ -18,7 +18,7 @@ extern crate grin_wallet_controller as wallet; extern crate grin_wallet_impls as impls; extern crate grin_wallet_libwallet as libwallet; -use self::libwallet::{InitTxArgs, Slate}; +use self::libwallet::{InitTxArgs, Slate, TxFlow}; use impls::test_framework::{self, LocalWalletClient}; use std::sync::atomic::Ordering; use std::thread; @@ -91,7 +91,7 @@ fn late_lock_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false)?; - let mut slate = Slate::blank(2, false); + let mut slate = Slate::blank(2, TxFlow::Standard); let amount = 100_000_000_000; wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { diff --git a/controller/tests/no_change.rs b/controller/tests/no_change.rs index d171e195c..b12eddd95 100644 --- a/controller/tests/no_change.rs +++ b/controller/tests/no_change.rs @@ -21,7 +21,7 @@ use grin_wallet_util::grin_core as core; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient}; -use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate}; +use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate, TxFlow}; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -76,7 +76,7 @@ fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let fee = core::libtx::tx_fee(1, 1, 1); // send a single block's worth of transactions with minimal strategy - let mut slate = Slate::blank(2, false); + let mut slate = Slate::blank(2, TxFlow::Standard); let mut stored_excess = None; wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { let args = InitTxArgs { diff --git a/controller/tests/payment_proofs.rs b/controller/tests/payment_proofs.rs index 1176643db..31086edfc 100644 --- a/controller/tests/payment_proofs.rs +++ b/controller/tests/payment_proofs.rs @@ -20,7 +20,7 @@ extern crate grin_wallet_util; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient}; -use libwallet::{InitTxArgs, Slate}; +use libwallet::{InitTxArgs, Slate, TxFlow}; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -84,7 +84,7 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err println!("Public address is: {:?}", address); let amount = 60_000_000_000; - let mut slate = Slate::blank(1, false); + let mut slate = Slate::blank(1, TxFlow::Standard); wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { // note this will increment the block count as part of the transaction "Posting" let args = InitTxArgs { diff --git a/controller/tests/repost.rs b/controller/tests/repost.rs index 41bd29603..a44d72110 100644 --- a/controller/tests/repost.rs +++ b/controller/tests/repost.rs @@ -20,7 +20,7 @@ extern crate grin_wallet_libwallet as libwallet; use grin_wallet_util::grin_core as core; -use self::libwallet::{InitTxArgs, Slate}; +use self::libwallet::{InitTxArgs, Slate, TxFlow}; use impls::test_framework::{self, LocalWalletClient}; use impls::{PathToSlate, SlateGetter as _, SlatePutter as _}; use std::sync::atomic::Ordering; @@ -99,7 +99,7 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> let send_file = format!("{}/part_tx_1.tx", test_dir); let receive_file = format!("{}/part_tx_2.tx", test_dir); - let mut slate = Slate::blank(2, false); + let mut slate = Slate::blank(2, TxFlow::Standard); // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { @@ -198,7 +198,7 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> w.set_parent_key_id_by_name("account1")?; } - let mut slate = Slate::blank(2, false); + let mut slate = Slate::blank(2, TxFlow::Standard); let amount = 60_000_000_000; wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { diff --git a/controller/tests/slatepack.rs b/controller/tests/slatepack.rs index 68168f22d..1e3116c03 100644 --- a/controller/tests/slatepack.rs +++ b/controller/tests/slatepack.rs @@ -28,7 +28,7 @@ use std::time::Duration; use grin_wallet_libwallet::{ InitTxArgs, IssueInvoiceTxArgs, Slate, Slatepack, SlatepackAddress, Slatepacker, - SlatepackerArgs, + SlatepackerArgs, TxFlow, }; use ed25519_dalek::PublicKey as edDalekPublicKey; @@ -300,7 +300,7 @@ fn slatepack_exchange_test_impl( ), }; - let mut slate = Slate::blank(2, true); + let mut slate = Slate::blank(2, TxFlow::Invoice); wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { let args = IssueInvoiceTxArgs { @@ -375,7 +375,7 @@ fn slatepack_exchange_test_impl( ), }; - let mut slate = Slate::blank(2, true); + let mut slate = Slate::blank(2, TxFlow::Invoice); let mut address = None; wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { address = Some(api.get_slatepack_address(m, 0)?); diff --git a/controller/tests/transaction.rs b/controller/tests/transaction.rs index 1a66672dc..af8833301 100644 --- a/controller/tests/transaction.rs +++ b/controller/tests/transaction.rs @@ -22,7 +22,7 @@ use grin_wallet_util::grin_core as core; use self::core::core::transaction; use self::core::global; -use self::libwallet::{InitTxArgs, OutputStatus, Slate, SlateState}; +use self::libwallet::{InitTxArgs, OutputStatus, Slate, SlateState, TxFlow}; use impls::test_framework::{self, LocalWalletClient}; use std::convert::TryInto; use std::sync::atomic::Ordering; @@ -98,7 +98,7 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> // assert wallet contents // and a single use api for a send command let amount = 60_000_000_000; - let mut slate = Slate::blank(1, false); + let mut slate = Slate::blank(1, TxFlow::Standard); wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { // note this will increment the block count as part of the transaction "Posting" let args = InitTxArgs { @@ -405,7 +405,7 @@ fn tx_rollback(test_dir: &'static str) -> Result<(), libwallet::Error> { let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false); let amount = 30_000_000_000; - let mut slate = Slate::blank(1, false); + let mut slate = Slate::blank(1, TxFlow::Standard); wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { // note this will increment the block count as part of the transaction "Posting" let args = InitTxArgs { diff --git a/controller/tests/ttl_cutoff.rs b/controller/tests/ttl_cutoff.rs index 428caaa44..e4f4aaca7 100644 --- a/controller/tests/ttl_cutoff.rs +++ b/controller/tests/ttl_cutoff.rs @@ -20,7 +20,7 @@ extern crate grin_wallet_util; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient}; -use libwallet::{InitTxArgs, Slate, TxLogEntryType}; +use libwallet::{InitTxArgs, Slate, TxFlow, TxLogEntryType}; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -77,7 +77,7 @@ fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); let amount = 60_000_000_000; - let mut slate = Slate::blank(1, false); + let mut slate = Slate::blank(1, TxFlow::Standard); wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { // note this will increment the block count as part of the transaction "Posting" let args = InitTxArgs { @@ -127,7 +127,7 @@ fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> })?; // try again, except try and send off the transaction for completion beyond the expiry - let mut slate = Slate::blank(1, false); + let mut slate = Slate::blank(1, TxFlow::Standard); wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { // note this will increment the block count as part of the transaction "Posting" let args = InitTxArgs { diff --git a/impls/src/adapters/file.rs b/impls/src/adapters/file.rs index b65803edb..134b9b2a3 100644 --- a/impls/src/adapters/file.rs +++ b/impls/src/adapters/file.rs @@ -16,7 +16,7 @@ use std::fs::File; use std::io::{Read, Write}; -use crate::libwallet::{Error, ErrorKind, Slate, SlateVersion, VersionedBinSlate, VersionedSlate}; +use crate::libwallet::{Error, ErrorKind, Slate, VersionedBinSlate, VersionedSlate}; use crate::{SlateGetter, SlatePutter}; use grin_wallet_util::byte_ser; use std::convert::TryFrom; @@ -37,7 +37,7 @@ impl SlatePutter for PathToSlate { }*/ let mut pub_tx = File::create(&self.0)?; // TODO: - let out_slate = VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?; + let out_slate = VersionedSlate::into_version(slate.clone(), slate.version())?; if as_bin { let bin_slate = VersionedBinSlate::try_from(out_slate).map_err(|_| ErrorKind::SlateSer)?; diff --git a/impls/src/adapters/http.rs b/impls/src/adapters/http.rs index 7d9f360d7..58078e99a 100644 --- a/impls/src/adapters/http.rs +++ b/impls/src/adapters/http.rs @@ -15,7 +15,7 @@ /// HTTP Wallet 'plugin' implementation use crate::client_utils::{Client, ClientError, ClientErrorKind}; use crate::libwallet::slate_versions::{SlateVersion, VersionedSlate}; -use crate::libwallet::{Error, ErrorKind, Slate}; +use crate::libwallet::{Error, ErrorKind, Slate, SlateState}; use crate::SlateSender; use serde::Serialize; use serde_json::{json, Value}; @@ -145,6 +145,10 @@ impl HttpSlateSender { return Err(ErrorKind::ClientCallback(report).into()); } + if supported_slate_versions.contains(&"V5".to_owned()) { + return Ok(SlateVersion::V5); + } + if supported_slate_versions.contains(&"V4".to_owned()) { return Ok(SlateVersion::V4); } @@ -190,27 +194,62 @@ impl SlateSender for HttpSlateSender { let slate_send = match self.check_other_version(&url_str)? { SlateVersion::V4 => VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?, + SlateVersion::V5 => VersionedSlate::into_version(slate.clone(), SlateVersion::V5)?, }; // Note: not using easy-jsonrpc as don't want the dependencies in this crate let req = match finalize { - false => json!({ - "jsonrpc": "2.0", - "method": "receive_tx", - "id": 1, - "params": [ - slate_send, - null, - null - ] - }), - true => json!({ - "jsonrpc": "2.0", - "method": "finalize_tx", - "id": 1, - "params": [ - slate_send - ] - }), + false => match slate.state { + SlateState::Standard1 | SlateState::Invoice1 => json!({ + "jsonrpc": "2.0", + "method": "receive_tx", + "id": 1, + "params": [ + slate_send, + null, + null + ] + }), + SlateState::Atomic1 => json!({ + "jsonrpc": "2.0", + "method": "receive_atomic_tx", + "id": 1, + "params": [ + slate_send, + null, + null + ] + }), + SlateState::Atomic2 => json!({ + "jsonrpc": "2.0", + "method": "countersign_atomic_swap", + "id": 1, + "params": [ + slate_send, + null, + null + ] + }), + _ => return Err(ErrorKind::ClientCallback("invalid slate state".into()).into()), + }, + true => match slate.state { + SlateState::Standard2 | SlateState::Invoice2 => json!({ + "jsonrpc": "2.0", + "method": "finalize_tx", + "id": 1, + "params": [ + slate_send + ] + }), + SlateState::Atomic3 => json!({ + "jsonrpc": "2.0", + "method": "finalize_atomic_swap", + "id": 1, + "params": [ + slate_send + ] + }), + _ => return Err(ErrorKind::ClientCallback("invalid slate state".into()).into()), + }, }; trace!("Sending receive_tx request: {}", req); diff --git a/impls/src/backends/lmdb.rs b/impls/src/backends/lmdb.rs index 6a8bd0343..bebadc5c2 100644 --- a/impls/src/backends/lmdb.rs +++ b/impls/src/backends/lmdb.rs @@ -36,6 +36,7 @@ use crate::libwallet::{ }; use crate::util::secp::constants::SECRET_KEY_SIZE; use crate::util::secp::key::SecretKey; +use crate::util::secp::pedersen::Commitment; use crate::util::{self, secp, ToHex}; use rand::rngs::mock::StepRng; @@ -55,6 +56,9 @@ const LAST_SCANNED_BLOCK: u8 = b'l'; const LAST_SCANNED_KEY: &str = "LAST_SCANNED_KEY"; const WALLET_INIT_STATUS: u8 = b'w'; const WALLET_INIT_STATUS_KEY: &str = "WALLET_INIT_STATUS"; +const ATOMIC_ID_PREFIX: u8 = b'm'; +const ATOMIC_SECRET_PREFIX: u8 = b's'; +const RECOVERED_ATOMIC_SECRET_PREFIX: u8 = b'r'; /// test to see if database files exist in the current directory. If so, /// use a DB backend for all operations @@ -96,6 +100,71 @@ where Ok((ret_blind, ret_nonce)) } +/// XOR key to encrypt tau x key for multisig bulletproofs +fn tau_x_xor_key(keychain: &K, slate_id: &[u8]) -> Result<[u8; SECRET_KEY_SIZE], Error> +where + K: Keychain, +{ + let root_key = keychain.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?; + + let mut hasher = Blake2b::new(SECRET_KEY_SIZE); + hasher.update(&root_key.0[..]); + hasher.update(&slate_id[..]); + hasher.update(&b"tau_x"[..]); + let tau_x_xor_key = hasher.finalize(); + let mut ret_tau_x = [0; SECRET_KEY_SIZE]; + ret_tau_x.copy_from_slice(&tau_x_xor_key.as_bytes()[..SECRET_KEY_SIZE]); + + Ok(ret_tau_x) +} + +fn atomic_xor_key(keychain: &K, atomic_id: &Identifier) -> Result<[u8; SECRET_KEY_SIZE], Error> +where + K: Keychain, +{ + let root_key = keychain.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?; + + //derive XOE value for storing atomic public key + // h(root_key|atomic_id|"atomic_secret") + let mut hasher = Blake2b::new(SECRET_KEY_SIZE); + hasher.update(&root_key.0); + hasher.update(&atomic_id.to_bytes()); + hasher.update(&b"atomic_secret"[..]); + let atomic_xor_key = hasher.finalize(); + let mut ret_atomic = [0; SECRET_KEY_SIZE]; + ret_atomic.copy_from_slice(&atomic_xor_key.as_bytes()[0..SECRET_KEY_SIZE]); + + Ok(ret_atomic) +} + +fn recovered_atomic_xor_key( + keychain: &K, + atomic_id: &Identifier, +) -> Result<[u8; SECRET_KEY_SIZE], Error> +where + K: Keychain, +{ + let root_key = keychain.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?; + //derive XOE value for storing public atomic secret + // h(root_key|atomic_id|"recovered_atomic_secret") + let mut hasher = Blake2b::new(SECRET_KEY_SIZE); + hasher.update(&root_key.0); + hasher.update(&atomic_id.to_bytes()); + hasher.update(&b"recovered_atomic_secret"[..]); + let atomic_xor_key = hasher.finalize(); + let mut ret_atomic = [0; SECRET_KEY_SIZE]; + ret_atomic.copy_from_slice(&atomic_xor_key.as_bytes()[0..SECRET_KEY_SIZE]); + Ok(ret_atomic) +} + +fn default_parent_atomic_id() -> Identifier { + ExtKeychain::derive_key_id( + 2, 0x6d776174, /* 'mwat' */ + 0x6f6d6963, /* 'omic' */ + 0, 0, + ) +} + pub struct LMDBBackend<'ck, C, K> where C: NodeClient + 'ck, @@ -109,6 +178,8 @@ where pub master_checksum: Box>, /// Parent path to use by default for output operations parent_key_id: Identifier, + /// Atomic swap path to use by default for nonce operations + parent_atomic_id: Identifier, /// wallet to node client w2n_client: C, ///phantom @@ -154,6 +225,7 @@ where keychain: None, master_checksum: Box::new(None), parent_key_id: LMDBBackend::::default_path(), + parent_atomic_id: LMDBBackend::::default_atomic_path(), w2n_client: n_client, _phantom: &PhantomData, }; @@ -167,6 +239,13 @@ where ExtKeychain::derive_key_id(2, 0, 0, 0, 0) } + fn default_atomic_path() -> Identifier { + // return the default atomic secret wallet path, corresponding to the default account + // offset for atomic secret keys. Parent is account `hex(b"mwatomic")` at level 2, + // child atomic secret identifiers are all at level 3 + default_parent_atomic_id() + } + /// Just test to see if database files exist in the current directory. If /// so, use a DB backend for all operations pub fn exists(data_file_dir: &str) -> bool { @@ -273,6 +352,24 @@ where /*}*/ } + /// return the version of the commit for caching + fn calc_multisig_commit_for_cache( + &mut self, + keychain_mask: Option<&SecretKey>, + amount: u64, + id: &Identifier, + partial_commit: &Commitment, + ) -> Result<(Option, Option), Error> { + let keychain = self.keychain(keychain_mask)?; + let secp = keychain.secp(); + // TODO: proper support for different switch commitment schemes + let commit_key = keychain.derive_key(amount, id, SwitchCommitmentType::Regular)?; + let commit = secp.commit(0, commit_key)?; + let commit_sum = secp.commit_sum(vec![commit.clone(), partial_commit.clone()], vec![])?; + + Ok((Some(commit), Some(commit_sum))) + } + /// Set parent path by account name fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error> { let label = label.to_owned(); @@ -334,16 +431,23 @@ where slate_id: &[u8], ) -> Result { let ctx_key = to_key_u64(PRIVATE_TX_CONTEXT_PREFIX, &mut slate_id.to_vec(), 0); - let (blind_xor_key, nonce_xor_key) = - private_ctx_xor_keys(&self.keychain(keychain_mask)?, slate_id)?; + let k = self.keychain(keychain_mask)?; + let (blind_xor_key, nonce_xor_key) = private_ctx_xor_keys(&k, slate_id)?; + let tau_x_xor_key = tau_x_xor_key(&k, slate_id)?; let mut ctx: Context = option_to_not_found(self.db.get_ser(&ctx_key), || { format!("Slate id: {:x?}", slate_id.to_vec()) })?; + let mut tau_none = [0; SECRET_KEY_SIZE]; + let tau_x_bytes = match ctx.tau_x.as_mut() { + Some(x) => &mut x.0, + None => &mut tau_none, + }; for i in 0..SECRET_KEY_SIZE { ctx.sec_key.0[i] ^= blind_xor_key[i]; ctx.sec_nonce.0[i] ^= nonce_xor_key[i]; + tau_x_bytes[i] ^= tau_x_xor_key[i]; } Ok(ctx) @@ -445,6 +549,65 @@ where Ok(Identifier::from_path(&return_path)) } + fn current_atomic_id<'a>(&mut self) -> Result { + let index = { + let batch = self.db.batch()?; + let atomic_key = to_key( + ATOMIC_ID_PREFIX, + &mut self.parent_atomic_id.to_bytes().to_vec(), + ); + match batch.get_ser(&atomic_key)? { + Some(idx) => idx, + None => 0, + } + }; + let mut return_path = self.parent_atomic_id.to_path(); + return_path.depth += 1; + return_path.path[return_path.depth as usize - 1] = ChildNumber::from(index); + Ok(Identifier::from_path(&return_path)) + } + + fn next_atomic_id<'a>( + &mut self, + keychain_mask: Option<&SecretKey>, + ) -> Result { + let mut atomic_idx = { + let batch = self.db.batch()?; + let atomic_key = to_key( + ATOMIC_ID_PREFIX, + &mut self.parent_atomic_id.to_bytes().to_vec(), + ); + match batch.get_ser(&atomic_key)? { + Some(idx) => idx, + None => 0, + } + }; + let mut return_path = self.parent_atomic_id.to_path(); + return_path.depth += 1; + return_path.path[return_path.depth as usize - 1] = ChildNumber::from(atomic_idx); + atomic_idx += 1; + let mut batch = self.batch(keychain_mask)?; + batch.save_atomic_index(atomic_idx)?; + batch.commit()?; + Ok(Identifier::from_path(&return_path)) + } + + fn get_used_atomic_id(&mut self, id: &Uuid) -> Result { + let parent_atomic_id = self.parent_atomic_id.clone(); + let atomic_idx = { + let batch = self.db.batch()?; + let atomic_key = to_key(ATOMIC_ID_PREFIX, &mut id.as_bytes().to_vec()); + match batch.get_ser(&atomic_key)? { + Some(idx) => idx, + None => 0, + } + }; + let mut return_path = parent_atomic_id.to_path(); + return_path.depth += 1; + return_path.path[return_path.depth as usize - 1] = ChildNumber::from(atomic_idx); + Ok(Identifier::from_path(&return_path)) + } + fn last_confirmed_height<'a>(&mut self) -> Result { let batch = self.db.batch()?; let height_key = to_key( @@ -488,6 +651,49 @@ where }; Ok(status) } + + fn get_atomic_secret<'a>( + &mut self, + keychain_mask: Option<&SecretKey>, + atomic_id: &Identifier, + ) -> Result { + let keychain = self.keychain(keychain_mask)?; + let mut xor_key = atomic_xor_key(&keychain, atomic_id)?; + let secret_key = to_key(ATOMIC_SECRET_PREFIX, &mut atomic_id.to_bytes().to_vec()); + let batch = self.db.batch()?; + let secret: Vec = match batch.get_ser(&secret_key)? { + Some(s) => s, + None => return Err(ErrorKind::GenericError("missing atomic secret".into()).into()), + }; + for (x, s) in xor_key.iter_mut().zip(secret.iter()) { + *x ^= s; + } + Ok(SecretKey::from_slice(keychain.secp(), xor_key.as_ref())?) + } + + fn get_recovered_atomic_secret<'a>( + &mut self, + keychain_mask: Option<&SecretKey>, + atomic_id: &Identifier, + ) -> Result { + let keychain = self.keychain(keychain_mask)?; + let mut xor_key = recovered_atomic_xor_key(&keychain, atomic_id)?; + let nonce_key = to_key( + RECOVERED_ATOMIC_SECRET_PREFIX, + &mut atomic_id.to_bytes().to_vec(), + ); + let batch = self.db.batch()?; + let secret: Vec = match batch.get_ser(&nonce_key)? { + Some(s) => s, + None => { + return Err(ErrorKind::GenericError("missing atomic secret".into()).into()) + } + }; + for (x, s) in xor_key.iter_mut().zip(secret.iter()) { + *x ^= s; + } + Ok(SecretKey::from_slice(keychain.secp(), xor_key.as_ref())?) + } } /// An atomic batch in which all changes can be committed all at once or @@ -641,6 +847,27 @@ where Ok(()) } + fn save_atomic_index(&mut self, atomic_idx: u32) -> Result<(), Error> { + let parent_atomic_id = default_parent_atomic_id(); + let atomic_key = to_key(ATOMIC_ID_PREFIX, &mut parent_atomic_id.to_bytes().to_vec()); + self.db + .borrow() + .as_ref() + .unwrap() + .put_ser(&atomic_key, &atomic_idx)?; + Ok(()) + } + + fn save_used_atomic_index(&mut self, id: &Uuid, atomic_idx: u32) -> Result<(), Error> { + let atomic_key = to_key(ATOMIC_ID_PREFIX, &mut id.as_bytes().to_vec()); + self.db + .borrow() + .as_ref() + .unwrap() + .put_ser(&atomic_key, &atomic_idx)?; + Ok(()) + } + fn save_tx_log_entry( &mut self, tx_in: TxLogEntry, @@ -691,12 +918,20 @@ where fn save_private_context(&mut self, slate_id: &[u8], ctx: &Context) -> Result<(), Error> { let ctx_key = to_key_u64(PRIVATE_TX_CONTEXT_PREFIX, &mut slate_id.to_vec(), 0); - let (blind_xor_key, nonce_xor_key) = private_ctx_xor_keys(self.keychain(), slate_id)?; + let k = self.keychain(); + let (blind_xor_key, nonce_xor_key) = private_ctx_xor_keys(k, slate_id)?; + let tau_x_xor_key = tau_x_xor_key(k, slate_id)?; let mut s_ctx = ctx.clone(); + let mut tau_none = [0; SECRET_KEY_SIZE]; + let tau_x_bytes = match s_ctx.tau_x.as_mut() { + Some(x) => &mut x.0, + None => &mut tau_none, + }; for i in 0..SECRET_KEY_SIZE { s_ctx.sec_key.0[i] ^= blind_xor_key[i]; s_ctx.sec_nonce.0[i] ^= nonce_xor_key[i]; + tau_x_bytes[i] ^= tau_x_xor_key[i]; } self.db @@ -722,4 +957,43 @@ where db.unwrap().commit()?; Ok(()) } + + fn save_atomic_secret( + &mut self, + atomic_id: &Identifier, + secret: &SecretKey, + ) -> Result<(), Error> { + let mut xor_key = atomic_xor_key(self.keychain(), atomic_id)?; + let secret_key = to_key(ATOMIC_SECRET_PREFIX, &mut atomic_id.to_bytes().to_vec()); + for (x, s) in xor_key.iter_mut().zip(secret.0[..].iter()) { + *x ^= s; + } + self.db + .borrow() + .as_ref() + .unwrap() + .put_ser(&secret_key, &xor_key.to_vec())?; + Ok(()) + } + + fn save_recovered_atomic_secret( + &mut self, + atomic_id: &Identifier, + secret: &SecretKey, + ) -> Result<(), Error> { + let mut xor_key = recovered_atomic_xor_key(self.keychain(), atomic_id)?; + let secret_key = to_key( + RECOVERED_ATOMIC_SECRET_PREFIX, + &mut atomic_id.to_bytes().to_vec(), + ); + for (x, s) in xor_key.iter_mut().zip(secret.0[..].iter()) { + *x ^= s; + } + self.db + .borrow() + .as_ref() + .unwrap() + .put_ser(&secret_key, &xor_key.to_vec())?; + Ok(()) + } } diff --git a/impls/src/node_clients/http.rs b/impls/src/node_clients/http.rs index 6e383e811..606ffcca1 100644 --- a/impls/src/node_clients/http.rs +++ b/impls/src/node_clients/http.rs @@ -303,20 +303,34 @@ impl NodeClient for HTTPNodeClient { ( u64, u64, - Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>, + Vec<( + pedersen::Commitment, + pedersen::RangeProof, + bool, + bool, + u64, + u64, + )>, ), libwallet::Error, > { - let mut api_outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)> = - Vec::new(); + let mut api_outputs: Vec<( + pedersen::Commitment, + pedersen::RangeProof, + bool, + bool, + u64, + u64, + )> = Vec::new(); let params = json!([start_index, end_index, max_outputs, Some(true)]); let res = self.send_json_request::("get_unspent_outputs", ¶ms)?; // We asked for unspent outputs via the api but defensively filter out spent outputs just in case. for out in res.outputs.into_iter().filter(|out| out.spent == false) { - let is_coinbase = match out.output_type { - api::OutputType::Coinbase => true, - api::OutputType::Transaction => false, + let (is_coinbase, is_multisig) = match out.output_type { + api::OutputType::Coinbase => (true, false), + api::OutputType::Transaction => (false, false), + api::OutputType::Multisig => (false, true), }; let range_proof = match out.range_proof() { Ok(r) => r, @@ -344,6 +358,7 @@ impl NodeClient for HTTPNodeClient { out.commit, range_proof, is_coinbase, + is_multisig, block_height, out.mmr_index, )); diff --git a/impls/src/test_framework/testclient.rs b/impls/src/test_framework/testclient.rs index 29abfcccb..bbf53ad17 100644 --- a/impls/src/test_framework/testclient.rs +++ b/impls/src/test_framework/testclient.rs @@ -562,7 +562,14 @@ impl NodeClient for LocalWalletClient { ( u64, u64, - Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>, + Vec<( + pedersen::Commitment, + pedersen::RangeProof, + bool, + bool, + u64, + u64, + )>, ), libwallet::Error, > { @@ -589,18 +596,26 @@ impl NodeClient for LocalWalletClient { let m = r.recv().unwrap(); let o: api::OutputListing = serde_json::from_str(&m.body).unwrap(); - let mut api_outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)> = - Vec::new(); + let mut api_outputs: Vec<( + pedersen::Commitment, + pedersen::RangeProof, + bool, + bool, + u64, + u64, + )> = Vec::new(); for out in o.outputs { - let is_coinbase = match out.output_type { - api::OutputType::Coinbase => true, - api::OutputType::Transaction => false, + let (is_coinbase, is_multisig) = match out.output_type { + api::OutputType::Coinbase => (true, false), + api::OutputType::Transaction => (false, false), + api::OutputType::Multisig => (false, true), }; api_outputs.push(( out.commit, out.range_proof().unwrap(), is_coinbase, + is_multisig, out.block_height.unwrap(), out.mmr_index, )); diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index d8cfdaac3..f6a320c54 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -15,11 +15,12 @@ //! Generic implementation of owner API functions use strum::IntoEnumIterator; -use crate::api_impl::owner::finalize_tx as owner_finalize; use crate::api_impl::owner::{check_ttl, post_tx}; +use crate::api_impl::owner::{finalize_atomic_swap, finalize_tx as owner_finalize}; use crate::grin_core::core::FeeFields; -use crate::grin_keychain::Keychain; -use crate::grin_util::secp::key::SecretKey; +use crate::grin_keychain::{Keychain, SwitchCommitmentType}; +use crate::grin_util::secp::key::{PublicKey, SecretKey}; +use crate::grin_util::ToHex; use crate::internal::{selection, tx, updater}; use crate::slate_versions::SlateVersion; use crate::{ @@ -106,8 +107,12 @@ where use_test_rng, )?; - // Add our contribution to the offset - ret_slate.adjust_offset(&keychain, &context)?; + let is_multisig = slate.is_multisig(); + + if !is_multisig { + // Add our contribution to the offset + ret_slate.adjust_offset(&keychain, &context)?; + } let excess = ret_slate.calc_excess(keychain.secp())?; @@ -122,10 +127,134 @@ where p.receiver_signature = Some(sig); } - ret_slate.amount = 0; - ret_slate.fee_fields = FeeFields::zero(); - ret_slate.remove_other_sigdata(&keychain, &context.sec_nonce, &context.sec_key)?; - ret_slate.state = SlateState::Standard2; + if ret_slate.is_multisig() { + ret_slate.state = SlateState::Multisig2; + } else { + ret_slate.amount = 0; + ret_slate.fee_fields = FeeFields::zero(); + ret_slate.remove_other_sigdata(&keychain, &context.sec_nonce, &context.sec_key)?; + ret_slate.state = SlateState::Standard2; + } + + Ok(ret_slate) +} + +/// Receive an atomic tx as recipient +pub fn receive_atomic_tx<'a, T: ?Sized, C, K>( + w: &mut T, + keychain_mask: Option<&SecretKey>, + slate: &Slate, + dest_acct_name: Option<&str>, + use_test_rng: bool, +) -> Result +where + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, +{ + let mut ret_slate = slate.clone(); + check_ttl(w, &ret_slate)?; + let parent_key_id = match dest_acct_name { + Some(d) => { + let pm = w.get_acct_path(d.to_owned())?; + match pm { + Some(p) => p.path, + None => w.parent_key_id(), + } + } + None => w.parent_key_id(), + }; + // Don't do this multiple times + let tx = updater::retrieve_txs( + w, + None, + Some(ret_slate.id), + Some(&parent_key_id), + use_test_rng, + )?; + for t in &tx { + if t.tx_type == TxLogEntryType::TxReceived { + return Err(ErrorKind::TransactionAlreadyReceived(ret_slate.id.to_string()).into()); + } + } + + ret_slate.tx = Some(Slate::empty_transaction()); + + let height = w.last_confirmed_height()?; + let keychain = w.keychain(keychain_mask)?; + + let is_height_lock = ret_slate.kernel_features == 2; + // derive atomic nonce from the slate's `atomic_id` + let (atomic_id, atomic_secret) = { + let atomic_id = w.next_atomic_id(keychain_mask)?; + let atomic = + keychain.derive_key(ret_slate.amount, &atomic_id, SwitchCommitmentType::Regular)?; + + let pub_atomic = PublicKey::from_secret_key(keychain.secp(), &atomic)?; + + debug!( + "Your public atomic nonce: {}", + pub_atomic + .serialize_vec(keychain.secp(), true) + .as_ref() + .to_hex() + ); + debug!("Use this key to lock funds on the other chain.\n"); + + (atomic_id, Some(atomic)) + }; + + let min_confirmations = if use_test_rng { 0 } else { 10 }; + let (input_ids, output_ids) = if is_height_lock { + // add input(s) and change output to slate + let ctx = tx::add_inputs_to_atomic_slate( + w, + keychain_mask, + &mut ret_slate, + height, + min_confirmations, + 500, // max_outputs + 1, // num_change_outputs + true, // selection_strategy_is_use_all + &parent_key_id, + atomic_secret.clone(), + use_test_rng, + )?; + + (ctx.input_ids, ctx.output_ids) + } else { + (vec![], vec![]) + }; + + let mut context = tx::add_output_to_atomic_slate( + w, + keychain_mask, + &mut ret_slate, + height, + &parent_key_id, + atomic_secret, + use_test_rng, + )?; + + { + let atomic_idx = Slate::atomic_id_to_int(&atomic_id)?; + let mut batch = w.batch(keychain_mask)?; + batch.save_used_atomic_index(&ret_slate.id, atomic_idx)?; + batch.commit()?; + } + + context.fee = Some(ret_slate.fee_fields.clone()); + + if is_height_lock { + ret_slate.compact()?; + context.input_ids = input_ids; + context.output_ids.extend_from_slice(&output_ids); + let mut batch = w.batch(keychain_mask)?; + batch.save_private_context(ret_slate.id.as_bytes(), &context)?; + batch.commit()?; + } + + ret_slate.state = SlateState::Atomic2; Ok(ret_slate) } @@ -143,7 +272,7 @@ where K: Keychain + 'a, { let mut sl = slate.clone(); - let context = w.get_private_context(keychain_mask, sl.id.as_bytes())?; + let mut context = w.get_private_context(keychain_mask, sl.id.as_bytes())?; if sl.state == SlateState::Invoice2 { check_ttl(w, &sl)?; @@ -164,6 +293,35 @@ where } sl.state = SlateState::Invoice3; sl.amount = 0; + } else if sl.state == SlateState::Multisig3 { + let tau_x = + selection::finalize_multisig_bulletproof(w, keychain_mask, &mut sl, &mut context)?; + let k = w.keychain(keychain_mask)?; + let (_, pub_nonce) = context.get_public_keys(k.secp()); + { + let mut part_data = sl + .participant_data + .iter_mut() + .find(|d| d.public_nonce == pub_nonce) + .ok_or(Error::from(ErrorKind::GenericError( + "missing local participant data".into(), + )))?; + part_data.tau_x = tau_x; + } + + sl.adjust_offset(&k, &context)?; + + debug!( + "multisig ID path: {}", + slate.create_multisig_id().to_bip_32_string() + ); + debug!("Use with commands spending this multisig output"); + + let mut batch = w.batch(keychain_mask)?; + batch.save_private_context(sl.id.as_bytes().as_ref(), &context)?; + batch.commit()?; + } else if sl.state == SlateState::Atomic3 { + sl = finalize_atomic_swap(w, keychain_mask, slate)?; } else { sl = owner_finalize(w, keychain_mask, slate)?; } diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 67092d391..86ff2d1ea 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -14,23 +14,27 @@ //! Generic implementation of owner API functions +use rand::thread_rng; use uuid::Uuid; use crate::grin_core::core::hash::Hashed; use crate::grin_core::core::Transaction; -use crate::grin_util::secp::key::SecretKey; -use crate::grin_util::Mutex; +use crate::grin_core::libtx::{proof, tx_fee}; +use crate::grin_util::secp::key::{PublicKey, SecretKey}; +use crate::grin_util::{Mutex, ToHex}; use crate::util::{OnionV3Address, OnionV3AddressError}; use crate::api_impl::owner_updater::StatusMessage; -use crate::grin_keychain::{Identifier, Keychain}; +use crate::grin_keychain::{Identifier, Keychain, SwitchCommitmentType}; use crate::internal::{keys, scan, selection, tx, updater}; -use crate::slate::{PaymentInfo, Slate, SlateState}; -use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletBackend, WalletInfo}; +use crate::slate::{KernelFeaturesArgs, PaymentInfo, Slate, SlateState, TxFlow}; +use crate::types::{ + AcctPathMapping, NodeClient, OutputData, OutputStatus, TxLogEntry, WalletBackend, WalletInfo, +}; use crate::{ - address, wallet_lock, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping, - PaymentProof, ScannedBlockInfo, Slatepack, SlatepackAddress, Slatepacker, SlatepackerArgs, - TxLogEntryType, WalletInitStatus, WalletInst, WalletLCProvider, + address, wallet_lock, Context, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, + OutputCommitMapping, PaymentProof, ScannedBlockInfo, Slatepack, SlatepackAddress, Slatepacker, + SlatepackerArgs, TxLogEntryType, WalletInitStatus, WalletInst, WalletLCProvider, }; use crate::{Error, ErrorKind}; use ed25519_dalek::PublicKey as DalekPublicKey; @@ -471,7 +475,7 @@ where let mut slate = tx::new_tx_slate( &mut *w, args.amount, - false, + TxFlow::Standard, 2, use_test_rng, args.ttl_blocks, @@ -500,6 +504,7 @@ where } let height = w.w2n_client().get_chain_tip()?.0; + let is_multisig = args.is_multisig.unwrap_or(false); let mut context = if args.late_lock.unwrap_or(false) { tx::create_late_lock_context( &mut *w, @@ -522,6 +527,7 @@ where args.selection_strategy_is_use_all, &parent_key_id, true, + is_multisig, use_test_rng, )? }; @@ -556,6 +562,10 @@ where slate.compact()?; + if slate.is_multisig() { + slate.state = SlateState::Multisig1; + } + Ok(slate) } @@ -582,7 +592,7 @@ where None => w.parent_key_id(), }; - let mut slate = tx::new_tx_slate(&mut *w, args.amount, true, 2, use_test_rng, None)?; + let mut slate = tx::new_tx_slate(&mut *w, args.amount, TxFlow::Invoice, 2, use_test_rng, None)?; let height = w.w2n_client().get_chain_tip()?.0; let context = tx::add_output_to_slate( &mut *w, @@ -675,6 +685,7 @@ where args.selection_strategy_is_use_all, &parent_key_id, false, + false, use_test_rng, )?; @@ -728,6 +739,156 @@ where Ok(ret_slate) } +/// Perform initiator's step 1 + 2 of the multisig bulletproof to create +/// tau_x, tau_one, tau_two keys +pub fn process_multisig_tx<'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 mut context = w.get_private_context(keychain_mask, slate.id.as_bytes())?; + let mut ret_slate = slate.clone(); + + let secp = keychain.secp(); + let key_id = slate.create_multisig_id(); + let (_, pub_nonce) = context.get_public_keys(secp); + let oth_part_data = if use_test_rng && slate.participant_data.len() == 1 { + &slate.participant_data[0] + } else { + slate + .participant_data + .iter() + .find(|d| d.public_nonce != pub_nonce) + .ok_or(Error::from(ErrorKind::GenericError( + "missing other participant data".into(), + )))? + }; + + let oth_part_commit = + oth_part_data + .part_commit + .clone() + .ok_or(Error::from(ErrorKind::Commit( + "missing other partial commit".into(), + )))?; + let partial_commit = keychain.commit(slate.amount, &key_id, SwitchCommitmentType::Regular)?; + let commit_sum = secp.commit_sum(vec![partial_commit, oth_part_commit], vec![])?; + + let common_nonce = context.create_common_nonce(secp, &oth_part_data.public_nonce)?; + + // calculate initiator's tau_one and tau_two public keys for the multisig bulletproof + context.tau_one = Some(PublicKey::new()); + context.tau_two = Some(PublicKey::new()); + let _ = proof::create_multisig( + &keychain, + &proof::ProofBuilder::new(&keychain), + slate.amount, + &key_id, + SwitchCommitmentType::Regular, + &common_nonce, + None, + context.tau_one.as_mut(), + context.tau_two.as_mut(), + &[commit_sum.clone()], + 1, + None, + )?; + + let (oth_tau_one, oth_tau_two) = match ( + oth_part_data.tau_one.as_ref(), + oth_part_data.tau_two.as_ref(), + ) { + (Some(one), Some(two)) => (one, two), + _ => { + return Err(ErrorKind::GenericError( + "missing other participant's tau public key(s)".into(), + ) + .into()) + } + }; + + // calculate tau_one_sum and tau_two_sum + let mut tau_one_sum = Some(PublicKey::from_combination( + secp, + vec![context.tau_one.as_ref().unwrap(), oth_tau_one], + )?); + let mut tau_two_sum = Some(PublicKey::from_combination( + secp, + vec![context.tau_two.as_ref().unwrap(), oth_tau_two], + )?); + + // calculate initiator's tau_x secret key for the multisig bulletproof + context.tau_x = Some(SecretKey::new(secp, &mut thread_rng())); + let _ = proof::create_multisig( + &keychain, + &proof::ProofBuilder::new(&keychain), + slate.amount, + &key_id, + SwitchCommitmentType::Regular, + &common_nonce, + context.tau_x.as_mut(), + tau_one_sum.as_mut(), + tau_two_sum.as_mut(), + &[commit_sum], + 2, + None, + )?; + + let part_data = ret_slate + .participant_data + .iter_mut() + .find(|d| d.public_nonce == pub_nonce) + .ok_or(Error::from(ErrorKind::GenericError( + "missing local participant data".into(), + )))?; + + part_data.tau_x = context.tau_x.clone(); + part_data.tau_one = context.tau_one.clone(); + part_data.tau_two = context.tau_two.clone(); + + context.tau_one = tau_one_sum; + context.tau_two = tau_two_sum; + + // Don't calculate partial excess signature, wait for tau_x from the receiver + // After receiving tau_x, compute the final bulletproof over the shared output + // + // Then, calculate the partial excess signature, add to receiver's signature, + // and finalize the multisig transaction + + // Save the multisig output and context in our DB + { + let height = w.last_confirmed_height()?; + let mut batch = w.batch(keychain_mask)?; + let commit = Some(commit_sum.0.to_vec().to_hex()); + batch.save(OutputData { + root_key_id: key_id.clone(), + key_id: key_id.clone(), + mmr_index: None, + n_child: key_id.to_path().last_path_index(), + commit, + value: slate.amount, + status: OutputStatus::Unconfirmed, + height: height, + lock_height: 0, + is_coinbase: false, + is_multisig: true, + tx_log_entry: None, + })?; + batch.save_private_context(slate.id.as_bytes(), &context)?; + batch.commit()?; + } + + ret_slate.state = SlateState::Multisig3; + Ok(ret_slate) +} + /// Lock sender outputs pub fn tx_lock_outputs<'a, T: ?Sized, C, K>( w: &mut T, @@ -765,6 +926,180 @@ where ) } +/// Initialize atomic swap transaction +/// +/// To initialize as the receiver for the `heigh_lock`ed +/// refund transaction, set the `late_lock` field in the +/// `InitTxArgs` +/// +/// Otherwise, the transaction will initialize as the sender +/// in the main transaction +pub fn init_atomic_swap<'a, T: ?Sized, C, K>( + w: &mut T, + keychain_mask: Option<&SecretKey>, + args: InitTxArgs, + use_test_rng: bool, +) -> Result +where + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, +{ + let parent_key_id = match &args.src_acct_name { + Some(d) => { + let pm = w.get_acct_path(d.clone())?; + match pm { + Some(p) => p.path, + None => w.parent_key_id(), + } + } + None => w.parent_key_id(), + }; + + let multisig_path_str = args + .multisig_path + .ok_or(Error::from(ErrorKind::GenericError( + "missing BIP32 multisig path".into(), + )))?; + let multisig_id = Identifier::from_bip_32_string(&multisig_path_str)?; + + let mut slate = tx::new_tx_slate( + &mut *w, + args.amount, + TxFlow::Atomic, + 2, + use_test_rng, + args.ttl_blocks, + )?; + + slate.multisig_key_id = Some(multisig_id.clone()); + + let height = w.w2n_client().get_chain_tip()?.0; + let keychain = w.keychain(keychain_mask)?; + + let output = w + .iter() + .find(|d| d.key_id == multisig_id) + .ok_or(Error::from(ErrorKind::GenericError( + "missing multisig output".into(), + )))?; + + let context = if args.late_lock.unwrap_or(false) { + // use late_lock context for initial height_lock tx, + // initiated by the atomic swap receiver + let mut context = Context::new(keychain.secp(), &parent_key_id, use_test_rng, true); + context.amount = args.amount; + context.fee = Some((tx_fee(1, 1, 1) as u32).into()); + context.input_ids = vec![(output.key_id, output.mmr_index, output.value)]; + + slate.fill_round_1(&keychain, &mut context)?; + + // Create a height_lock kernel, with a lock_height set to an + // arbitrary amount of blocks in the future + // + // FIXME: add option to specify the lock_height? + slate.kernel_features = 2; + slate.kernel_features_args = Some(KernelFeaturesArgs { + lock_height: height + 60, + }); + + context + } else { + let mut context = tx::add_inputs_to_atomic_slate( + w, + keychain_mask, + &mut slate, + height, + args.minimum_confirmations, + args.max_outputs as usize, + args.num_change_outputs as usize, + args.selection_strategy_is_use_all, + &parent_key_id, + None, + use_test_rng, + )?; + slate.fill_round_1(&keychain, &mut context)?; + context + }; + + // Save the aggsig context in our DB for when we + // receive the transaction back + { + let mut batch = w.batch(keychain_mask)?; + batch.save_private_context(slate.id.as_bytes(), &context)?; + batch.commit()?; + } + + slate.compact()?; + + Ok(slate) +} + +/// Complete the third round of the atomic swap +/// +/// In this round, the adaptor signature from round +/// two is saved, and the counterparty completes their +/// half of the kernel signature. +/// +/// For the refund transaction, the receiver is the counterparty. +/// +/// For the main transaction, the sender is the counterparty. +pub fn countersign_atomic_swap<'a, T: ?Sized, C, K>( + w: &mut T, + slate: &Slate, + keychain_mask: Option<&SecretKey>, +) -> Result +where + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, +{ + let mut ret_slate = slate.clone(); + check_ttl(w, &ret_slate)?; + + let mut context = w.get_private_context(keychain_mask, ret_slate.id.as_bytes())?; + + let keychain = w.keychain(keychain_mask)?; + // Save `s` from the adaptor signature to be able to extract the + // atomic secret using the full signature + // + // This is the atomic secret used to recover funds on the other chain + let adaptor_sig = ret_slate.fill_round_3_atomic(&keychain, &mut context)?; + let atomic_secret = SecretKey::from_slice(keychain.secp(), &adaptor_sig.as_ref()[32..])?; + + { + let atomic_id = w.next_atomic_id(keychain_mask)?; + let atomic = + keychain.derive_key(slate.amount, &atomic_id, SwitchCommitmentType::Regular)?; + let pub_atomic = PublicKey::from_secret_key(keychain.secp(), &atomic)?; + + debug!( + "Your public atomic key: {}", + pub_atomic + .serialize_vec(keychain.secp(), true) + .as_ref() + .to_hex() + ); + debug!("Use this key to lock funds on the other chain.\n"); + + let mut batch = w.batch(keychain_mask)?; + batch.save_recovered_atomic_secret(&atomic_id, &atomic_secret)?; + let atomic_idx = Slate::atomic_id_to_int(&atomic_id)?; + batch.save_used_atomic_index(&slate.id, atomic_idx)?; + batch.commit()?; + } + + ret_slate.adjust_offset(&keychain, &context)?; + + if ret_slate.kernel_features != 2 { + selection::repopulate_tx(&mut *w, keychain_mask, &mut ret_slate, &context, true)?; + } + + ret_slate.state = SlateState::Atomic3; + + Ok(ret_slate) +} + /// Finalize slate pub fn finalize_tx<'a, T: ?Sized, C, K>( w: &mut T, @@ -787,8 +1122,14 @@ where // and insert into original context let current_height = w.w2n_client().get_chain_tip()?.0; - let mut temp_sl = - tx::new_tx_slate(&mut *w, context.amount, false, 2, false, args.ttl_blocks)?; + let mut temp_sl = tx::new_tx_slate( + &mut *w, + context.amount, + TxFlow::Standard, + 2, + false, + args.ttl_blocks, + )?; let temp_context = selection::build_send_tx( w, &keychain, @@ -801,6 +1142,7 @@ where args.selection_strategy_is_use_all, Some(context.fee.map(|f| f.fee()).unwrap_or(0)), parent_key_id.clone(), + None, false, true, )?; @@ -820,25 +1162,120 @@ where tx_lock_outputs(w, keychain_mask, &sl)?; } + // finalize multisig bulletproof + let is_multisig = sl.is_multisig(); + let multisig_init_final = is_multisig && context.tau_x.is_some(); + if is_multisig { + let tau_x = + selection::finalize_multisig_bulletproof(w, keychain_mask, &mut sl, &mut context)?; + let k = w.keychain(keychain_mask)?; + let (_, pub_nonce) = context.get_public_keys(k.secp()); + { + let mut part_data = sl + .participant_data + .iter_mut() + .find(|d| d.public_nonce == pub_nonce) + .ok_or(Error::from(ErrorKind::GenericError( + "missing local participant data".into(), + )))?; + part_data.tau_x = tau_x; + } + } + + if multisig_init_final { + context + .output_ids + .push((sl.create_multisig_id(), None, sl.amount)); + } + // Add our contribution to the offset sl.adjust_offset(&keychain, &context)?; - selection::repopulate_tx(&mut *w, keychain_mask, &mut sl, &context, true)?; + if multisig_init_final { + context.output_ids.pop(); + } - tx::complete_tx(&mut *w, keychain_mask, &mut sl, &context)?; - tx::verify_slate_payment_proof(&mut *w, keychain_mask, &parent_key_id, &context, &sl)?; - tx::update_stored_tx(&mut *w, keychain_mask, &context, &sl, false)?; + if multisig_init_final || !is_multisig { + selection::repopulate_tx(&mut *w, keychain_mask, &mut sl, &context, true)?; + + tx::complete_tx(&mut *w, keychain_mask, &mut sl, &context)?; + tx::verify_slate_payment_proof(&mut *w, keychain_mask, &parent_key_id, &context, &sl)?; + tx::update_stored_tx(&mut *w, keychain_mask, &context, &sl, false)?; + } { let mut batch = w.batch(keychain_mask)?; batch.delete_private_context(sl.id.as_bytes())?; batch.commit()?; } - sl.state = SlateState::Standard3; - sl.amount = 0; + if is_multisig { + debug!( + "multisig ID path: {}", + slate.create_multisig_id().to_bip_32_string() + ); + debug!("Use with commands spending this multisig output"); + sl.state = SlateState::Multisig4; + } else { + sl.state = SlateState::Standard3; + sl.amount = 0; + } Ok(sl) } +/// Complete the atomic swap +pub fn finalize_atomic_swap<'a, T: ?Sized, C, K>( + w: &mut T, + keychain_mask: Option<&SecretKey>, + slate: &Slate, +) -> Result +where + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, +{ + let mut ret_slate = slate.clone(); + check_ttl(w, &ret_slate)?; + let mut context = w.get_private_context(keychain_mask, ret_slate.id.as_bytes())?; + let parent_key_id = w.parent_key_id(); + let is_height_lock = ret_slate.kernel_features == 2; + + if !is_height_lock { + // check that the multisig ID matches a locally stored multisig output + let multisig_id = + slate + .multisig_key_id + .as_ref() + .ok_or(Error::from(ErrorKind::GenericError( + "missing multisig ouptut ID".into(), + )))?; + let output = w + .iter() + .find(|o| &o.key_id == multisig_id) + .ok_or(Error::from(ErrorKind::GenericError( + "missing multisig output".into(), + )))?; + context.input_ids = vec![(output.key_id, output.mmr_index, output.value)]; + } + + ret_slate.adjust_offset(&w.keychain(keychain_mask)?, &context)?; + + if is_height_lock { + ret_slate.tx = Some(Slate::empty_transaction()); + selection::repopulate_tx(&mut *w, keychain_mask, &mut ret_slate, &context, true)?; + } + + ret_slate.tx_or_err_mut()?.offset = ret_slate.offset.clone(); + + tx::complete_atomic_tx(&mut *w, keychain_mask, &mut ret_slate, &context)?; + tx::verify_slate_payment_proof(&mut *w, keychain_mask, &parent_key_id, &context, &ret_slate)?; + tx::update_stored_tx(&mut *w, keychain_mask, &context, &ret_slate, true)?; + + ret_slate.state = SlateState::Atomic4; + ret_slate.amount = 0; + + Ok(ret_slate) +} + /// cancel tx pub fn cancel_tx<'a, L, C, K>( wallet_inst: Arc>>>, @@ -904,7 +1341,7 @@ where let tx_res = w.get_stored_tx(&format!("{}", id))?; match tx_res { Some(tx) => { - let mut slate = Slate::blank(2, false); + let mut slate = Slate::blank(2, TxFlow::Standard); slate.tx = Some(tx.clone()); slate.fee_fields = tx.aggregate_fee_fields().unwrap(); // apply fee mask past HF4 slate.id = id; @@ -936,6 +1373,41 @@ where } } +/// Recover atomic secret from an adaptor signature and finalized kernel excess signature +pub fn recover_atomic_secret<'a, L, C, K>( + wallet_inst: &mut Arc>>>, + keychain_mask: Option<&SecretKey>, + slate: &Slate, +) -> Result +where + L: WalletLCProvider<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, +{ + let mut w_lock = wallet_inst.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + let mut client = w.w2n_client().clone(); + let keychain = w.keychain(keychain_mask)?; + if let Some((kernel, _, _)) = client.get_kernel( + &slate.calc_excess(keychain.secp())?, + Some(slate.ttl_cutoff_height.saturating_sub(60)), + Some(slate.ttl_cutoff_height), + )? { + let atomic = tx::recover_atomic_secret(&mut **w, keychain_mask, slate, &kernel)?; + let atomic_id = w.get_used_atomic_id(&slate.id)?; + info!("Saving atomic secret with atomic ID: {}, use with `get_atomic_secrets` to retrieve from storage", Slate::atomic_id_to_int(&atomic_id)?); + let mut batch = w.batch(keychain_mask)?; + batch.save_recovered_atomic_secret(&atomic_id, &atomic)?; + batch.commit()?; + Ok(atomic_id) + } else { + Err( + ErrorKind::StoredTx("missing finalized transaction kernel for the atomic swap".into()) + .into(), + ) + } +} + /// check repair /// Accepts a wallet inst instead of a raw wallet so it can /// lock as little as possible diff --git a/libwallet/src/api_impl/types.rs b/libwallet/src/api_impl/types.rs index 1a9fd238e..fb7812039 100644 --- a/libwallet/src/api_impl/types.rs +++ b/libwallet/src/api_impl/types.rs @@ -78,6 +78,11 @@ pub struct InitTxArgs { /// Sender arguments. If present, the underlying function will also attempt to send the /// transaction to a destination and optionally finalize the result pub send_args: Option, + /// If true, the transaction should contain a multisignature output shared by all the + /// participants + pub is_multisig: Option, + /// BIP32 path for the multisig output spent in an atomic swap transaction + pub multisig_path: Option, } /// Send TX API Args, for convenience functionality that inits the transaction and sends @@ -109,6 +114,8 @@ impl Default for InitTxArgs { payment_proof_recipient_address: None, late_lock: Some(false), send_args: None, + is_multisig: None, + multisig_path: None, } } } diff --git a/libwallet/src/internal/scan.rs b/libwallet/src/internal/scan.rs index d8cb34968..ef0354e13 100644 --- a/libwallet/src/internal/scan.rs +++ b/libwallet/src/internal/scan.rs @@ -49,6 +49,8 @@ struct OutputResult { pub lock_height: u64, /// pub is_coinbase: bool, + /// + pub is_multisig: bool, } #[derive(Debug, Clone)] @@ -65,7 +67,14 @@ struct RestoredTxStats { fn identify_utxo_outputs<'a, K>( keychain: &K, - outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>, + outputs: Vec<( + pedersen::Commitment, + pedersen::RangeProof, + bool, + bool, + u64, + u64, + )>, status_send_channel: &Option>, percentage_complete: u8, ) -> Result, Error> @@ -79,7 +88,7 @@ where let legacy_version = HeaderVersion(1); for output in outputs.iter() { - let (commit, proof, is_coinbase, height, mmr_index) = output; + let (commit, proof, is_coinbase, is_multisig, height, mmr_index) = output; // attempt to unwind message from the RP and get a value // will fail if it's not ours let info = { @@ -136,6 +145,7 @@ where height: *height, lock_height: lock_height, is_coinbase: *is_coinbase, + is_multisig: *is_multisig, mmr_index: *mmr_index, }); } @@ -264,6 +274,7 @@ where height: output.height, lock_height: output.lock_height, is_coinbase: output.is_coinbase, + is_multisig: output.is_multisig, tx_log_entry: Some(log_id), }); diff --git a/libwallet/src/internal/selection.rs b/libwallet/src/internal/selection.rs index 7c66b9672..3710203c7 100644 --- a/libwallet/src/internal/selection.rs +++ b/libwallet/src/internal/selection.rs @@ -14,19 +14,22 @@ //! Selection of inputs for building transactions +use rand::thread_rng; + use crate::address; use crate::error::{Error, ErrorKind}; -use crate::grin_core::core::amount_to_hr_string; +use crate::grin_core::core::{amount_to_hr_string, Output, OutputFeatures}; use crate::grin_core::libtx::{ build, - proof::{ProofBuild, ProofBuilder}, + proof::{create_multisig, ProofBuild, ProofBuilder}, tx_fee, }; -use crate::grin_keychain::{Identifier, Keychain}; -use crate::grin_util::secp::key::SecretKey; +use crate::grin_keychain::{Identifier, Keychain, SwitchCommitmentType}; +use crate::grin_util::secp::key::{PublicKey, SecretKey}; use crate::grin_util::secp::pedersen; +use crate::grin_util::{from_hex, ToHex}; use crate::internal::keys; -use crate::slate::Slate; +use crate::slate::{Slate, SlateState}; use crate::types::*; use crate::util::OnionV3Address; use std::collections::HashMap; @@ -49,6 +52,7 @@ pub fn build_send_tx<'a, T: ?Sized, C, K>( selection_strategy_is_use_all: bool, fixed_fee: Option, parent_key_id: Identifier, + multisig_key_id: Option<&Identifier>, use_test_nonce: bool, is_initiator: bool, ) -> Result @@ -67,6 +71,7 @@ where change_outputs, selection_strategy_is_use_all, &parent_key_id, + multisig_key_id, false, )?; @@ -219,6 +224,7 @@ where height: height, lock_height: 0, is_coinbase: false, + is_multisig: slate.is_multisig(), tx_log_entry: Some(log_id), })?; } @@ -250,19 +256,22 @@ where C: NodeClient + 'a, K: Keychain + 'a, { + let is_multisig = slate + .participant_data + .iter() + .fold(false, |t, d| t | d.part_commit.is_some()); + // Create a potential output for this transaction - let key_id = keys::next_available_key(wallet, keychain_mask).unwrap(); + let key_id = match is_multisig { + true => slate.create_multisig_id(), + false => keys::next_available_key(wallet, keychain_mask).unwrap(), + }; let keychain = wallet.keychain(keychain_mask)?; let key_id_inner = key_id.clone(); let amount = slate.amount; let height = current_height; let slate_id = slate.id; - slate.add_transaction_elements( - &keychain, - &ProofBuilder::new(&keychain), - vec![build::output(amount, key_id.clone())], - )?; // Add blinding sum to our context let mut context = Context::new(keychain.secp(), &parent_key_id, use_test_rng, is_initiator); @@ -270,7 +279,66 @@ where context.add_output(&key_id, &None, amount); context.amount = amount; context.fee = slate.fee_fields.as_opt(); - let commit = wallet.calc_commit_for_cache(keychain_mask, amount, &key_id_inner)?; + + let (commit, output) = if is_multisig { + let (_, public_nonce) = context.get_public_keys(keychain.secp()); + let data = slate + .participant_data + .iter() + .find(|d| d.public_nonce != public_nonce) + .ok_or(Error::from(ErrorKind::GenericError( + "missing other participant data".into(), + )))?; + + let oth_partial_commit = data.part_commit.ok_or(Error::from(ErrorKind::Commit( + "missing partial commit".into(), + )))?; + + // calculate the commit sum of the participants' partial commits + let (partial_commit, commit_sum) = wallet.calc_multisig_commit_for_cache( + keychain_mask, + amount, + &key_id_inner, + &oth_partial_commit, + )?; + + context.partial_commit = partial_commit; + context.tau_one = Some(PublicKey::new()); + context.tau_two = Some(PublicKey::new()); + + // create the common nonce: SecretKey(SHA3("multisig_common_nonce" || secNonce*pubNonce)) + let oth_public_nonce = &data.public_nonce; + let common_nonce = context.create_common_nonce(keychain.secp(), oth_public_nonce)?; + + // calculate receiver's tau_one and tau_two public keys for the multisig bulletproof + let _ = create_multisig( + &keychain, + &ProofBuilder::new(&keychain), + amount, + &key_id_inner, + SwitchCommitmentType::Regular, + &common_nonce, + None, + context.tau_one.as_mut(), + context.tau_two.as_mut(), + &[commit_sum.clone().unwrap()], + 1, + None, + )?; + + ( + Some(commit_sum.unwrap().0.to_vec().to_hex()), + build::multisig_output(amount, key_id.clone(), oth_partial_commit.clone()), + ) + } else { + ( + wallet.calc_commit_for_cache(keychain_mask, amount, &key_id_inner)?, + build::output(amount, key_id.clone()), + ) + }; + + slate.add_transaction_elements(&keychain, &ProofBuilder::new(&keychain), vec![output])?; + let mut batch = wallet.batch(keychain_mask)?; let log_id = batch.next_tx_log_id(&parent_key_id)?; let mut t = TxLogEntry::new(parent_key_id.clone(), TxLogEntryType::TxReceived, log_id); @@ -297,6 +365,7 @@ where height: height, lock_height: 0, is_coinbase: false, + is_multisig: slate.is_multisig(), tx_log_entry: Some(log_id), })?; batch.save_tx_log_entry(t.clone(), &parent_key_id)?; @@ -318,6 +387,7 @@ pub fn select_send_tx<'a, T: ?Sized, C, K, B>( change_outputs: usize, selection_strategy_is_use_all: bool, parent_key_id: &Identifier, + multisig_key_id: Option<&Identifier>, include_inputs_in_sum: bool, ) -> Result< ( @@ -343,6 +413,7 @@ where change_outputs, selection_strategy_is_use_all, &parent_key_id, + multisig_key_id, )?; // build transaction skeleton with inputs and change @@ -369,6 +440,7 @@ pub fn select_coins_and_fee<'a, T: ?Sized, C, K>( change_outputs: usize, selection_strategy_is_use_all: bool, parent_key_id: &Identifier, + multisig_key_id: Option<&Identifier>, ) -> Result< ( Vec, @@ -392,6 +464,7 @@ where max_outputs, selection_strategy_is_use_all, parent_key_id, + multisig_key_id, ); // sender is responsible for setting the fee on the partial tx @@ -454,6 +527,7 @@ where max_outputs, selection_strategy_is_use_all, parent_key_id, + multisig_key_id, ) .1; fee = tx_fee(coins.len(), num_outputs, 1); @@ -553,6 +627,7 @@ pub fn select_coins<'a, T: ?Sized, C, K>( max_outputs: usize, select_all: bool, parent_key_id: &Identifier, + multisig_key_id: Option<&Identifier>, ) -> (usize, Vec) // max_outputs_available, Outputs where @@ -561,13 +636,15 @@ where K: Keychain + 'a, { // first find all eligible outputs based on number of confirmations - let mut eligible = wallet - .iter() - .filter(|out| { - out.root_key_id == *parent_key_id - && out.eligible_to_spend(current_height, minimum_confirmations) - }) - .collect::>(); + let key_id = multisig_key_id.unwrap_or(parent_key_id); + let mut eligible = vec![]; + for out in wallet.iter() { + if (out.root_key_id == *key_id || out.key_id == *key_id) + && out.eligible_to_spend(current_height, minimum_confirmations) + { + eligible.push(out.clone()); + } + } let max_available = eligible.len(); @@ -672,6 +749,12 @@ where if let Some(i) = input { if i.is_coinbase { parts.push(build::coinbase_input(*value, i.key_id.clone())); + } else if i.is_multisig { + let commit_str = i.commit.ok_or(Error::from(ErrorKind::GenericError( + "missing multisig output commitment".into(), + )))?; + let commit = pedersen::Commitment::from_hex(&commit_str)?; + parts.push(build::multisig_input(*value, i.key_id.clone(), commit)); } else { parts.push(build::input(*value, i.key_id.clone())); } @@ -688,3 +771,142 @@ where slate.tx_or_err_mut()?.offset = slate.offset.clone(); Ok(()) } + +pub fn finalize_multisig_bulletproof<'a, T: ?Sized, C, K>( + wallet: &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, +{ + let keychain = wallet.keychain(keychain_mask)?; + let secp = keychain.secp(); + let (_, pub_nonce) = context.get_public_keys(secp); + let oth_data = slate + .participant_data + .iter() + .find(|d| d.public_nonce != pub_nonce) + .ok_or(Error::from(ErrorKind::GenericError( + "missing other participant data".into(), + )))?; + + let common_nonce = context.create_common_nonce(secp, &oth_data.public_nonce)?; + + let key_id = slate.create_multisig_id(); + + let amount = slate.amount; + let out = wallet + .iter() + .find(|o| o.key_id == key_id) + .ok_or(Error::from(ErrorKind::GenericError( + "missing multisig output".into(), + )))?; + let commit_str = out + .commit + .as_ref() + .ok_or(Error::from(ErrorKind::GenericError( + "missing multisig output commit".into(), + )))?; + let commit_hex = from_hex(&commit_str) + .map_err(|e| ErrorKind::GenericError(format!("invalid hex: {}", e)))?; + let commit = pedersen::Commitment::from_vec(commit_hex); + + let is_initiator_final = context.tau_x.is_some(); + if !is_initiator_final { + let tau_one = context.tau_one.ok_or(Error::from(ErrorKind::GenericError( + "missing tau one multisig key".into(), + )))?; + let tau_two = context.tau_two.ok_or(Error::from(ErrorKind::GenericError( + "missing tau two multisig key".into(), + )))?; + let oth_tau_one = oth_data.tau_one.ok_or(Error::from(ErrorKind::GenericError( + "missing other tau one multisig key".into(), + )))?; + let oth_tau_two = oth_data.tau_two.ok_or(Error::from(ErrorKind::GenericError( + "missing other tau two multisig key".into(), + )))?; + context.tau_one = Some(PublicKey::from_combination( + secp, + vec![&tau_one, &oth_tau_one], + )?); + context.tau_two = Some(PublicKey::from_combination( + secp, + vec![&tau_two, &oth_tau_two], + )?); + context.tau_x = Some(SecretKey::new(secp, &mut thread_rng())); + let _ = create_multisig( + &keychain, + &ProofBuilder::new(&keychain), + amount, + &key_id, + SwitchCommitmentType::Regular, + &common_nonce, + context.tau_x.as_mut(), + context.tau_one.as_mut(), + context.tau_two.as_mut(), + &[commit], + 2, + None, + )?; + } + let mut tau_x_sum = context + .tau_x + .clone() + .ok_or(Error::from(ErrorKind::GenericError( + "missing local tau x".into(), + )))?; + // Save for receiver to add to the slate, can be ignored for initiator finalization + let ret_tau_x = Some(tau_x_sum.clone()); + let oth_tau_x = oth_data + .tau_x + .as_ref() + .ok_or(Error::from(ErrorKind::GenericError( + "missing other tau x".into(), + )))?; + tau_x_sum.add_assign(secp, oth_tau_x)?; + + if is_initiator_final { + let proof = create_multisig( + &keychain, + &ProofBuilder::new(&keychain), + amount, + &key_id, + SwitchCommitmentType::Regular, + &common_nonce, + Some(&mut tau_x_sum), + context.tau_one.as_mut(), + context.tau_two.as_mut(), + &[commit.clone()], + 0, + None, + )? + .ok_or(Error::from(ErrorKind::GenericError( + "error creating final multisig proof".into(), + )))?; + + let output = Output::new(OutputFeatures::Multisig, commit.clone(), proof); + output.verify_proof()?; + + // replace the multisig output's rangeproof with the finalized multisig proof + let mut new_outs = vec![output]; + let old_outs: Vec = slate + .tx_or_err()? + .outputs() + .iter() + .filter(|o| o.identifier.commit != commit) + .map(|o| o.clone()) + .collect(); + new_outs.extend_from_slice(&old_outs[..]); + slate.tx_or_err_mut()?.body.outputs = new_outs; + } else { + context.tau_x = Some(tau_x_sum); + } + + slate.state = SlateState::Multisig4; + + Ok(ret_tau_x) +} diff --git a/libwallet/src/internal/tx.rs b/libwallet/src/internal/tx.rs index 505b554db..87d13b306 100644 --- a/libwallet/src/internal/tx.rs +++ b/libwallet/src/internal/tx.rs @@ -19,13 +19,13 @@ use std::io::Cursor; use uuid::Uuid; use crate::grin_core::consensus::valid_header_version; -use crate::grin_core::core::HeaderVersion; -use crate::grin_keychain::{Identifier, Keychain}; +use crate::grin_core::core::{HeaderVersion, TxKernel}; +use crate::grin_keychain::{Identifier, Keychain, SwitchCommitmentType}; use crate::grin_util::secp::key::SecretKey; use crate::grin_util::secp::pedersen; use crate::grin_util::Mutex; use crate::internal::{selection, updater}; -use crate::slate::Slate; +use crate::slate::{Slate, TxFlow}; use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType, WalletBackend}; use crate::util::OnionV3Address; use crate::InitTxArgs; @@ -47,7 +47,7 @@ lazy_static! { pub fn new_tx_slate<'a, T: ?Sized, C, K>( wallet: &mut T, amount: u64, - is_invoice: bool, + tx_flow: TxFlow, num_participants: u8, use_test_rng: bool, ttl_blocks: Option, @@ -58,7 +58,7 @@ where K: Keychain + 'a, { let current_height = wallet.w2n_client().get_chain_tip()?.0; - let mut slate = Slate::blank(num_participants, is_invoice); + let mut slate = Slate::blank(num_participants, tx_flow); if let Some(b) = ttl_blocks { slate.ttl_cutoff_height = current_height + b; } @@ -134,10 +134,70 @@ where num_change_outputs, selection_strategy_is_use_all, parent_key_id, + None, )?; Ok((total, fee)) } +/// Add inputs to the slate (effectively becoming the sender) +pub fn add_inputs_to_atomic_slate<'a, T: ?Sized, C, K>( + wallet: &mut T, + keychain_mask: Option<&SecretKey>, + slate: &mut Slate, + current_height: u64, + minimum_confirmations: u64, + max_outputs: usize, + num_change_outputs: usize, + selection_strategy_is_use_all: bool, + parent_key_id: &Identifier, + atomic_secret: Option, + use_test_rng: bool, +) -> Result +where + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, +{ + // sender should always refresh outputs + updater::refresh_outputs(wallet, keychain_mask, parent_key_id, false)?; + let is_initiator = atomic_secret.is_none(); + + if slate.multisig_key_id.is_none() { + return Err(ErrorKind::GenericError("missing multisig key id".into()).into()); + } + + // Sender selects outputs into a new slate and save our corresponding keys in + // a transaction context. The secret key in our transaction context will be + // randomly selected. This returns the public slate, and a closure that locks + // our inputs and outputs once we're convinced the transaction exchange went + // according to plan + // This function is just a big helper to do all of that, in theory + // this process can be split up in any way + let multisig_key_id = slate.multisig_key_id.clone(); + let mut context = selection::build_send_tx( + wallet, + &wallet.keychain(keychain_mask)?, + keychain_mask, + slate, + current_height, + minimum_confirmations, + max_outputs, + num_change_outputs, + selection_strategy_is_use_all, + None, + parent_key_id.clone(), + multisig_key_id.as_ref(), + use_test_rng, + is_initiator, + )?; + + context.sec_atomic = atomic_secret; + + context.initial_sec_key = context.sec_key.clone(); + + Ok(context) +} + /// Add inputs to the slate (effectively becoming the sender) pub fn add_inputs_to_slate<'a, T: ?Sized, C, K>( wallet: &mut T, @@ -150,6 +210,7 @@ pub fn add_inputs_to_slate<'a, T: ?Sized, C, K>( selection_strategy_is_use_all: bool, parent_key_id: &Identifier, is_initiator: bool, + is_multisig: bool, use_test_rng: bool, ) -> Result where @@ -179,10 +240,21 @@ where selection_strategy_is_use_all, None, parent_key_id.clone(), + None, use_test_rng, is_initiator, )?; + if is_multisig { + // calculate partial commit to the amount + // used with receiver partial commit to calculate tau_one and tau_two in + // multisig bulletproof step 1 + let k = wallet.keychain(keychain_mask)?; + let key_id = slate.create_multisig_id(); + let partial_commit = k.commit(slate.amount, &key_id, SwitchCommitmentType::Regular)?; + context.partial_commit = Some(partial_commit); + } + // Generate a kernel offset and subtract from our context's secret key. Store // the offset in the slate's transaction kernel, and adds our public key // information to the slate @@ -234,12 +306,20 @@ where context.initial_sec_key = context.sec_key.clone(); + let is_multisig = slate + .participant_data + .iter() + .fold(false, |t, d| t | d.part_commit.is_some()); + if !is_initiator { // perform partial sig slate.fill_round_2(&keychain, &context.sec_key, &context.sec_nonce)?; // update excess in stored transaction let mut batch = wallet.batch(keychain_mask)?; tx.kernel_excess = Some(slate.calc_excess(keychain.secp())?); + if is_multisig { + batch.save_private_context(slate.id.as_bytes().as_ref(), &context)?; + } batch.save_tx_log_entry(tx.clone(), &parent_key_id)?; batch.commit()?; } @@ -247,6 +327,52 @@ where Ok(context) } +/// Add receiver output to the atomic slate +pub fn add_output_to_atomic_slate<'a, T: ?Sized, C, K>( + wallet: &mut T, + keychain_mask: Option<&SecretKey>, + slate: &mut Slate, + current_height: u64, + parent_key_id: &Identifier, + atomic_secret: Option, + use_test_rng: bool, +) -> Result +where + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, +{ + let keychain = wallet.keychain(keychain_mask)?; + let is_initiator = atomic_secret.is_none(); + + // create an output using the amount in the slate + let (_, mut context, mut tx) = selection::build_recipient_output( + wallet, + keychain_mask, + slate, + current_height, + parent_key_id.clone(), + use_test_rng, + is_initiator, + )?; + + context.sec_atomic = atomic_secret; + // fill public keys + slate.fill_round_1(&keychain, &mut context)?; + // Create partial signature using the atomic secret, + // allows sender to recover the atomic secret when the + // full kernel signature is published + slate.fill_round_2_atomic(&keychain, &context)?; + // update excess in stored transaction + let mut batch = wallet.batch(keychain_mask)?; + tx.kernel_excess = Some(slate.calc_excess(keychain.secp())?); + batch.save_private_context(slate.id.as_bytes(), &context)?; + batch.save_tx_log_entry(tx.clone(), &parent_key_id)?; + batch.commit()?; + + Ok(context) +} + /// Create context, without adding inputs to slate pub fn create_late_lock_context<'a, T: ?Sized, C, K>( wallet: &mut T, @@ -276,6 +402,7 @@ where init_tx_args.num_change_outputs as usize, init_tx_args.selection_strategy_is_use_all, &parent_key_id, + None, )?; slate.fee_fields = FeeFields::new(0, fee)?; @@ -287,6 +414,16 @@ where context.amount = slate.amount; context.late_lock_args = Some(init_tx_args.clone()); + if init_tx_args.is_multisig.unwrap_or(false) { + // calculate partial commit to the amount + // used with receiver partial commit to calculate tau_one and tau_two in + // multisig bulletproof step 1 + let k = wallet.keychain(keychain_mask)?; + let key_id = slate.create_multisig_id(); + let partial_commit = k.commit(slate.amount, &key_id, SwitchCommitmentType::Regular)?; + context.partial_commit = Some(partial_commit); + } + // Generate a blinding factor for the tx and add // public key info to the slate slate.fill_round_1(&wallet.keychain(keychain_mask)?, &mut context)?; @@ -327,6 +464,80 @@ where Ok(()) } +/// Complete an atomic swap transaction +pub fn complete_atomic_tx<'a, T: ?Sized, C, K>( + wallet: &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, +{ + // Final transaction can be built by anyone at this stage + trace!("Slate to finalize is: {}", slate); + let _ = slate.finalize_atomic(&wallet.keychain(keychain_mask)?, context)?; + Ok(()) +} + +/// Recover atomic secret from final signature and adaptor signature +pub fn recover_atomic_secret<'a, T: ?Sized, C, K>( + wallet: &mut T, + keychain_mask: Option<&SecretKey>, + slate: &Slate, + tx_kernel: &TxKernel, +) -> Result +where + T: WalletBackend<'a, C, K>, + C: NodeClient + 'a, + K: Keychain + 'a, +{ + let full_sig = tx_kernel.excess_sig.clone(); + let keychain = wallet.keychain(keychain_mask)?; + let secp = keychain.secp(); + + let context = wallet.get_private_context(keychain_mask, slate.id.as_bytes())?; + + let pdata_idx = slate.find_participant_data_index(secp, &context)?; + let part_sig = match slate.participant_data[pdata_idx].part_sig { + Some(ref s) => s, + None => { + return Err(ErrorKind::Signature( + "Could not recover partial signature from atomic swap slate".into(), + ) + .into()) + } + }; + + let mut sr = SecretKey::from_slice(secp, &full_sig.as_ref()[32..])?; + // Use the initiator's partial signature to recover the responder's partial + // signature from the full signature + let mut sp_s = SecretKey::from_slice(secp, &part_sig.as_ref()[32..])?; + // The atomic_secret contains sr' from the responder's adaptor signature + let atomic_id = wallet.get_used_atomic_id(&slate.id)?; + let mut srp = wallet.get_recovered_atomic_secret(keychain_mask, &atomic_id)?; + + // Subtract the initiator's partial signature from the full signature + sp_s.neg_assign(secp)?; + sr.add_assign(secp, &sp_s)?; + + // Now the signature only contains the responder's partial signature + // calculate sr' - sr to recover the atomic secret + // x = (kr + x + rr*xr) - (kr + rr*xr) + sr.neg_assign(secp)?; + srp.add_assign(secp, &sr)?; + + { + // No longer need to keep the slate around, clean up + let mut batch = wallet.batch(keychain_mask)?; + batch.delete_private_context(slate.id.as_bytes())?; + } + + Ok(srp) +} + /// Rollback outputs associated with a transaction in the wallet pub fn cancel_tx<'a, T: ?Sized, C, K>( wallet: &mut T, diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index da2209e3f..e2ebf9f20 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -613,6 +613,7 @@ where height: height, lock_height: lock_height, is_coinbase: true, + is_multisig: false, tx_log_entry: None, })?; batch.commit()?; diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index 2ae08f060..d68acbd70 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -56,7 +56,7 @@ pub mod slatepack; mod types; pub use crate::error::{Error, ErrorKind}; -pub use crate::slate::{ParticipantData, Slate, SlateState}; +pub use crate::slate::{ParticipantData, Slate, SlateState, TxFlow}; pub use crate::slate_versions::v4::sig_is_blank; pub use crate::slate_versions::{ SlateVersion, VersionedBinSlate, VersionedCoinbase, VersionedSlate, CURRENT_SLATE_VERSION, @@ -71,6 +71,7 @@ pub use api_impl::types::{ OutputCommitMapping, PaymentProof, VersionInfo, }; pub use internal::scan::scan; +pub use internal::tx::recover_atomic_secret; pub use slate_versions::ser as dalek_ser; pub use types::{ AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData, diff --git a/libwallet/src/slate.rs b/libwallet/src/slate.rs index f7dec6391..3be7d705f 100644 --- a/libwallet/src/slate.rs +++ b/libwallet/src/slate.rs @@ -15,6 +15,7 @@ //! Functions for building partial transactions to be passed //! around during an interactive wallet exchange +use crate::blake2::blake2b::Blake2b; use crate::error::{Error, ErrorKind}; use crate::grin_core::core::amount_to_hr_string; use crate::grin_core::core::transaction::{ @@ -23,11 +24,14 @@ use crate::grin_core::core::transaction::{ }; use crate::grin_core::libtx::{aggsig, build, proof::ProofBuild, tx_fee}; use crate::grin_core::map_vec; -use crate::grin_keychain::{BlindSum, BlindingFactor, Keychain, SwitchCommitmentType}; +use crate::grin_keychain::{ + BlindSum, BlindingFactor, ExtKeychain, ExtKeychainPath, Identifier, Keychain, + SwitchCommitmentType, +}; 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 crate::grin_util::{secp, static_secp_instance, ToHex}; use ed25519_dalek::PublicKey as DalekPublicKey; use ed25519_dalek::Signature as DalekSignature; use serde::ser::{Serialize, Serializer}; @@ -39,10 +43,16 @@ use crate::slate_versions::v4::{ CommitsV4, KernelFeaturesArgsV4, OutputFeaturesV4, ParticipantDataV4, PaymentInfoV4, SlateStateV4, SlateV4, VersionCompatInfoV4, }; -use crate::slate_versions::VersionedSlate; +use crate::slate_versions::v5::{ + CommitsV5, KernelFeaturesArgsV5, OutputFeaturesV5, ParticipantDataV5, PaymentInfoV5, + SlateStateV5, SlateV5, VersionCompatInfoV5, +}; +use crate::slate_versions::{SlateVersion, VersionedSlate}; use crate::slate_versions::{CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION}; use crate::Context; +pub const ATOMIC_ID_PREFIX: &'static [u8] = b"\x03mwatomic"; + #[derive(Debug, Clone)] pub struct PaymentInfo { /// Sender address @@ -60,8 +70,18 @@ pub struct ParticipantData { pub public_blind_excess: PublicKey, /// Public key corresponding to private nonce pub public_nonce: PublicKey, + /// Public key corresponding to the atomic secret + pub public_atomic: Option, /// Public partial signature pub part_sig: Option, + /// Public partial commitment to multisig output value + pub part_commit: Option, + /// Tau X key for multiparty output rangeproof + pub tau_x: Option, + /// Tau one partial public key for multiparty output rangeproof + pub tau_one: Option, + /// Tau two partial public key for multiparty output rangeproof + pub tau_two: Option, } impl ParticipantData { @@ -98,7 +118,7 @@ pub struct Slate { pub state: SlateState, /// The core transaction data: /// inputs, outputs, kernels, kernel offset - /// Optional as of V4 to allow for a compact + /// Optional as of V5 to allow for a compact /// transaction initiation pub tx: Option, /// base amount (excluding fee) @@ -125,6 +145,8 @@ pub struct Slate { pub payment_proof: Option, /// Kernel features arguments pub kernel_features_args: Option, + /// Multisig key id for shared output + pub multisig_key_id: Option, } impl fmt::Display for Slate { @@ -150,6 +172,22 @@ pub enum SlateState { Invoice2, /// Invoice flow, ready for tranasction posting Invoice3, + /// Multisig flow, freshly init + Multisig1, + ///Multisig flow, step 1 proof build + Multisig2, + /// Multisig flow, step 2 proof build + Multisig3, + /// Multisig flow, final proof step + Multisig4, + /// Atomic flow, freshly init + Atomic1, + /// Atomic flow, return journey + Atomic2, + /// Atomic flow, partial signature from initiator + Atomic3, + /// Atomic flow, ready for transaction posting + Atomic4, } impl fmt::Display for SlateState { @@ -162,11 +200,32 @@ impl fmt::Display for SlateState { SlateState::Invoice1 => "I1", SlateState::Invoice2 => "I2", SlateState::Invoice3 => "I3", + SlateState::Multisig1 => "M1", + SlateState::Multisig2 => "M2", + SlateState::Multisig3 => "M3", + SlateState::Multisig4 => "M4", + SlateState::Atomic1 => "A1", + SlateState::Atomic2 => "A2", + SlateState::Atomic3 => "A3", + SlateState::Atomic4 => "A4", }; write!(f, "{}", res) } } +/// Transaction flow definition +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum TxFlow { + /// Standard transaction flow, sender-receiver-sender + Standard, + /// Invoice transaction flow, receiver-sender-receiver + Invoice, + /// Multisig transaction flow + Multisig, + /// Atomic swap transaction flow + Atomic, +} + #[derive(Debug, Clone, PartialEq, Eq)] /// Kernel features arguments definition pub struct KernelFeaturesArgs { @@ -226,10 +285,10 @@ impl Slate { /// Upgrade a versioned slate pub fn upgrade(v_slate: VersionedSlate) -> Result { - let v4: SlateV4 = match v_slate { - VersionedSlate::V4(s) => s, - }; - Ok(v4.into()) + match v_slate { + VersionedSlate::V4(s) => Ok(s.into()), + VersionedSlate::V5(s) => Ok(s.into()), + } } /// Compact the slate for initial sending, storing the excess + offset explicit /// and removing my input/output data @@ -239,6 +298,16 @@ impl Slate { Ok(()) } + /// Get if the slate is for a multisig transaction + pub fn is_multisig(&self) -> bool { + self.participant_data.iter().fold(false, |t, d| { + t | d.tau_x.is_some() + | d.tau_one.is_some() + | d.tau_two.is_some() + | d.part_commit.is_some() + }) + } + /// Build a new empty transaction. /// Wallet currently only supports tx with "features and commit" inputs. pub fn empty_transaction() -> Transaction { @@ -248,14 +317,16 @@ impl Slate { } /// Create a new slate - pub fn blank(num_participants: u8, is_invoice: bool) -> Slate { + pub fn blank(num_participants: u8, tx_flow: TxFlow) -> Slate { let np = match num_participants { 0 => 2, n => n, }; - let state = match is_invoice { - true => SlateState::Invoice1, - false => SlateState::Standard1, + let state = match tx_flow { + TxFlow::Standard => SlateState::Standard1, + TxFlow::Invoice => SlateState::Invoice1, + TxFlow::Multisig => SlateState::Multisig1, + TxFlow::Atomic => SlateState::Atomic1, }; Slate { num_participants: np, // assume 2 if not present @@ -274,8 +345,10 @@ impl Slate { }, payment_proof: None, kernel_features_args: None, + multisig_key_id: None, } } + /// Removes any signature data that isn't mine, for compacting /// slates for a return journey pub fn remove_other_sigdata( @@ -332,6 +405,60 @@ impl Slate { Ok(()) } + /// Find the pariticipant data index associated with the given index + pub fn find_participant_data_index( + &self, + secp: &secp::Secp256k1, + context: &Context, + ) -> Result { + let (public_blind, public_nonce) = context.get_public_keys(secp); + if let Some((i, _p)) = + self.participant_data.iter().enumerate().find(|(_i, p)| { + p.public_blind_excess == public_blind && p.public_nonce == public_nonce + }) { + Ok(i) + } else { + Err(ErrorKind::StoredTx("Missing participant data".into()).into()) + } + } + + /// In a two-party transaction, find the participant data index other than the one provided + pub fn find_other_participant_data_index(&self, idx: usize) -> Result { + for i in 0..self.participant_data.len() { + if i != idx { + return Ok(i); + } + } + Err(ErrorKind::StoredTx("Missing participant data".into()).into()) + } + + /// Create an atomic secret identifier with the prefix b'\x04mwatomic' + pub fn create_atomic_id(id: u32) -> Identifier { + ExtKeychain::derive_key_id( + 3, 0x6d776174, /* 'mwat' */ + 0x6f6d6963, /* 'omic' */ + id, 0, + ) + } + + /// Check that an atomic secret identifier is valid + pub fn check_atomic_id(id: &Identifier) -> Result<(), Error> { + let id_bytes = id.to_bytes(); + if &id_bytes[..9] == ATOMIC_ID_PREFIX { + Ok(()) + } else { + Err(ErrorKind::GenericError("Invalid atomic ID".into()).into()) + } + } + + /// Convert the atomic secret identifier to an unsigned integer + pub fn atomic_id_to_int(id: &Identifier) -> Result { + Self::check_atomic_id(id)?; + let mut id_bytes = [0; 4]; + id_bytes.copy_from_slice(&id.to_bytes()[9..13]); + Ok(u32::from_be_bytes(id_bytes)) + } + /// Completes callers part of round 1, adding public key info /// to the slate pub fn fill_round_1(&mut self, keychain: &K, context: &mut Context) -> Result<(), Error> @@ -400,6 +527,7 @@ impl Slate { keychain.secp(), sec_key, sec_nonce, + None, &self.pub_nonce_sum(keychain.secp())?, Some(&self.pub_blind_sum(keychain.secp())?), &self.msg_to_sign()?, @@ -418,6 +546,115 @@ impl Slate { Ok(()) } + /// Create a partial signature over the Slate using the atomic swap receiver's + /// atomic secret + pub fn fill_round_2_atomic(&mut self, keychain: &K, context: &Context) -> Result<(), Error> + where + K: Keychain, + { + let secp = keychain.secp(); + let part_sig = aggsig::calculate_partial_sig( + secp, + &context.sec_key, + &context.sec_nonce, + context.get_secret_atomic(), + &self.pub_nonce_sum(secp)?, + Some(&self.pub_blind_sum(secp)?), + &self.msg_to_sign()?, + )?; + + let pdata_idx = self.find_participant_data_index(secp, context)?; + self.participant_data[pdata_idx].part_sig = Some(part_sig); + Ok(()) + } + + /// Verify the receiver's adaptor signature, and create the sender's partial signature + pub fn fill_round_3_atomic( + &mut self, + keychain: &K, + context: &Context, + ) -> Result + where + K: Keychain, + { + let secp = keychain.secp(); + let pdata_idx = self.find_participant_data_index(secp, context)?; + let opdata_idx = self.find_other_participant_data_index(pdata_idx)?; + let part_sig = &self.participant_data[opdata_idx].part_sig.ok_or::( + ErrorKind::Signature("Missing round 2 atomic swap adaptor signature".into()).into(), + )?; + let msg = self.msg_to_sign()?; + let nonce_sum = self.pub_nonce_sum(secp)?; + let key_sum = self.pub_blind_sum(secp)?; + let pub_atomic = self.participant_data[opdata_idx] + .public_atomic + .as_ref() + .ok_or(Error::from(ErrorKind::GenericError( + "Missing atomic public key".into(), + )))?; + + debug!( + "Other party's atomic public key: {}", + pub_atomic.serialize_vec(secp, true).as_ref().to_hex() + ); + debug!("Validate against the key used to lock funds on the other chain.\n"); + + aggsig::verify_partial_sig( + secp, + part_sig, + &nonce_sum, + self.participant_data[opdata_idx].public_atomic.as_ref(), + &self.participant_data[opdata_idx].public_blind_excess, + Some(&key_sum), + &msg, + )?; + + let a_part_sig = aggsig::calculate_partial_sig( + secp, + &context.sec_key, + &context.sec_nonce, + None, + &nonce_sum, + Some(&key_sum), + &msg, + )?; + + self.participant_data[pdata_idx].part_sig = Some(a_part_sig); + + // return the receiver's adaptor signature from round 2 + Ok((*part_sig).clone()) + } + + /// Finalize the atomic swap transaction, return the receiver's partial signature + pub fn finalize_atomic( + &mut self, + keychain: &K, + context: &Context, + ) -> Result + where + K: Keychain, + { + let secp = keychain.secp(); + + let part_sig = aggsig::calculate_partial_sig( + secp, + &context.sec_key, + &context.sec_nonce, + None, + &self.pub_nonce_sum(secp)?, + Some(&self.pub_blind_sum(secp)?), + &self.msg_to_sign()?, + )?; + + let pdata_idx = self.find_participant_data_index(secp, context)?; + self.participant_data[pdata_idx].part_sig = Some(part_sig.clone()); + + let final_sig = self.finalize_signature(secp)?; + self.finalize_transaction(keychain, &final_sig)?; + + Ok(part_sig) + } + /// Creates the final signature, callable by either the sender or recipient /// (after phase 3: sender confirmation) pub fn finalize(&mut self, keychain: &K) -> Result<(), Error> @@ -488,6 +725,11 @@ impl Slate { let pub_key = PublicKey::from_secret_key(keychain.secp(), &context.sec_key)?; let pub_nonce = PublicKey::from_secret_key(keychain.secp(), &context.sec_nonce)?; let mut part_sig = part_sig; + let part_commit = context.partial_commit.clone(); + let tau_x = context.tau_x.clone(); + let tau_one = context.tau_one.clone(); + let tau_two = context.tau_two.clone(); + let pub_atomic = context.get_public_atomic(keychain.secp())?; // Remove if already here and replace self.participant_data = self @@ -508,7 +750,12 @@ impl Slate { self.participant_data.push(ParticipantData { public_blind_excess: pub_key, public_nonce: pub_nonce, + public_atomic: pub_atomic, part_sig: part_sig, + part_commit, + tau_x: tau_x, + tau_one: tau_one, + tau_two: tau_two, }); Ok(()) } @@ -580,6 +827,7 @@ impl Slate { secp, p.part_sig.as_ref().unwrap(), &self.pub_nonce_sum(secp)?, + None, &p.public_blind_excess, Some(&self.pub_blind_sum(secp)?), &self.msg_to_sign()?, @@ -681,6 +929,40 @@ impl Slate { Ok(()) } } + + /// Get the SlateVersion of the Slate + pub fn version(&self) -> SlateVersion { + match self.version_info.version { + 4 => SlateVersion::V4, + 5 | _ => SlateVersion::V5, + } + } + + /// Calculate multisig key ID + pub fn create_multisig_id(&self) -> Identifier { + let mut hasher = Blake2b::new(16); + hasher.update(b"multisig_id"); + hasher.update(self.id.as_bytes().as_ref()); + hasher.update(self.amount.to_be_bytes().as_ref()); + let hash = hasher.finalize(); + let hash_bytes = hash.as_bytes(); + + let mut id_bytes = [[0; 4]; 4]; + id_bytes[0].copy_from_slice(&hash_bytes[..4]); + id_bytes[1].copy_from_slice(&hash_bytes[4..8]); + id_bytes[2].copy_from_slice(&hash_bytes[8..12]); + id_bytes[3].copy_from_slice(&hash_bytes[12..16]); + + let key_path = ExtKeychainPath::new( + 4, + u32::from_be_bytes(id_bytes[0]), + u32::from_be_bytes(id_bytes[1]), + u32::from_be_bytes(id_bytes[2]), + u32::from_be_bytes(id_bytes[3]), + ); + + Identifier::from_path(&key_path) + } } impl Serialize for Slate { @@ -688,8 +970,16 @@ impl Serialize for Slate { where S: Serializer, { - let v4 = SlateV4::from(self); - v4.serialize(serializer) + match self.version() { + SlateVersion::V4 => { + let v4 = SlateV4::from(self); + v4.serialize(serializer) + } + SlateVersion::V5 => { + let v5 = SlateV5::from(self); + v5.serialize(serializer) + } + } } } // Current slate version to versioned conversions @@ -711,6 +1001,7 @@ impl From for SlateV4 { version_info, payment_proof, kernel_features_args, + multisig_key_id: _, } = slate.clone(); let participant_data = map_vec!(participant_data, |data| ParticipantDataV4::from(data)); let ver = VersionCompatInfoV4::from(&version_info); @@ -741,6 +1032,55 @@ impl From for SlateV4 { } } +// Slate to versioned +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, + multisig_key_id, + } = 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, + multisig_key_id, + } + } +} + impl From<&Slate> for SlateV4 { fn from(slate: &Slate) -> SlateV4 { let Slate { @@ -757,6 +1097,7 @@ impl From<&Slate> for SlateV4 { version_info, payment_proof, kernel_features_args, + multisig_key_id: _, } = slate; let num_parts = *num_parts; let id = *id; @@ -794,6 +1135,62 @@ impl From<&Slate> for SlateV4 { } } +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, + multisig_key_id, + } = 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, + }; + let multisig_key_id = multisig_key_id.clone(); + 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, + multisig_key_id, + } + } +} + impl From<&Slate> for Option> { fn from(slate: &Slate) -> Self { match slate.tx { @@ -817,12 +1214,40 @@ impl From<&Slate> for Option> { } } +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 ParticipantDataV4 { fn from(data: &ParticipantData) -> ParticipantDataV4 { let ParticipantData { public_blind_excess, public_nonce, + public_atomic: _, part_sig, + part_commit: _, + tau_x: _, + tau_one: _, + tau_two: _, } = data; let public_blind_excess = *public_blind_excess; let public_nonce = *public_nonce; @@ -835,6 +1260,39 @@ impl From<&ParticipantData> for ParticipantDataV4 { } } +impl From<&ParticipantData> for ParticipantDataV5 { + fn from(data: &ParticipantData) -> ParticipantDataV5 { + let ParticipantData { + public_blind_excess, + public_nonce, + public_atomic, + part_sig, + part_commit, + tau_x, + tau_one, + tau_two, + } = data; + let public_blind_excess = *public_blind_excess; + let public_nonce = *public_nonce; + let public_atomic = *public_atomic; + let part_sig = *part_sig; + let part_commit = *part_commit; + let tau_x = tau_x.clone(); + let tau_one = tau_one.clone(); + let tau_two = tau_two.clone(); + ParticipantDataV5 { + xs: public_blind_excess, + nonce: public_nonce, + atomic: public_atomic, + part: part_sig, + part_commit, + tau_x, + tau_one, + tau_two, + } + } +} + impl From<&SlateState> for SlateStateV4 { fn from(data: &SlateState) -> SlateStateV4 { match data { @@ -845,6 +1303,29 @@ impl From<&SlateState> for SlateStateV4 { SlateState::Invoice1 => SlateStateV4::Invoice1, SlateState::Invoice2 => SlateStateV4::Invoice2, SlateState::Invoice3 => SlateStateV4::Invoice3, + _ => SlateStateV4::Unknown, + } + } +} + +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, + SlateState::Atomic1 => SlateStateV5::Atomic1, + SlateState::Atomic2 => SlateStateV5::Atomic2, + SlateState::Atomic3 => SlateStateV5::Atomic3, + SlateState::Atomic4 => SlateStateV5::Atomic4, + SlateState::Multisig1 => SlateStateV5::Multisig1, + SlateState::Multisig2 => SlateStateV5::Multisig2, + SlateState::Multisig3 => SlateStateV5::Multisig3, + SlateState::Multisig4 => SlateStateV5::Multisig4, } } } @@ -857,6 +1338,14 @@ impl From<&KernelFeaturesArgs> for KernelFeaturesArgsV4 { } } +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 VersionCompatInfoV4 { fn from(data: &VersionCompatInfo) -> VersionCompatInfoV4 { let VersionCompatInfo { @@ -872,6 +1361,21 @@ impl From<&VersionCompatInfo> for VersionCompatInfoV4 { } } +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 PaymentInfoV4 { fn from(data: &PaymentInfo) -> PaymentInfoV4 { let PaymentInfo { @@ -890,16 +1394,46 @@ impl From<&PaymentInfo> for PaymentInfoV4 { } } +impl From<&PaymentInfo> for PaymentInfoV5 { + fn from(data: &PaymentInfo) -> PaymentInfoV5 { + let PaymentInfo { + sender_address, + receiver_address, + receiver_signature, + } = data; + let sender_address = *sender_address; + let receiver_address = *receiver_address; + let receiver_signature = *receiver_signature; + PaymentInfoV5 { + saddr: sender_address, + raddr: receiver_address, + rsig: receiver_signature, + } + } +} + impl From for OutputFeaturesV4 { fn from(of: OutputFeatures) -> OutputFeaturesV4 { let index = match of { OutputFeatures::Plain => 0, OutputFeatures::Coinbase => 1, + OutputFeatures::Multisig => 2, }; OutputFeaturesV4(index) } } +impl From for OutputFeaturesV5 { + fn from(of: OutputFeatures) -> OutputFeaturesV5 { + let index = match of { + OutputFeatures::Plain => 0, + OutputFeatures::Coinbase => 1, + OutputFeatures::Multisig => 2, + }; + OutputFeaturesV5(index) + } +} + // Versioned to current slate impl From for Slate { fn from(slate: SlateV4) -> Slate { @@ -943,6 +1477,56 @@ impl From for Slate { version_info, payment_proof, kernel_features_args, + multisig_key_id: None, + } + } +} + +// Versioned to current slate +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, + multisig_key_id, + } = 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, + multisig_key_id, } } } @@ -954,13 +1538,89 @@ pub fn tx_from_slate_v4(slate: &SlateV4) -> Option { }; let secp = static_secp_instance(); let secp = secp.lock(); - let mut calc_slate = Slate::blank(2, false); + let mut calc_slate = Slate::blank(2, TxFlow::Standard); + 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, + public_atomic: None, + part_sig: d.part, + part_commit: None, + tau_x: None, + tau_one: None, + tau_two: None, + }); + } + 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) +} + +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, TxFlow::Standard); 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, + public_atomic: d.atomic, part_sig: d.part, + part_commit: d.part_commit, + tau_x: d.tau_x.clone(), + tau_one: d.tau_one.clone(), + tau_two: d.tau_two.clone(), }); } let excess = match calc_slate.calc_excess(&secp) { @@ -1013,13 +1673,20 @@ pub fn tx_from_slate_v4(slate: &SlateV4) -> Option { Some(tx) } -// Node's Transaction object and lock height to SlateV4 `coms` +// Node's Transaction object and lock height to SlateV5 `coms` impl From<&SlateV4> for Option { fn from(slate: &SlateV4) -> Option { tx_from_slate_v4(slate) } } +// 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<&ParticipantDataV4> for ParticipantData { fn from(data: &ParticipantDataV4) -> ParticipantData { let ParticipantDataV4 { @@ -1033,7 +1700,45 @@ impl From<&ParticipantDataV4> for ParticipantData { ParticipantData { public_blind_excess, public_nonce, + public_atomic: None, + part_sig, + part_commit: None, + tau_x: None, + tau_one: None, + tau_two: None, + } + } +} + +impl From<&ParticipantDataV5> for ParticipantData { + fn from(data: &ParticipantDataV5) -> ParticipantData { + let ParticipantDataV5 { + xs: public_blind_excess, + nonce: public_nonce, + part: part_sig, + atomic: public_atomic, + part_commit, + tau_x, + tau_one, + tau_two, + } = data; + let public_blind_excess = *public_blind_excess; + let public_nonce = *public_nonce; + let public_atomic = *public_atomic; + let part_sig = *part_sig; + let part_commit = *part_commit; + let tau_x = tau_x.clone(); + let tau_one = tau_one.clone(); + let tau_two = tau_two.clone(); + ParticipantData { + public_blind_excess, + public_nonce, + public_atomic, part_sig, + part_commit, + tau_x, + tau_one, + tau_two, } } } @@ -1046,6 +1751,14 @@ impl From<&KernelFeaturesArgsV4> for KernelFeaturesArgs { } } +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<&SlateStateV4> for SlateState { fn from(data: &SlateStateV4) -> SlateState { match data { @@ -1060,6 +1773,28 @@ impl From<&SlateStateV4> for SlateState { } } +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, + SlateStateV5::Multisig1 => SlateState::Multisig1, + SlateStateV5::Multisig2 => SlateState::Multisig2, + SlateStateV5::Multisig3 => SlateState::Multisig3, + SlateStateV5::Multisig4 => SlateState::Multisig4, + SlateStateV5::Atomic1 => SlateState::Atomic1, + SlateStateV5::Atomic2 => SlateState::Atomic2, + SlateStateV5::Atomic3 => SlateState::Atomic3, + SlateStateV5::Atomic4 => SlateState::Atomic4, + } + } +} + impl From<&VersionCompatInfoV4> for VersionCompatInfo { fn from(data: &VersionCompatInfoV4) -> VersionCompatInfo { let VersionCompatInfoV4 { @@ -1075,6 +1810,21 @@ impl From<&VersionCompatInfoV4> for VersionCompatInfo { } } +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<&PaymentInfoV4> for PaymentInfo { fn from(data: &PaymentInfoV4) -> PaymentInfo { let PaymentInfoV4 { @@ -1093,6 +1843,24 @@ impl From<&PaymentInfoV4> for PaymentInfo { } } +impl From<&PaymentInfoV5> for PaymentInfo { + fn from(data: &PaymentInfoV5) -> PaymentInfo { + let PaymentInfoV5 { + saddr: sender_address, + raddr: receiver_address, + rsig: receiver_signature, + } = data; + let sender_address = *sender_address; + let receiver_address = *receiver_address; + let receiver_signature = *receiver_signature; + PaymentInfo { + sender_address, + receiver_address, + receiver_signature, + } + } +} + impl From for OutputFeatures { fn from(of: OutputFeaturesV4) -> OutputFeatures { match of.0 { @@ -1101,3 +1869,13 @@ impl From for OutputFeatures { } } } + +impl From for OutputFeatures { + fn from(of: OutputFeaturesV5) -> OutputFeatures { + match of.0 { + 2 => OutputFeatures::Multisig, + 1 => OutputFeatures::Coinbase, + 0 | _ => OutputFeatures::Plain, + } + } +} diff --git a/libwallet/src/slate_versions/mod.rs b/libwallet/src/slate_versions/mod.rs index ca2e749f8..4f63d5ca1 100644 --- a/libwallet/src/slate_versions/mod.rs +++ b/libwallet/src/slate_versions/mod.rs @@ -20,6 +20,8 @@ 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; @@ -31,8 +33,13 @@ 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 = 4; +pub const CURRENT_SLATE_VERSION: u16 = 5; /// The grin block header this slate is intended to be compatible with pub const GRIN_BLOCK_HEADER_VERSION: u16 = 3; @@ -40,6 +47,8 @@ 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 (next version) + V5, /// V4 (most current) V4, } @@ -49,6 +58,8 @@ 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 { + /// Next (5.1.0 Onwards) + V5(SlateV5), /// Current (4.0.0 Onwards ) V4(SlateV4), } @@ -58,6 +69,7 @@ impl VersionedSlate { pub fn version(&self) -> SlateVersion { match *self { VersionedSlate::V4(_) => SlateVersion::V4, + VersionedSlate::V5(_) => SlateVersion::V5, } } @@ -65,6 +77,7 @@ impl VersionedSlate { pub fn into_version(slate: Slate, version: SlateVersion) -> Result { match version { SlateVersion::V4 => Ok(VersionedSlate::V4(slate.into())), + SlateVersion::V5 => Ok(VersionedSlate::V5(slate.into())), } } } @@ -73,6 +86,7 @@ impl From for Slate { fn from(slate: VersionedSlate) -> Slate { match slate { VersionedSlate::V4(s) => Slate::from(s), + VersionedSlate::V5(s) => Slate::from(s), } } } @@ -82,6 +96,8 @@ impl From for Slate { /// Binary versions, can only be parsed 1:1 into the appropriate /// version, and VersionedSlate can up/downgrade from there pub enum VersionedBinSlate { + /// Version 5, binary + V5(SlateV5Bin), /// Version 4, binary V4(SlateV4Bin), } @@ -91,6 +107,7 @@ impl TryFrom for VersionedBinSlate { fn try_from(slate: VersionedSlate) -> Result { match slate { VersionedSlate::V4(s) => Ok(VersionedBinSlate::V4(SlateV4Bin(s))), + VersionedSlate::V5(s) => Ok(VersionedBinSlate::V5(SlateV5Bin(s))), } } } @@ -99,6 +116,7 @@ impl From for VersionedSlate { fn from(slate: VersionedBinSlate) -> VersionedSlate { match slate { VersionedBinSlate::V4(s) => VersionedSlate::V4(s.0), + VersionedBinSlate::V5(s) => VersionedSlate::V5(s.0), } } } @@ -108,6 +126,8 @@ impl From for VersionedSlate { /// Versions are ordered newest to oldest so serde attempts to /// deserialize newer versions first, then falls back to older versions. pub enum VersionedCoinbase { + /// Next supported coinbase version. + V5(CoinbaseV5), /// Current supported coinbase version. V4(CoinbaseV4), } @@ -117,6 +137,7 @@ impl VersionedCoinbase { pub fn into_version(cb: CbData, version: SlateVersion) -> VersionedCoinbase { match version { SlateVersion::V4 => VersionedCoinbase::V4(cb.into()), + SlateVersion::V5 => VersionedCoinbase::V5(cb.into()), } } } diff --git a/libwallet/src/slate_versions/ser.rs b/libwallet/src/slate_versions/ser.rs index 58f866a08..e700e88e9 100644 --- a/libwallet/src/slate_versions/ser.rs +++ b/libwallet/src/slate_versions/ser.rs @@ -514,6 +514,48 @@ pub mod version_info_v4 { } } +/// Serializes slates 'version_info' field +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 pub mod slate_state_v4 { use serde::de::Error; @@ -559,6 +601,67 @@ pub mod slate_state_v4 { } } +/// Serializes slates 'state' field +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", + SlateStateV5::Atomic1 => "A1", + SlateStateV5::Atomic2 => "A2", + SlateStateV5::Atomic3 => "A3", + SlateStateV5::Atomic4 => "A4", + SlateStateV5::Multisig1 => "M1", + SlateStateV5::Multisig2 => "M2", + SlateStateV5::Multisig3 => "M3", + SlateStateV5::Multisig4 => "M4", + }; + 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, + "A1" => SlateStateV5::Atomic1, + "A2" => SlateStateV5::Atomic2, + "A3" => SlateStateV5::Atomic3, + "A4" => SlateStateV5::Atomic4, + "M1" => SlateStateV5::Multisig1, + "M2" => SlateStateV5::Multisig2, + "M3" => SlateStateV5::Multisig3, + "M4" => SlateStateV5::Multisig4, + _ => 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 fc0b1e998..afdcf1259 100644 --- a/libwallet/src/slate_versions/v4_bin.rs +++ b/libwallet/src/slate_versions/v4_bin.rs @@ -488,11 +488,11 @@ impl Readable for SlateV4Bin { fn slate_v4_serialize_deserialize() { use crate::grin_util::from_hex; use crate::grin_util::secp::key::PublicKey; - use crate::Slate; + use crate::{Slate, TxFlow}; use grin_wallet_util::grin_core::global::{set_local_chain_type, ChainTypes}; use grin_wallet_util::grin_keychain::{ExtKeychain, Keychain, SwitchCommitmentType}; set_local_chain_type(ChainTypes::Mainnet); - let slate = Slate::blank(1, false); + let slate = Slate::blank(1, TxFlow::Standard); let mut v4 = SlateV4::from(slate); let keychain = ExtKeychain::from_random_seed(true).unwrap(); diff --git a/libwallet/src/slate_versions/v5.rs b/libwallet/src/slate_versions/v5.rs new file mode 100644 index 000000000..81997390c --- /dev/null +++ b/libwallet/src/slate_versions/v5.rs @@ -0,0 +1,419 @@ +// 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. + +//! Contains V5 of the slate (grin-wallet 5.1.0) +//! Changes from V4: +//! /#### ParticipantData Struct +//! +//! * `tau_x` is added for creating multisig output range proofs +//! * `tau_one` is added for creating multisig output range proofs +//! * `tau_two` is added for creating multisig output range proofs + +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, SecretKey}; +use crate::grin_util::secp::pedersen::{Commitment, RangeProof}; +use crate::grin_util::secp::Signature; +use crate::{slate_versions::ser, CbData}; +use ed25519_dalek::PublicKey as DalekPublicKey; +use ed25519_dalek::Signature as DalekSignature; +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, + /// Multisig output identifier + #[serde(default = "default_multisig_id_none")] + #[serde(skip_serializing_if = "Option::is_none")] + pub multisig_key_id: 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 +} + +fn default_multisig_id_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, + /// Multisig flow, freshly init + Multisig1, + ///Multisig flow, step 1 proof build + Multisig2, + /// Multisig flow, step 2 proof build + Multisig3, + /// Multisig flow, final proof step + Multisig4, + /// Atomic flow, freshly init + Atomic1, + ///Atomic flow, return journey + Atomic2, + /// Atomic flow, partial signature from initiator + Atomic3, + /// Atomic flow, ready for tranasction posting + Atomic4, +} + +#[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 key corresponding to atomic secret + #[serde(default = "default_atomic_none")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "secp_ser::option_pubkey_serde")] + pub atomic: Option, + /// 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, + /// Public partial commitment to multisig output value + #[serde(default = "default_part_com_none")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "secp_ser::option_commitment_serde")] + pub part_commit: Option, + /// Tau X key for shared outputs + #[serde(default = "default_tau_x_none")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "secp_ser::option_seckey_serde")] + pub tau_x: Option, + /// Tau part one key for shared outputs + #[serde(default = "default_tau_part_none")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "secp_ser::option_pubkey_serde")] + pub tau_one: Option, + /// Tau part two key for shared outputs + #[serde(default = "default_tau_part_none")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "secp_ser::option_pubkey_serde")] + pub tau_two: Option, +} + +fn default_atomic_none() -> Option { + None +} + +fn default_part_sig_none() -> Option { + None +} + +fn default_part_com_none() -> Option { + None +} + +fn default_tau_x_none() -> Option { + None +} + +fn default_tau_part_none() -> Option { + None +} + +#[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(default = "default_receiver_signature_none")] + #[serde(with = "ser::option_dalek_sig_serde")] + #[serde(skip_serializing_if = "Option::is_none")] + pub rsig: Option, +} + +fn default_receiver_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 new file mode 100644 index 000000000..814460aac --- /dev/null +++ b/libwallet/src/slate_versions/v5_bin.rs @@ -0,0 +1,695 @@ +// 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. + +//! Wraps a V5 Slate into a V5 Binary slate + +use crate::grin_core::core::transaction::{FeeFields, OutputFeatures}; +use crate::grin_core::ser as grin_ser; +use crate::grin_core::ser::{Readable, Reader, Writeable, Writer}; +use crate::grin_keychain::{BlindingFactor, Identifier, IDENTIFIER_SIZE}; +use crate::grin_util::secp::key::{PublicKey, SecretKey}; +use crate::grin_util::secp::pedersen::{Commitment, RangeProof}; +use crate::grin_util::secp::Signature; +use crate::grin_util::static_secp_instance; +use ed25519_dalek::PublicKey as DalekPublicKey; +use ed25519_dalek::Signature as DalekSignature; +use std::convert::TryFrom; +use uuid::Uuid; + +use crate::slate_versions::v5::{ + CommitsV5, KernelFeaturesArgsV5, ParticipantDataV5, PaymentInfoV5, SlateStateV5, SlateV5, + VersionCompatInfoV5, +}; + +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, + SlateStateV5::Multisig1 => 7, + SlateStateV5::Multisig2 => 8, + SlateStateV5::Multisig3 => 9, + SlateStateV5::Multisig4 => 10, + SlateStateV5::Atomic1 => 11, + SlateStateV5::Atomic2 => 12, + SlateStateV5::Atomic3 => 13, + SlateStateV5::Atomic4 => 14, + }; + 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, + 7 => SlateStateV5::Multisig1, + 8 => SlateStateV5::Multisig2, + 9 => SlateStateV5::Multisig3, + 10 => SlateStateV5::Multisig4, + 11 => SlateStateV5::Atomic1, + 12 => SlateStateV5::Atomic2, + 13 => SlateStateV5::Atomic3, + 14 => SlateStateV5::Atomic4, + _ => SlateStateV5::Unknown, + }; + Ok(sta) + } +} + +/// Allow serializing of Uuids not defined in crate +struct UuidWrap(Uuid); + +impl Writeable for UuidWrap { + fn write(&self, writer: &mut W) -> Result<(), grin_ser::Error> { + writer.write_fixed_bytes(&self.0.as_bytes()) + } +} + +impl Readable for UuidWrap { + fn read(reader: &mut R) -> Result { + let bytes = reader.read_fixed_bytes(16)?; + let mut b = [0u8; 16]; + b.copy_from_slice(&bytes[0..16]); + Ok(UuidWrap(Uuid::from_bytes(b))) + } +} + +/// Helper struct to serialize optional fields efficiently +struct SlateOptFields { + /// num parts, default 2 + pub num_parts: u8, + /// amt, default 0 + pub amt: u64, + /// fee_fields, default FeeFields::zero() + pub fee: FeeFields, + /// kernel features, default 0 + pub feat: u8, + /// ttl, default 0 + pub ttl: u64, +} + +impl Writeable for SlateOptFields { + fn write(&self, writer: &mut W) -> Result<(), grin_ser::Error> { + // Status byte, bits determing which optional fields are serialized + // 0 0 0 1 1 1 1 1 + // t f f a n + let mut status = 0u8; + if self.num_parts != 2 { + status |= 0x01; + } + if self.amt > 0 { + status |= 0x02; + } + if self.fee.fee() > 0 { + // apply fee mask past HF4 + status |= 0x04; + } + if self.feat > 0 { + status |= 0x08; + } + if self.ttl > 0 { + status |= 0x10; + } + writer.write_u8(status)?; + if status & 0x01 > 0 { + writer.write_u8(self.num_parts)?; + } + if status & 0x02 > 0 { + writer.write_u64(self.amt)?; + } + if status & 0x04 > 0 { + self.fee.write(writer)?; + } + if status & 0x08 > 0 { + writer.write_u8(self.feat)?; + } + if status & 0x10 > 0 { + writer.write_u64(self.ttl)?; + } + Ok(()) + } +} + +impl Readable for SlateOptFields { + fn read(reader: &mut R) -> Result { + let status = reader.read_u8()?; + let num_parts = if status & 0x01 > 0 { + reader.read_u8()? + } else { + 2 + }; + let amt = if status & 0x02 > 0 { + reader.read_u64()? + } else { + 0 + }; + let fee = if status & 0x04 > 0 { + FeeFields::read(reader)? + } else { + FeeFields::zero() + }; + let feat = if status & 0x08 > 0 { + reader.read_u8()? + } else { + 0 + }; + let ttl = if status & 0x10 > 0 { + reader.read_u64()? + } else { + 0 + }; + Ok(SlateOptFields { + num_parts, + amt, + fee, + feat, + ttl, + }) + } +} + +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 bit set means part sig included + //2 bit set means atomic included + //4 bit set means part commit included + //8 bit set means tau_x included + //16 bit set means tau_one included + //32 bit set means tau_two included + let mut optional = s.part.is_some() as u8; + if s.atomic.is_some() { + optional |= 2; + } + if s.part_commit.is_some() { + optional |= 4; + } + if s.tau_x.is_some() { + optional |= 8; + } + if s.tau_one.is_some() { + optional |= 16; + } + if s.tau_two.is_some() { + optional |= 32; + } + writer.write_u8(optional)?; + s.xs.write(writer)?; + s.nonce.write(writer)?; + if let Some(a) = s.atomic { + a.write(writer)?; + } + if let Some(s) = s.part { + s.write(writer)?; + } + if let Some(c) = s.part_commit { + c.write(writer)?; + } + if let Some(tx) = s.tau_x.as_ref() { + writer.write_fixed_bytes(tx.0)?; + } + if let Some(to) = s.tau_one.as_ref() { + to.write(writer)?; + } + if let Some(tt) = s.tau_two.as_ref() { + tt.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_optional = reader.read_u8()?; + let has_atomic = has_optional & 2 != 0; + let has_partial = has_optional & 1 != 0; + let has_part_com = has_optional & 4 != 0; + let has_tau_x = has_optional & 8 != 0; + let has_tau_one = has_optional & 16 != 0; + let has_tau_two = has_optional & 32 != 0; + let c = ParticipantDataV5 { + xs: PublicKey::read(reader)?, + nonce: PublicKey::read(reader)?, + atomic: match has_atomic { + true => Some(PublicKey::read(reader)?), + false => None, + }, + part: match has_partial { + true => Some(Signature::read(reader)?), + false => None, + }, + part_commit: match has_part_com { + true => Some(Commitment::read(reader)?), + false => None, + }, + tau_x: match has_tau_x { + true => { + let secp = static_secp_instance(); + let secp = secp.lock(); + let key_bytes = reader.read_fixed_bytes(32)?; + Some( + SecretKey::from_slice(&secp, &key_bytes) + .map_err(|_| grin_ser::Error::CorruptedData)?, + ) + } + false => None, + }, + tau_one: match has_tau_one { + true => Some(PublicKey::read(reader)?), + false => None, + }, + tau_two: match has_tau_two { + true => Some(PublicKey::read(reader)?), + false => 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())?; + match self.0.rsig { + Some(s) => { + writer.write_u8(1)?; + writer.write_fixed_bytes(&s.to_bytes().to_vec())?; + } + 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 rsig = match reader.read_u8()? { + 0 => None, + 1 | _ => Some(DalekSignature::try_from(&reader.read_fixed_bytes(64)?[..]).unwrap()), + }; + Ok(ProofWrap(PaymentInfoV5 { saddr, raddr, rsig })) + } +} + +#[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(5), 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)) + .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)?; + } + if let Some(mid) = v5.multisig_key_id.as_ref() { + writer.write_u8(1)?; + writer.write_fixed_bytes(mid.to_bytes())?; + } else { + writer.write_u8(0)?; + } + 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 + }; + + let multisig_key_id = if reader.read_u8()? != 0 { + let id_bytes = reader.read_fixed_bytes(IDENTIFIER_SIZE)?; + Some(Identifier::from_bytes(id_bytes.as_ref())) + } 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, + multisig_key_id, + })) + } +} + +#[test] +fn slate_v5_serialize_deserialize() { + use crate::grin_util::from_hex; + use crate::grin_util::secp::key::PublicKey; + use crate::{Slate, TxFlow}; + use grin_wallet_util::grin_core::global::{set_local_chain_type, ChainTypes}; + use grin_wallet_util::grin_keychain::{ExtKeychain, Keychain, SwitchCommitmentType}; + set_local_chain_type(ChainTypes::Mainnet); + let slate = Slate::blank(1, TxFlow::Standard); + let mut v5 = SlateV5::from(slate); + + 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, + atomic: None, + part: None, + part_commit: None, + tau_x: None, + tau_one: None, + tau_two: None, + }; + let part2 = ParticipantDataV5 { + xs, + nonce, + atomic: None, + part: Some(Signature::from_raw_data(&[11; 64]).unwrap()), + part_commit: None, + tau_x: None, + tau_one: None, + tau_two: None, + }; + 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 v5_1 = v5.clone(); + let v5_1_copy = 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); + let v5_2_coms = v5_2.coms.as_ref().unwrap().clone(); + for (i, c) in v5_1.coms.unwrap().iter().enumerate() { + assert_eq!(c.f, v5_2_coms[i].f); + assert_eq!(c.c, v5_2_coms[i].c); + assert_eq!(c.p, v5_2_coms[i].p); + } + assert_eq!(v5_1.sigs, v5_2.sigs); + assert_eq!(v5_1.proof, v5_2.proof); + + // Include Payment proof, remove coms to mix it up a bit + let mut v5 = v5_1_copy; + let raw_pubkey_str = "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb"; + let b = from_hex(raw_pubkey_str).unwrap(); + let d_pkey = DalekPublicKey::from_bytes(&b).unwrap(); + v5.proof = Some(PaymentInfoV5 { + raddr: d_pkey.clone(), + saddr: d_pkey.clone(), + rsig: None, + }); + 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/packer.rs b/libwallet/src/slatepack/packer.rs index 9ef31ccaa..c26fc8fba 100644 --- a/libwallet/src/slatepack/packer.rs +++ b/libwallet/src/slatepack/packer.rs @@ -17,8 +17,8 @@ use std::str; use super::armor::HEADER; use crate::{ - slatepack, Slate, SlateVersion, Slatepack, SlatepackAddress, SlatepackArmor, SlatepackBin, - VersionedBinSlate, VersionedSlate, + slatepack, Slate, Slatepack, SlatepackAddress, SlatepackArmor, SlatepackBin, VersionedBinSlate, + VersionedSlate, }; use crate::{Error, ErrorKind}; @@ -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::V4)?; + let out_slate = VersionedSlate::into_version(slate.clone(), slate.version())?; let bin_slate = VersionedBinSlate::try_from(out_slate).map_err(|_| ErrorKind::SlatepackSer)?; let mut slatepack = Slatepack::default(); diff --git a/libwallet/src/slatepack/types.rs b/libwallet/src/slatepack/types.rs index 52375da0e..60bc095a4 100644 --- a/libwallet/src/slatepack/types.rs +++ b/libwallet/src/slatepack/types.rs @@ -20,7 +20,7 @@ use x25519_dalek::StaticSecret; use crate::dalek_ser; use crate::grin_core::ser::{self, Readable, Reader, Writeable, Writer}; -use crate::{Error, ErrorKind}; +use crate::{Error, ErrorKind, CURRENT_SLATE_VERSION}; use grin_wallet_util::byte_ser; use super::SlatepackAddress; @@ -260,8 +260,12 @@ impl serde::Serialize for SlatepackBin { S: serde::Serializer, { let mut vec = vec![]; - ser::serialize(&mut vec, ser::ProtocolVersion(4), self) - .map_err(|err| serde::ser::Error::custom(err.to_string()))?; + ser::serialize( + &mut vec, + ser::ProtocolVersion(CURRENT_SLATE_VERSION as u32), + self, + ) + .map_err(|err| serde::ser::Error::custom(err.to_string()))?; serializer.serialize_bytes(&vec) } } @@ -285,8 +289,11 @@ impl<'de> serde::Deserialize<'de> for SlatepackBin { E: serde::de::Error, { let mut reader = std::io::Cursor::new(value.to_vec()); - let s = ser::deserialize(&mut reader, ser::ProtocolVersion(4)) - .map_err(|err| serde::de::Error::custom(err.to_string()))?; + let s = ser::deserialize( + &mut reader, + ser::ProtocolVersion(CURRENT_SLATE_VERSION as u32), + ) + .map_err(|err| serde::de::Error::custom(err.to_string()))?; Ok(s) } } @@ -488,8 +495,12 @@ impl serde::Serialize for SlatepackEncMetadataBin { S: serde::Serializer, { let mut vec = vec![]; - ser::serialize(&mut vec, ser::ProtocolVersion(4), self) - .map_err(|err| serde::ser::Error::custom(err.to_string()))?; + ser::serialize( + &mut vec, + ser::ProtocolVersion(CURRENT_SLATE_VERSION as u32), + self, + ) + .map_err(|err| serde::ser::Error::custom(err.to_string()))?; serializer.serialize_bytes(&vec) } } @@ -513,8 +524,11 @@ impl<'de> serde::Deserialize<'de> for SlatepackEncMetadataBin { E: serde::de::Error, { let mut reader = std::io::Cursor::new(value.to_vec()); - let s = ser::deserialize(&mut reader, ser::ProtocolVersion(4)) - .map_err(|err| serde::de::Error::custom(err.to_string()))?; + let s = ser::deserialize( + &mut reader, + ser::ProtocolVersion(CURRENT_SLATE_VERSION as u32), + ) + .map_err(|err| serde::de::Error::custom(err.to_string()))?; Ok(s) } } @@ -744,7 +758,7 @@ fn slatepack_bin_future() -> Result<(), grin_wallet_util::byte_ser::Error> { #[test] fn slatepack_encrypted_meta() -> Result<(), Error> { use crate::grin_core::global; - use crate::{Slate, SlateVersion, VersionedBinSlate, VersionedSlate}; + use crate::{Slate, SlateVersion, TxFlow, VersionedBinSlate, VersionedSlate}; use ed25519_dalek::PublicKey as edDalekPublicKey; use ed25519_dalek::SecretKey as edDalekSecretKey; use rand::{thread_rng, Rng}; @@ -766,7 +780,8 @@ fn slatepack_encrypted_meta() -> Result<(), Error> { slatepack.add_recipient(SlatepackAddress::random()); slatepack.add_recipient(SlatepackAddress::random()); - let v_slate = VersionedSlate::into_version(Slate::blank(2, false), SlateVersion::V4)?; + let v_slate = + VersionedSlate::into_version(Slate::blank(2, TxFlow::Standard), SlateVersion::V4)?; let bin_slate = VersionedBinSlate::try_from(v_slate).map_err(|_| ErrorKind::SlatepackSer)?; slatepack.payload = byte_ser::to_bytes(&bin_slate).map_err(|_| ErrorKind::SlatepackSer)?; @@ -793,7 +808,7 @@ fn slatepack_encrypted_meta() -> Result<(), Error> { #[test] fn slatepack_encrypted_meta_future() -> Result<(), Error> { use crate::grin_core::global; - use crate::{Slate, SlateVersion, VersionedBinSlate, VersionedSlate}; + use crate::{Slate, SlateVersion, TxFlow, VersionedBinSlate, VersionedSlate}; use ed25519_dalek::PublicKey as edDalekPublicKey; use ed25519_dalek::SecretKey as edDalekSecretKey; use rand::{thread_rng, Rng}; @@ -815,7 +830,8 @@ fn slatepack_encrypted_meta_future() -> Result<(), Error> { slatepack.add_recipient(SlatepackAddress::random()); slatepack.add_recipient(SlatepackAddress::random()); - let v_slate = VersionedSlate::into_version(Slate::blank(2, false), SlateVersion::V4)?; + let v_slate = + VersionedSlate::into_version(Slate::blank(2, TxFlow::Standard), SlateVersion::V4)?; let bin_slate = VersionedBinSlate::try_from(v_slate).map_err(|_| ErrorKind::SlatepackSer)?; slatepack.payload = byte_ser::to_bytes(&bin_slate).map_err(|_| ErrorKind::SlatepackSer)?; diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index 6f1ee585b..0c3c61c01 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -28,6 +28,7 @@ use crate::grin_util::secp::key::{PublicKey, SecretKey}; use crate::grin_util::secp::{self, pedersen, Secp256k1}; use crate::grin_util::{ToHex, ZeroingString}; use crate::slate_versions::ser as dalek_ser; +use crate::util::sha3::{Digest, Sha3_256}; use crate::InitTxArgs; use chrono::prelude::*; use ed25519_dalek::PublicKey as DalekPublicKey; @@ -173,6 +174,15 @@ where id: &Identifier, ) -> Result, Error>; + /// return the multisig commit sum for caching if allowed, none otherwise + fn calc_multisig_commit_for_cache( + &mut self, + keychain_mask: Option<&SecretKey>, + amount: u64, + id: &Identifier, + partial_commit: &pedersen::Commitment, + ) -> Result<(Option, Option), Error>; + /// Set parent key id by stored account name fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error>; @@ -229,6 +239,15 @@ where /// Next child ID when we want to create a new output, based on current parent fn next_child(&mut self, keychain_mask: Option<&SecretKey>) -> Result; + /// Return the current atomic secret index + fn current_atomic_id(&mut self) -> Result; + + /// Next atomic ID when we want to create a new atomic secret + fn next_atomic_id(&mut self, keychain_mask: Option<&SecretKey>) -> Result; + + /// Get the atomic ID for the atomic swap associated with the given UUID + fn get_used_atomic_id(&mut self, id: &Uuid) -> Result; + /// last verified height of outputs directly descending from the given parent key fn last_confirmed_height(&mut self) -> Result; @@ -237,6 +256,20 @@ where /// Flag whether the wallet needs a full UTXO scan on next update attempt fn init_status(&mut self) -> Result; + + /// Get the secret for an atomic swap transaction + fn get_atomic_secret( + &mut self, + keychain_mask: Option<&SecretKey>, + atomic_id: &Identifier, + ) -> Result; + + /// Get the recovered secret for an atomic swap transaction + fn get_recovered_atomic_secret( + &mut self, + keychain_mask: Option<&SecretKey>, + atomic_id: &Identifier, + ) -> Result; } /// Batch trait to update the output data backend atomically. Trying to use a @@ -266,6 +299,12 @@ where /// Save last stored child index of a given parent fn save_child_index(&mut self, parent_key_id: &Identifier, child_n: u32) -> Result<(), Error>; + /// Save global atomic index under the current keychain mask + fn save_atomic_index(&mut self, atomic_idx: u32) -> Result<(), Error>; + + /// Save an atomic index that has been used in an atomic swap + fn save_used_atomic_index(&mut self, id: &Uuid, atomic_idx: u32) -> Result<(), Error>; + /// Save last confirmed height of outputs for a given parent fn save_last_confirmed_height( &mut self, @@ -305,6 +344,20 @@ where /// Write the wallet data to backend file fn commit(&self) -> Result<(), Error>; + + /// Save secret for an atomic swap transaction + fn save_atomic_secret( + &mut self, + atomic_id: &Identifier, + secret: &SecretKey, + ) -> Result<(), Error>; + + /// Save recovered secret for an atomic swap transaction + fn save_recovered_atomic_secret( + &mut self, + atomic_id: &Identifier, + secret: &SecretKey, + ) -> Result<(), Error>; } /// Encapsulate all wallet-node communication functions. No functions within libwallet @@ -362,7 +415,14 @@ pub trait NodeClient: Send + Sync + Clone { ( u64, u64, - Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>, + Vec<( + pedersen::Commitment, + pedersen::RangeProof, + bool, + bool, + u64, + u64, + )>, ), Error, >; @@ -419,6 +479,8 @@ pub struct OutputData { pub lock_height: u64, /// Is this a coinbase output? Is it subject to coinbase locktime? pub is_coinbase: bool, + /// Is this a multisig output? + pub is_multisig: bool, /// Optional corresponding internal entry in tx entry log pub tx_log_entry: Option, } @@ -545,6 +607,8 @@ pub struct Context { pub initial_sec_key: SecretKey, /// as above pub initial_sec_nonce: SecretKey, + /// Secret key (of which public is shared, atomic swap only) + pub sec_atomic: Option, /// store my outputs + amounts between invocations /// Id, mmr_index (if known), amount pub output_ids: Vec<(Identifier, Option, u64)>, @@ -564,6 +628,14 @@ 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, + /// For multisig only, store the partial commitment to the output value + pub partial_commit: Option, + /// For multisig only, store the tau_one public key + pub tau_one: Option, + /// For multisig only, store the tau_two public key + pub tau_two: Option, + /// For multisig only, store the tau_x secret key + pub tau_x: Option, } impl Context { @@ -606,6 +678,7 @@ impl Context { sec_nonce: sec_nonce.clone(), initial_sec_key: sec_key, initial_sec_nonce: sec_nonce, + sec_atomic: None, input_ids: vec![], output_ids: vec![], amount: 0, @@ -613,6 +686,10 @@ impl Context { payment_proof_derivation_index: None, late_lock_args: None, calculated_excess: None, + partial_commit: None, + tau_one: None, + tau_two: None, + tau_x: None, } } } @@ -653,6 +730,46 @@ impl Context { PublicKey::from_secret_key(secp, &self.sec_nonce).unwrap(), ) } + + /// Derive a common nonce using a Diffie-Hellman of the local secret nonce and + /// public nonce of the other participant + /// + /// The common nonce is: + /// + /// c = SecretKey(SHA3("multisig_common_nonce" || secNonce*pubNonce)) + pub fn create_common_nonce( + &self, + secp: &Secp256k1, + nonce: &PublicKey, + ) -> Result { + let mut common = nonce.clone(); + common.mul_assign(secp, &self.sec_nonce)?; + let mut hasher = Sha3_256::new(); + hasher.input(b"multisig_common_nonce"); + hasher.input(&common.serialize_vec(secp, true)); + SecretKey::from_slice(secp, &hasher.result()).map_err(|e| e.into()) + } + + /// Set an atomic secret + pub fn set_secret_atomic(&mut self, secret: SecretKey) { + self.sec_atomic = Some(secret); + } + + /// Get the atomic secret + pub fn get_secret_atomic(&self) -> Option<&SecretKey> { + match &self.sec_atomic { + Some(a) => Some(a), + None => None, + } + } + + /// Get the atomic public key + pub fn get_public_atomic(&self, secp: &Secp256k1) -> Result, Error> { + match &self.sec_atomic { + Some(a) => Ok(Some(PublicKey::from_secret_key(secp, a)?)), + None => Ok(None), + } + } } impl ser::Writeable for Context { @@ -1097,4 +1214,46 @@ mod tests { let none2 = serde_json::from_str::("{}").unwrap(); assert_eq!(none, none2); } + + #[test] + fn context_tau_serde() { + let secp = Secp256k1::new(); + let mut ctx = Context::new( + &secp, + &Identifier::zero(), + true, /*use_test_rng*/ + false, /*is_initiator*/ + ); + + let sec_key = SecretKey::new(&secp, &mut thread_rng()); + ctx.tau_one = Some(PublicKey::from_secret_key(&secp, &sec_key).unwrap()); + ctx.tau_two = Some(PublicKey::from_secret_key(&secp, &sec_key).unwrap()); + ctx.tau_x = Some(sec_key); + + let val = serde_json::to_value(&ctx).unwrap(); + let des_ctx: Context = serde_json::from_value(val).unwrap(); + + assert!(des_ctx.tau_x.is_some()); + assert!(des_ctx.tau_one.is_some()); + assert!(des_ctx.tau_two.is_some()); + + ctx.tau_x = None; + + let val = serde_json::to_value(&ctx).unwrap(); + let des_ctx: Context = serde_json::from_value(val).unwrap(); + + assert!(des_ctx.tau_x.is_none()); + assert!(des_ctx.tau_one.is_some()); + assert!(des_ctx.tau_two.is_some()); + + ctx.tau_one = None; + ctx.tau_two = None; + + let val = serde_json::to_value(&ctx).unwrap(); + let des_ctx: Context = serde_json::from_value(val).unwrap(); + + assert!(des_ctx.tau_x.is_none()); + assert!(des_ctx.tau_one.is_none()); + assert!(des_ctx.tau_two.is_none()); + } } diff --git a/libwallet/tests/libwallet.rs b/libwallet/tests/libwallet.rs index bd14a95a6..ca90d3ddc 100644 --- a/libwallet/tests/libwallet.rs +++ b/libwallet/tests/libwallet.rs @@ -118,6 +118,7 @@ fn aggsig_sender_receiver_interaction() { &keychain.secp(), &rx_cx.sec_key, &rx_cx.sec_nonce, + None, &pub_nonce_sum, Some(&pub_key_sum), &msg, @@ -135,6 +136,7 @@ fn aggsig_sender_receiver_interaction() { &keychain.secp(), &rx_sig_part, &pub_nonce_sum, + None, &receiver_pub_excess, Some(&pub_key_sum), &msg, @@ -150,6 +152,7 @@ fn aggsig_sender_receiver_interaction() { &keychain.secp(), &s_cx.sec_key, &s_cx.sec_nonce, + None, &pub_nonce_sum, Some(&pub_key_sum), &msg, @@ -167,6 +170,7 @@ fn aggsig_sender_receiver_interaction() { &keychain.secp(), &sender_sig_part, &pub_nonce_sum, + None, &sender_pub_excess, Some(&pub_key_sum), &msg, @@ -183,6 +187,7 @@ fn aggsig_sender_receiver_interaction() { &keychain.secp(), &rx_cx.sec_key, &rx_cx.sec_nonce, + None, &pub_nonce_sum, Some(&pub_key_sum), &msg, @@ -336,6 +341,7 @@ fn aggsig_sender_receiver_interaction_offset() { &keychain.secp(), &rx_cx.sec_key, &rx_cx.sec_nonce, + None, &pub_nonce_sum, Some(&pub_key_sum), &msg, @@ -353,6 +359,7 @@ fn aggsig_sender_receiver_interaction_offset() { &keychain.secp(), &sig_part, &pub_nonce_sum, + None, &receiver_pub_excess, Some(&pub_key_sum), &msg, @@ -368,6 +375,7 @@ fn aggsig_sender_receiver_interaction_offset() { &keychain.secp(), &s_cx.sec_key, &s_cx.sec_nonce, + None, &pub_nonce_sum, Some(&pub_key_sum), &msg, @@ -385,6 +393,7 @@ fn aggsig_sender_receiver_interaction_offset() { &keychain.secp(), &sender_sig_part, &pub_nonce_sum, + None, &sender_pub_excess, Some(&pub_key_sum), &msg, @@ -400,6 +409,7 @@ fn aggsig_sender_receiver_interaction_offset() { &keychain.secp(), &rx_cx.sec_key, &rx_cx.sec_nonce, + None, &pub_nonce_sum, Some(&pub_key_sum), &msg, @@ -525,3 +535,156 @@ fn test_rewind_range_proof() { .unwrap(); assert!(proof_info.is_none()); } + +#[test] +fn test_atomic_swap_multisig_tx() { + use grin_wallet_libwallet::{Slate, TxFlow}; + use grin_wallet_util::grin_core::global; + + global::set_local_chain_type(global::ChainTypes::AutomatedTesting); + + let mut slate = Slate::blank(2, TxFlow::Atomic); + let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); + let switch = SwitchCommitmentType::Regular; + + let sender_keychain = ExtKeychain::from_random_seed(true).unwrap(); + let receiver_keychain = ExtKeychain::from_random_seed(true).unwrap(); + let sender_blind = sender_keychain.derive_key(0, &key_id, switch).unwrap(); + let receiver_blind = receiver_keychain.derive_key(0, &key_id, switch).unwrap(); + let in_commit = sender_keychain.commit(23000015, &key_id, switch).unwrap(); + let change_commit = sender_keychain.commit(10, &key_id, switch).unwrap(); + let out_commit = receiver_keychain.commit(5, &key_id, switch).unwrap(); + + let input = transaction::Input::new(transaction::OutputFeatures::Plain, in_commit); + + // create the change output range proof + let change_builder = proof::ProofBuilder::new(&receiver_keychain); + let change_proof = proof::create( + &sender_keychain, + &change_builder, + 10, + &key_id, + switch, + change_commit, + None, + ) + .unwrap(); + + let change = transaction::Output::new( + transaction::OutputFeatures::Plain, + change_commit, + change_proof, + ); + + // create the output range proof + let out_builder = proof::ProofBuilder::new(&receiver_keychain); + let out_proof = proof::create( + &receiver_keychain, + &out_builder, + 5, + &key_id, + switch, + out_commit, + None, + ) + .unwrap(); + + let output = + transaction::Output::new(transaction::OutputFeatures::Plain, out_commit, out_proof); + + // set the fee to the minimum for a single (input, output, kernel) tx + let kernel = transaction::TxKernel { + features: transaction::KernelFeatures::Plain { + fee: 23000000.into(), + }, + excess: in_commit, + excess_sig: secp::Signature::from_raw_data(&[0; 64]).unwrap(), + }; + + let mut sender_ctx; + { + // dealing with an input here so we need to negate the blinding_factor + // rather than use it as is + let bs = BlindSum::new(); + let blinding_factor = sender_keychain + .blind_sum(&bs.sub_blinding_factor(BlindingFactor::from_secret_key(sender_blind))) + .unwrap(); + + let blind = blinding_factor.secret_key(sender_keychain.secp()).unwrap(); + + sender_ctx = Context::with_excess(sender_keychain.secp(), blind, &key_id, false); + } + let mut receiver_ctx = + Context::with_excess(receiver_keychain.secp(), receiver_blind, &key_id, false); + + // set the atomic secret used for the adaptor signature + let atomic_secret = SecretKey::from_slice(receiver_keychain.secp(), &[2; 32]).unwrap(); + receiver_ctx.sec_atomic = Some(atomic_secret.clone()); + + // set kernel features, fee, and total amount to sign the correct kernel message + slate.kernel_features = 0; + slate.fee_fields = transaction::FeeFields::new(0, 23000000).unwrap(); + slate.amount = 23000015; + + slate + .fill_round_1(&sender_keychain, &mut sender_ctx) + .unwrap(); + + // adjust kernel offset to match correct kernel sum + sender_ctx.input_ids = vec![(key_id.clone(), None, 23000015)]; + sender_ctx.output_ids = vec![(key_id.clone(), None, 10)]; + slate.adjust_offset(&sender_keychain, &sender_ctx).unwrap(); + + slate + .fill_round_1(&receiver_keychain, &mut receiver_ctx) + .unwrap(); + + // adjust kernel offset to match correct kernel sum + receiver_ctx.output_ids = vec![(key_id.clone(), None, 5)]; + slate + .adjust_offset(&receiver_keychain, &receiver_ctx) + .unwrap(); + + slate + .fill_round_2_atomic(&receiver_keychain, &mut receiver_ctx) + .unwrap(); + + let adaptor_sig = slate + .fill_round_3_atomic(&sender_keychain, &mut sender_ctx) + .unwrap(); + + let mut tx = transaction::Transaction::empty() + .with_input(input) + .with_output(change) + .with_output(output) + .with_kernel(kernel); + tx.offset = slate.offset.clone(); + slate.tx = Some(tx); + + let rec_part_sig = slate + .finalize_atomic(&receiver_keychain, &mut receiver_ctx) + .unwrap(); + + // calculate sr' - sr, and validate it equals the receiver's atomic secret + let secp = receiver_keychain.secp(); + let mut srp = SecretKey::from_slice(secp, &adaptor_sig.as_ref()[32..]).unwrap(); + let mut sr = SecretKey::from_slice(secp, &rec_part_sig.as_ref()[32..]).unwrap(); + sr.neg_assign(secp).unwrap(); + srp.add_assign(secp, &sr).unwrap(); + assert_eq!(srp, atomic_secret); + + // now, do the same thing, but with the final signature + let full_sig = slate.tx.unwrap().kernels()[0].excess_sig.clone(); + let send_part_sig = slate.participant_data[0].part_sig.unwrap(); + sr = SecretKey::from_slice(secp, &full_sig.as_ref()[32..]).unwrap(); + // subtract the sender's partial sig from the final sig + let mut sp_s = SecretKey::from_slice(secp, &send_part_sig.as_ref()[32..]).unwrap(); + sp_s.neg_assign(secp).unwrap(); + sr.add_assign(secp, &sp_s).unwrap(); + // now sr contains the receiver's partial sig + // calculate sr' - sr, and validate it equals the receiver's atomic secret + sr.neg_assign(secp).unwrap(); + srp = SecretKey::from_slice(secp, &adaptor_sig.as_ref()[32..]).unwrap(); + srp.add_assign(secp, &sr).unwrap(); + assert_eq!(srp, atomic_secret); +} diff --git a/src/bin/grin-wallet.yml b/src/bin/grin-wallet.yml index 59b227b7b..0bc008668 100644 --- a/src/bin/grin-wallet.yml +++ b/src/bin/grin-wallet.yml @@ -111,6 +111,9 @@ subcommands: help: EXPERIMENTAL - Do not lock the coins immediately, instead only lock them during finalization. short: l long: late-lock + - multisig: + help: EXPERIMENTAL - Create a multisig output with shared ownership. + long: multisig - change_outputs: help: Number of change outputs to generate (mainly for testing) short: o @@ -174,6 +177,23 @@ subcommands: short: u long: outfile takes_value: true + - process_multisig: + about: Processes a Slatepack Message to perform step 1 + 2 in multisig bulletproof building + args: + - input: + help: File containing a Slatepack Message + short: i + long: input + takes_value: true + - manual: + help: If present, don't attempt to send the resulting Slatepack via TOR + short: m + long: manual + - outfile: + help: If present, overrides the filename and location of the output Slatepack file. + short: u + long: outfile + takes_value: true - finalize: about: Processes a Slatepack Message to finalize a transfer. args: @@ -257,6 +277,156 @@ subcommands: short: u long: outfile takes_value: true + - send_atomic: + about: Builds an atomic swap main transaction to send coins and sends to the recipient via the Slatepack workflow + args: + - multisig_path: + help: BIP32 path of the multisig output to spend in the atomic swap + long: multisig_path + takes_value: true + - refund: + help: Create an atomic swap refund transaction + short: r + long: refund + - amount: + help: Number of coins to send with optional fraction, e.g. 12.423 + index: 1 + - minimum_confirmations: + help: Minimum number of confirmations required for an output to be spendable + short: c + long: min_conf + default_value: "10" + takes_value: true + - selection_strategy: + help: Coin/Output selection strategy. + short: s + long: selection + possible_values: + - all + - smallest + default_value: smallest + takes_value: true + - estimate_selection_strategies: + help: Estimates all possible Coin/Output selection strategies. + short: e + long: estimate-selection + - late_lock: + help: EXPERIMENTAL - Do not lock the coins immediately, instead only lock them during finalization. + short: l + long: late-lock + - change_outputs: + help: Number of change outputs to generate (mainly for testing) + short: o + long: change_outputs + default_value: "1" + takes_value: true + - dest: + help: Intended recipient's Slatepack Address (or http listener address (DEPRECATED)) + short: d + long: dest + takes_value: true + - no_payment_proof: + help: Don't request a payment proof, even if the Recipient's Slatepack address is provided in the -dest argument + short: n + long: no_payment_proof + - fluff: + help: Fluff the transaction (ignore Dandelion relay protocol) + short: f + long: fluff + - stored_tx: + help: If present, use the previously stored Unconfirmed transaction with given id + short: t + long: stored_tx + takes_value: true + - ttl_blocks: + help: If present, the number of blocks from the current after which wallets should refuse to process transactions further + short: b + long: ttl_blocks + takes_value: true + - manual: + help: If present, don't attempt to send the resulting Slatepack via TOR + short: m + long: manual + - outfile: + help: If present, overrides the filename and location of the output Slatepack file. + short: u + long: outfile + takes_value: true + - receive_atomic: + about: Processes a Slatepack Message to accept an atomic swap transfer from a sender + args: + - input: + help: File containing a Slatepack Message + short: i + long: input + takes_value: true + - manual: + help: If present, don't attempt to send the resulting Slatepack via TOR + short: m + long: manual + - outfile: + help: If present, overrides the filename and location of the output Slatepack file. + short: u + long: outfile + takes_value: true + - countersign_atomic: + about: Third round of an atomic swap, creates a signature, and recovers adaptor signature from the other party + args: + - input: + help: File containing a Slatepack Message + short: i + long: input + takes_value: true + - manual: + help: If present, don't attempt to send the resulting Slatepack via TOR + short: m + long: manual + - outfile: + help: If present, overrides the filename and location of the output Slatepack file. + short: u + long: outfile + takes_value: true + - finalize_atomic: + about: Processes a Slatepack Message to finalize an atomic swap transfer. + args: + - input: + help: Partial transaction to process, expects the receiver's transaction file. + short: i + long: input + takes_value: true + - fluff: + help: Fluff the transaction (ignore Dandelion relay protocol) + short: f + long: fluff + - nopost: + help: Do not post the transaction. + short: n + long: nopost + - outfile: + help: If present, overrides the filename and location of the output Slatepack file. + short: u + long: outfile + takes_value: true + - recover_atomic_secret: + about: Processes a Slatepack Message to recover an atomic secret for recovering funds on the other chain. + args: + - input: + help: Finalized transaction to process, expects the initiator's transaction file. + short: i + long: input + takes_value: true + - get_atomic_secrets: + about: Uses the supplied ID to recover atomic secrets from storage + args: + - id: + help: Unique identifier to recover atomic swap secrets + short: i + long: id + takes_value: true + - amount: + help: Atomic swap amount + long: amount + takes_value: true - outputs: about: Raw wallet output info (list of outputs) - txs: diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index 70d542cd6..d111c7940 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -26,7 +26,7 @@ use grin_wallet_config::{config_file_exists, TorConfig, WalletConfig}; use grin_wallet_controller::command; use grin_wallet_controller::{Error, ErrorKind}; use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl}; -use grin_wallet_libwallet::{self, Slate, SlatepackAddress, SlatepackArmor}; +use grin_wallet_libwallet::{self, Slate, SlatepackAddress, SlatepackArmor, TxFlow}; use grin_wallet_libwallet::{IssueInvoiceTxArgs, NodeClient, WalletInst, WalletLCProvider}; use grin_wallet_util::grin_core as core; use grin_wallet_util::grin_core::core::amount_to_hr_string; @@ -289,6 +289,29 @@ fn parse_u64_or_none(arg: Option<&str>) -> Option { } } +fn parse_f64_or_none(arg: Option<&str>) -> Option { + let val = match arg { + Some(a) => a.parse::(), + None => return None, + }; + match val { + Ok(v) => Some(v), + Err(_) => None, + } +} + +// parses a number, returns None if argument is None, or value is absent +fn parse_u32_or_none(arg: Option<&str>) -> Option { + let val = match arg { + Some(a) => a.parse::(), + None => return None, + }; + match val { + Ok(v) => Some(v), + Err(_) => None, + } +} + pub fn parse_global_args( config: &WalletConfig, args: &ArgMatches, @@ -446,7 +469,7 @@ pub fn parse_send_args(args: &ArgMatches) -> Result Result Result Result Result { + // input file + let input_file = match args.is_present("input") { + true => { + let file = args.value_of("input").unwrap().to_owned(); + // validate input + if !Path::new(&file).is_file() { + let msg = format!("File {} not found.", &file); + return Err(ParseError::ArgumentError(msg)); + } + Some(file) + } + false => None, + }; + + let mut input_slatepack_message = None; + if input_file.is_none() { + input_slatepack_message = Some(prompt_slatepack()?); + } + + let outfile = parse_optional(args, "outfile")?; + + Ok(command::RecoverAtomicArgs { + input_file, + input_slatepack_message, + outfile, + }) +} + +pub fn parse_get_atomic_secrets_args( + args: &ArgMatches, +) -> Result { + let id = parse_u32_or_none(args.value_of("id")) + .ok_or(ParseError::ArgumentError("missing atomic ID".into()))?; + let amount = parse_f64_or_none(args.value_of("amount")).ok_or(ParseError::ArgumentError( + "missing atomic swap amount".into(), + ))?; + + Ok(command::GetAtomicSecretsArgs { id, amount }) +} + pub fn parse_unpack_args(args: &ArgMatches) -> Result { // input file let input_file = match args.is_present("input") { @@ -737,6 +811,36 @@ pub fn parse_process_invoice_args( }) } +pub fn parse_process_multisig_args(args: &ArgMatches) -> Result { + // input file + let input_file = match args.is_present("input") { + true => { + let file = args.value_of("input").unwrap().to_owned(); + // validate input + if !Path::new(&file).is_file() { + let msg = format!("File {} not found.", &file); + return Err(ParseError::ArgumentError(msg)); + } + Some(file) + } + false => None, + }; + + let mut input_slatepack_message = None; + if input_file.is_none() { + input_slatepack_message = Some(prompt_slatepack()?); + } + + let outfile = parse_optional(args, "outfile")?; + + Ok(command::MultisigArgs { + input_file, + input_slatepack_message, + skip_tor: args.is_present("manual"), + outfile, + }) +} + pub fn parse_info_args(args: &ArgMatches) -> Result { // minimum_confirmations let mc = parse_required(args, "minimum_confirmations")?; @@ -1123,6 +1227,7 @@ where a, wallet_config.dark_background_color_scheme.unwrap_or(true), test_mode, + TxFlow::Standard, ) } ("receive", Some(args)) => { @@ -1134,6 +1239,7 @@ where a, Some(tor_config.clone()), test_mode, + TxFlow::Standard, ) } ("unpack", Some(args)) => { @@ -1142,7 +1248,7 @@ where } ("finalize", Some(args)) => { let a = arg_parse!(parse_finalize_args(&args)); - command::finalize(owner_api, km, a) + command::finalize(owner_api, km, a, TxFlow::Standard) } ("invoice", Some(args)) => { let a = arg_parse!(parse_issue_invoice_args(&args)); @@ -1164,6 +1270,50 @@ where test_mode, ) } + ("process_multisig", Some(args)) => { + let a = arg_parse!(parse_process_multisig_args(&args)); + command::process_multisig(owner_api, km, a, Some(tor_config.clone()), test_mode) + } + ("send_atomic", Some(args)) => { + let a = arg_parse!(parse_send_args(&args)); + command::send( + owner_api, + km, + Some(tor_config.clone()), + a, + wallet_config.dark_background_color_scheme.unwrap_or(true), + test_mode, + TxFlow::Atomic, + ) + } + ("receive_atomic", Some(args)) => { + let a = arg_parse!(parse_receive_args(&args)); + command::receive( + owner_api, + km, + &global_wallet_args, + a, + Some(tor_config.clone()), + test_mode, + TxFlow::Atomic, + ) + } + ("countersign_atomic", Some(args)) => { + let a = arg_parse!(parse_receive_args(&args)); + command::countersign_atomic(owner_api, km, a, Some(tor_config.clone()), test_mode) + } + ("finalize_atomic", Some(args)) => { + let a = arg_parse!(parse_finalize_args(&args)); + command::finalize(owner_api, km, a, TxFlow::Atomic) + } + ("recover_atomic_secret", Some(args)) => { + let a = arg_parse!(parse_recover_atomic_args(&args)); + command::recover_atomic_secret(owner_api, km, a) + } + ("get_atomic_secrets", Some(args)) => { + let a = arg_parse!(parse_get_atomic_secrets_args(&args)); + command::get_atomic_secrets(owner_api, km, a) + } ("info", Some(args)) => { let a = arg_parse!(parse_info_args(&args)); command::info( diff --git a/tests/cmd_line_atomic.rs b/tests/cmd_line_atomic.rs new file mode 100644 index 000000000..be213cbe4 --- /dev/null +++ b/tests/cmd_line_atomic.rs @@ -0,0 +1,452 @@ +// 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 wallet command line works as expected +#[macro_use] +extern crate clap; + +#[macro_use] +extern crate log; + +extern crate grin_wallet; + +use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy}; + +use clap::App; +use std::thread; +use std::time::Duration; + +use grin_wallet_impls::DefaultLCProvider; +use grin_wallet_util::grin_keychain::ExtKeychain; + +mod common; +use common::{clean_output_dir, execute_command, initial_setup_wallet, instantiate_wallet, setup}; + +fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller::Error> { + setup(test_dir); + // Create a new proxy to simulate server and wallet responses + let mut wallet_proxy: WalletProxy< + DefaultLCProvider, + LocalWalletClient, + ExtKeychain, + > = WalletProxy::new(test_dir); + let chain = wallet_proxy.chain.clone(); + + // load app yaml. If it don't exist, just say so and exit + let yml = load_yaml!("../src/bin/grin-wallet.yml"); + let app = App::from_yaml(yml); + + // wallet init + let arg_vec = vec!["grin-wallet", "-p", "password1", "init", "-h"]; + // should create new wallet file + let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); + execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone())?; + + // trying to init twice - should fail + assert!(execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone()).is_err()); + let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); + + // add wallet to proxy + //let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone()); + let config1 = initial_setup_wallet(test_dir, "wallet1"); + let wallet_config1 = config1.clone().members.unwrap().wallet; + + let (wallet1, mask1_i) = instantiate_wallet( + wallet_config1.clone(), + client1.clone(), + "password1", + "default", + )?; + wallet_proxy.add_wallet( + "wallet1", + client1.get_send_instance(), + wallet1.clone(), + mask1_i.clone(), + ); + + // Create wallet 2 + let arg_vec = vec!["grin-wallet", "-p", "password2", "init", "-h"]; + let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); + execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?; + + let config2 = initial_setup_wallet(test_dir, "wallet2"); + let wallet_config2 = config2.clone().members.unwrap().wallet; + let (wallet2, mask2_i) = instantiate_wallet( + wallet_config2.clone(), + client2.clone(), + "password2", + "default", + )?; + wallet_proxy.add_wallet( + "wallet2", + client2.get_send_instance(), + wallet2.clone(), + mask2_i.clone(), + ); + + // Set the wallet proxy listener running + thread::spawn(move || { + if let Err(e) = wallet_proxy.run() { + error!("Wallet Proxy error: {}", e); + } + }); + + // Create some accounts in wallet 1 + let arg_vec = vec!["grin-wallet", "-p", "password1", "account", "-c", "mining"]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + + let arg_vec = vec![ + "grin-wallet", + "-p", + "password1", + "account", + "-c", + "account_1", + ]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + + // Create some accounts in wallet 2 + let arg_vec = vec![ + "grin-wallet", + "-p", + "password2", + "account", + "-c", + "account_1", + ]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?; + + // Mine a bit into wallet 1 so we have something to send + let wallet_config1 = config1.clone().members.unwrap().wallet; + let (wallet1, mask1_i) = + instantiate_wallet(wallet_config1, client1.clone(), "password1", "default")?; + let mask1 = (&mask1_i).as_ref(); + grin_wallet_controller::controller::owner_single_use( + Some(wallet1.clone()), + mask1, + None, + |api, m| { + api.set_active_account(m, "mining")?; + Ok(()) + }, + )?; + + // Mine a bit into wallet 2 so we have something to send + let wallet_config2 = config2.clone().members.unwrap().wallet; + let (wallet2, mask2_i) = + instantiate_wallet(wallet_config2, client2.clone(), "password2", "default")?; + let mask2 = (&mask2_i).as_ref(); + grin_wallet_controller::controller::owner_single_use( + Some(wallet2.clone()), + mask2, + None, + |api, m| { + api.set_active_account(m, "account_1")?; + Ok(()) + }, + )?; + + let bh = 10u64; + let _ = + test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); + let _ = + test_framework::award_blocks_to_wallet(&chain, wallet2.clone(), mask2, bh as usize, false); + + // Update info and check + let arg_vec = vec!["grin-wallet", "-p", "password1", "-a", "mining", "info"]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + + // create multisig output funding transaction + let arg_vec = vec![ + "grin-wallet", + "-p", + "password1", + "-a", + "mining", + "send", + "--multisig", + "5", + ]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + let arg_vec = vec!["grin-wallet", "-a", "mining", "-p", "password1", "txs"]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + + let file_name = format!( + "{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b00.M1.slatepack", + test_dir + ); + + let arg_vec = vec![ + "grin-wallet", + "-p", + "password2", + "-a", + "account_1", + "receive", + "-i", + &file_name, + ]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?; + + let file_name = format!( + "{}/wallet2/slatepack/0436430c-2b02-624c-2032-570501212b00.M2.slatepack", + test_dir + ); + + let arg_vec = vec![ + "grin-wallet", + "-p", + "password1", + "-a", + "mining", + "process_multisig", + "-i", + &file_name, + ]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + + let file_name = format!( + "{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b00.M3.slatepack", + test_dir + ); + + let arg_vec = vec![ + "grin-wallet", + "-a", + "account_1", + "-p", + "password2", + "finalize", + "-n", + "-i", + &file_name, + ]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; + + let file_name = format!( + "{}/wallet2/slatepack/0436430c-2b02-624c-2032-570501212b00.M4.slatepack", + test_dir + ); + + let arg_vec = vec![ + "grin-wallet", + "-p", + "password1", + "-a", + "mining", + "finalize", + "-i", + &file_name, + ]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + + // mine some more coins to add confirmations to the multisig transaction + let _ = + test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); + + let arg_vec = vec!["grin-wallet", "-a", "mining", "-p", "password1", "txs"]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + + let arg_vec = vec!["grin-wallet", "-a", "account_1", "-p", "password2", "txs"]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; + + // create atomic swap refund transaction + let arg_vec = vec![ + "grin-wallet", + "-p", + "password1", + "-a", + "mining", + "send_atomic", + "-r", // create a refund transaction + "--multisig_path", + "m/2622924661/3526545887/2606926411/331176156", + "--min_conf", + "0", + "4.9875", + ]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + let arg_vec = vec!["grin-wallet", "-a", "mining", "-p", "password1", "txs"]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + + let file_name = format!( + "{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b01.A1.slatepack", + test_dir + ); + + let arg_vec = vec![ + "grin-wallet", + "-p", + "password2", + "-a", + "account_1", + "receive_atomic", + "-i", + &file_name, + ]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?; + + // shouldn't be allowed to receive twice + assert!(execute_command(&app, test_dir, "wallet2", &client2, arg_vec).is_err()); + + let file_name = format!( + "{}/wallet2/slatepack/0436430c-2b02-624c-2032-570501212b01.A2.slatepack", + test_dir + ); + + let arg_vec = vec![ + "grin-wallet", + "-p", + "password1", + "-a", + "mining", + "countersign_atomic", + "-i", + &file_name, + ]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + + let file_name = format!( + "{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b01.A3.slatepack", + test_dir + ); + + let arg_vec = vec![ + "grin-wallet", + "-a", + "account_1", + "-p", + "password2", + "finalize_atomic", + "-n", // don't post the refund transaction + "-i", + &file_name, + ]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; + + // create atomic swap main transaction + let arg_vec = vec![ + "grin-wallet", + "-p", + "password2", + "-a", + "account_1", + "send_atomic", + "--multisig_path", + "m/2622924661/3526545887/2606926411/331176156", + "--min_conf", + "0", + "4.9875", + ]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; + let arg_vec = vec!["grin-wallet", "-a", "account_1", "-p", "password2", "txs"]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; + + let file_name = format!( + "{}/wallet2/slatepack/0436430c-2b02-624c-2032-570501212b02.A1.slatepack", + test_dir + ); + + let arg_vec = vec![ + "grin-wallet", + "-p", + "password1", + "-a", + "mining", + "receive_atomic", + "-i", + &file_name, + ]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone())?; + + // shouldn't be allowed to receive twice + assert!(execute_command(&app, test_dir, "wallet1", &client1, arg_vec).is_err()); + + let file_name = format!( + "{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b02.A2.slatepack", + test_dir + ); + + let arg_vec = vec![ + "grin-wallet", + "-p", + "password2", + "-a", + "account_1", + "countersign_atomic", + "-i", + &file_name, + ]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; + + let file_name = format!( + "{}/wallet2/slatepack/0436430c-2b02-624c-2032-570501212b02.A3.slatepack", + test_dir + ); + + let arg_vec = vec![ + "grin-wallet", + "-a", + "mining", + "-p", + "password1", + "finalize_atomic", + "-i", + &file_name, + ]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + + let file_name = format!( + "{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b02.A4.slatepack", + test_dir + ); + + let arg_vec = vec![ + "grin-wallet", + "-a", + "account_1", + "-p", + "password2", + "recover_atomic_secret", + "-i", + &file_name, + ]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; + + let arg_vec = vec![ + "grin-wallet", + "-a", + "account_1", + "-p", + "password2", + "get_atomic_secrets", + "-i", + "1", // atomic ID + "--amount", + "4.9875", + ]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; + + // let logging finish + thread::sleep(Duration::from_millis(200)); + clean_output_dir(test_dir); + Ok(()) +} + +#[test] +fn wallet_command_line() { + let test_dir = "target/test_output/command_line_atomic"; + if let Err(e) = command_line_test_impl(test_dir) { + panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap()); + } +} diff --git a/tests/owner_v3_lifecycle.rs b/tests/owner_v3_lifecycle.rs index 7cbe0e3be..bf97d4080 100644 --- a/tests/owner_v3_lifecycle.rs +++ b/tests/owner_v3_lifecycle.rs @@ -27,7 +27,7 @@ use std::thread; use std::time::Duration; use grin_wallet_impls::DefaultLCProvider; -use grin_wallet_libwallet::{InitTxArgs, Slate, SlateVersion, VersionedSlate}; +use grin_wallet_libwallet::{InitTxArgs, Slate, VersionedSlate}; use grin_wallet_util::grin_keychain::ExtKeychain; use serde_json; @@ -405,12 +405,13 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { //16) Finalize the invoice tx (to foreign api) // (Tests that foreign API on same port also has its stored mask updated) + let v = slate.version(); let req = serde_json::json!({ "jsonrpc": "2.0", "id": 1, "method": "finalize_tx", "params": { - "slate": VersionedSlate::into_version(slate, SlateVersion::V4)?, + "slate": VersionedSlate::into_version(slate, v)?, } }); let res = diff --git a/util/Cargo.toml b/util/Cargo.toml index db1e9e40a..e8f524e84 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -35,12 +35,12 @@ sha3 = "0.8" # grin_store = { git = "https://github.com/mimblewimble/grin", tag = "v5.0.0-beta.2" } # For bleeding edge -grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" } -grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" } -grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "master" } -grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" } -grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" } -grin_store = { git = "https://github.com/mimblewimble/grin", branch = "master" } +grin_core = { git = "https://github.com/geneferneau/grin", branch = "atomic" } +grin_keychain = { git = "https://github.com/geneferneau/grin", branch = "atomic" } +grin_chain = { git = "https://github.com/geneferneau/grin", branch = "atomic" } +grin_util = { git = "https://github.com/geneferneau/grin", branch = "atomic" } +grin_api = { git = "https://github.com/geneferneau/grin", branch = "atomic" } +grin_store = { git = "https://github.com/geneferneau/grin", branch = "atomic" } # For local testing # grin_core = { path = "../../grin/core"} diff --git a/util/src/lib.rs b/util/src/lib.rs index 8b8bd8dec..2482d2a3a 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -26,6 +26,7 @@ extern crate serde_derive; mod ov3; pub use ov3::OnionV3Address; pub use ov3::OnionV3Error as OnionV3AddressError; +pub use sha3; #[allow(missing_docs)] pub mod byte_ser;