mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 15:21:39 +02:00
Slatepack address index on send (#821)
* Store address_index into tx log entry to get address and decrypt slatepack message, increment over all values * Provide address index on send and issue_invoice command * Keep 0 index for receive * Use sender address index at payment proof Rust API users need to update to SlatepackAddressIndex and the Slatepack methods Keep a wallet database backup for pending transactions and saved address indices. Seed recovery alone does not restore these, and the address counter restarts at 1.
This commit is contained in:
Generated
+1
@@ -2457,6 +2457,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -36,6 +36,7 @@ linefeed = "0.6"
|
||||
semver = "1.0.28"
|
||||
rustyline = "9"
|
||||
lazy_static = "1"
|
||||
uuid = { version = "1.23.4", features = ["v4"] }
|
||||
|
||||
grin_wallet_api = { path = "./api", version = "5.5.0" }
|
||||
grin_wallet_impls = { path = "./impls", version = "5.5.0" }
|
||||
|
||||
+75
-38
@@ -23,10 +23,11 @@ use crate::keychain::{Identifier, Keychain};
|
||||
use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage};
|
||||
use crate::libwallet::api_impl::types::update_tx_slate_state;
|
||||
use crate::libwallet::api_impl::{owner, owner_updater};
|
||||
use crate::libwallet::wallet_lock;
|
||||
use crate::libwallet::{
|
||||
AcctPathMapping, BuiltOutput, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient,
|
||||
NodeHeightResult, OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress,
|
||||
TxLogEntry, ViewWallet, WalletInfo, WalletLCProvider,
|
||||
TxLogEntry, ViewWallet, WalletBackend, WalletInfo, WalletLCProvider,
|
||||
};
|
||||
use crate::util::logger::LoggingConfig;
|
||||
use crate::util::secp::{key::SecretKey, pedersen::Commitment};
|
||||
@@ -35,12 +36,12 @@ use grin_wallet_config::config::{
|
||||
reload_global_config, update_global_config, WALLET_CONFIG_FILE_NAME,
|
||||
};
|
||||
use grin_wallet_libwallet::mwixnet::{MixnetReqCreationParams, SwapReq};
|
||||
use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
|
||||
use grin_wallet_libwallet::RetrieveTxQueryArgs;
|
||||
use grin_wallet_util::OnionV3Address;
|
||||
|
||||
use chrono::prelude::*;
|
||||
use ed25519_dalek::SigningKey as DalekSecretKey;
|
||||
use libwallet::wallet_lock;
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
@@ -782,7 +783,7 @@ where
|
||||
pub fn init_send_tx(
|
||||
&self,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
args: InitTxArgs,
|
||||
mut args: InitTxArgs,
|
||||
) -> Result<Slate, Error> {
|
||||
let send_args = args.send_args.clone();
|
||||
let dest = match send_args.as_ref() {
|
||||
@@ -797,6 +798,18 @@ where
|
||||
.map(|_| crate::tor_config::load(&self.config_path()))
|
||||
.transpose()?;
|
||||
|
||||
let init_send_tx = |w: &mut WalletBackend<C, K>, args: &mut InitTxArgs| {
|
||||
if args.address_index.is_none() && args.estimate_only != Some(true) {
|
||||
let parent_key_id = match &args.src_acct_name {
|
||||
Some(name) => w.get_acct_path(name.clone())?.map(|account| account.path),
|
||||
None => None,
|
||||
}
|
||||
.unwrap_or_else(|| w.parent_key_id());
|
||||
args.address_index = Some(w.next_tx_slatepack_address_index(&parent_key_id)?);
|
||||
}
|
||||
owner::init_send_tx(w, keychain_mask, args.clone(), self.doctest_mode)
|
||||
};
|
||||
|
||||
// Helper functionality. If send arguments exist, attempt to send sync and
|
||||
// finalize
|
||||
match send_args {
|
||||
@@ -804,7 +817,7 @@ where
|
||||
let slate = {
|
||||
wallet_lock!(self.wallet_inst, w);
|
||||
let late_lock = args.late_lock.unwrap_or(false);
|
||||
let slate = owner::init_send_tx(w, keychain_mask, args, self.doctest_mode)?;
|
||||
let slate = init_send_tx(w, &mut args)?;
|
||||
let tc = tor_config.as_ref().ok_or_else(|| {
|
||||
Error::TorConfig("Tor config was not loaded with send arguments".into())
|
||||
})?;
|
||||
@@ -815,12 +828,29 @@ where
|
||||
if !late_lock {
|
||||
owner::tx_lock_outputs(w, keychain_mask, &slate)?;
|
||||
}
|
||||
|
||||
slate
|
||||
};
|
||||
let res =
|
||||
try_slatepack_sync_workflow(&slate, &dest.unwrap(), tor_config, None, false);
|
||||
let res = try_slatepack_sync_workflow(
|
||||
&slate,
|
||||
dest.as_ref().unwrap(),
|
||||
tor_config,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
match res {
|
||||
Ok(s) => {
|
||||
// Output slatepack message to file.
|
||||
match output_slatepack_file(
|
||||
self,
|
||||
keychain_mask,
|
||||
&s,
|
||||
dest,
|
||||
args.address_index,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => error!("Error on saving output slatepack message: {}", e),
|
||||
}
|
||||
let ret_slate = self.finalize_tx(keychain_mask, &s)?;
|
||||
if sa.post_tx {
|
||||
let result = self.post_tx(keychain_mask, &ret_slate, sa.fluff);
|
||||
@@ -846,7 +876,7 @@ where
|
||||
}
|
||||
None => {
|
||||
wallet_lock!(self.wallet_inst, w);
|
||||
let slate = owner::init_send_tx(w, keychain_mask, args, self.doctest_mode)?;
|
||||
let slate = init_send_tx(w, &mut args)?;
|
||||
Ok(slate)
|
||||
}
|
||||
}
|
||||
@@ -938,7 +968,7 @@ where
|
||||
/// let mut api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml"));
|
||||
///
|
||||
/// // . . .
|
||||
/// // The slate has been recieved from the invoicer, somehow
|
||||
/// // The slate has been received from the invoicer, somehow
|
||||
/// # let slate = Slate::blank(2, true);
|
||||
/// let args = InitTxArgs {
|
||||
/// src_acct_name: None,
|
||||
@@ -970,19 +1000,17 @@ where
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let (slate, tor_config) = {
|
||||
let tor_config = send_args
|
||||
.as_ref()
|
||||
.map(|_| crate::tor_config::load(&self.config_path()))
|
||||
.transpose()?;
|
||||
let slate = {
|
||||
let mut w_lock = self.wallet_inst.lock();
|
||||
let tor_config = send_args
|
||||
.as_ref()
|
||||
.map(|_| crate::tor_config::load(&self.config_path()))
|
||||
.transpose()?;
|
||||
let w = w_lock.lc_provider()?.wallet_inst()?;
|
||||
let slate =
|
||||
owner::process_invoice_tx(w, keychain_mask, slate, args, self.doctest_mode)?;
|
||||
(slate, tor_config)
|
||||
owner::process_invoice_tx(w, keychain_mask, slate, args.clone(), self.doctest_mode)?
|
||||
};
|
||||
// Helper functionality. If send arguments exist, attempt to send
|
||||
match send_args {
|
||||
match send_args.as_ref() {
|
||||
Some(sa) => {
|
||||
let tc = tor_config.ok_or_else(|| {
|
||||
Error::TorConfig("Tor config was not loaded with send arguments".into())
|
||||
@@ -1006,7 +1034,13 @@ where
|
||||
}
|
||||
}
|
||||
// Output slatepack message to file.
|
||||
match output_slatepack_file(self, keychain_mask, &s, Some(dest)) {
|
||||
match output_slatepack_file(
|
||||
self,
|
||||
keychain_mask,
|
||||
&s,
|
||||
Some(dest),
|
||||
args.address_index,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => error!("Error on saving output slatepack message: {}", e),
|
||||
}
|
||||
@@ -2193,7 +2227,7 @@ where
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
|
||||
/// * `derivation_index` - The index along the derivation path to retrieve an address for
|
||||
/// * `derivation_index` - The index along the derivation path to retrieve an address for, should be not higher than 2^31 - 1
|
||||
///
|
||||
/// # Returns
|
||||
/// * Ok with a SlatepackAddress representing the address
|
||||
@@ -2205,13 +2239,14 @@ where
|
||||
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
|
||||
///
|
||||
/// use grin_core::global::ChainTypes;
|
||||
/// use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// // Set up as above
|
||||
/// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml"));
|
||||
///
|
||||
/// let res = api_owner.get_slatepack_address(None, 0);
|
||||
/// let res = api_owner.get_slatepack_address(None, SlatepackAddressIndex(0));
|
||||
///
|
||||
/// if let Ok(_) = res {
|
||||
/// // ...
|
||||
@@ -2221,7 +2256,7 @@ where
|
||||
pub fn get_slatepack_address(
|
||||
&self,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
derivation_index: u32,
|
||||
derivation_index: SlatepackAddressIndex,
|
||||
) -> Result<SlatepackAddress, Error> {
|
||||
owner::get_slatepack_address(self.wallet_inst.clone(), keychain_mask, derivation_index)
|
||||
}
|
||||
@@ -2231,9 +2266,8 @@ where
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
|
||||
/// * `derivation_index` - The index along the derivation path to for which to retrieve the secret key
|
||||
///
|
||||
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using
|
||||
/// * `derivation_index` - The index along the derivation path to for which to retrieve the secret key, should be not higher than 2^31 - 1
|
||||
/// # Returns
|
||||
/// * Ok with an ed25519_dalek::SecretKey if successful
|
||||
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
|
||||
@@ -2244,13 +2278,14 @@ where
|
||||
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
|
||||
///
|
||||
/// use grin_core::global::ChainTypes;
|
||||
/// use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// // Set up as above
|
||||
/// # let api_owner = Owner::new(wallet.clone(), None, std::path::PathBuf::from("grin-wallet.toml"));
|
||||
///
|
||||
/// let res = api_owner.get_slatepack_secret_key(None, 0);
|
||||
/// let res = api_owner.get_slatepack_secret_key(None, SlatepackAddressIndex(0));
|
||||
///
|
||||
/// if let Ok(_) = res {
|
||||
/// // ...
|
||||
@@ -2260,7 +2295,7 @@ where
|
||||
pub fn get_slatepack_secret_key(
|
||||
&self,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
derivation_index: u32,
|
||||
derivation_index: SlatepackAddressIndex,
|
||||
) -> Result<DalekSecretKey, Error> {
|
||||
owner::get_slatepack_secret_key(self.wallet_inst.clone(), keychain_mask, derivation_index)
|
||||
}
|
||||
@@ -2271,7 +2306,7 @@ where
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
|
||||
/// * `sender_index` - If Some(n), the index along the derivation path to include as the sender
|
||||
/// * `sender_index` - If Some(n), the index along the derivation path to include as the sender, should be not higher than 2^31 - 1
|
||||
/// * `recipients` - Optional recipients for which to encrypt the slatepack's payload (i.e. the
|
||||
/// slate). If an empty vec, the payload will remain unencrypted
|
||||
///
|
||||
@@ -2285,6 +2320,7 @@ where
|
||||
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
|
||||
///
|
||||
/// use grin_core::global::ChainTypes;
|
||||
/// use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
@@ -2310,7 +2346,7 @@ where
|
||||
/// let slatepack = api_owner.create_slatepack_message(
|
||||
/// None,
|
||||
/// &slate,
|
||||
/// Some(0),
|
||||
/// Some(SlatepackAddressIndex(0)),
|
||||
/// vec![],
|
||||
/// );
|
||||
/// }
|
||||
@@ -2320,7 +2356,7 @@ where
|
||||
&self,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
slate: &Slate,
|
||||
sender_index: Option<u32>,
|
||||
sender_index: Option<SlatepackAddressIndex>,
|
||||
recipients: Vec<SlatepackAddress>,
|
||||
) -> Result<String, Error> {
|
||||
owner::create_slatepack_message(
|
||||
@@ -2340,8 +2376,7 @@ where
|
||||
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
|
||||
/// * `slatepack` - A string representing an armored slatepack
|
||||
/// * `secret_indices` - Indices along this wallet's derivation path with which to attempt
|
||||
/// decryption. This function will attempt to use secret keys at each index along this path
|
||||
/// to attempt to decrypt the payload, returning an error if none of the keys match.
|
||||
/// decryption. This function will attempt to use secret keys at each index along this path to attempt to decrypt the payload, returning an error if none of the keys match. Should be not higher than 2^31 - 1 each.
|
||||
///
|
||||
/// # Returns
|
||||
/// * Ok with a [Slate](../grin_wallet_libwallet/slate/struct.Slate.html) if successful
|
||||
@@ -2353,6 +2388,7 @@ where
|
||||
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
|
||||
///
|
||||
/// use grin_core::global::ChainTypes;
|
||||
/// use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
@@ -2363,14 +2399,14 @@ where
|
||||
/// let res = api_owner.slate_from_slatepack_message(
|
||||
/// None,
|
||||
/// slatepack_string,
|
||||
/// vec![0, 1, 2],
|
||||
/// vec![SlatepackAddressIndex(0), SlatepackAddressIndex(1), SlatepackAddressIndex(2)],
|
||||
/// );
|
||||
/// ```
|
||||
pub fn slate_from_slatepack_message(
|
||||
&self,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
slatepack: String,
|
||||
secret_indices: Vec<u32>,
|
||||
secret_indices: Vec<SlatepackAddressIndex>,
|
||||
) -> Result<Slate, Error> {
|
||||
owner::slate_from_slatepack_message(
|
||||
self.wallet_inst.clone(),
|
||||
@@ -2389,8 +2425,7 @@ where
|
||||
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using
|
||||
/// * `slatepack` - A string representing an armored slatepack
|
||||
/// * `secret_indices` - Indices along this wallet's derivation path with which to attempt
|
||||
/// decryption. If this wallet can't decrypt this slatepack, the payload of the returned
|
||||
/// Slatepack will remain encrypted.
|
||||
/// decryption. If this wallet can't decrypt this slatepack, the payload of the returned Slatepack will remain encrypted. Should be not higher than 2^31 - 1 each.
|
||||
///
|
||||
/// # Returns
|
||||
/// * Ok with a [Slatepack](../grin_wallet_libwallet/slatepack/types/struct.Slatepack.html) if successful
|
||||
@@ -2402,6 +2437,7 @@ where
|
||||
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
|
||||
///
|
||||
/// use grin_core::global::ChainTypes;
|
||||
/// use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
@@ -2412,7 +2448,7 @@ where
|
||||
/// let res = api_owner.decode_slatepack_message(
|
||||
/// None,
|
||||
/// slatepack_string,
|
||||
/// vec![0, 1, 2],
|
||||
/// vec![SlatepackAddressIndex(0), SlatepackAddressIndex(1), SlatepackAddressIndex(2)],
|
||||
/// );
|
||||
///
|
||||
/// ```
|
||||
@@ -2420,7 +2456,7 @@ where
|
||||
&self,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
slatepack: String,
|
||||
secret_indices: Vec<u32>,
|
||||
secret_indices: Vec<SlatepackAddressIndex>,
|
||||
) -> Result<Slatepack, Error> {
|
||||
owner::decode_slatepack_message(
|
||||
self.wallet_inst.clone(),
|
||||
@@ -2758,6 +2794,7 @@ fn output_slatepack_file<L, C, K>(
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
slate: &Slate,
|
||||
dest: Option<SlatepackAddress>,
|
||||
sender_index: Option<SlatepackAddressIndex>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K> + 'static,
|
||||
@@ -2769,7 +2806,7 @@ where
|
||||
Some(a) => vec![a],
|
||||
None => vec![],
|
||||
};
|
||||
let message = api.create_slatepack_message(keychain_mask, slate, Some(0), recipients)?;
|
||||
let message = api.create_slatepack_message(keychain_mask, slate, sender_index, recipients)?;
|
||||
let tld = api.get_top_level_directory()?;
|
||||
|
||||
// Create a directory to which files will be output.
|
||||
|
||||
+18
-11
@@ -33,6 +33,7 @@ use crate::util::secp::pedersen::Commitment;
|
||||
use crate::util::{from_hex, static_secp_instance, Mutex, ZeroingString};
|
||||
use crate::{ECDHPubkey, Ed25519SecretKey, Owner, Token};
|
||||
use grin_wallet_util::OnionV3Address;
|
||||
use libwallet::slatepack::SlatepackAddressIndex;
|
||||
use rand::thread_rng;
|
||||
use std::convert::TryFrom;
|
||||
use std::path::PathBuf;
|
||||
@@ -359,6 +360,7 @@ pub trait OwnerRpc {
|
||||
true,
|
||||
[
|
||||
{
|
||||
"address_index": null,
|
||||
"amount_credited": "60000000000",
|
||||
"amount_debited": "0",
|
||||
"confirmation_ts": "2019-01-15T16:01:26Z",
|
||||
@@ -380,6 +382,7 @@ pub trait OwnerRpc {
|
||||
"tx_type": "ConfirmedCoinbase"
|
||||
},
|
||||
{
|
||||
"address_index": null,
|
||||
"amount_credited": "60000000000",
|
||||
"amount_debited": "0",
|
||||
"confirmation_ts": "2019-01-15T16:01:26Z",
|
||||
@@ -451,6 +454,7 @@ pub trait OwnerRpc {
|
||||
true,
|
||||
[
|
||||
{
|
||||
"address_index": null,
|
||||
"amount_credited": "60000000000",
|
||||
"amount_debited": "0",
|
||||
"confirmation_ts": "2019-01-15T16:01:26Z",
|
||||
@@ -472,6 +476,7 @@ pub trait OwnerRpc {
|
||||
"tx_type": "ConfirmedCoinbase"
|
||||
},
|
||||
{
|
||||
"address_index": null,
|
||||
"amount_credited": "60000000000",
|
||||
"amount_debited": "0",
|
||||
"confirmation_ts": "2019-01-15T16:01:26Z",
|
||||
@@ -598,7 +603,7 @@ pub trait OwnerRpc {
|
||||
"id": "0436430c-2b02-624c-2032-570501212b00",
|
||||
"proof": {
|
||||
"raddr": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3",
|
||||
"saddr": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3"
|
||||
"saddr": "4b2adcc4efc1d547c08ed7c9f7d987ff085f1788d62805e73c8f632e4b065039"
|
||||
},
|
||||
"sigs": [
|
||||
{
|
||||
@@ -1706,7 +1711,7 @@ pub trait OwnerRpc {
|
||||
fn get_slatepack_address(
|
||||
&self,
|
||||
token: Token,
|
||||
derivation_index: u32,
|
||||
derivation_index: SlatepackAddressIndex,
|
||||
) -> Result<SlatepackAddress, Error>;
|
||||
|
||||
/**
|
||||
@@ -1740,7 +1745,7 @@ pub trait OwnerRpc {
|
||||
fn get_slatepack_secret_key(
|
||||
&self,
|
||||
token: Token,
|
||||
derivation_index: u32,
|
||||
derivation_index: SlatepackAddressIndex,
|
||||
) -> Result<Ed25519SecretKey, Error>;
|
||||
|
||||
/**
|
||||
@@ -1790,7 +1795,7 @@ pub trait OwnerRpc {
|
||||
&self,
|
||||
token: Token,
|
||||
slate: VersionedSlate,
|
||||
sender_index: Option<u32>,
|
||||
sender_index: Option<SlatepackAddressIndex>,
|
||||
recipients: Vec<SlatepackAddress>,
|
||||
) -> Result<String, Error>;
|
||||
|
||||
@@ -1844,7 +1849,7 @@ pub trait OwnerRpc {
|
||||
&self,
|
||||
token: Token,
|
||||
message: String,
|
||||
secret_indices: Vec<u32>,
|
||||
secret_indices: Vec<SlatepackAddressIndex>,
|
||||
) -> Result<VersionedSlate, Error>;
|
||||
|
||||
/**
|
||||
@@ -1885,7 +1890,7 @@ pub trait OwnerRpc {
|
||||
&self,
|
||||
token: Token,
|
||||
message: String,
|
||||
secret_indices: Vec<u32>,
|
||||
secret_indices: Vec<SlatepackAddressIndex>,
|
||||
) -> Result<Slatepack, Error>;
|
||||
|
||||
/**
|
||||
@@ -1917,6 +1922,7 @@ pub trait OwnerRpc {
|
||||
"recipient_address": "tgrin10qlk22rxjap2ny8qltc2tl996kenxr3hhwuu6hrzs6tdq08yaqgqq6t83r",
|
||||
"recipient_sig": "02868f2d2b983981f8f98043701687a8531ed2de564ea3df48e9e7e0229ccbe8359efe506896df2efbe3528e977252c50e4a41ca3cc9896e7c5a30bbb1d33604",
|
||||
"sender_address": "tgrin1xtxavwfgs48ckf3gk8wwgcndmn0nt4tvkl8a7ltyejjcy2mc6nfs9gm2lp",
|
||||
"sender_address_path": 0,
|
||||
"sender_sig": "c511764f3f61ed3d1cbca9514df8bc6811fad5662b1cb0e0587b9c9e49db9f33183cce71af6cb24b507fabf525a2bc405c6e84e63a60334edff0b451ae5e6102"
|
||||
}
|
||||
}
|
||||
@@ -1949,6 +1955,7 @@ pub trait OwnerRpc {
|
||||
"recipient_address": "tgrin10qlk22rxjap2ny8qltc2tl996kenxr3hhwuu6hrzs6tdq08yaqgqq6t83r",
|
||||
"recipient_sig": "02868f2d2b983981f8f98043701687a8531ed2de564ea3df48e9e7e0229ccbe8359efe506896df2efbe3528e977252c50e4a41ca3cc9896e7c5a30bbb1d33604",
|
||||
"sender_address": "tgrin1xtxavwfgs48ckf3gk8wwgcndmn0nt4tvkl8a7ltyejjcy2mc6nfs9gm2lp",
|
||||
"sender_address_path": 0,
|
||||
"sender_sig": "c511764f3f61ed3d1cbca9514df8bc6811fad5662b1cb0e0587b9c9e49db9f33183cce71af6cb24b507fabf525a2bc405c6e84e63a60334edff0b451ae5e6102"
|
||||
}
|
||||
},
|
||||
@@ -2424,7 +2431,7 @@ where
|
||||
fn get_slatepack_address(
|
||||
&self,
|
||||
token: Token,
|
||||
derivation_index: u32,
|
||||
derivation_index: SlatepackAddressIndex,
|
||||
) -> Result<SlatepackAddress, Error> {
|
||||
Owner::get_slatepack_address(self, token.keychain_mask.as_ref(), derivation_index)
|
||||
}
|
||||
@@ -2432,7 +2439,7 @@ where
|
||||
fn get_slatepack_secret_key(
|
||||
&self,
|
||||
token: Token,
|
||||
derivation_index: u32,
|
||||
derivation_index: SlatepackAddressIndex,
|
||||
) -> Result<Ed25519SecretKey, Error> {
|
||||
let key =
|
||||
Owner::get_slatepack_secret_key(self, token.keychain_mask.as_ref(), derivation_index)?;
|
||||
@@ -2443,7 +2450,7 @@ where
|
||||
&self,
|
||||
token: Token,
|
||||
slate: VersionedSlate,
|
||||
sender_index: Option<u32>,
|
||||
sender_index: Option<SlatepackAddressIndex>,
|
||||
recipients: Vec<SlatepackAddress>,
|
||||
) -> Result<String, Error> {
|
||||
let res = Owner::create_slatepack_message(
|
||||
@@ -2460,7 +2467,7 @@ where
|
||||
&self,
|
||||
token: Token,
|
||||
message: String,
|
||||
secret_indices: Vec<u32>,
|
||||
secret_indices: Vec<SlatepackAddressIndex>,
|
||||
) -> Result<VersionedSlate, Error> {
|
||||
let slate = Owner::slate_from_slatepack_message(
|
||||
self,
|
||||
@@ -2476,7 +2483,7 @@ where
|
||||
&self,
|
||||
token: Token,
|
||||
message: String,
|
||||
secret_indices: Vec<u32>,
|
||||
secret_indices: Vec<SlatepackAddressIndex>,
|
||||
) -> Result<Slatepack, Error> {
|
||||
Owner::decode_slatepack_message(self, token.keychain_mask.as_ref(), message, secret_indices)
|
||||
}
|
||||
|
||||
+70
-68
@@ -19,19 +19,20 @@ use crate::apiwallet::{try_slatepack_sync_workflow, Owner};
|
||||
use crate::config::{TorConfig, WalletConfig, WALLET_CONFIG_FILE_NAME};
|
||||
use crate::core::{core, global};
|
||||
use crate::error::Error;
|
||||
use crate::impls::PathToSlatepack;
|
||||
use crate::impls::SlateGetter as _;
|
||||
use crate::keychain;
|
||||
use crate::libwallet::api_impl::owner::{self, decrypt_slatepack, deserialize_slatepack};
|
||||
use crate::libwallet::api_impl::types::update_tx_slate_state;
|
||||
use crate::libwallet::slatepack::get_slatepack_file_contents;
|
||||
use crate::libwallet::slatepack::SlatepackAddressIndex;
|
||||
use crate::libwallet::{
|
||||
self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState, Slatepack,
|
||||
SlatepackAddress, Slatepacker, SlatepackerArgs, WalletLCProvider,
|
||||
SlatepackAddress, WalletLCProvider,
|
||||
};
|
||||
use crate::libwallet::{wallet_lock, WalletHandle};
|
||||
use crate::util::secp::key::SecretKey;
|
||||
use crate::util::{Mutex, ZeroingString};
|
||||
use crate::{controller, display};
|
||||
|
||||
use grin_wallet_libwallet::wallet_lock;
|
||||
use qr_code::QrCode;
|
||||
use serde_json as json;
|
||||
use std::fs::File;
|
||||
@@ -341,6 +342,7 @@ pub struct SendArgs {
|
||||
pub outfile: Option<String>,
|
||||
pub bridge: Option<String>,
|
||||
pub slatepack_qr: bool,
|
||||
pub address_index: Option<SlatepackAddressIndex>,
|
||||
}
|
||||
|
||||
fn max_retry_args(mut init_args: InitTxArgs, amount: u64, max_inputs: u32) -> InitTxArgs {
|
||||
@@ -412,6 +414,7 @@ where
|
||||
selection_strategy_is_use_all: strategy == "all",
|
||||
refresh_outputs_from_node: !info_updated,
|
||||
estimate_only: Some(true),
|
||||
address_index: args.address_index,
|
||||
..Default::default()
|
||||
};
|
||||
let result = owner_api.init_send_tx(keychain_mask, init_args.clone());
|
||||
@@ -456,6 +459,7 @@ where
|
||||
payment_proof_recipient_address,
|
||||
ttl_blocks: args.ttl_blocks,
|
||||
late_lock: Some(args.late_lock),
|
||||
address_index: args.address_index,
|
||||
..Default::default()
|
||||
};
|
||||
let init_send_tx = |init_args: InitTxArgs| -> Result<Slate, libwallet::Error> {
|
||||
@@ -505,6 +509,11 @@ where
|
||||
}
|
||||
|
||||
let output_sp = |owner_api: &mut Owner<L, C, K>, lock_outputs: bool| -> Result<(), Error> {
|
||||
let sender_index = {
|
||||
wallet_lock!(owner_api.wallet_inst, w);
|
||||
let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?;
|
||||
context.payment_proof_derivation_index
|
||||
};
|
||||
Ok(output_slatepack(
|
||||
owner_api,
|
||||
keychain_mask,
|
||||
@@ -514,6 +523,7 @@ where
|
||||
lock_outputs,
|
||||
false,
|
||||
args.slatepack_qr,
|
||||
sender_index,
|
||||
)?)
|
||||
};
|
||||
|
||||
@@ -567,6 +577,7 @@ pub fn output_slatepack<L, C, K>(
|
||||
lock: bool,
|
||||
finalizing: bool,
|
||||
show_qr: bool,
|
||||
sender_index: Option<SlatepackAddressIndex>,
|
||||
) -> Result<(), libwallet::Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K> + 'static,
|
||||
@@ -579,7 +590,8 @@ where
|
||||
Some(a) => vec![a],
|
||||
None => vec![],
|
||||
};
|
||||
let message = owner_api.create_slatepack_message(keychain_mask, slate, Some(0), recipients)?;
|
||||
let message =
|
||||
owner_api.create_slatepack_message(keychain_mask, slate, sender_index, recipients)?;
|
||||
let tld = owner_api.get_top_level_directory()?;
|
||||
|
||||
// create a directory to which files will be output
|
||||
@@ -653,42 +665,30 @@ where
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
let mut ret_address = None;
|
||||
let slate = match filename {
|
||||
let (slate, sender_address) = match filename {
|
||||
Some(f) => {
|
||||
// otherwise, get slate from slatepack
|
||||
let dec_key = owner_api.get_slatepack_secret_key(keychain_mask, 0)?;
|
||||
let packer = Slatepacker::new(SlatepackerArgs {
|
||||
sender: None,
|
||||
recipients: vec![],
|
||||
dec_key: Some(&dec_key),
|
||||
});
|
||||
let pts = PathToSlatepack::new(f.into(), &packer, true);
|
||||
let sl = Some(pts.get_tx()?.0);
|
||||
ret_address = pts.get_slatepack(true)?.sender;
|
||||
sl
|
||||
let data = get_slatepack_file_contents(&f.into())?;
|
||||
let sp = deserialize_slatepack(
|
||||
&data,
|
||||
owner_api.wallet_inst.clone(),
|
||||
keychain_mask,
|
||||
vec![SlatepackAddressIndex(0)],
|
||||
)?;
|
||||
let sl = sp.get_slate()?;
|
||||
(sl, sp.sender)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let slate = match slate {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
// try and parse directly from input_slatepack_message
|
||||
match message {
|
||||
Some(message) => {
|
||||
let slate = owner_api.slate_from_slatepack_message(
|
||||
let sp = owner_api.decode_slatepack_message(
|
||||
keychain_mask,
|
||||
message.clone(),
|
||||
vec![0],
|
||||
vec![SlatepackAddressIndex(0)],
|
||||
)?;
|
||||
let slatepack = owner_api.decode_slatepack_message(
|
||||
keychain_mask,
|
||||
message.clone(),
|
||||
vec![0],
|
||||
)?;
|
||||
ret_address = slatepack.sender;
|
||||
slate
|
||||
let sl = sp.get_slate()?;
|
||||
(sl, sp.sender)
|
||||
}
|
||||
None => {
|
||||
let msg = "No slate provided via file or direct input";
|
||||
@@ -697,7 +697,7 @@ where
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok((slate, ret_address))
|
||||
Ok((slate, sender_address))
|
||||
}
|
||||
|
||||
/// Receive command argument
|
||||
@@ -775,6 +775,7 @@ where
|
||||
false,
|
||||
false,
|
||||
args.slatepack_qr,
|
||||
None,
|
||||
)?)
|
||||
};
|
||||
|
||||
@@ -790,8 +791,7 @@ where
|
||||
Ok(s) => {
|
||||
// Update slate state.
|
||||
{
|
||||
let mut w_lock = owner_api.wallet_inst.lock();
|
||||
let w = w_lock.lc_provider()?.wallet_inst()?;
|
||||
wallet_lock!(owner_api.wallet_inst, w);
|
||||
let parent_key_id = w.parent_key_id();
|
||||
match update_tx_slate_state(w, keychain_mask, &parent_key_id, &s) {
|
||||
Ok(_) => {}
|
||||
@@ -814,25 +814,18 @@ where
|
||||
}
|
||||
|
||||
pub fn read_slatepack(args: ReceiveArgs) -> Result<Slatepack, Error> {
|
||||
let packer = Slatepacker::new(SlatepackerArgs {
|
||||
sender: None,
|
||||
recipients: vec![],
|
||||
dec_key: None,
|
||||
});
|
||||
let slatepack = match args.input_file {
|
||||
Some(f) => PathToSlatepack::new(f.into(), &packer, true).get_slatepack(false)?,
|
||||
let data = match args.input_file {
|
||||
Some(f) => get_slatepack_file_contents(&f.into())?,
|
||||
None => match args.input_slatepack_message {
|
||||
Some(message) => packer.deser_slatepack(message.as_bytes(), false)?,
|
||||
None => {
|
||||
return Err(Error::ArgumentError("Invalid Slatepack Input".into()));
|
||||
}
|
||||
Some(message) => message.into_bytes(),
|
||||
None => return Err(Error::ArgumentError("Invalid Slatepack Input".into())),
|
||||
},
|
||||
};
|
||||
Ok(slatepack)
|
||||
Ok(owner::parse_slatepack(&data)?)
|
||||
}
|
||||
|
||||
pub fn unpack<L, C, K>(
|
||||
owner_api: &mut Owner<L, C, K>,
|
||||
wallet_inst: WalletHandle<'static, L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
mut slatepack: Slatepack,
|
||||
) -> Result<(), Error>
|
||||
@@ -847,33 +840,32 @@ where
|
||||
println!("{}", slatepack);
|
||||
println!("------------------");
|
||||
|
||||
let packer = Slatepacker::new(SlatepackerArgs {
|
||||
sender: None,
|
||||
recipients: vec![],
|
||||
dec_key: None,
|
||||
});
|
||||
|
||||
if slatepack.mode == 1 {
|
||||
let dec_key = owner_api.get_slatepack_secret_key(keychain_mask, 0)?;
|
||||
match slatepack.try_decrypt_payload(Some(&dec_key)) {
|
||||
Ok(_) => {
|
||||
match decrypt_slatepack(
|
||||
&mut slatepack,
|
||||
wallet_inst,
|
||||
keychain_mask,
|
||||
vec![SlatepackAddressIndex(0)],
|
||||
)
|
||||
.and_then(|_| slatepack.get_slate())
|
||||
{
|
||||
Ok(s) => {
|
||||
println!("Slatepack is encrypted for this wallet");
|
||||
println!();
|
||||
println!("DECRYPTED SLATEPACK");
|
||||
println!("-------------------");
|
||||
println!("{}", slatepack);
|
||||
let slate = packer.get_slate(&slatepack)?;
|
||||
println!();
|
||||
println!("DECRYPTED SLATE");
|
||||
println!("---------------");
|
||||
println!("{}", slate);
|
||||
println!("{}", s);
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Slatepack payload cannot be decrypted by this wallet");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let slate = packer.get_slate(&slatepack)?;
|
||||
let slate = slatepack.get_slate()?;
|
||||
println!("Slatepack is not encrypted");
|
||||
println!();
|
||||
println!("SLATE");
|
||||
@@ -904,7 +896,7 @@ where
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
let (mut slate, _ret_address) = parse_slatepack(
|
||||
let (mut slate, _) = parse_slatepack(
|
||||
owner_api,
|
||||
keychain_mask,
|
||||
args.input_file.clone(),
|
||||
@@ -975,6 +967,7 @@ where
|
||||
false,
|
||||
true,
|
||||
args.slatepack_qr,
|
||||
None,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -984,11 +977,11 @@ where
|
||||
pub struct IssueInvoiceArgs {
|
||||
/// Slatepack address
|
||||
pub dest: Option<String>,
|
||||
/// issue invoice tx args
|
||||
/// Issue invoice tx args
|
||||
pub issue_args: IssueInvoiceTxArgs,
|
||||
/// output file override
|
||||
/// Output file override
|
||||
pub outfile: Option<String>,
|
||||
/// show slatepack as QR code
|
||||
/// Show slatepack as QR code
|
||||
pub slatepack_qr: bool,
|
||||
}
|
||||
|
||||
@@ -1009,8 +1002,13 @@ where
|
||||
.transpose()?;
|
||||
let issue_args = args.issue_args.clone();
|
||||
|
||||
let slate = owner_api.issue_invoice_tx(keychain_mask, issue_args)?;
|
||||
let slate = owner_api.issue_invoice_tx(keychain_mask, issue_args.clone())?;
|
||||
|
||||
let sender_index = {
|
||||
wallet_lock!(owner_api.wallet_inst, w);
|
||||
let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?;
|
||||
context.payment_proof_derivation_index
|
||||
};
|
||||
output_slatepack(
|
||||
owner_api,
|
||||
keychain_mask,
|
||||
@@ -1020,6 +1018,7 @@ where
|
||||
false,
|
||||
false,
|
||||
args.slatepack_qr,
|
||||
sender_index,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1133,6 +1132,7 @@ where
|
||||
true,
|
||||
false,
|
||||
args.slatepack_qr,
|
||||
None,
|
||||
)?)
|
||||
};
|
||||
|
||||
@@ -1314,7 +1314,7 @@ where
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
let (slate, _ret_address) = parse_slatepack(
|
||||
let (slate, _) = parse_slatepack(
|
||||
owner_api,
|
||||
keychain_mask,
|
||||
args.input_file,
|
||||
@@ -1462,7 +1462,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Payment Proof Address
|
||||
/// Show wallet's slatepack address.
|
||||
pub fn address<L, C, K>(
|
||||
owner_api: &mut Owner<L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
@@ -1472,8 +1472,10 @@ where
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
// Just address at derivation index 0 for now
|
||||
let address = owner_api.get_slatepack_address(keychain_mask, 0)?;
|
||||
// Just address at derivation index 0 for now,
|
||||
// cause sender encrypts message for address,
|
||||
// and we don't know its derivation path index.
|
||||
let address = owner_api.get_slatepack_address(keychain_mask, SlatepackAddressIndex(0))?;
|
||||
let account = account_label(owner_api)?;
|
||||
println!();
|
||||
println!("Address for account - {}", account);
|
||||
|
||||
@@ -55,6 +55,7 @@ use grin_wallet_config::config::{
|
||||
};
|
||||
use grin_wallet_impls::tor::arti::{start_tor_service, stop_tor_service};
|
||||
use grin_wallet_impls::tor::process::TorProcess;
|
||||
use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::body::Incoming;
|
||||
use tokio::sync::mpsc;
|
||||
@@ -255,8 +256,9 @@ where
|
||||
let w_inst = lc.wallet_inst()?;
|
||||
let k = w_inst.keychain(mask.as_ref())?;
|
||||
let parent_key_id = w_inst.parent_key_id();
|
||||
let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0)
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
let sec_key =
|
||||
address::address_from_derivation_path(&k, &parent_key_id, SlatepackAddressIndex(0))
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?);
|
||||
let onion_address = OnionV3Address::from_private(&sec_key.0)
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::libwallet::{
|
||||
TxLogEntry, TxLogEntryType, ViewWallet, WalletInfo,
|
||||
};
|
||||
use crate::util::ToHex;
|
||||
|
||||
use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
|
||||
use prettytable;
|
||||
use prettytable::format::{FormatBuilder, LinePosition, LineSeparator};
|
||||
use std::io::prelude::Write;
|
||||
@@ -541,7 +541,8 @@ pub fn accounts(acct_mappings: Vec<AcctPathMapping>) {
|
||||
bMG->"Spendable balance",
|
||||
]);
|
||||
for m in acct_mappings {
|
||||
let slatepack_path = address::address_derivation_path(&m.path, 0).to_bip_32_string();
|
||||
let slatepack_path =
|
||||
address::address_derivation_path(&m.path, SlatepackAddressIndex(0)).to_bip_32_string();
|
||||
let spendable = if let Some(info) = m.info {
|
||||
amount_to_hr_string(info.amount_currently_spendable, true)
|
||||
} else {
|
||||
|
||||
@@ -32,6 +32,7 @@ use grin_wallet_libwallet::{InitTxArgs, IssueInvoiceTxArgs};
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallet_proxy, setup};
|
||||
use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
|
||||
|
||||
/// self send impl
|
||||
fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), libwallet::Error> {
|
||||
@@ -235,7 +236,7 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(),
|
||||
format!("{}/standard_pp_S3.txbin", test_dir),
|
||||
),
|
||||
};
|
||||
let address = Some(api2.get_slatepack_address(mask2, 0)?);
|
||||
let address = Some(api2.get_slatepack_address(mask2, SlatepackAddressIndex(0))?);
|
||||
|
||||
// send to send
|
||||
let args = InitTxArgs {
|
||||
|
||||
@@ -28,6 +28,7 @@ use std::time::Duration;
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallet_proxy, setup};
|
||||
use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
|
||||
|
||||
/// Various tests on accounts within the same wallet
|
||||
fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
@@ -78,7 +79,7 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
|
||||
|
||||
let address = Some(api2.get_slatepack_address(mask2, 0)?);
|
||||
let address = Some(api2.get_slatepack_address(mask2, SlatepackAddressIndex(0))?);
|
||||
|
||||
println!("Public address is: {:?}", address);
|
||||
let amount = 60_000_000_000;
|
||||
@@ -121,7 +122,7 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err
|
||||
slate_i.payment_proof.as_ref().unwrap().receiver_address
|
||||
);
|
||||
assert!(pp.receiver_signature.is_some());
|
||||
assert_eq!(pp.sender_address_path, 0);
|
||||
assert_eq!(pp.sender_address_path, SlatepackAddressIndex(1));
|
||||
assert_eq!(pp.sender_signature, None);
|
||||
|
||||
// check we should get an error at this point since proof is not complete
|
||||
|
||||
@@ -30,6 +30,7 @@ use std::time::Duration;
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallet_proxy, setup};
|
||||
use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
|
||||
|
||||
/// self send impl
|
||||
fn self_send_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
@@ -171,7 +172,9 @@ fn late_lock_self_send_proof() -> Result<(), libwallet::Error> {
|
||||
amount: core::consensus::REWARD,
|
||||
minimum_confirmations: 2,
|
||||
late_lock: Some(true),
|
||||
payment_proof_recipient_address: Some(api1.get_slatepack_address(mask1, 0)?),
|
||||
payment_proof_recipient_address: Some(
|
||||
api1.get_slatepack_address(mask1, SlatepackAddressIndex(0))?,
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
+298
-93
@@ -20,25 +20,30 @@ extern crate grin_wallet_impls as impls;
|
||||
use grin_core as core;
|
||||
use grin_wallet_config::GlobalWalletConfig;
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use impls::test_framework::{self, LocalWalletClient};
|
||||
use impls::{PathToSlatepack, SlatePutter as _};
|
||||
use impls::DefaultLCProvider;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::{fs, thread};
|
||||
|
||||
use grin_wallet_libwallet::{
|
||||
InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, Slate, Slatepack, SlatepackAddress,
|
||||
Slatepacker, SlatepackerArgs,
|
||||
slatepack, Error, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, Slate, Slatepack,
|
||||
SlatepackAddress, SlatepackBin, WalletHandle,
|
||||
};
|
||||
|
||||
use ed25519_dalek::SigningKey as edDalekSecretKey;
|
||||
use ed25519_dalek::VerifyingKey as edDalekPublicKey;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallet_proxy, setup};
|
||||
use grin_keychain::ExtKeychain;
|
||||
use grin_util::secp::SecretKey;
|
||||
use grin_wallet_libwallet::api_impl::owner::{decrypt_slatepack, deserialize_slatepack};
|
||||
use grin_wallet_libwallet::slatepack::{get_slatepack_file_contents, SlatepackAddressIndex};
|
||||
use grin_wallet_util::byte_ser;
|
||||
|
||||
fn output_slatepack(
|
||||
slate: &Slate,
|
||||
@@ -47,35 +52,55 @@ fn output_slatepack(
|
||||
use_bin: bool,
|
||||
sender: Option<SlatepackAddress>,
|
||||
recipients: Vec<SlatepackAddress>,
|
||||
) -> Result<(), libwallet::Error> {
|
||||
let packer = Slatepacker::new(SlatepackerArgs {
|
||||
sender,
|
||||
recipients,
|
||||
dec_key: None,
|
||||
});
|
||||
) -> Result<(), Error> {
|
||||
let mut file = file.into();
|
||||
if armored {
|
||||
file = format!("{}.armored", file);
|
||||
}
|
||||
PathToSlatepack::new(file.into(), &packer, armored).put_tx(slate, use_bin)
|
||||
let slatepack = slate.create_slatepack(sender, recipients)?;
|
||||
let mut pub_tx = File::create(&file)?;
|
||||
if use_bin {
|
||||
if armored {
|
||||
let armored = slatepack.armor()?;
|
||||
pub_tx.write_all(armored.as_bytes())?;
|
||||
} else {
|
||||
pub_tx.write_all(
|
||||
&byte_ser::to_bytes(&SlatepackBin(slatepack)).map_err(|_| Error::SlatepackSer)?,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
pub_tx.write_all(
|
||||
serde_json::to_string_pretty(&slatepack)
|
||||
.map_err(|_| Error::SlateSer)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
}
|
||||
pub_tx.sync_all()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn slate_from_packed(
|
||||
wallet_inst: WalletHandle<
|
||||
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
|
||||
LocalWalletClient,
|
||||
ExtKeychain,
|
||||
>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
file: &str,
|
||||
armored: bool,
|
||||
dec_key: Option<&edDalekSecretKey>,
|
||||
) -> Result<(Slatepack, Slate), libwallet::Error> {
|
||||
let packer = Slatepacker::new(SlatepackerArgs {
|
||||
sender: None,
|
||||
recipients: vec![],
|
||||
dec_key,
|
||||
});
|
||||
) -> Result<(Slatepack, Slate), Error> {
|
||||
let mut file = file.into();
|
||||
if armored {
|
||||
file = format!("{}.armored", file);
|
||||
}
|
||||
let slatepack = PathToSlatepack::new(file.into(), &packer, armored).get_slatepack(true)?;
|
||||
Ok((slatepack.clone(), packer.get_slate(&slatepack)?))
|
||||
let data = get_slatepack_file_contents(&file.into())?;
|
||||
let slatepack = deserialize_slatepack(
|
||||
&data,
|
||||
wallet_inst,
|
||||
keychain_mask,
|
||||
vec![SlatepackAddressIndex(0)],
|
||||
)?;
|
||||
Ok((slatepack.clone(), slatepack.get_slate()?))
|
||||
}
|
||||
|
||||
/// self send impl
|
||||
@@ -84,7 +109,7 @@ fn slatepack_exchange_test_impl(
|
||||
use_bin: bool,
|
||||
use_armored: bool,
|
||||
use_encryption: bool,
|
||||
) -> Result<(), libwallet::Error> {
|
||||
) -> Result<(), 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();
|
||||
@@ -134,41 +159,49 @@ fn slatepack_exchange_test_impl(
|
||||
api2.create_account_path(mask2, "account1")?;
|
||||
api2.create_account_path(mask2, "account2")?;
|
||||
|
||||
// Get some mining done
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_account_by_name("mining")?;
|
||||
}
|
||||
|
||||
{
|
||||
wallet_inst!(wallet2, w);
|
||||
w.set_account_by_name("account1")?;
|
||||
}
|
||||
|
||||
// Get some mining done
|
||||
let mut bh = 10u64;
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
|
||||
|
||||
let (recipients_1, dec_key_1, sender_1) = match use_encryption {
|
||||
let (recipients_1, sender_1) = match use_encryption {
|
||||
true => {
|
||||
let sec_key = api1.get_slatepack_secret_key(mask1, 0)?;
|
||||
let sec_key = api1.get_slatepack_secret_key(mask1, SlatepackAddressIndex(0))?;
|
||||
let pub_key = edDalekPublicKey::from(&sec_key);
|
||||
let rec_address = SlatepackAddress::new(&pub_key);
|
||||
(
|
||||
vec![rec_address.clone()],
|
||||
Some(sec_key),
|
||||
Some(rec_address.clone()),
|
||||
)
|
||||
|
||||
let sen_index = SlatepackAddressIndex(7);
|
||||
let sec_key = api1.get_slatepack_secret_key(mask1, sen_index)?;
|
||||
let pub_key = edDalekPublicKey::from(&sec_key);
|
||||
let sen_address = SlatepackAddress::new(&pub_key);
|
||||
(vec![rec_address.clone()], Some(sen_address.clone()))
|
||||
}
|
||||
false => (vec![], None, None),
|
||||
false => (vec![], None),
|
||||
};
|
||||
|
||||
let (recipients_2, dec_key_2, sender_2) = match use_encryption {
|
||||
let (recipients_2, sender_2) = match use_encryption {
|
||||
true => {
|
||||
let sec_key = api2.get_slatepack_secret_key(mask2, 0)?;
|
||||
let sec_key = api2.get_slatepack_secret_key(mask2, SlatepackAddressIndex(0))?;
|
||||
let pub_key = edDalekPublicKey::from(&sec_key);
|
||||
let rec_address = SlatepackAddress::new(&pub_key);
|
||||
(
|
||||
vec![rec_address.clone()],
|
||||
Some(sec_key),
|
||||
Some(rec_address.clone()),
|
||||
)
|
||||
|
||||
let sen_index = SlatepackAddressIndex(11);
|
||||
let sec_key = api2.get_slatepack_secret_key(mask2, sen_index)?;
|
||||
let pub_key = edDalekPublicKey::from(&sec_key);
|
||||
let sen_address = SlatepackAddress::new(&pub_key);
|
||||
(vec![rec_address.clone()], Some(sen_address.clone()))
|
||||
}
|
||||
false => (vec![], None, None),
|
||||
false => (vec![], None),
|
||||
};
|
||||
|
||||
let (send_file, receive_file, final_file) = match use_bin {
|
||||
@@ -190,7 +223,7 @@ fn slatepack_exchange_test_impl(
|
||||
assert_eq!(wallet1_info.total, bh * reward);
|
||||
// send to send
|
||||
let args = InitTxArgs {
|
||||
src_acct_name: Some("mining".to_owned()),
|
||||
address_index: Some(SlatepackAddressIndex(7)),
|
||||
amount: reward * 2,
|
||||
minimum_confirmations: 2,
|
||||
max_outputs: 500,
|
||||
@@ -210,14 +243,8 @@ fn slatepack_exchange_test_impl(
|
||||
)?;
|
||||
api1.tx_lock_outputs(mask1, &slate)?;
|
||||
|
||||
// Get some mining done
|
||||
{
|
||||
wallet_inst!(wallet2, w);
|
||||
w.set_account_by_name("account1")?;
|
||||
}
|
||||
|
||||
let (mut slatepack, mut slate) =
|
||||
slate_from_packed(&send_file, use_armored, dec_key_2.as_ref())?;
|
||||
let (slatepack, mut slate) =
|
||||
slate_from_packed(wallet2.clone(), mask2, &send_file, use_armored)?;
|
||||
|
||||
// wallet 2 receives file, completes, sends file back
|
||||
wallet::controller::foreign_single_use(
|
||||
@@ -231,8 +258,8 @@ fn slatepack_exchange_test_impl(
|
||||
&receive_file,
|
||||
use_armored,
|
||||
use_bin,
|
||||
// re-encrypt for sender!
|
||||
sender_2.clone(),
|
||||
// re-encrypt for sender!
|
||||
match slatepack.sender.clone() {
|
||||
Some(s) => vec![s.clone()],
|
||||
None => vec![],
|
||||
@@ -243,23 +270,34 @@ fn slatepack_exchange_test_impl(
|
||||
)?;
|
||||
|
||||
// wallet 1 finalizes and posts
|
||||
let (_, mut slate) = slate_from_packed(&receive_file, use_armored, dec_key_1.as_ref())?;
|
||||
let (slatepack, mut slate) =
|
||||
slate_from_packed(wallet1.clone(), mask1, &receive_file, use_armored)?;
|
||||
slate = api1.finalize_tx(mask1, &slate)?;
|
||||
// Output final file for reference
|
||||
output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?;
|
||||
output_slatepack(
|
||||
&slate,
|
||||
&final_file,
|
||||
use_armored,
|
||||
use_bin,
|
||||
sender_1.clone(),
|
||||
match slatepack.sender {
|
||||
Some(s) => vec![s.clone()],
|
||||
None => vec![],
|
||||
},
|
||||
)?;
|
||||
api1.post_tx(mask1, &slate, false)?;
|
||||
bh += 1;
|
||||
|
||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
|
||||
bh += 3;
|
||||
|
||||
// Check total in mining account
|
||||
// Check total in wallet 1 account
|
||||
let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?;
|
||||
assert!(wallet1_refreshed);
|
||||
assert_eq!(wallet1_info.last_confirmed_height, bh);
|
||||
assert_eq!(wallet1_info.total, bh * reward - reward * 2);
|
||||
|
||||
// Check total in 'wallet 2' account
|
||||
// Check total in wallet 2 account
|
||||
let (wallet2_refreshed, wallet2_info) = api2.retrieve_summary_info(mask2, true, 1)?;
|
||||
assert!(wallet2_refreshed);
|
||||
assert_eq!(wallet2_info.last_confirmed_height, bh);
|
||||
@@ -281,10 +319,11 @@ fn slatepack_exchange_test_impl(
|
||||
};
|
||||
|
||||
let args = IssueInvoiceTxArgs {
|
||||
address_index: Some(SlatepackAddressIndex(11)),
|
||||
amount: 1000000000,
|
||||
..Default::default()
|
||||
};
|
||||
let mut slate = api2.issue_invoice_tx(mask2, args)?;
|
||||
let slate = api2.issue_invoice_tx(mask2, args)?;
|
||||
output_slatepack(
|
||||
&slate,
|
||||
&send_file,
|
||||
@@ -303,9 +342,8 @@ fn slatepack_exchange_test_impl(
|
||||
selection_strategy_is_use_all: true,
|
||||
..Default::default()
|
||||
};
|
||||
let res = slate_from_packed(&send_file, use_armored, dec_key_1.as_ref())?;
|
||||
slatepack = res.0;
|
||||
slate = res.1;
|
||||
let (slatepack, mut slate) =
|
||||
slate_from_packed(wallet1.clone(), mask1, &send_file, use_armored)?;
|
||||
slate = api1.process_invoice_tx(mask1, &slate, args)?;
|
||||
api1.tx_lock_outputs(mask1, &slate)?;
|
||||
output_slatepack(
|
||||
@@ -314,25 +352,16 @@ fn slatepack_exchange_test_impl(
|
||||
use_armored,
|
||||
use_bin,
|
||||
sender_1.clone(),
|
||||
match slatepack.sender.clone() {
|
||||
match slatepack.sender {
|
||||
Some(s) => vec![s.clone()],
|
||||
None => vec![],
|
||||
},
|
||||
)?;
|
||||
wallet::controller::foreign_single_use(
|
||||
wallet2.clone(),
|
||||
PathBuf::from(test_dir),
|
||||
mask2_i.clone(),
|
||||
|api| {
|
||||
// Wallet 2 receives the invoice transaction
|
||||
let res = slate_from_packed(&receive_file, use_armored, dec_key_2.as_ref())?;
|
||||
slate = res.1;
|
||||
slate = api.finalize_tx(&slate, false)?;
|
||||
output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?;
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
api1.post_tx(mask1, &slate, false)?;
|
||||
// Wallet 2 receives the invoice transaction
|
||||
let (_, mut slate) = slate_from_packed(wallet2.clone(), mask2, &receive_file, use_armored)?;
|
||||
slate = api2.finalize_tx(mask2, &slate)?;
|
||||
output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?;
|
||||
api2.post_tx(mask2, &slate, false)?;
|
||||
|
||||
// Standard, with payment proof
|
||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
|
||||
@@ -349,17 +378,17 @@ fn slatepack_exchange_test_impl(
|
||||
),
|
||||
};
|
||||
|
||||
let address = Some(api2.get_slatepack_address(mask2, 0)?);
|
||||
|
||||
// send to send
|
||||
// send from 1st wallet
|
||||
let args = InitTxArgs {
|
||||
src_acct_name: Some("mining".to_owned()),
|
||||
address_index: Some(SlatepackAddressIndex(7)),
|
||||
amount: reward,
|
||||
minimum_confirmations: 2,
|
||||
max_outputs: 500,
|
||||
num_change_outputs: 1,
|
||||
selection_strategy_is_use_all: true,
|
||||
payment_proof_recipient_address: address.clone(),
|
||||
payment_proof_recipient_address: Some(
|
||||
api2.get_slatepack_address(mask2, SlatepackAddressIndex(0))?,
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
let mut slate = api1.init_send_tx(mask1, args)?;
|
||||
@@ -373,21 +402,21 @@ fn slatepack_exchange_test_impl(
|
||||
)?;
|
||||
api1.tx_lock_outputs(mask1, &slate)?;
|
||||
|
||||
// receive at 2nd wallet
|
||||
wallet::controller::foreign_single_use(
|
||||
wallet2.clone(),
|
||||
PathBuf::from(test_dir),
|
||||
mask2_i.clone(),
|
||||
|api| {
|
||||
let res = slate_from_packed(&send_file, use_armored, dec_key_2.as_ref())?;
|
||||
let slatepack = res.0;
|
||||
slate = res.1;
|
||||
let (slatepack, mut slate) =
|
||||
slate_from_packed(wallet2.clone(), mask2, &send_file, use_armored)?;
|
||||
slate = api.receive_tx(&slate, None, None)?;
|
||||
output_slatepack(
|
||||
&slate,
|
||||
&receive_file,
|
||||
use_armored,
|
||||
use_bin,
|
||||
sender_2,
|
||||
sender_2.clone(),
|
||||
match slatepack.sender {
|
||||
Some(s) => vec![s.clone()],
|
||||
None => vec![],
|
||||
@@ -398,7 +427,7 @@ fn slatepack_exchange_test_impl(
|
||||
)?;
|
||||
|
||||
// wallet 1 finalizes and posts
|
||||
let res = slate_from_packed(&receive_file, use_armored, dec_key_1.as_ref())?;
|
||||
let res = slate_from_packed(wallet1.clone(), mask1, &receive_file, use_armored)?;
|
||||
slate = res.1;
|
||||
slate = api1.finalize_tx(mask1, &slate)?;
|
||||
// Output final file for reference
|
||||
@@ -450,7 +479,6 @@ fn slatepack_api_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
|
||||
|
||||
let args = InitTxArgs {
|
||||
src_acct_name: Some("mining".to_owned()),
|
||||
amount: reward * 2,
|
||||
minimum_confirmations: 2,
|
||||
max_outputs: 500,
|
||||
@@ -458,15 +486,141 @@ fn slatepack_api_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
selection_strategy_is_use_all: true,
|
||||
..Default::default()
|
||||
};
|
||||
for index in [SlatepackAddressIndex::MAX + 1, u32::MAX] {
|
||||
assert!(matches!(
|
||||
api1.init_send_tx(
|
||||
mask1,
|
||||
InitTxArgs {
|
||||
address_index: Some(SlatepackAddressIndex(index)),
|
||||
..args.clone()
|
||||
}
|
||||
),
|
||||
Err(Error::SlatepackAddress(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
api1.issue_invoice_tx(
|
||||
mask1,
|
||||
IssueInvoiceTxArgs {
|
||||
amount: reward,
|
||||
address_index: Some(SlatepackAddressIndex(index)),
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
Err(Error::SlatepackAddress(_))
|
||||
));
|
||||
}
|
||||
api1.init_send_tx(
|
||||
mask1,
|
||||
InitTxArgs {
|
||||
estimate_only: Some(true),
|
||||
..args.clone()
|
||||
},
|
||||
)?;
|
||||
let slate = api1.init_send_tx(mask1, args)?;
|
||||
// create an encrypted slatepack (just encrypted for self)
|
||||
let enc_addr = api1.get_slatepack_address(mask1, 0)?;
|
||||
let slatepack = api1.create_slatepack_message(mask1, &slate, Some(0), vec![enc_addr])?;
|
||||
println!("{}", slatepack);
|
||||
let slatepack_raw = api1.decode_slatepack_message(mask1, slatepack.clone(), vec![0])?;
|
||||
println!("{}", slatepack_raw);
|
||||
let decoded_slate = api1.slate_from_slatepack_message(mask1, slatepack, vec![0])?;
|
||||
println!("{}", decoded_slate);
|
||||
let address_index = SlatepackAddressIndex(0);
|
||||
let enc_addr = api1.get_slatepack_address(mask1, address_index)?;
|
||||
let slatepack =
|
||||
api1.create_slatepack_message(mask1, &slate, Some(address_index), vec![enc_addr])?;
|
||||
let mut encrypted = api1.decode_slatepack_message(mask1, slatepack.clone(), vec![])?;
|
||||
assert_eq!(encrypted.mode, 1);
|
||||
decrypt_slatepack(&mut encrypted, wallet1.clone(), mask1, vec![address_index])?;
|
||||
assert_eq!(encrypted.get_slate()?.id, slate.id);
|
||||
let slatepack_raw =
|
||||
api1.decode_slatepack_message(mask1, slatepack.clone(), vec![address_index])?;
|
||||
assert_eq!(slatepack_raw.mode, 0);
|
||||
assert_eq!(slatepack_raw.get_slate()?.id, slate.id);
|
||||
let decoded_slate = api1.slate_from_slatepack_message(mask1, slatepack, vec![address_index])?;
|
||||
assert_eq!(decoded_slate.id, slate.id);
|
||||
|
||||
let unknown_index = SlatepackAddressIndex(77);
|
||||
let unknown_address = api1.get_slatepack_address(mask1, unknown_index)?;
|
||||
let message = api1.create_slatepack_message(mask1, &slate, None, vec![unknown_address])?;
|
||||
let encrypted = api1.decode_slatepack_message(mask1, message.clone(), vec![])?;
|
||||
let decoded = api1.decode_slatepack_message(mask1, message.clone(), vec![address_index])?;
|
||||
assert_eq!(decoded.mode, 1);
|
||||
assert_eq!(decoded.payload, encrypted.payload);
|
||||
assert!(matches!(
|
||||
api1.slate_from_slatepack_message(mask1, message.clone(), vec![address_index]),
|
||||
Err(Error::SlatepackDecryption(_))
|
||||
));
|
||||
let mut api1 = api1;
|
||||
assert!(matches!(
|
||||
wallet::command::parse_slatepack(&mut api1, mask1, None, Some(message)),
|
||||
Err(wallet::Error::LibWallet(Error::SlatepackDecryption(_)))
|
||||
));
|
||||
|
||||
let index = {
|
||||
wallet_inst!(wallet1, w);
|
||||
w.get_private_context(mask1, slate.id.as_bytes())?
|
||||
.payment_proof_derivation_index
|
||||
.unwrap()
|
||||
};
|
||||
assert_eq!(index, SlatepackAddressIndex(1));
|
||||
let address = api1.get_slatepack_address(mask1, index)?;
|
||||
let message = api1.create_slatepack_message(mask1, &slate, None, vec![address])?;
|
||||
let decoded = api1.decode_slatepack_message(mask1, message.clone(), vec![address_index])?;
|
||||
assert_eq!(decoded.mode, 0);
|
||||
assert_eq!(decoded.get_slate()?.id, slate.id);
|
||||
|
||||
api1.create_account_path(mask1, "other")?;
|
||||
api1.set_active_account(mask1, "other")?;
|
||||
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 6, false)?;
|
||||
api1.set_active_account(mask1, "default")?;
|
||||
let invoice = api1.issue_invoice_tx(
|
||||
mask1,
|
||||
IssueInvoiceTxArgs {
|
||||
amount: reward,
|
||||
dest_acct_name: Some("other".into()),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
let send = api1.init_send_tx(
|
||||
mask1,
|
||||
InitTxArgs {
|
||||
amount: reward,
|
||||
src_acct_name: Some("other".into()),
|
||||
minimum_confirmations: 2,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
api1.set_active_account(mask1, "other")?;
|
||||
let next = api1.issue_invoice_tx(
|
||||
mask1,
|
||||
IssueInvoiceTxArgs {
|
||||
amount: reward,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
for (slate, expected) in [(&invoice, 1), (&send, 2), (&next, 3)] {
|
||||
wallet_inst!(wallet1, w);
|
||||
let context = w.get_private_context(mask1, slate.id.as_bytes())?;
|
||||
assert_eq!(context.parent_key_id, w.parent_key_id());
|
||||
assert_eq!(
|
||||
context.payment_proof_derivation_index,
|
||||
Some(SlatepackAddressIndex(expected))
|
||||
);
|
||||
}
|
||||
api1.set_active_account(mask1, "default")?;
|
||||
let invoice = api1.issue_invoice_tx(
|
||||
mask1,
|
||||
IssueInvoiceTxArgs {
|
||||
amount: reward,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
assert_eq!(
|
||||
w.get_private_context(mask1, invoice.id.as_bytes())?
|
||||
.payment_proof_derivation_index,
|
||||
Some(SlatepackAddressIndex(2))
|
||||
);
|
||||
}
|
||||
|
||||
api1.close_wallet(None)?;
|
||||
let encrypted = api1.decode_slatepack_message(None, message, vec![])?;
|
||||
assert_eq!(encrypted.mode, 1);
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
@@ -526,7 +680,6 @@ fn slatepack_address_validation(test_dir: &'static str) -> Result<(), libwallet:
|
||||
)?;
|
||||
|
||||
let args = InitTxArgs {
|
||||
src_acct_name: Some("mining".to_owned()),
|
||||
amount: reward,
|
||||
minimum_confirmations: 2,
|
||||
max_outputs: 500,
|
||||
@@ -566,6 +719,15 @@ fn slatepack_address_validation(test_dir: &'static str) -> Result<(), libwallet:
|
||||
fluff: false,
|
||||
skip_tor: Some(true),
|
||||
});
|
||||
let missing_config = PathBuf::from(test_dir).join("missing.toml");
|
||||
let api = grin_wallet_api::Owner::new(wallet1.clone(), None, missing_config);
|
||||
assert!(api
|
||||
.process_invoice_tx(mask1, &slate, valid_args.clone())
|
||||
.is_err());
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
assert!(w.get_private_context(mask1, slate.id.as_bytes()).is_err());
|
||||
}
|
||||
api1.process_invoice_tx(mask1, &slate, valid_args.clone())?;
|
||||
api1.init_send_tx(mask1, valid_args)?;
|
||||
|
||||
@@ -660,3 +822,46 @@ fn slatepack_address() {
|
||||
}
|
||||
clean_output_dir(test_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slatepack_file_contents() {
|
||||
let test_dir = "test_output/slatepack_file_contents";
|
||||
setup(test_dir);
|
||||
|
||||
fs::create_dir_all(test_dir).unwrap();
|
||||
let sp_path = PathBuf::from(test_dir).join("pack_file");
|
||||
|
||||
// set Slatepack file to minimum allowable size
|
||||
{
|
||||
let f = File::create(sp_path.clone()).unwrap();
|
||||
f.set_len(slatepack::min_size()).unwrap();
|
||||
}
|
||||
|
||||
let mut pack_path = get_slatepack_file_contents(&sp_path);
|
||||
assert!(pack_path.is_ok());
|
||||
|
||||
// set Slatepack file to maximum allowable size
|
||||
{
|
||||
let f = File::create(sp_path.clone()).unwrap();
|
||||
f.set_len(slatepack::max_size()).unwrap();
|
||||
}
|
||||
pack_path = get_slatepack_file_contents(&sp_path);
|
||||
assert!(pack_path.is_ok());
|
||||
|
||||
// set Slatepack file below minimum allowable size
|
||||
{
|
||||
let f = File::create(sp_path.clone()).unwrap();
|
||||
f.set_len(slatepack::min_size() - 1).unwrap();
|
||||
}
|
||||
pack_path = get_slatepack_file_contents(&sp_path);
|
||||
assert!(pack_path.is_err());
|
||||
|
||||
// set Slatepack file above maximum allowable size
|
||||
{
|
||||
let f = File::create(sp_path.clone()).unwrap();
|
||||
f.set_len(slatepack::max_size() + 1).unwrap();
|
||||
}
|
||||
pack_path = get_slatepack_file_contents(&sp_path);
|
||||
assert!(pack_path.is_err());
|
||||
clean_output_dir(test_dir);
|
||||
}
|
||||
|
||||
@@ -13,11 +13,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
mod file;
|
||||
mod slatepack;
|
||||
pub mod tor;
|
||||
|
||||
pub use self::file::PathToSlate;
|
||||
pub use self::slatepack::PathToSlatepack;
|
||||
pub use self::tor::TorSlateSender;
|
||||
|
||||
use crate::config::WalletConfig;
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
// Copyright 2021 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/// Slatepack Output 'plugin' implementation
|
||||
use std::fs::{metadata, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::libwallet::{slatepack, Error, Slate, Slatepack, SlatepackBin, Slatepacker};
|
||||
use crate::{SlateGetter, SlatePutter};
|
||||
use grin_wallet_util::byte_ser;
|
||||
|
||||
// And Slate putter impls to output to files
|
||||
pub struct PathToSlatepack<'a> {
|
||||
pub pathbuf: PathBuf,
|
||||
pub packer: &'a Slatepacker<'a>,
|
||||
pub armor_output: bool,
|
||||
}
|
||||
|
||||
impl<'a> PathToSlatepack<'a> {
|
||||
/// Create with pathbuf and recipients
|
||||
pub fn new(pathbuf: PathBuf, packer: &'a Slatepacker<'a>, armor_output: bool) -> Self {
|
||||
Self {
|
||||
pathbuf,
|
||||
packer,
|
||||
armor_output,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_slatepack_file_contents(&self) -> Result<Vec<u8>, Error> {
|
||||
let metadata = metadata(&self.pathbuf)?;
|
||||
let len = metadata.len();
|
||||
let min_len = slatepack::min_size();
|
||||
let max_len = slatepack::max_size();
|
||||
if len < min_len || len > max_len {
|
||||
let msg = format!(
|
||||
"Data is invalid length: {} | min: {}, max: {} |",
|
||||
len, min_len, max_len
|
||||
);
|
||||
return Err(Error::SlatepackDeser(msg));
|
||||
}
|
||||
let mut pub_tx_f = File::open(&self.pathbuf)?;
|
||||
let mut data = Vec::new();
|
||||
pub_tx_f.read_to_end(&mut data)?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn get_slatepack(&self, decrypt: bool) -> Result<Slatepack, Error> {
|
||||
let data = self.get_slatepack_file_contents()?;
|
||||
self.packer.deser_slatepack(&data, decrypt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SlatePutter for PathToSlatepack<'a> {
|
||||
fn put_tx(&self, slate: &Slate, as_bin: bool) -> Result<(), Error> {
|
||||
let slatepack = self.packer.create_slatepack(slate)?;
|
||||
let mut pub_tx = File::create(&self.pathbuf)?;
|
||||
if as_bin {
|
||||
if self.armor_output {
|
||||
let armored = self.packer.armor_slatepack(&slatepack)?;
|
||||
pub_tx.write_all(armored.as_bytes())?;
|
||||
} else {
|
||||
pub_tx.write_all(
|
||||
&byte_ser::to_bytes(&SlatepackBin(slatepack))
|
||||
.map_err(|_| Error::SlatepackSer)?,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
pub_tx.write_all(
|
||||
serde_json::to_string_pretty(&slatepack)
|
||||
.map_err(|_| Error::SlateSer)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
}
|
||||
pub_tx.sync_all()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SlateGetter for PathToSlatepack<'a> {
|
||||
fn get_tx(&self) -> Result<(Slate, bool), Error> {
|
||||
let data = self.get_slatepack_file_contents()?;
|
||||
let slatepack = self.packer.deser_slatepack(&data, true)?;
|
||||
Ok((self.packer.get_slate(&slatepack)?, true))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
|
||||
use grin_core::global;
|
||||
|
||||
fn clean_output_dir(test_dir: &str) {
|
||||
let _ = fs::remove_dir_all(test_dir);
|
||||
}
|
||||
|
||||
fn setup(test_dir: &str) {
|
||||
clean_output_dir(test_dir);
|
||||
}
|
||||
|
||||
const SLATEPACK_DIR: &str = "target/test_output/slatepack";
|
||||
|
||||
#[test]
|
||||
fn pathbuf_get_file_contents() {
|
||||
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||
setup(SLATEPACK_DIR);
|
||||
|
||||
fs::create_dir_all(SLATEPACK_DIR).unwrap();
|
||||
let sp_path = PathBuf::from(SLATEPACK_DIR).join("pack_file");
|
||||
|
||||
// set Slatepack file to minimum allowable size
|
||||
{
|
||||
let f = File::create(sp_path.clone()).unwrap();
|
||||
f.set_len(slatepack::min_size()).unwrap();
|
||||
}
|
||||
|
||||
let args = slatepack::SlatepackerArgs {
|
||||
sender: None,
|
||||
recipients: vec![],
|
||||
dec_key: None,
|
||||
};
|
||||
let packer = Slatepacker::new(args);
|
||||
|
||||
let mut pack_path = PathToSlatepack::new(sp_path.clone(), &packer, true);
|
||||
assert!(pack_path.get_slatepack_file_contents().is_ok());
|
||||
|
||||
pack_path = PathToSlatepack::new(sp_path.clone(), &packer, false);
|
||||
assert!(pack_path.get_slatepack_file_contents().is_ok());
|
||||
|
||||
// set Slatepack file to maximum allowable size
|
||||
{
|
||||
let f = File::create(sp_path.clone()).unwrap();
|
||||
f.set_len(slatepack::max_size()).unwrap();
|
||||
}
|
||||
|
||||
pack_path = PathToSlatepack::new(sp_path.clone(), &packer, true);
|
||||
assert!(pack_path.get_slatepack_file_contents().is_ok());
|
||||
|
||||
pack_path = PathToSlatepack::new(sp_path.clone(), &packer, false);
|
||||
assert!(pack_path.get_slatepack_file_contents().is_ok());
|
||||
|
||||
// set Slatepack file below minimum allowable size
|
||||
{
|
||||
let f = File::create(sp_path.clone()).unwrap();
|
||||
f.set_len(slatepack::min_size() - 1).unwrap();
|
||||
}
|
||||
|
||||
pack_path = PathToSlatepack::new(sp_path.clone(), &packer, true);
|
||||
assert!(pack_path.get_slatepack_file_contents().is_err());
|
||||
|
||||
pack_path = PathToSlatepack::new(sp_path.clone(), &packer, false);
|
||||
assert!(pack_path.get_slatepack_file_contents().is_err());
|
||||
|
||||
// set Slatepack file above maximum allowable size
|
||||
{
|
||||
let f = File::create(sp_path.clone()).unwrap();
|
||||
f.set_len(slatepack::max_size() + 1).unwrap();
|
||||
}
|
||||
|
||||
pack_path = PathToSlatepack::new(sp_path.clone(), &packer, true);
|
||||
assert!(pack_path.get_slatepack_file_contents().is_err());
|
||||
|
||||
pack_path = PathToSlatepack::new(sp_path.clone(), &packer, false);
|
||||
assert!(pack_path.get_slatepack_file_contents().is_err());
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -42,8 +42,7 @@ pub mod test_framework;
|
||||
pub mod tor;
|
||||
|
||||
pub use crate::adapters::{
|
||||
PathToSlate, PathToSlatepack, SlateGetter, SlatePutter, SlateReceiver, SlateSender,
|
||||
TorSlateSender,
|
||||
PathToSlate, SlateGetter, SlatePutter, SlateReceiver, SlateSender, TorSlateSender,
|
||||
};
|
||||
pub use crate::error::Error;
|
||||
pub use crate::lifecycle::DefaultLCProvider;
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
//! Functions defining wallet 'addresses', i.e. ed2559 keys based on
|
||||
//! a derivation path
|
||||
|
||||
use crate::blake2::blake2b::blake2b;
|
||||
use crate::slatepack::SlatepackAddressIndex;
|
||||
use crate::Error;
|
||||
|
||||
use grin_keychain::{ChildNumber, Identifier, Keychain, SwitchCommitmentType};
|
||||
use grin_util::secp::key::SecretKey;
|
||||
|
||||
use crate::blake2::blake2b::blake2b;
|
||||
|
||||
/// Derive a Slatepack address path from an output parent path and index.
|
||||
///
|
||||
/// Output paths:
|
||||
@@ -30,11 +31,14 @@ use crate::blake2::blake2b::blake2b;
|
||||
/// Slatepack address paths:
|
||||
/// - account m/0: m/0/1/0, m/0/1/1
|
||||
/// - account m/1: m/1/1/0, m/1/1/1
|
||||
pub fn address_derivation_path(parent_key_id: &Identifier, index: u32) -> Identifier {
|
||||
pub fn address_derivation_path(
|
||||
parent_key_id: &Identifier,
|
||||
index: SlatepackAddressIndex,
|
||||
) -> Identifier {
|
||||
let mut key_path = parent_key_id.to_path();
|
||||
key_path.path[1] = ChildNumber::from(1);
|
||||
key_path.depth += 1;
|
||||
key_path.path[key_path.depth as usize - 1] = ChildNumber::from(index);
|
||||
key_path.path[key_path.depth as usize - 1] = ChildNumber::from(index.value());
|
||||
Identifier::from_path(&key_path)
|
||||
}
|
||||
|
||||
@@ -42,7 +46,7 @@ pub fn address_derivation_path(parent_key_id: &Identifier, index: u32) -> Identi
|
||||
pub fn address_from_derivation_path<K>(
|
||||
keychain: &K,
|
||||
parent_key_id: &Identifier,
|
||||
index: u32,
|
||||
index: SlatepackAddressIndex,
|
||||
) -> Result<SecretKey, Error>
|
||||
where
|
||||
K: Keychain,
|
||||
@@ -62,6 +66,9 @@ mod tests {
|
||||
fn slatepack_address_path() {
|
||||
let parent = ExtKeychain::derive_key_id(2, 2, 0, 0, 0);
|
||||
let expected = ExtKeychain::derive_key_id(3, 2, 1, 3, 0);
|
||||
assert_eq!(address_derivation_path(&parent, 3), expected);
|
||||
assert_eq!(
|
||||
address_derivation_path(&parent, SlatepackAddressIndex(3)),
|
||||
expected
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::api_impl::types::update_tx_slate_state;
|
||||
use crate::backend::WalletBackend;
|
||||
use crate::internal::{selection, tx, updater};
|
||||
use crate::slate_versions::SlateVersion;
|
||||
use crate::slatepack::SlatepackAddressIndex;
|
||||
use crate::{
|
||||
address, BlockFees, CbData, Error, NodeClient, Slate, SlateState, TxLogEntryType, VersionInfo,
|
||||
};
|
||||
@@ -124,7 +125,11 @@ where
|
||||
ret_slate.amount,
|
||||
&excess,
|
||||
p.sender_address,
|
||||
address::address_from_derivation_path(&keychain, &parent_key_id, 0)?,
|
||||
address::address_from_derivation_path(
|
||||
&keychain,
|
||||
&parent_key_id,
|
||||
SlatepackAddressIndex(0),
|
||||
)?,
|
||||
)?;
|
||||
|
||||
p.receiver_signature = Some(sig);
|
||||
|
||||
+255
-96
@@ -34,9 +34,9 @@ use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletInfo};
|
||||
use crate::{
|
||||
address,
|
||||
mwixnet::{create_onion, ComSignature, Hop, MixnetReqCreationParams, SwapReq},
|
||||
wallet_lock, BuiltOutput, Error, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult,
|
||||
slatepack, wallet_lock, BuiltOutput, Error, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult,
|
||||
OutputCommitMapping, PaymentProof, RetrieveTxQueryArgs, ScannedBlockInfo, Slatepack,
|
||||
SlatepackAddress, Slatepacker, SlatepackerArgs, TxLogEntryType, ViewWallet, WalletBackend,
|
||||
SlatepackAddress, SlatepackArmor, SlatepackBin, TxLogEntryType, ViewWallet, WalletBackend,
|
||||
WalletInitStatus, WalletLCProvider,
|
||||
};
|
||||
use grin_keychain::{BlindingFactor, Identifier, Keychain, SwitchCommitmentType};
|
||||
@@ -46,6 +46,8 @@ use ed25519_dalek::Verifier;
|
||||
use ed25519_dalek::VerifyingKey as DalekPublicKey;
|
||||
use x25519_dalek::{PublicKey as xPublicKey, StaticSecret};
|
||||
|
||||
use crate::slatepack::SlatepackAddressIndex;
|
||||
use grin_wallet_util::byte_ser;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
@@ -121,13 +123,14 @@ where
|
||||
pub fn get_slatepack_address<'a, L, C, K>(
|
||||
wallet_inst: crate::WalletHandle<'a, L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
index: u32,
|
||||
index: SlatepackAddressIndex,
|
||||
) -> Result<SlatepackAddress, Error>
|
||||
where
|
||||
L: WalletLCProvider<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
index.check()?;
|
||||
wallet_lock!(wallet_inst, w);
|
||||
let parent_key_id = w.parent_key_id();
|
||||
let k = w.keychain(keychain_mask)?;
|
||||
@@ -140,13 +143,14 @@ where
|
||||
pub fn get_slatepack_secret_key<'a, L, C, K>(
|
||||
wallet_inst: crate::WalletHandle<'a, L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
index: u32,
|
||||
index: SlatepackAddressIndex,
|
||||
) -> Result<DalekSecretKey, Error>
|
||||
where
|
||||
L: WalletLCProvider<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
index.check()?;
|
||||
wallet_lock!(wallet_inst, w);
|
||||
let parent_key_id = w.parent_key_id();
|
||||
let k = w.keychain(keychain_mask)?;
|
||||
@@ -160,7 +164,7 @@ pub fn create_slatepack_message<'a, L, C, K>(
|
||||
wallet_inst: crate::WalletHandle<'a, L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
slate: &Slate,
|
||||
sender_index: Option<u32>,
|
||||
sender_index: Option<SlatepackAddressIndex>,
|
||||
recipients: Vec<SlatepackAddress>,
|
||||
) -> Result<String, Error>
|
||||
where
|
||||
@@ -168,17 +172,16 @@ where
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
let sender = match sender_index {
|
||||
Some(i) => Some(get_slatepack_address(wallet_inst, keychain_mask, i)?),
|
||||
let sender = match sender_index.as_ref() {
|
||||
Some(i) => Some(get_slatepack_address(
|
||||
wallet_inst.clone(),
|
||||
keychain_mask,
|
||||
*i,
|
||||
)?),
|
||||
None => None,
|
||||
};
|
||||
let packer = Slatepacker::new(SlatepackerArgs {
|
||||
sender,
|
||||
recipients,
|
||||
dec_key: None,
|
||||
});
|
||||
let slatepack = packer.create_slatepack(slate)?;
|
||||
packer.armor_slatepack(&slatepack)
|
||||
let slatepack = slate.create_slatepack(sender, recipients)?;
|
||||
slatepack.armor()
|
||||
}
|
||||
|
||||
/// Unpack a slate from the given slatepack message,
|
||||
@@ -187,47 +190,16 @@ pub fn slate_from_slatepack_message<'a, L, C, K>(
|
||||
wallet_inst: crate::WalletHandle<'a, L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
slatepack: String,
|
||||
secret_indices: Vec<u32>,
|
||||
secret_indices: Vec<SlatepackAddressIndex>,
|
||||
) -> Result<Slate, Error>
|
||||
where
|
||||
L: WalletLCProvider<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
if secret_indices.is_empty() {
|
||||
let packer = Slatepacker::new(SlatepackerArgs {
|
||||
sender: None,
|
||||
recipients: vec![],
|
||||
dec_key: None,
|
||||
});
|
||||
let slatepack = packer.deser_slatepack(slatepack.as_bytes(), true)?;
|
||||
packer.get_slate(&slatepack)
|
||||
} else {
|
||||
for index in secret_indices {
|
||||
let dec_key = Some(get_slatepack_secret_key(
|
||||
wallet_inst.clone(),
|
||||
keychain_mask,
|
||||
index,
|
||||
)?);
|
||||
let packer = Slatepacker::new(SlatepackerArgs {
|
||||
sender: None,
|
||||
recipients: vec![],
|
||||
dec_key: dec_key.as_ref(),
|
||||
});
|
||||
let res = packer.deser_slatepack(slatepack.as_bytes(), true);
|
||||
let slatepack = match res {
|
||||
Ok(sp) => sp,
|
||||
Err(_) => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
return packer.get_slate(&slatepack);
|
||||
}
|
||||
Err(Error::SlatepackDecryption(
|
||||
"Could not decrypt slatepack with any provided index on the address derivation path"
|
||||
.to_owned(),
|
||||
))
|
||||
}
|
||||
let sp = decode_slatepack_message(wallet_inst, keychain_mask, slatepack, secret_indices)
|
||||
.map_err(|e| Error::SlatepackDecryption(format!("Could not decrypt slatepack: {}", e)))?;
|
||||
sp.get_slate()
|
||||
}
|
||||
|
||||
/// Decode a slatepack message, to allow viewing
|
||||
@@ -237,43 +209,199 @@ pub fn decode_slatepack_message<'a, L, C, K>(
|
||||
wallet_inst: crate::WalletHandle<'a, L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
slatepack: String,
|
||||
secret_indices: Vec<u32>,
|
||||
secret_indices: Vec<SlatepackAddressIndex>,
|
||||
) -> Result<Slatepack, Error>
|
||||
where
|
||||
L: WalletLCProvider<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
let packer = Slatepacker::new(SlatepackerArgs {
|
||||
sender: None,
|
||||
recipients: vec![],
|
||||
dec_key: None,
|
||||
});
|
||||
if secret_indices.is_empty() {
|
||||
packer.deser_slatepack(slatepack.as_bytes(), false)
|
||||
} else {
|
||||
for index in secret_indices {
|
||||
let dec_key = Some(get_slatepack_secret_key(
|
||||
wallet_inst.clone(),
|
||||
keychain_mask,
|
||||
index,
|
||||
)?);
|
||||
let packer = Slatepacker::new(SlatepackerArgs {
|
||||
sender: None,
|
||||
recipients: vec![],
|
||||
dec_key: dec_key.as_ref(),
|
||||
});
|
||||
let res = packer.deser_slatepack(slatepack.as_bytes(), true);
|
||||
let slatepack = match res {
|
||||
Ok(sp) => sp,
|
||||
Err(_) => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
return Ok(slatepack);
|
||||
}
|
||||
packer.deser_slatepack(slatepack.as_bytes(), false)
|
||||
for i in &secret_indices {
|
||||
i.check()?;
|
||||
}
|
||||
let slatepack = parse_slatepack(slatepack.as_bytes())?;
|
||||
if slatepack.mode == 1 && !secret_indices.is_empty() {
|
||||
let mut decrypted = slatepack.clone();
|
||||
if decrypt_slatepack(&mut decrypted, wallet_inst, keychain_mask, secret_indices).is_ok() {
|
||||
return Ok(decrypted);
|
||||
}
|
||||
}
|
||||
Ok(slatepack)
|
||||
}
|
||||
|
||||
/// Deserialize provided data to slatepack
|
||||
pub fn deserialize_slatepack<'a, L, C, K>(
|
||||
data: &[u8],
|
||||
wallet_inst: crate::WalletHandle<'a, L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
secret_indices: Vec<SlatepackAddressIndex>,
|
||||
) -> Result<Slatepack, Error>
|
||||
where
|
||||
L: WalletLCProvider<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
let mut slatepack = parse_slatepack(data)?;
|
||||
decrypt_slatepack(&mut slatepack, wallet_inst, keychain_mask, secret_indices)?;
|
||||
Ok(slatepack)
|
||||
}
|
||||
|
||||
/// Parse a slatepack without accessing wallet keys
|
||||
pub fn parse_slatepack(data: &[u8]) -> Result<Slatepack, Error> {
|
||||
// check if data is armored, if so, remove and continue
|
||||
let data_len = data.len() as u64;
|
||||
if data_len < slatepack::min_size() || data_len > slatepack::max_size() {
|
||||
return Err(Error::SlatepackDeser("Data invalid length".to_string()));
|
||||
}
|
||||
|
||||
let test_header = &data[..slatepack::HEADER.len()];
|
||||
|
||||
let data = match str::from_utf8(test_header) {
|
||||
Ok(s) => {
|
||||
if s == slatepack::HEADER {
|
||||
SlatepackArmor::decode(data)?
|
||||
} else {
|
||||
data.to_vec()
|
||||
}
|
||||
}
|
||||
Err(_) => data.to_vec(),
|
||||
};
|
||||
|
||||
// try as bin first, then as json
|
||||
let slatepack = match byte_ser::from_bytes::<SlatepackBin>(&data) {
|
||||
Ok(s) => s.0,
|
||||
Err(e) => {
|
||||
debug!("Not a valid binary slatepack: {} - Will try JSON", e);
|
||||
let content = String::from_utf8(data).map_err(|e| {
|
||||
let msg = format!("{}", e);
|
||||
Error::SlatepackDeser(msg)
|
||||
})?;
|
||||
serde_json::from_str(&content).map_err(|e| {
|
||||
let msg = format!("Error reading JSON slatepack: {}", e);
|
||||
Error::SlatepackDeser(msg)
|
||||
})?
|
||||
}
|
||||
};
|
||||
|
||||
slatepack.ver_check_warn();
|
||||
Ok(slatepack)
|
||||
}
|
||||
|
||||
/// Decrypt a parsed slatepack using wallet indices
|
||||
pub fn decrypt_slatepack<'a, L, C, K>(
|
||||
slatepack: &mut Slatepack,
|
||||
wallet_inst: crate::WalletHandle<'a, L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
mut secret_indices: Vec<SlatepackAddressIndex>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
L: WalletLCProvider<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
secret_indices.sort_unstable();
|
||||
secret_indices.dedup();
|
||||
if slatepack.mode == 1 {
|
||||
// Try decrypt with selected indices.
|
||||
let mut try_decrypt_with = |indices: &Vec<SlatepackAddressIndex>| -> Result<(), Error> {
|
||||
for i in indices {
|
||||
let dec_key = get_slatepack_secret_key(wallet_inst.clone(), keychain_mask, *i)?;
|
||||
match slatepack.try_decrypt_payload(Some(&dec_key)) {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(e) => {
|
||||
if matches!(e, Error::Age(_)) {
|
||||
continue;
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Error::SlatepackDecryption(
|
||||
"Can not decrypt slatepack with provided indexes".to_string(),
|
||||
))
|
||||
};
|
||||
|
||||
// Try decrypt with initial indices.
|
||||
return match try_decrypt_with(&secret_indices) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => {
|
||||
// Collect indexes for transactions.
|
||||
let collect_indexes = |outstanding: bool,
|
||||
secret_indices: &Vec<SlatepackAddressIndex>|
|
||||
-> Result<Vec<SlatepackAddressIndex>, Error> {
|
||||
wallet_lock!(wallet_inst, w);
|
||||
let mut indices: Vec<SlatepackAddressIndex> = w
|
||||
.tx_log_iter()?
|
||||
.flatten()
|
||||
.filter(|tx| {
|
||||
let tx_type = if outstanding {
|
||||
!tx.confirmed
|
||||
&& (tx.tx_type == TxLogEntryType::TxReceived
|
||||
|| tx.tx_type == TxLogEntryType::TxSent
|
||||
|| tx.tx_type == TxLogEntryType::TxReverted)
|
||||
} else {
|
||||
tx.confirmed
|
||||
&& tx.tx_type != TxLogEntryType::TxReceivedCancelled
|
||||
&& tx.tx_type != TxLogEntryType::TxSentCancelled
|
||||
};
|
||||
tx_type && tx.parent_key_id == w.parent_key_id()
|
||||
})
|
||||
.filter_map(|t| t.address_index)
|
||||
.filter(|i| !secret_indices.contains(i))
|
||||
.collect();
|
||||
indices.sort_unstable();
|
||||
indices.dedup();
|
||||
Ok(indices)
|
||||
};
|
||||
let mut indexes = collect_indexes(true, &secret_indices)?;
|
||||
match try_decrypt_with(&mut indexes) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => {
|
||||
for i in indexes {
|
||||
if !secret_indices.contains(&i) {
|
||||
secret_indices.push(i);
|
||||
}
|
||||
}
|
||||
let indexes = collect_indexes(false, &secret_indices)?;
|
||||
match try_decrypt_with(&indexes) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_) => {
|
||||
for i in indexes {
|
||||
if !secret_indices.contains(&i) {
|
||||
secret_indices.push(i);
|
||||
}
|
||||
}
|
||||
let mut indexes: Vec<SlatepackAddressIndex> = {
|
||||
wallet_lock!(wallet_inst, w);
|
||||
w.private_context_iter()?
|
||||
.flatten()
|
||||
.filter(|c| {
|
||||
let index =
|
||||
if let Some(i) = c.payment_proof_derivation_index {
|
||||
!secret_indices.contains(&i)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
c.parent_key_id == w.parent_key_id() && index
|
||||
})
|
||||
.map(|c| c.payment_proof_derivation_index.unwrap())
|
||||
.collect()
|
||||
};
|
||||
indexes.sort_unstable();
|
||||
indexes.dedup();
|
||||
match try_decrypt_with(&indexes) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// retrieve outputs
|
||||
@@ -532,6 +660,7 @@ where
|
||||
recipient_sig: r_sig,
|
||||
sender_address: SlatepackAddress::new(&proof.sender_address),
|
||||
sender_sig: s_sig,
|
||||
sender_address_path: Some(proof.sender_address_path),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -546,6 +675,10 @@ where
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
if let Some(index) = args.address_index {
|
||||
index.check()?;
|
||||
}
|
||||
|
||||
if args.amount == 0 {
|
||||
return Err(Error::InvalidAmount);
|
||||
}
|
||||
@@ -629,15 +762,12 @@ where
|
||||
)?
|
||||
};
|
||||
|
||||
// Payment Proof, add addresses to slate and save address
|
||||
// TODO: Note we only use single derivation path for now,
|
||||
// probably want to allow sender to specify which one
|
||||
let deriv_path = 0u32;
|
||||
|
||||
let address_index = args.address_index.unwrap_or(SlatepackAddressIndex(0));
|
||||
if let Some(a) = payment_proof_address {
|
||||
let k = w.keychain(keychain_mask)?;
|
||||
|
||||
let sec_addr_key = address::address_from_derivation_path(&k, &parent_key_id, deriv_path)?;
|
||||
let sec_addr_key =
|
||||
address::address_from_derivation_path(&k, &parent_key_id, address_index)?;
|
||||
let sender_address = OnionV3Address::from_private(&sec_addr_key.0)?;
|
||||
|
||||
slate.payment_proof = Some(PaymentInfo {
|
||||
@@ -645,14 +775,13 @@ where
|
||||
receiver_address: a.pub_key,
|
||||
receiver_signature: None,
|
||||
});
|
||||
|
||||
context.payment_proof_derivation_index = Some(deriv_path);
|
||||
}
|
||||
|
||||
// Save the aggsig context in our DB for when we
|
||||
// recieve the transaction back
|
||||
// receive the transaction back
|
||||
{
|
||||
let mut batch = w.batch(keychain_mask)?;
|
||||
context.payment_proof_derivation_index = Some(address_index);
|
||||
batch.save_private_context(slate.id.as_bytes(), &context)?;
|
||||
batch.commit()?;
|
||||
}
|
||||
@@ -666,13 +795,17 @@ where
|
||||
pub fn issue_invoice_tx<C, K>(
|
||||
w: &mut WalletBackend<C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
args: IssueInvoiceTxArgs,
|
||||
mut args: IssueInvoiceTxArgs,
|
||||
use_test_rng: bool,
|
||||
) -> Result<Slate, Error>
|
||||
where
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
if let Some(index) = args.address_index {
|
||||
index.check()?;
|
||||
}
|
||||
|
||||
if args.amount == 0 {
|
||||
return Err(Error::InvalidAmount);
|
||||
}
|
||||
@@ -688,9 +821,13 @@ where
|
||||
None => w.parent_key_id(),
|
||||
};
|
||||
|
||||
if args.address_index.is_none() {
|
||||
args.address_index = Some(w.next_tx_slatepack_address_index(&parent_key_id)?);
|
||||
}
|
||||
|
||||
let mut slate = tx::new_tx_slate(w, args.amount, true, 2, use_test_rng, None)?;
|
||||
let height = w.w2n_client().get_chain_tip()?.0;
|
||||
let context = tx::add_output_to_slate(
|
||||
let mut context = tx::add_output_to_slate(
|
||||
w,
|
||||
keychain_mask,
|
||||
&mut slate,
|
||||
@@ -704,10 +841,20 @@ where
|
||||
slate.version_info.version = v;
|
||||
};
|
||||
|
||||
// Update transaction log address index
|
||||
let mut tx = w.get_tx_log_entry(&slate.id, &parent_key_id)?;
|
||||
if let Some(tx) = tx.as_mut() {
|
||||
tx.address_index = args.address_index;
|
||||
let mut batch = w.batch(keychain_mask)?;
|
||||
batch.save_tx_log_entry(tx.clone(), &parent_key_id)?;
|
||||
batch.commit()?;
|
||||
}
|
||||
|
||||
// Save the aggsig context in our DB for when we
|
||||
// recieve the transaction back
|
||||
// receive the transaction back
|
||||
{
|
||||
let mut batch = w.batch(keychain_mask)?;
|
||||
context.payment_proof_derivation_index = args.address_index;
|
||||
batch.save_private_context(slate.id.as_bytes(), &context)?;
|
||||
batch.commit()?;
|
||||
}
|
||||
@@ -813,6 +960,7 @@ where
|
||||
|
||||
// if self-sending, merge contexts
|
||||
if let Ok(c) = context_res {
|
||||
context.payment_proof_derivation_index = c.payment_proof_derivation_index;
|
||||
context.initial_sec_key = c.initial_sec_key;
|
||||
context.initial_sec_nonce = c.initial_sec_nonce;
|
||||
context.fee = c.fee;
|
||||
@@ -828,7 +976,7 @@ where
|
||||
selection::repopulate_tx(w, keychain_mask, &mut ret_slate, &context, false)?;
|
||||
|
||||
// Save the aggsig context in our DB for when we
|
||||
// recieve the transaction back
|
||||
// receive the transaction back
|
||||
{
|
||||
let mut batch = w.batch(keychain_mask)?;
|
||||
batch.save_private_context(slate.id.as_bytes(), &context)?;
|
||||
@@ -1324,12 +1472,23 @@ where
|
||||
};
|
||||
|
||||
// for now, simple test whether one of the addresses belongs to this wallet
|
||||
let sec_key = address::address_from_derivation_path(&keychain, &parent_key_id, 0)?;
|
||||
let sec_key =
|
||||
address::address_from_derivation_path(&keychain, &parent_key_id, SlatepackAddressIndex(0))?;
|
||||
let d_skey = DalekSecretKey::from_bytes(&sec_key.0);
|
||||
let my_address_pubkey: DalekPublicKey = (&d_skey).into();
|
||||
let possible_receiver_key: DalekPublicKey = (&d_skey).into();
|
||||
|
||||
let sender_mine = my_address_pubkey == sender_pubkey;
|
||||
let recipient_mine = my_address_pubkey == recipient_pubkey;
|
||||
let sec_key = address::address_from_derivation_path(
|
||||
&keychain,
|
||||
&parent_key_id,
|
||||
proof
|
||||
.sender_address_path
|
||||
.unwrap_or(SlatepackAddressIndex(0)),
|
||||
)?;
|
||||
let d_skey = DalekSecretKey::from_bytes(&sec_key.0);
|
||||
let possible_sender_key: DalekPublicKey = (&d_skey).into();
|
||||
|
||||
let sender_mine = possible_sender_key == sender_pubkey;
|
||||
let recipient_mine = possible_receiver_key == recipient_pubkey;
|
||||
|
||||
Ok((sender_mine, recipient_mine))
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ use grin_keychain::Keychain;
|
||||
use grin_util::secp::SecretKey;
|
||||
|
||||
pub use crate::mwixnet::{Hop, MixnetReqCreationParams, SwapReq};
|
||||
use crate::slatepack::SlatepackAddressIndex;
|
||||
|
||||
/// Type for storing amounts (in nanogrins).
|
||||
/// Serializes as a string but can deserialize from a string or u64.
|
||||
@@ -38,7 +39,7 @@ pub struct Amount(#[serde(with = "secp_ser::string_or_u64")] pub u64);
|
||||
/// V2 Init / Send TX API Args
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct InitTxArgs {
|
||||
/// The human readable account name from which to draw outputs
|
||||
/// The human-readable account name from which to draw outputs
|
||||
/// for the transaction, overriding whatever the active account is as set via the
|
||||
/// [`set_active_account`](../grin_wallet_api/owner/struct.Owner.html#method.set_active_account) method.
|
||||
pub src_acct_name: Option<String>,
|
||||
@@ -91,6 +92,9 @@ pub struct InitTxArgs {
|
||||
/// inputs until just before finalization
|
||||
#[serde(default)]
|
||||
pub late_lock: Option<bool>,
|
||||
/// Slatepack address index
|
||||
#[serde(default)]
|
||||
pub address_index: Option<SlatepackAddressIndex>,
|
||||
/// 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<InitTxSendArgs>,
|
||||
@@ -126,6 +130,7 @@ impl Default for InitTxArgs {
|
||||
estimate_only: Some(false),
|
||||
payment_proof_recipient_address: None,
|
||||
late_lock: Some(false),
|
||||
address_index: None,
|
||||
send_args: None,
|
||||
}
|
||||
}
|
||||
@@ -134,7 +139,7 @@ impl Default for InitTxArgs {
|
||||
/// V2 Issue Invoice Tx Args
|
||||
#[derive(Clone, Serialize, Deserialize, Default)]
|
||||
pub struct IssueInvoiceTxArgs {
|
||||
/// The human readable account name to which the received funds should be added
|
||||
/// The human-readable account name to which the received funds should be added
|
||||
/// overriding whatever the active account is as set via the
|
||||
/// [`set_active_account`](../grin_wallet_api/owner/struct.Owner.html#method.set_active_account) method.
|
||||
pub dest_acct_name: Option<String>,
|
||||
@@ -145,6 +150,8 @@ pub struct IssueInvoiceTxArgs {
|
||||
/// down to the minimum slate version compatible with the current. If `None` the slate
|
||||
/// is generated with the latest version.
|
||||
pub target_slate_version: Option<u16>,
|
||||
/// Issuer slatepack address derivation path index.
|
||||
pub address_index: Option<SlatepackAddressIndex>,
|
||||
}
|
||||
|
||||
/// Sort tx retrieval order
|
||||
@@ -179,7 +186,7 @@ pub struct RetrieveTxQueryArgs {
|
||||
/// Retrieve transactions with an id higher than or equal to the given
|
||||
/// If None, consider items from the first transaction and later
|
||||
pub min_id: Option<u32>,
|
||||
/// Retrieve tranactions with an id less than or equal to the given
|
||||
/// Retrieve transactions with an id less than or equal to the given
|
||||
/// If None, consider items from the last transaction and earlier
|
||||
pub max_id: Option<u32>,
|
||||
/// The maximum number of transactions to return
|
||||
@@ -216,7 +223,7 @@ pub struct RetrieveTxQueryArgs {
|
||||
pub min_confirmed_timestamp: Option<DateTime<Utc>>,
|
||||
/// higher bound on the confirmation timestamp, inclusive
|
||||
pub max_confirmed_timestamp: Option<DateTime<Utc>>,
|
||||
/// Field within the tranasction list on which to sort
|
||||
/// Field within the transaction list on which to sort
|
||||
/// defaults to ID if not present
|
||||
pub sort_field: Option<RetrieveTxQuerySortField>,
|
||||
/// Sort order, defaults to ASC if not present (earliest is first)
|
||||
@@ -268,7 +275,7 @@ impl BlockFees {
|
||||
}
|
||||
}
|
||||
|
||||
/// Map Outputdata to commits
|
||||
/// Map [OutputData] to commits
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct OutputCommitMapping {
|
||||
/// Output Data
|
||||
@@ -324,6 +331,9 @@ pub struct PaymentProof {
|
||||
/// Sender Signature
|
||||
#[serde(with = "dalek_ser::dalek_sig_serde")]
|
||||
pub sender_sig: DalekSignature,
|
||||
/// Sender Slatepack address derivation path index
|
||||
#[serde(default)]
|
||||
pub sender_address_path: Option<SlatepackAddressIndex>,
|
||||
}
|
||||
|
||||
/// Build output result
|
||||
|
||||
@@ -21,6 +21,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::blake2::blake2b::{Blake2b, Blake2bResult};
|
||||
|
||||
use crate::slatepack::SlatepackAddressIndex;
|
||||
use crate::{
|
||||
AcctPathMapping, Context, Error, NodeClient, OutputData, ScannedBlockInfo, TxLogEntry,
|
||||
WalletInitStatus,
|
||||
@@ -44,19 +45,21 @@ const CONFIRMED_HEIGHT_PREFIX: u8 = b'c';
|
||||
const PRIVATE_TX_CONTEXT_PREFIX: u8 = b'p';
|
||||
const TX_LOG_ENTRY_PREFIX: u8 = b't';
|
||||
const TX_LOG_ID_PREFIX: u8 = b'i';
|
||||
const TX_ADDRESS_INDEX_PREFIX: u8 = b's';
|
||||
const ACCOUNT_PATH_MAPPING_PREFIX: u8 = b'a';
|
||||
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 DB_PREFIXES: [u8; 9] = [
|
||||
const DB_PREFIXES: [u8; 10] = [
|
||||
OUTPUT_PREFIX,
|
||||
DERIV_PREFIX,
|
||||
CONFIRMED_HEIGHT_PREFIX,
|
||||
PRIVATE_TX_CONTEXT_PREFIX,
|
||||
TX_LOG_ENTRY_PREFIX,
|
||||
TX_LOG_ID_PREFIX,
|
||||
TX_ADDRESS_INDEX_PREFIX,
|
||||
ACCOUNT_PATH_MAPPING_PREFIX,
|
||||
LAST_SCANNED_BLOCK,
|
||||
WALLET_INIT_STATUS,
|
||||
@@ -369,10 +372,16 @@ where
|
||||
}
|
||||
|
||||
/// Get an (Optional) tx log entry by uuid.
|
||||
pub fn get_tx_log_entry(&self, u: &Uuid) -> Result<Option<TxLogEntry>, Error> {
|
||||
self.db
|
||||
.get_ser(Some(TX_LOG_ENTRY_PREFIX), u.as_bytes(), None)
|
||||
.map_err(|e| e.into())
|
||||
pub fn get_tx_log_entry(
|
||||
&self,
|
||||
id: &Uuid,
|
||||
parent_id: &Identifier,
|
||||
) -> Result<Option<TxLogEntry>, Error> {
|
||||
let tx = self
|
||||
.tx_log_iter()?
|
||||
.flatten()
|
||||
.find(|tx| tx.parent_key_id == *parent_id && tx.tx_slate_id == Some(*id));
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Iterate over all tx log data stored by the backend.
|
||||
@@ -392,6 +401,54 @@ where
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
/// Next transaction slatepack address derivation path index.
|
||||
pub fn next_tx_slatepack_address_index(
|
||||
&mut self,
|
||||
parent_key_id: &Identifier,
|
||||
) -> Result<SlatepackAddressIndex, Error> {
|
||||
let mut index = {
|
||||
let batch = self.db.batch()?;
|
||||
batch
|
||||
.get_ser(
|
||||
Some(TX_ADDRESS_INDEX_PREFIX),
|
||||
&parent_key_id.to_bytes(),
|
||||
None,
|
||||
)?
|
||||
.unwrap_or(0)
|
||||
};
|
||||
if index >= SlatepackAddressIndex::MAX {
|
||||
return Err(Error::SlatepackAddress(
|
||||
"Slatepack address indices exhausted".into(),
|
||||
));
|
||||
}
|
||||
index += 1;
|
||||
let mut batch = self.db.batch()?;
|
||||
batch.put_ser(
|
||||
Some(TX_ADDRESS_INDEX_PREFIX),
|
||||
&parent_key_id.to_bytes(),
|
||||
&index,
|
||||
)?;
|
||||
batch.commit()?;
|
||||
Ok(SlatepackAddressIndex(index))
|
||||
}
|
||||
|
||||
/// Iterator over private tx contexts.
|
||||
pub fn private_context_iter(
|
||||
&self,
|
||||
) -> Result<impl Iterator<Item = Result<Context, grin_store::Error>>, Error> {
|
||||
let protocol_version = self.db.protocol_version();
|
||||
self.db
|
||||
.iter(Some(PRIVATE_TX_CONTEXT_PREFIX), move |_, mut v| {
|
||||
ser::deserialize(
|
||||
&mut v,
|
||||
protocol_version,
|
||||
ser::DeserializationMode::default(),
|
||||
)
|
||||
.map_err(From::from)
|
||||
})
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
/// Retrieve the private context associated with a given slate id.
|
||||
pub fn get_private_context(
|
||||
&mut self,
|
||||
|
||||
@@ -208,6 +208,7 @@ where
|
||||
sender_signature: None,
|
||||
});
|
||||
};
|
||||
t.address_index = context.payment_proof_derivation_index;
|
||||
|
||||
// write the output representing our change
|
||||
for (id, _, _) in &context.get_outputs() {
|
||||
|
||||
@@ -20,6 +20,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::internal::{selection, updater};
|
||||
use crate::slate::Slate;
|
||||
use crate::slatepack::SlatepackAddressIndex;
|
||||
use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType};
|
||||
use crate::util::OnionV3Address;
|
||||
use crate::{address, Error};
|
||||
@@ -368,7 +369,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update the stored transaction (this update needs to happen when the TX is finalised)
|
||||
/// Update the stored transaction (this update needs to happen when the TX is finalized)
|
||||
pub fn update_stored_tx<C, K>(
|
||||
wallet: &mut WalletBackend<C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
@@ -405,19 +406,21 @@ where
|
||||
}
|
||||
|
||||
if let Some(ref p) = slate.clone().payment_proof {
|
||||
let derivation_index = context.payment_proof_derivation_index.unwrap_or(0);
|
||||
let sender_address_path = context
|
||||
.payment_proof_derivation_index
|
||||
.unwrap_or(SlatepackAddressIndex(0));
|
||||
let keychain = wallet.keychain(keychain_mask)?;
|
||||
let parent_key_id = wallet.parent_key_id();
|
||||
let excess = slate.calc_excess(keychain.secp())?;
|
||||
let sender_key =
|
||||
address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?;
|
||||
address::address_from_derivation_path(&keychain, &parent_key_id, sender_address_path)?;
|
||||
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
|
||||
let sig =
|
||||
create_payment_proof_signature(slate.amount, &excess, p.sender_address, sender_key)?;
|
||||
tx.payment_proof = Some(StoredProofInfo {
|
||||
receiver_address: p.receiver_address,
|
||||
receiver_signature: p.receiver_signature,
|
||||
sender_address_path: derivation_index,
|
||||
sender_address_path,
|
||||
sender_address: sender_address.to_ed25519()?,
|
||||
sender_signature: Some(sig),
|
||||
})
|
||||
|
||||
@@ -57,9 +57,7 @@ pub use crate::slate_versions::{
|
||||
SlateVersion, VersionedBinSlate, VersionedCoinbase, VersionedSlate, CURRENT_SLATE_VERSION,
|
||||
GRIN_BLOCK_HEADER_VERSION,
|
||||
};
|
||||
pub use crate::slatepack::{
|
||||
Slatepack, SlatepackAddress, SlatepackArmor, SlatepackBin, Slatepacker, SlatepackerArgs,
|
||||
};
|
||||
pub use crate::slatepack::{Slatepack, SlatepackAddress, SlatepackArmor, SlatepackBin};
|
||||
pub use api_impl::owner_updater::StatusMessage;
|
||||
pub use api_impl::types::{
|
||||
Amount, BlockFees, BuiltOutput, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs,
|
||||
|
||||
+23
-8
@@ -16,6 +16,13 @@
|
||||
//! around during an interactive wallet exchange
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::slate_versions::v4::{
|
||||
CommitsV4, KernelFeaturesArgsV4, OutputFeaturesV4, ParticipantDataV4, PaymentInfoV4,
|
||||
SlateStateV4, SlateV4, VersionCompatInfoV4,
|
||||
};
|
||||
use crate::slate_versions::VersionedSlate;
|
||||
use crate::slate_versions::{CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION};
|
||||
use crate::{Context, SlateVersion, Slatepack, SlatepackAddress, VersionedBinSlate};
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
use ed25519_dalek::VerifyingKey as DalekPublicKey;
|
||||
use grin_core::core::amount_to_hr_string;
|
||||
@@ -30,18 +37,11 @@ use grin_util::secp::key::{PublicKey, SecretKey};
|
||||
use grin_util::secp::pedersen::Commitment;
|
||||
use grin_util::secp::Signature;
|
||||
use grin_util::{secp, static_secp_instance};
|
||||
use grin_wallet_util::byte_ser;
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
use std::fmt;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::slate_versions::v4::{
|
||||
CommitsV4, KernelFeaturesArgsV4, OutputFeaturesV4, ParticipantDataV4, PaymentInfoV4,
|
||||
SlateStateV4, SlateV4, VersionCompatInfoV4,
|
||||
};
|
||||
use crate::slate_versions::VersionedSlate;
|
||||
use crate::slate_versions::{CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION};
|
||||
use crate::Context;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaymentInfo {
|
||||
/// Sender address
|
||||
@@ -670,6 +670,21 @@ impl Slate {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create slatepack from this slate.
|
||||
pub fn create_slatepack(
|
||||
&self,
|
||||
sender: Option<SlatepackAddress>,
|
||||
recipients: Vec<SlatepackAddress>,
|
||||
) -> Result<Slatepack, Error> {
|
||||
let out_slate = VersionedSlate::into_version(self.clone(), SlateVersion::V4)?;
|
||||
let bin_slate = VersionedBinSlate::try_from(out_slate).map_err(|_| Error::SlatepackSer)?;
|
||||
let mut slatepack = Slatepack::default();
|
||||
slatepack.payload = byte_ser::to_bytes(&bin_slate).map_err(|_| Error::SlatepackSer)?;
|
||||
slatepack.sender = sender;
|
||||
slatepack.try_encrypt_payload(recipients)?;
|
||||
Ok(slatepack)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Slate {
|
||||
|
||||
@@ -29,9 +29,11 @@ use std::str;
|
||||
|
||||
use super::types::{Slatepack, SlatepackBin};
|
||||
|
||||
// Framing and formatting for slate armor
|
||||
/// Slatepack file header.
|
||||
pub static HEADER: &str = "BEGINSLATEPACK.";
|
||||
static FOOTER: &str = ". ENDSLATEPACK.";
|
||||
/// Slatepack file footer.
|
||||
pub static FOOTER: &str = ". ENDSLATEPACK.";
|
||||
|
||||
const WORD_LENGTH: usize = 15;
|
||||
const WORDS_PER_LINE: usize = 200;
|
||||
const WEIGHT_RATIO: u64 = 32;
|
||||
|
||||
@@ -16,10 +16,31 @@
|
||||
|
||||
mod address;
|
||||
mod armor;
|
||||
mod packer;
|
||||
mod types;
|
||||
|
||||
pub use self::address::SlatepackAddress;
|
||||
pub use self::armor::{max_size, min_size, SlatepackArmor};
|
||||
pub use self::packer::{Slatepacker, SlatepackerArgs};
|
||||
pub use self::types::{Slatepack, SlatepackBin};
|
||||
pub use self::armor::{max_size, min_size, SlatepackArmor, FOOTER, HEADER};
|
||||
pub use self::types::{Slatepack, SlatepackAddressIndex, SlatepackBin};
|
||||
|
||||
use std::fs::{metadata, File};
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Get slatepack file contents.
|
||||
pub fn get_slatepack_file_contents(path: &PathBuf) -> Result<Vec<u8>, crate::Error> {
|
||||
let metadata = metadata(path)?;
|
||||
let len = metadata.len();
|
||||
let min_len = min_size();
|
||||
let max_len = max_size();
|
||||
if len < min_len || len > max_len {
|
||||
let msg = format!(
|
||||
"Data is invalid length: {} | min: {}, max: {} |",
|
||||
len, min_len, max_len
|
||||
);
|
||||
return Err(crate::Error::SlatepackDeser(msg));
|
||||
}
|
||||
let mut pub_tx_f = File::open(path)?;
|
||||
let mut data = Vec::new();
|
||||
pub_tx_f.read_to_end(&mut data)?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
// Copyright 2021 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::str;
|
||||
|
||||
use super::armor::HEADER;
|
||||
use crate::Error;
|
||||
use crate::{
|
||||
slatepack, Slate, SlateVersion, Slatepack, SlatepackAddress, SlatepackArmor, SlatepackBin,
|
||||
VersionedBinSlate, VersionedSlate,
|
||||
};
|
||||
|
||||
use grin_wallet_util::byte_ser;
|
||||
|
||||
use ed25519_dalek::SigningKey as edSecretKey;
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Arguments, mostly for encrypting decrypting a slatepack
|
||||
pub struct SlatepackerArgs<'a> {
|
||||
/// Optional sender to include in slatepack
|
||||
pub sender: Option<SlatepackAddress>,
|
||||
/// Optional list of recipients, for encryption
|
||||
pub recipients: Vec<SlatepackAddress>,
|
||||
/// Optional decryption key
|
||||
pub dec_key: Option<&'a edSecretKey>,
|
||||
}
|
||||
|
||||
/// Helper struct to pack and unpack slatepacks
|
||||
#[derive(Clone)]
|
||||
pub struct Slatepacker<'a>(SlatepackerArgs<'a>);
|
||||
|
||||
impl<'a> Slatepacker<'a> {
|
||||
/// Create with pathbuf and recipients
|
||||
pub fn new(args: SlatepackerArgs<'a>) -> Self {
|
||||
Self(args)
|
||||
}
|
||||
|
||||
/// return slatepack
|
||||
pub fn deser_slatepack(&self, data: &[u8], decrypt: bool) -> Result<Slatepack, Error> {
|
||||
// check if data is armored, if so, remove and continue
|
||||
let data_len = data.len() as u64;
|
||||
if data_len < slatepack::min_size() || data_len > slatepack::max_size() {
|
||||
let msg = "Data invalid length".to_string();
|
||||
return Err(Error::SlatepackDeser(msg));
|
||||
}
|
||||
|
||||
let test_header = &data[..HEADER.len()];
|
||||
|
||||
let data = match str::from_utf8(test_header) {
|
||||
Ok(s) => {
|
||||
if s == HEADER {
|
||||
SlatepackArmor::decode(data)?
|
||||
} else {
|
||||
data.to_vec()
|
||||
}
|
||||
}
|
||||
Err(_) => data.to_vec(),
|
||||
};
|
||||
|
||||
// try as bin first, then as json
|
||||
let mut slatepack = match byte_ser::from_bytes::<SlatepackBin>(&data) {
|
||||
Ok(s) => s.0,
|
||||
Err(e) => {
|
||||
debug!("Not a valid binary slatepack: {} - Will try JSON", e);
|
||||
let content = String::from_utf8(data).map_err(|e| {
|
||||
let msg = format!("{}", e);
|
||||
Error::SlatepackDeser(msg)
|
||||
})?;
|
||||
serde_json::from_str(&content).map_err(|e| {
|
||||
let msg = format!("Error reading JSON slatepack: {}", e);
|
||||
Error::SlatepackDeser(msg)
|
||||
})?
|
||||
}
|
||||
};
|
||||
|
||||
slatepack.ver_check_warn();
|
||||
if decrypt {
|
||||
slatepack.try_decrypt_payload(self.0.dec_key)?;
|
||||
}
|
||||
Ok(slatepack)
|
||||
}
|
||||
|
||||
/// Create slatepack from slate and args
|
||||
pub fn create_slatepack(&self, slate: &Slate) -> Result<Slatepack, Error> {
|
||||
let out_slate = VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?;
|
||||
let bin_slate = VersionedBinSlate::try_from(out_slate).map_err(|_| Error::SlatepackSer)?;
|
||||
let mut slatepack = Slatepack::default();
|
||||
slatepack.payload = byte_ser::to_bytes(&bin_slate).map_err(|_| Error::SlatepackSer)?;
|
||||
slatepack.sender = self.0.sender.clone();
|
||||
slatepack.try_encrypt_payload(self.0.recipients.clone())?;
|
||||
Ok(slatepack)
|
||||
}
|
||||
|
||||
/// Armor a slatepack
|
||||
pub fn armor_slatepack(&self, slatepack: &Slatepack) -> Result<String, Error> {
|
||||
SlatepackArmor::encode(slatepack)
|
||||
}
|
||||
|
||||
/// Return/upgrade slate from slatepack
|
||||
pub fn get_slate(&self, slatepack: &Slatepack) -> Result<Slate, Error> {
|
||||
let slate_bin =
|
||||
byte_ser::from_bytes::<VersionedBinSlate>(&slatepack.payload).map_err(|e| {
|
||||
error!("Error reading slate from armored slatepack: {}", e);
|
||||
let msg = format!("{}", e);
|
||||
Error::SlatepackDeser(msg)
|
||||
})?;
|
||||
Slate::upgrade(slate_bin.into())
|
||||
}
|
||||
}
|
||||
@@ -12,22 +12,19 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use bech32::{self, ToBase32};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
/// Slatepack Types + Serialization implementation
|
||||
use ed25519_dalek::SigningKey as edSecretKey;
|
||||
use sha2::{Digest, Sha512};
|
||||
use x25519_dalek::StaticSecret;
|
||||
|
||||
use crate::dalek_ser;
|
||||
use super::SlatepackAddress;
|
||||
use crate::Error;
|
||||
use crate::{dalek_ser, Slate, SlatepackArmor, VersionedBinSlate};
|
||||
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
|
||||
use grin_wallet_util::byte_ser;
|
||||
|
||||
use super::SlatepackAddress;
|
||||
|
||||
use bech32::{self, ToBase32};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use ed25519_dalek::SigningKey as edSecretKey;
|
||||
use sha2::{Digest, Sha512};
|
||||
use std::fmt;
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use x25519_dalek::StaticSecret;
|
||||
|
||||
pub const SLATEPACK_MAJOR_VERSION: u8 = 1;
|
||||
pub const SLATEPACK_MINOR_VERSION: u8 = 0;
|
||||
@@ -260,6 +257,52 @@ impl Slatepack {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return/upgrade slate from slatepack.
|
||||
pub fn get_slate(&self) -> Result<Slate, Error> {
|
||||
if self.mode == 1 {
|
||||
return Err(Error::SlatepackDecryption(
|
||||
"Slatepack payload is encrypted".into(),
|
||||
));
|
||||
}
|
||||
let slate_bin = byte_ser::from_bytes::<VersionedBinSlate>(&self.payload).map_err(|e| {
|
||||
error!("Error reading slate from armored slatepack: {}", e);
|
||||
let msg = format!("{}", e);
|
||||
Error::SlatepackDeser(msg)
|
||||
})?;
|
||||
Slate::upgrade(slate_bin.into())
|
||||
}
|
||||
|
||||
/// Armor this slatepack.
|
||||
pub fn armor(&self) -> Result<String, Error> {
|
||||
SlatepackArmor::encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Slatepack address derivation path index
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct SlatepackAddressIndex(pub u32);
|
||||
|
||||
impl SlatepackAddressIndex {
|
||||
/// The max BIP32 index.
|
||||
pub const MAX: u32 = 2_147_483_647;
|
||||
|
||||
/// Check the index range
|
||||
pub fn check(self) -> Result<Self, Error> {
|
||||
if self.0 > Self::MAX {
|
||||
return Err(Error::SlatepackAddress(format!(
|
||||
"Slatepack index {} is out of bound, maximum value is {}",
|
||||
self.0,
|
||||
Self::MAX
|
||||
)));
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Index value.
|
||||
pub fn value(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for outputting slate as binary
|
||||
@@ -335,7 +378,7 @@ impl Writeable for SlatepackBin {
|
||||
};
|
||||
|
||||
// encrypted metadata is only included in the payload
|
||||
// on encryption, and is not serialised here
|
||||
// on encryption, and is not serialized here
|
||||
|
||||
// Now write payload (length prefixed)
|
||||
writer.write_bytes(sp.payload.clone())
|
||||
@@ -684,7 +727,7 @@ fn slatepack_bin_opt_fields_ser() -> Result<(), grin_wallet_util::byte_ser::Erro
|
||||
|
||||
// ensure that a slatepack with unknown data in the optional fields can be read
|
||||
#[test]
|
||||
fn slatepack_bin_future() -> Result<(), grin_wallet_util::byte_ser::Error> {
|
||||
fn slatepack_bin_future() -> Result<(), byte_ser::Error> {
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use grin_core::global;
|
||||
use grin_wallet_util::byte_ser;
|
||||
@@ -908,3 +951,16 @@ fn slatepack_decrypt_rejects_malformed_plaintexts() -> Result<(), Error> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_bounds() {
|
||||
for index in [0, SlatepackAddressIndex::MAX] {
|
||||
assert_eq!(SlatepackAddressIndex(index).check().unwrap().value(), index);
|
||||
}
|
||||
for index in [SlatepackAddressIndex::MAX + 1, u32::MAX] {
|
||||
assert!(matches!(
|
||||
SlatepackAddressIndex(index).check(),
|
||||
Err(Error::SlatepackAddress(_))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
use crate::config::{TorConfig, WalletConfig};
|
||||
use crate::error::Error;
|
||||
use crate::slate_versions::ser as dalek_ser;
|
||||
use crate::slatepack::SlatepackAddressIndex;
|
||||
use crate::{InitTxArgs, SlateState, WalletBackend};
|
||||
use chrono::prelude::*;
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
@@ -386,7 +387,7 @@ pub struct Context {
|
||||
/// store the calculated fee
|
||||
pub fee: Option<FeeFields>,
|
||||
/// Payment proof sender address derivation path, if needed
|
||||
pub payment_proof_derivation_index: Option<u32>,
|
||||
pub payment_proof_derivation_index: Option<SlatepackAddressIndex>,
|
||||
/// If late-locking, store my tranasction creation prefs
|
||||
/// for later
|
||||
pub late_lock_args: Option<InitTxArgs>,
|
||||
@@ -675,6 +676,9 @@ pub struct TxLogEntry {
|
||||
/// Track the time it took for a transaction to get reverted
|
||||
#[serde(with = "option_duration_as_secs", default)]
|
||||
pub reverted_after: Option<Duration>,
|
||||
/// Slatepack address derivation path index
|
||||
#[serde(default)]
|
||||
pub address_index: Option<SlatepackAddressIndex>,
|
||||
}
|
||||
|
||||
impl ser::Writeable for TxLogEntry {
|
||||
@@ -713,6 +717,7 @@ impl TxLogEntry {
|
||||
kernel_lookup_min_height: None,
|
||||
payment_proof: None,
|
||||
reverted_after: None,
|
||||
address_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,7 +746,7 @@ pub struct StoredProofInfo {
|
||||
/// receiver signature
|
||||
pub receiver_signature: Option<DalekSignature>,
|
||||
/// sender address derivation path index
|
||||
pub sender_address_path: u32,
|
||||
pub sender_address_path: SlatepackAddressIndex,
|
||||
/// sender address
|
||||
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
||||
pub sender_address: DalekPublicKey,
|
||||
|
||||
@@ -185,6 +185,11 @@ subcommands:
|
||||
- amount_includes_fee:
|
||||
help: Transaction amount includes transaction fee. Recipient will receive (amount - fee).
|
||||
long: amount_includes_fee
|
||||
- address_index:
|
||||
help: Slatepack address derivation path index
|
||||
short: a
|
||||
long: address_index
|
||||
takes_value: true
|
||||
- unpack:
|
||||
about: Unpack and display an armored Slatepack Message, decrypting if possible
|
||||
args:
|
||||
@@ -264,6 +269,11 @@ subcommands:
|
||||
help: Show slatepack data as QR code
|
||||
short: q
|
||||
long: slatepack_qr
|
||||
- address_index:
|
||||
help: Slatepack address derivation path index
|
||||
short: a
|
||||
long: address_index
|
||||
takes_value: true
|
||||
- pay:
|
||||
about: Spend coins to pay the provided invoice transaction
|
||||
args:
|
||||
|
||||
+67
-65
@@ -20,7 +20,6 @@ use crate::util::file::get_first_line;
|
||||
use crate::util::secp::key::SecretKey;
|
||||
use crate::util::{Mutex, ZeroingString};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use grin_core as core;
|
||||
use grin_core::core::amount_to_hr_string;
|
||||
use grin_keychain as keychain;
|
||||
@@ -29,8 +28,11 @@ use grin_wallet_config::{GlobalWalletConfig, TorConfig, WalletConfig};
|
||||
use grin_wallet_controller::command::GlobalArgs;
|
||||
use grin_wallet_controller::{command, Error};
|
||||
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl};
|
||||
use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
|
||||
use grin_wallet_libwallet::{self, Slate, SlatepackAddress, SlatepackArmor};
|
||||
use grin_wallet_libwallet::{IssueInvoiceTxArgs, NodeClient, WalletInst, WalletLCProvider};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use linefeed::terminal::Signal;
|
||||
use linefeed::{Interface, ReadResult};
|
||||
use rpassword;
|
||||
@@ -38,6 +40,7 @@ use std::convert::TryFrom;
|
||||
use std::io::{IsTerminal, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
// define what to do on argument error
|
||||
macro_rules! arg_parse {
|
||||
@@ -526,6 +529,8 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseErro
|
||||
|
||||
let slatepack_qr = args.is_present("slatepack_qr");
|
||||
|
||||
let address_index = parse_address_index_arg(args)?;
|
||||
|
||||
Ok(command::SendArgs {
|
||||
amount,
|
||||
amount_includes_fee,
|
||||
@@ -545,23 +550,32 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseErro
|
||||
skip_tor,
|
||||
bridge,
|
||||
slatepack_qr,
|
||||
address_index,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_receive_args(args: &ArgMatches) -> Result<command::ReceiveArgs, ParseError> {
|
||||
// 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);
|
||||
fn parse_address_index_arg(args: &ArgMatches) -> Result<Option<SlatepackAddressIndex>, ParseError> {
|
||||
let address = if let Some(i) = parse_optional(args, "address_index")? {
|
||||
let val = i.parse::<u32>();
|
||||
match val {
|
||||
Ok(v) => Some(
|
||||
SlatepackAddressIndex(v)
|
||||
.check()
|
||||
.map_err(|e| ParseError::ArgumentError(e.to_string()))?,
|
||||
),
|
||||
Err(e) => {
|
||||
let msg = format!("Could not parse address_index as a whole number. e={}", e);
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
Some(file)
|
||||
}
|
||||
false => None,
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
pub fn parse_receive_args(args: &ArgMatches) -> Result<command::ReceiveArgs, ParseError> {
|
||||
let input_file = parse_input_file_arg(args)?;
|
||||
|
||||
let mut input_slatepack_message = None;
|
||||
if input_file.is_none() {
|
||||
@@ -590,8 +604,7 @@ pub fn parse_receive_args(args: &ArgMatches) -> Result<command::ReceiveArgs, Par
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_unpack_args(args: &ArgMatches) -> Result<command::ReceiveArgs, ParseError> {
|
||||
// input file
|
||||
fn parse_input_file_arg(args: &ArgMatches) -> Result<Option<String>, ParseError> {
|
||||
let input_file = match args.is_present("input") {
|
||||
true => {
|
||||
let file = args.value_of("input").unwrap().to_owned();
|
||||
@@ -604,6 +617,11 @@ pub fn parse_unpack_args(args: &ArgMatches) -> Result<command::ReceiveArgs, Pars
|
||||
}
|
||||
false => None,
|
||||
};
|
||||
Ok(input_file)
|
||||
}
|
||||
|
||||
pub fn parse_unpack_args(args: &ArgMatches) -> Result<command::ReceiveArgs, ParseError> {
|
||||
let input_file = parse_input_file_arg(args)?;
|
||||
|
||||
let mut input_slatepack_message = None;
|
||||
if input_file.is_none() {
|
||||
@@ -636,18 +654,7 @@ pub fn parse_finalize_args(args: &ArgMatches) -> Result<command::FinalizeArgs, P
|
||||
let fluff = args.is_present("fluff");
|
||||
let nopost = args.is_present("nopost");
|
||||
|
||||
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 input_file = parse_input_file_arg(args)?;
|
||||
|
||||
let mut input_slatepack_message = None;
|
||||
if input_file.is_none() {
|
||||
@@ -702,12 +709,15 @@ pub fn parse_issue_invoice_args(
|
||||
|
||||
let slatepack_qr = args.is_present("slatepack_qr");
|
||||
|
||||
let address_index = parse_address_index_arg(args)?;
|
||||
|
||||
Ok(command::IssueInvoiceArgs {
|
||||
dest,
|
||||
issue_args: IssueInvoiceTxArgs {
|
||||
dest_acct_name: None,
|
||||
amount,
|
||||
target_slate_version,
|
||||
address_index,
|
||||
},
|
||||
outfile,
|
||||
slatepack_qr,
|
||||
@@ -757,7 +767,7 @@ pub fn parse_process_invoice_args(
|
||||
args: &ArgMatches,
|
||||
prompt: bool,
|
||||
slate: Slate,
|
||||
ret_address: Option<SlatepackAddress>,
|
||||
mut ret_address: Option<SlatepackAddress>,
|
||||
) -> Result<command::ProcessInvoiceArgs, ParseError> {
|
||||
// minimum_confirmations
|
||||
let min_c = parse_required(args, "minimum_confirmations")?;
|
||||
@@ -776,9 +786,18 @@ pub fn parse_process_invoice_args(
|
||||
let max_outputs = 500;
|
||||
|
||||
if prompt {
|
||||
let dest = match ret_address.clone() {
|
||||
Some(a) => String::try_from(&a).unwrap(),
|
||||
None => String::from(""),
|
||||
let dest = parse_optional(args, "dest")?;
|
||||
let dest = match dest {
|
||||
Some(dest) => {
|
||||
ret_address = Some(SlatepackAddress::try_from(dest.as_str()).map_err(|e| {
|
||||
ParseError::ArgumentError(format!("Can not parse destination address {}", e))
|
||||
})?);
|
||||
dest
|
||||
}
|
||||
None => match ret_address.clone() {
|
||||
Some(a) => String::try_from(&a).unwrap(),
|
||||
None => String::from(""),
|
||||
},
|
||||
};
|
||||
// Now we need to prompt the user whether they want to do this,
|
||||
prompt_pay_invoice(&slate, &dest)?;
|
||||
@@ -842,16 +861,7 @@ pub fn parse_txs_args(args: &ArgMatches) -> Result<command::TxsArgs, ParseError>
|
||||
None => None,
|
||||
Some(tx) => Some(parse_u64(tx, "id")? as u32),
|
||||
};
|
||||
let tx_slate_id = match args.value_of("txid") {
|
||||
None => None,
|
||||
Some(tx) => match tx.parse() {
|
||||
Ok(t) => Some(t),
|
||||
Err(e) => {
|
||||
let msg = format!("Could not parse txid parameter. e={}", e);
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
},
|
||||
};
|
||||
let tx_slate_id = parse_tx_slate_id_arg(args)?;
|
||||
if tx_id.is_some() && tx_slate_id.is_some() {
|
||||
let msg = "At most one of 'id' (-i) or 'txid' (-t) may be provided.".to_string();
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
@@ -867,22 +877,24 @@ pub fn parse_txs_args(args: &ArgMatches) -> Result<command::TxsArgs, ParseError>
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_tx_slate_id_arg(args: &ArgMatches) -> Result<Option<Uuid>, ParseError> {
|
||||
let tx_slate_id = match args.value_of("txid") {
|
||||
None => None,
|
||||
Some(tx) => match tx.parse() {
|
||||
Ok(t) => Some(t),
|
||||
Err(e) => {
|
||||
let msg = format!("Could not parse txid parameter. e={}", e);
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
},
|
||||
};
|
||||
Ok(tx_slate_id)
|
||||
}
|
||||
|
||||
pub fn parse_post_args(args: &ArgMatches) -> Result<command::PostArgs, ParseError> {
|
||||
let fluff = args.is_present("fluff");
|
||||
|
||||
// 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 input_file = parse_input_file_arg(args)?;
|
||||
|
||||
let mut input_slatepack_message = None;
|
||||
if input_file.is_none() {
|
||||
@@ -948,16 +960,7 @@ pub fn parse_export_proof_args(args: &ArgMatches) -> Result<command::ProofExport
|
||||
None => None,
|
||||
Some(tx) => Some(parse_u64(tx, "id")? as u32),
|
||||
};
|
||||
let tx_slate_id = match args.value_of("txid") {
|
||||
None => None,
|
||||
Some(tx) => match tx.parse() {
|
||||
Ok(t) => Some(t),
|
||||
Err(e) => {
|
||||
let msg = format!("Could not parse txid parameter. e={}", e);
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
},
|
||||
};
|
||||
let tx_slate_id = parse_tx_slate_id_arg(args)?;
|
||||
if tx_id.is_some() && tx_slate_id.is_some() {
|
||||
let msg = "At most one of 'id' (-i) or 'txid' (-t) may be provided.".to_string();
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
@@ -1055,8 +1058,7 @@ where
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut owner_api = Owner::new(wallet.clone(), None, config.config_file_path.clone());
|
||||
command::unpack(&mut owner_api, mask.as_ref(), slatepack)?;
|
||||
command::unpack(wallet.clone(), mask.as_ref(), slatepack)?;
|
||||
return Ok("unpack".to_string());
|
||||
}
|
||||
("owner_api", _) => {
|
||||
@@ -1237,7 +1239,7 @@ where
|
||||
("unpack", Some(args)) => {
|
||||
let a = arg_parse!(parse_unpack_args(args));
|
||||
let slatepack = command::read_slatepack(a)?;
|
||||
command::unpack(owner_api, km, slatepack)
|
||||
command::unpack(owner_api.wallet_inst.clone(), km, slatepack)
|
||||
}
|
||||
("finalize", Some(args)) => {
|
||||
let a = arg_parse!(parse_finalize_args(args));
|
||||
|
||||
@@ -235,6 +235,12 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller::
|
||||
"10",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
let args = vec!["grin-wallet", "-p", "unused", "unpack", "-i", &file_name];
|
||||
assert_eq!(
|
||||
execute_command(&app, test_dir, "wallet1", &client1, args)?,
|
||||
"unpack"
|
||||
);
|
||||
|
||||
let arg_vec = vec!["grin-wallet", "-a", "mining", "-p", "password1", "txs"];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
@@ -810,6 +816,127 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller::
|
||||
let (_, wallet_info) = api2.retrieve_summary_info(mask2, true, 1)?;
|
||||
assert!(wallet_info.amount_currently_spendable < old_balance);
|
||||
|
||||
// Issue encrypted invoice using custom address index, wallet2.
|
||||
let arg_vec = vec![
|
||||
"grin-wallet",
|
||||
"-a",
|
||||
"default",
|
||||
"-p",
|
||||
"password2",
|
||||
"invoice",
|
||||
"--address_index",
|
||||
"3",
|
||||
"-d",
|
||||
"tgrin1fu5z9y4p8atxr83l6nfj0j2m067r9gjfkd6mzeuuz0v6sqftua2quv0ll3",
|
||||
"1",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||
let file_name = format!(
|
||||
"{}/wallet2/slatepack/0436430c-2b02-624c-2032-570501212b08.I1.slatepack",
|
||||
test_dir
|
||||
);
|
||||
|
||||
// Pay the invoice tx, wallet 1
|
||||
let arg_vec = vec![
|
||||
"grin-wallet",
|
||||
"-a",
|
||||
"default",
|
||||
"-p",
|
||||
"password1",
|
||||
"pay",
|
||||
"-i",
|
||||
&file_name,
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
let file_name = format!(
|
||||
"{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b08.I2.slatepack",
|
||||
test_dir
|
||||
);
|
||||
|
||||
// Finalize the invoice, wallet 2
|
||||
let arg_vec = vec![
|
||||
"grin-wallet",
|
||||
"-a",
|
||||
"default",
|
||||
"-p",
|
||||
"password2",
|
||||
"finalize",
|
||||
"-i",
|
||||
&file_name,
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||
|
||||
// Send encrypted to wallet 2 using custom address index
|
||||
let arg_vec = vec![
|
||||
"grin-wallet",
|
||||
"-p",
|
||||
"password1",
|
||||
"-a",
|
||||
"mining",
|
||||
"send",
|
||||
"-d",
|
||||
"tgrin1ak8aaxpjg6ct5uje4lgzvjp65l0nrmgxndp5xjy74sumzp7wasysje3kmf",
|
||||
"--address_index",
|
||||
"5",
|
||||
"1",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
// Receive, wallet 2
|
||||
let file_name = format!(
|
||||
"{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b09.S1.slatepack",
|
||||
test_dir
|
||||
);
|
||||
for (wallet, password, account, client) in [
|
||||
("wallet1", "password1", "mining", &client1),
|
||||
("wallet2", "password2", "account_1", &client2),
|
||||
] {
|
||||
let args = vec![
|
||||
"grin-wallet",
|
||||
"-p",
|
||||
password,
|
||||
"-a",
|
||||
account,
|
||||
"unpack",
|
||||
"-i",
|
||||
&file_name,
|
||||
];
|
||||
assert_eq!(
|
||||
execute_command(&app, test_dir, wallet, client, args)?,
|
||||
"unpack"
|
||||
);
|
||||
}
|
||||
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-570501212b09.S2.slatepack",
|
||||
test_dir
|
||||
);
|
||||
|
||||
// Finalize the send, wallet 1
|
||||
let arg_vec = vec![
|
||||
"grin-wallet",
|
||||
"-a",
|
||||
"mining",
|
||||
"-p",
|
||||
"password1",
|
||||
"finalize",
|
||||
"-i",
|
||||
&file_name,
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
// let logging finish
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
clean_output_dir(test_dir);
|
||||
|
||||
Reference in New Issue
Block a user