Move aggsig transaction building functions into separate lib (#1061)

* refactoring transaction building code

* serialise return transaction

* move shared functions into transactions, ensure wallet manipulation is only done outside of aggsig transaction lib

* remove unneeded wallet config from fn

* adding test functions to facilitate libwallet transaction testing

* rustfmt

* refactoring checker somewhat, adding ability to create and transactions against local copy of chain for simpler testing

* finish transaction testing functionality which verifies transactions work properly

* Remove wallet output manipulation from transaction building lib

* ensure sender expects full transaction back on last phase

* ensure sender expects full transaction back on last phase
This commit is contained in:
Yeastplume 2018-05-16 13:18:09 +01:00 committed by GitHub
parent 24e21f013d
commit 4bbaa8d05f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1413 additions and 659 deletions

2
Cargo.lock generated
View file

@ -799,6 +799,7 @@ dependencies = [
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
"grin_api 0.2.0",
"grin_chain 0.2.0",
"grin_core 0.2.0",
"grin_keychain 0.2.0",
"grin_util 0.2.0",
@ -813,6 +814,7 @@ dependencies = [
"serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"slog 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-core 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-retry 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"urlencoded 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -38,6 +38,7 @@ use util::LOGGER;
bitflags! {
/// Options for a kernel's structure or use
#[derive(Serialize, Deserialize)]
pub struct KernelFeatures: u8 {
/// No flags
const DEFAULT_KERNEL = 0b00000000;
@ -114,7 +115,7 @@ impl From<keychain::Error> for Error {
/// amount to zero.
/// The signature signs the fee and the lock_height, which are retained for
/// signature validation.
#[derive(Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TxKernel {
/// Options for a kernel's structure or use
pub features: KernelFeatures,
@ -234,7 +235,7 @@ impl PMMRable for TxKernel {
}
/// A transaction
#[derive(Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Transaction {
/// List of inputs spent by the transaction.
pub inputs: Vec<Input>,
@ -692,7 +693,7 @@ pub fn deaggregate(mk_tx: Transaction, txs: Vec<Transaction>) -> Result<Transact
/// A transaction input.
///
/// Primarily a reference to an output being spent by the transaction.
#[derive(Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Input {
/// The features of the output being spent.
/// We will check maturity for coinbase output.

View file

@ -32,3 +32,7 @@ grin_api = { path = "../api" }
grin_core = { path = "../core" }
grin_keychain = { path = "../keychain" }
grin_util = { path = "../util" }
[dev-dependencies]
grin_chain = { path = "../chain" }
time = "0.1"

View file

@ -57,19 +57,7 @@ pub fn refresh_outputs(config: &WalletConfig, keychain: &Keychain) -> Result<(),
fn refresh_missing_block_hashes(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> {
// build a local map of wallet outputs keyed by commit
// and a list of outputs we want to query the node for
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
for out in wallet_data.outputs.values().filter(|x| {
x.root_key_id == keychain.root_key_id() && x.block.is_none()
&& x.status == OutputStatus::Unspent
}) {
let commit = keychain
.commit_with_key_index(out.value, out.n_child)
.context(ErrorKind::Keychain)?;
wallet_outputs.insert(commit, out.key_id.clone());
}
Ok(())
});
let wallet_outputs = map_wallet_outputs_missing_block(config, keychain)?;
// nothing to do so return (otherwise we hit the api with a monster query...)
if wallet_outputs.is_empty() {
@ -164,13 +152,12 @@ fn refresh_missing_block_hashes(config: &WalletConfig, keychain: &Keychain) -> R
})
}
/// Builds a single api query to retrieve the latest output data from the node.
/// So we can refresh the local wallet outputs.
fn refresh_output_state(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> {
debug!(LOGGER, "Refreshing wallet outputs");
// build a local map of wallet outputs keyed by commit
// and a list of outputs we want to query the node for
/// build a local map of wallet outputs keyed by commit
/// and a list of outputs we want to query the node for
pub fn map_wallet_outputs(
config: &WalletConfig,
keychain: &Keychain,
) -> Result<HashMap<pedersen::Commitment, Identifier>, Error> {
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
let _ =
WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
@ -184,7 +171,62 @@ fn refresh_output_state(config: &WalletConfig, keychain: &Keychain) -> Result<()
}
Ok(())
});
Ok(wallet_outputs)
}
/// As above, but only return unspent outputs with missing block hashes
/// and a list of outputs we want to query the node for
pub fn map_wallet_outputs_missing_block(
config: &WalletConfig,
keychain: &Keychain,
) -> Result<HashMap<pedersen::Commitment, Identifier>, Error> {
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
for out in wallet_data.outputs.values().filter(|x| {
x.root_key_id == keychain.root_key_id() && x.block.is_none()
&& x.status == OutputStatus::Unspent
}) {
let commit = keychain
.commit_with_key_index(out.value, out.n_child)
.context(ErrorKind::Keychain)?;
wallet_outputs.insert(commit, out.key_id.clone());
}
Ok(())
});
Ok(wallet_outputs)
}
/// Apply refreshed API output data to the wallet
pub fn apply_api_outputs(
config: &WalletConfig,
wallet_outputs: &HashMap<pedersen::Commitment, Identifier>,
api_outputs: &HashMap<pedersen::Commitment, api::Output>,
) -> Result<(), Error> {
// now for each commit, find the output in the wallet and
// the corresponding api output (if it exists)
// and refresh it in-place in the wallet.
// Note: minimizing the time we spend holding the wallet lock.
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
for commit in wallet_outputs.keys() {
let id = wallet_outputs.get(&commit).unwrap();
if let Entry::Occupied(mut output) = wallet_data.outputs.entry(id.to_hex()) {
match api_outputs.get(&commit) {
Some(_) => mark_unspent_output(&mut output.get_mut()),
None => mark_spent_output(&mut output.get_mut()),
};
}
}
})
}
/// Builds a single api query to retrieve the latest output data from the node.
/// So we can refresh the local wallet outputs.
fn refresh_output_state(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> {
debug!(LOGGER, "Refreshing wallet outputs");
// build a local map of wallet outputs keyed by commit
// and a list of outputs we want to query the node for
let wallet_outputs = map_wallet_outputs(config, keychain)?;
// build the necessary query params -
// ?id=xxx&id=yyy&id=zzz
let query_params: Vec<String> = wallet_outputs
@ -238,21 +280,8 @@ fn refresh_output_state(config: &WalletConfig, keychain: &Keychain) -> Result<()
}
};
}
// now for each commit, find the output in the wallet and
// the corresponding api output (if it exists)
// and refresh it in-place in the wallet.
// Note: minimizing the time we spend holding the wallet lock.
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
for commit in wallet_outputs.keys() {
let id = wallet_outputs.get(&commit).unwrap();
if let Entry::Occupied(mut output) = wallet_data.outputs.entry(id.to_hex()) {
match api_outputs.get(&commit) {
Some(_) => mark_unspent_output(&mut output.get_mut()),
None => mark_spent_output(&mut output.get_mut()),
};
}
}
})
apply_api_outputs(config, &wallet_outputs, &api_outputs)?;
Ok(())
}
pub fn get_tip_from_node(config: &WalletConfig) -> Result<api::Tip, Error> {

View file

@ -21,6 +21,7 @@ use tokio_core::reactor;
use serde_json;
use types::*;
use core::core::Transaction;
use util::LOGGER;
use std::io;
@ -40,14 +41,6 @@ pub fn create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Erro
}
pub fn send_partial_tx(url: &str, partial_tx: &PartialTx, fluff: bool) -> Result<PartialTx, Error> {
single_send_partial_tx(url, partial_tx, fluff)
}
fn single_send_partial_tx(
url: &str,
partial_tx: &PartialTx,
fluff: bool,
) -> Result<PartialTx, Error> {
let mut core = reactor::Core::new().context(ErrorKind::Hyper)?;
let client = hyper::Client::new(&core.handle());
@ -67,15 +60,49 @@ fn single_send_partial_tx(
let work = client.request(req).and_then(|res| {
res.body().concat2().and_then(move |body| {
let partial_tx: PartialTx =
let tx: PartialTx =
serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(partial_tx)
Ok(tx)
})
});
let res = core.run(work).context(ErrorKind::Hyper)?;
Ok(res)
}
///TODO: Factor this out with above later, this version just returns a Transaction instead
///of a partial TX
pub fn send_partial_tx_final(
url: &str,
partial_tx: &PartialTx,
fluff: bool,
) -> Result<Transaction, Error> {
let mut core = reactor::Core::new().context(ErrorKind::Hyper)?;
let client = hyper::Client::new(&core.handle());
// In case we want to do an express send
let mut url_pool = url.to_owned();
if fluff {
url_pool = format!("{}{}", url, "?fluff");
}
let mut req = Request::new(
Method::Post,
url_pool.parse::<hyper::Uri>().context(ErrorKind::Hyper)?,
);
req.headers_mut().set(ContentType::json());
let json = serde_json::to_string(&partial_tx).context(ErrorKind::Hyper)?;
req.set_body(json);
let work = client.request(req).and_then(|res| {
res.body().concat2().and_then(move |body| {
let tx: Transaction =
serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(tx)
})
});
let res = core.run(work).context(ErrorKind::Hyper)?;
Ok(res)
}
/// Makes a single request to the wallet API to create a new coinbase output.
fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Error> {
let mut core =

View file

@ -0,0 +1,45 @@
// Copyright 2018 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.
//! Grin Wallet specific key management functions
use rand::thread_rng;
use uuid::Uuid;
use core::core::{amount_to_hr_string, Committed, Transaction};
use libwallet::{aggsig, build};
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
use types::*;
use util::{secp, LOGGER};
use util::secp::key::{PublicKey, SecretKey};
use util::secp::Signature;
use failure::ResultExt;
/// Get next available key in the wallet
pub fn next_available_key(wallet_data: &WalletData, keychain: &Keychain) -> (Identifier, u32) {
let root_key_id = keychain.root_key_id();
let derivation = wallet_data.next_child(root_key_id.clone());
let key_id = keychain.derive_key_id(derivation).unwrap();
(key_id, derivation)
}
/// Retrieve an existing key from a wallet
pub fn retrieve_existing_key(wallet_data: &WalletData, key_id: Identifier) -> (Identifier, u32) {
if let Some(existing) = wallet_data.get_output(&key_id) {
let key_id = existing.key_id.clone();
let derivation = existing.n_child;
(key_id, derivation)
} else {
panic!("should never happen");
}
}

View file

@ -0,0 +1,29 @@
// Copyright 2018 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.
//! Library specific to the Grin wallet implementation, as distinct from
//! libwallet, which should build transactions without any knowledge of the
//! wallet implementation.
// TODO: Once this is working, extract a set of traits that wallet
// implementations would need to provide
#![deny(non_upper_case_globals)]
#![deny(non_camel_case_types)]
#![deny(non_snake_case)]
#![deny(unused_mut)]
#![warn(missing_docs)]
pub mod selection;
pub mod keys;

View file

@ -0,0 +1,204 @@
// Copyright 2018 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.
//! Selection of inputs for building transactions
use core::core::{amount_to_hr_string, Committed, Transaction};
use libwallet::{aggsig, build};
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
use types::*;
use util::{secp, LOGGER};
use util::secp::key::{PublicKey, SecretKey};
use util::secp::Signature;
use failure::ResultExt;
/// Builds a transaction to send to someone from the HD seed associated with the
/// wallet and the amount to send. Handles reading through the wallet data file,
/// selecting outputs to spend and building the change.
pub fn build_send_tx(
config: &WalletConfig,
keychain: &Keychain,
amount: u64,
current_height: u64,
minimum_confirmations: u64,
lock_height: u64,
max_outputs: usize,
selection_strategy_is_use_all: bool,
) -> Result<
(
Transaction,
BlindingFactor,
Vec<OutputData>,
Option<Identifier>,
u64,
),
Error,
> {
let key_id = keychain.clone().root_key_id();
// select some spendable coins from the wallet
let mut coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
Ok(wallet_data.select_coins(
key_id.clone(),
amount,
current_height,
minimum_confirmations,
max_outputs,
selection_strategy_is_use_all,
))
})?;
// Get the maximum number of outputs in the wallet
let max_outputs = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
Ok(wallet_data.select_coins(
key_id.clone(),
amount,
current_height,
minimum_confirmations,
max_outputs,
true,
))
})?.len();
// sender is responsible for setting the fee on the partial tx
// recipient should double check the fee calculation and not blindly trust the
// sender
let mut fee;
// First attempt to spend without change
fee = tx_fee(coins.len(), 1, coins_proof_count(&coins), None);
let mut total: u64 = coins.iter().map(|c| c.value).sum();
let mut amount_with_fee = amount + fee;
if total == 0 {
return Err(ErrorKind::NotEnoughFunds(total as u64))?;
}
// Check if we need to use a change address
if total > amount_with_fee {
fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
amount_with_fee = amount + fee;
// Here check if we have enough outputs for the amount including fee otherwise
// look for other outputs and check again
while total < amount_with_fee {
// End the loop if we have selected all the outputs and still not enough funds
if coins.len() == max_outputs {
return Err(ErrorKind::NotEnoughFunds(total as u64))?;
}
// select some spendable coins from the wallet
coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
Ok(wallet_data.select_coins(
key_id.clone(),
amount_with_fee,
current_height,
minimum_confirmations,
max_outputs,
selection_strategy_is_use_all,
))
})?;
fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
total = coins.iter().map(|c| c.value).sum();
amount_with_fee = amount + fee;
}
}
// build transaction skeleton with inputs and change
let (mut parts, change_key) = inputs_and_change(&coins, config, keychain, amount, fee)?;
// This is more proof of concept than anything but here we set lock_height
// on tx being sent (based on current chain height via api).
parts.push(build::with_lock_height(lock_height));
let (tx, blind) = build::partial_transaction(parts, &keychain).context(ErrorKind::Keychain)?;
Ok((tx, blind, coins, change_key, amount_with_fee))
}
/// coins proof count
pub fn coins_proof_count(coins: &Vec<OutputData>) -> usize {
coins.iter().filter(|c| c.merkle_proof.is_some()).count()
}
/// Selects inputs and change for a transaction
pub fn inputs_and_change(
coins: &Vec<OutputData>,
config: &WalletConfig,
keychain: &Keychain,
amount: u64,
fee: u64,
) -> Result<(Vec<Box<build::Append>>, Option<Identifier>), Error> {
let mut parts = vec![];
// calculate the total across all inputs, and how much is left
let total: u64 = coins.iter().map(|c| c.value).sum();
parts.push(build::with_fee(fee));
// if we are spending 10,000 coins to send 1,000 then our change will be 9,000
// if the fee is 80 then the recipient will receive 1000 and our change will be
// 8,920
let change = total - amount - fee;
// build inputs using the appropriate derived key_ids
for coin in coins {
let key_id = keychain
.derive_key_id(coin.n_child)
.context(ErrorKind::Keychain)?;
if coin.is_coinbase {
let block = coin.block.clone();
let merkle_proof = coin.merkle_proof.clone();
let merkle_proof = merkle_proof.unwrap().merkle_proof();
parts.push(build::coinbase_input(
coin.value,
block.unwrap().hash(),
merkle_proof,
key_id,
));
} else {
parts.push(build::input(coin.value, key_id));
}
}
let change_key;
if change != 0 {
// track the output representing our change
change_key = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
let root_key_id = keychain.root_key_id();
let change_derivation = wallet_data.next_child(root_key_id.clone());
let change_key = keychain.derive_key_id(change_derivation).unwrap();
wallet_data.add_output(OutputData {
root_key_id: root_key_id.clone(),
key_id: change_key.clone(),
n_child: change_derivation,
value: change as u64,
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
is_coinbase: false,
block: None,
merkle_proof: None,
});
Some(change_key)
})?;
parts.push(build::output(change, change_key.clone().unwrap()));
} else {
change_key = None
}
Ok((parts, change_key))
}

View file

@ -49,17 +49,18 @@ extern crate grin_core as core;
extern crate grin_keychain as keychain;
extern crate grin_util as util;
mod checker;
pub mod checker;
mod handlers;
mod outputs;
mod info;
mod receiver;
pub mod receiver;
mod sender;
mod types;
pub mod types;
mod restore;
pub mod client;
pub mod server;
pub mod libwallet;
pub mod grinwallet;
pub use outputs::show_outputs;
pub use info::{retrieve_info, show_info};

View file

@ -35,9 +35,12 @@ pub struct Context {
/// Secret nonce (of which public is shared)
/// (basically a SecretKey)
pub sec_nonce: SecretKey,
/// If I'm the recipient, store my outputs between invocations (that I need
/// to sum)
/// If I'm the sender, store change key
pub change_key: Option<Identifier>,
/// store my outputs between invocations
pub output_ids: Vec<Identifier>,
/// store the calculated fee
pub fee: u64,
}
#[derive(Clone, Debug)]
@ -70,7 +73,9 @@ impl ContextManager {
sec_key: sec_key,
transaction_id: transaction_id.clone(),
sec_nonce: aggsig::export_secnonce_single(secp).unwrap(),
change_key: None,
output_ids: vec![],
fee: 0,
},
);
}

View file

@ -27,3 +27,4 @@ pub mod blind;
pub mod proof;
pub mod reward;
pub mod build;
pub mod transaction;

View file

@ -0,0 +1,472 @@
// Copyright 2018 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.
//! Functions for building partial transactions to be passed
//! around during an interactive wallet exchange
use rand::thread_rng;
use uuid::Uuid;
use core::core::{amount_to_hr_string, Committed, Transaction};
use libwallet::{aggsig, build};
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
use types::*; // TODO: Remove this?
use util::{secp, LOGGER};
use util::secp::key::{PublicKey, SecretKey};
use util::secp::Signature;
use failure::ResultExt;
// TODO: None of these functions should care about the wallet implementation,
/// Initiate a transaction for the aggsig exchange
/// with the given transaction data
pub fn sender_initiation(
keychain: &Keychain,
tx_id: &Uuid,
context_manager: &mut aggsig::ContextManager,
current_height: u64,
//TODO: Make this nicer, remove wallet-specific OutputData type
tx_data: (
Transaction,
BlindingFactor,
Vec<OutputData>,
Option<Identifier>,
u64,
),
) -> Result<PartialTx, Error> {
let lock_height = current_height;
let (tx, blind, coins, _change_key, amount_with_fee) = tx_data;
// TODO - wrap this up in build_send_tx or even the build() call?
// Generate a random kernel offset here
// and subtract it from the blind_sum so we create
// the aggsig context with the "split" key
let kernel_offset =
BlindingFactor::from_secret_key(SecretKey::new(&keychain.secp(), &mut thread_rng()));
let blind_offset = keychain
.blind_sum(&BlindSum::new()
.add_blinding_factor(blind)
.sub_blinding_factor(kernel_offset))
.unwrap();
//
// -Sender picks random blinding factors for all outputs it participates in,
// computes total blinding excess xS -Sender picks random nonce kS
// -Sender posts inputs, outputs, Message M=fee, xS * G and kS * G to Receiver
//
let skey = blind_offset
.secret_key(&keychain.secp())
.context(ErrorKind::Keychain)?;
// Create a new aggsig context
let mut context = context_manager.create_context(keychain.secp(), &tx_id, skey);
for coin in coins {
context.add_output(&coin.key_id);
}
let partial_tx = build_partial_tx(
&context,
keychain,
amount_with_fee,
lock_height,
kernel_offset,
None,
tx,
);
context_manager.save_context(context);
Ok(partial_tx)
}
/// Receive Part 1 of interactive transactions from sender, Sender Initiation
/// Return result of part 2, Recipient Initation, to sender
/// -Receiver receives inputs, outputs xS * G and kS * G
/// -Receiver picks random blinding factors for all outputs being received,
/// computes total blinding
/// excess xR
/// -Receiver picks random nonce kR
/// -Receiver computes Schnorr challenge e = H(M | kR * G + kS * G)
/// -Receiver computes their part of signature, sR = kR + e * xR
/// -Receiver responds with sR, blinding excess xR * G, public nonce kR * G
pub fn recipient_initiation(
keychain: &Keychain,
context_manager: &mut aggsig::ContextManager,
partial_tx: &PartialTx,
output_key_id: &Identifier,
) -> Result<PartialTx, Error> {
let (amount, _lock_height, _sender_pub_blinding, sender_pub_nonce, kernel_offset, _sig, tx) =
read_partial_tx(keychain, partial_tx)?;
// double check the fee amount included in the partial tx
// we don't necessarily want to just trust the sender
// we could just overwrite the fee here (but we won't) due to the sig
let fee = tx_fee(
tx.inputs.len(),
tx.outputs.len() + 1,
tx.input_proofs_count(),
None,
);
if fee > tx.fee() {
return Err(ErrorKind::FeeDispute {
sender_fee: tx.fee(),
recipient_fee: fee,
})?;
}
if fee > amount {
info!(
LOGGER,
"Rejected the transfer because transaction fee ({}) exceeds received amount ({}).",
amount_to_hr_string(fee),
amount_to_hr_string(amount)
);
return Err(ErrorKind::FeeExceedsAmount {
sender_amount: amount,
recipient_fee: fee,
})?;
}
let out_amount = amount - tx.fee();
// First step is just to get the excess sum of the outputs we're participating
// in Output and key needs to be stored until transaction finalisation time,
// somehow
// Still handy for getting the blinding sum
let (_, blind_sum) = build::partial_transaction(
vec![build::output(out_amount, output_key_id.clone())],
keychain,
).context(ErrorKind::Keychain)?;
// Create a new aggsig context
// this will create a new blinding sum and nonce, and store them
let blind = blind_sum
.secret_key(&keychain.secp())
.context(ErrorKind::Keychain)?;
debug!(LOGGER, "Creating new aggsig context");
let mut context = context_manager.create_context(keychain.secp(), &partial_tx.id, blind);
context.add_output(output_key_id);
context.fee = tx.fee();
let sig_part = context
.calculate_partial_sig(
keychain.secp(),
&sender_pub_nonce,
tx.fee(),
tx.lock_height(),
)
.unwrap();
// Build the response, which should contain sR, blinding excess xR * G, public
// nonce kR * G
let mut partial_tx = build_partial_tx(
&context,
keychain,
amount,
partial_tx.lock_height,
kernel_offset,
Some(sig_part),
tx,
);
partial_tx.phase = PartialTxPhase::ReceiverInitiation;
context_manager.save_context(context);
Ok(partial_tx)
}
/// -Sender receives xR * G, kR * G, sR
/// -Sender computes Schnorr challenge e = H(M | kR * G + kS * G)
/// -Sender verifies receivers sig, by verifying that kR * G + e * xR * G =
/// sR * G·
/// -Sender computes their part of signature, sS = kS + e * xS
pub fn sender_confirmation(
keychain: &Keychain,
context_manager: &mut aggsig::ContextManager,
partial_tx: PartialTx,
) -> Result<PartialTx, Error> {
let context = context_manager.get_context(&partial_tx.id);
let (amount, lock_height, recp_pub_blinding, recp_pub_nonce, kernel_offset, sig, tx) =
read_partial_tx(keychain, &partial_tx)?;
let res = context.verify_partial_sig(
&keychain.secp(),
&sig.unwrap(),
&recp_pub_nonce,
&recp_pub_blinding,
tx.fee(),
lock_height,
);
if !res {
error!(LOGGER, "Partial Sig from recipient invalid.");
return Err(ErrorKind::Signature("Partial Sig from recipient invalid."))?;
}
let sig_part = context
.calculate_partial_sig(
&keychain.secp(),
&recp_pub_nonce,
tx.fee(),
tx.lock_height(),
)
.unwrap();
// Build the next stage, containing sS (and our pubkeys again, for the
// recipient's convenience) offset has not been modified during tx building,
// so pass it back in
let mut partial_tx = build_partial_tx(
&context,
keychain,
amount,
lock_height,
kernel_offset,
Some(sig_part),
tx,
);
partial_tx.phase = PartialTxPhase::SenderConfirmation;
context_manager.save_context(context);
Ok(partial_tx)
}
/// Creates the final signature, callable by either the sender or recipient
/// (after phase 3: sender confirmation)
///
/// TODO: takes a partial Tx that just contains the other party's public
/// info at present, but this should be changed to something more appropriate
pub fn finalize_transaction(
keychain: &Keychain,
context_manager: &mut aggsig::ContextManager,
partial_tx: &PartialTx,
other_partial_tx: &PartialTx,
output_key_id: &Identifier,
output_key_derivation: u32,
) -> Result<Transaction, Error> {
let (
_amount,
_lock_height,
other_pub_blinding,
other_pub_nonce,
kernel_offset,
other_sig_part,
tx,
) = read_partial_tx(keychain, other_partial_tx)?;
let final_sig = create_final_signature(
keychain,
context_manager,
partial_tx,
&other_pub_blinding,
&other_pub_nonce,
&other_sig_part.unwrap(),
)?;
build_final_transaction(
keychain,
partial_tx.amount,
kernel_offset,
&final_sig,
tx.clone(),
output_key_id,
output_key_derivation,
)
}
/// This should be callable by either the sender or receiver
/// once phase 3 is done
///
/// Receive Part 3 of interactive transactions from sender, Sender Confirmation
/// Return Ok/Error
/// -Receiver receives sS
/// -Receiver verifies sender's sig, by verifying that
/// kS * G + e *xS * G = sS* G
/// -Receiver calculates final sig as s=(sS+sR, kS * G+kR * G)
/// -Receiver puts into TX kernel:
///
/// Signature S
/// pubkey xR * G+xS * G
/// fee (= M)
///
/// Returns completed transaction ready for posting to the chain
fn create_final_signature(
keychain: &Keychain,
context_manager: &mut aggsig::ContextManager,
partial_tx: &PartialTx,
other_pub_blinding: &PublicKey,
other_pub_nonce: &PublicKey,
other_sig_part: &Signature,
) -> Result<Signature, Error> {
let (_amount, _lock_height, _, _, _kernel_offset, _, tx) =
read_partial_tx(keychain, partial_tx)?;
let context = context_manager.get_context(&partial_tx.id);
let res = context.verify_partial_sig(
&keychain.secp(),
&other_sig_part,
&other_pub_nonce,
&other_pub_blinding,
tx.fee(),
tx.lock_height(),
);
if !res {
error!(LOGGER, "Partial Sig from other party invalid.");
return Err(ErrorKind::Signature(
"Partial Sig from other party invalid.",
))?;
}
// Just calculate our sig part again instead of storing
let our_sig_part = context
.calculate_partial_sig(
&keychain.secp(),
&other_pub_nonce,
tx.fee(),
tx.lock_height(),
)
.unwrap();
// And the final signature
let final_sig = context
.calculate_final_sig(
&keychain.secp(),
&other_sig_part,
&our_sig_part,
&other_pub_nonce,
)
.unwrap();
// Calculate the final public key (for our own sanity check)
let final_pubkey = context
.calculate_final_pubkey(&keychain.secp(), &other_pub_blinding)
.unwrap();
// Check our final sig verifies
let res = context.verify_final_sig_build_msg(
&keychain.secp(),
&final_sig,
&final_pubkey,
tx.fee(),
tx.lock_height(),
);
if !res {
error!(LOGGER, "Final aggregated signature invalid.");
return Err(ErrorKind::Signature("Final aggregated signature invalid."))?;
}
Ok(final_sig)
}
/// builds a final transaction after the aggregated sig exchange
fn build_final_transaction(
keychain: &Keychain,
amount: u64,
kernel_offset: BlindingFactor,
excess_sig: &secp::Signature,
tx: Transaction,
output_key_id: &Identifier,
output_key_derivation: u32,
) -> Result<Transaction, Error> {
let root_key_id = keychain.root_key_id();
// double check the fee amount included in the partial tx
// we don't necessarily want to just trust the sender
// we could just overwrite the fee here (but we won't) due to the ecdsa sig
let fee = tx_fee(
tx.inputs.len(),
tx.outputs.len() + 1,
tx.input_proofs_count(),
None,
);
if fee > tx.fee() {
return Err(ErrorKind::FeeDispute {
sender_fee: tx.fee(),
recipient_fee: fee,
})?;
}
if fee > amount {
info!(
LOGGER,
"Rejected the transfer because transaction fee ({}) exceeds received amount ({}).",
amount_to_hr_string(fee),
amount_to_hr_string(amount)
);
return Err(ErrorKind::FeeExceedsAmount {
sender_amount: amount,
recipient_fee: fee,
})?;
}
let out_amount = amount - tx.fee();
// Build final transaction, the sum of which should
// be the same as the exchanged excess values
let mut final_tx = build::transaction(
vec![
build::initial_tx(tx),
build::output(out_amount, output_key_id.clone()),
build::with_offset(kernel_offset),
],
keychain,
).context(ErrorKind::Keychain)?;
// build the final excess based on final tx and offset
let final_excess = {
// TODO - do we need to verify rangeproofs here?
for x in &final_tx.outputs {
x.verify_proof().context(ErrorKind::Transaction)?;
}
// sum the input/output commitments on the final tx
let overage = final_tx.fee() as i64;
let tx_excess = final_tx
.sum_commitments(overage, None)
.context(ErrorKind::Transaction)?;
// subtract the kernel_excess (built from kernel_offset)
let offset_excess = keychain
.secp()
.commit(0, kernel_offset.secret_key(&keychain.secp()).unwrap())
.unwrap();
keychain
.secp()
.commit_sum(vec![tx_excess], vec![offset_excess])
.context(ErrorKind::Transaction)?
};
// update the tx kernel to reflect the offset excess and sig
assert_eq!(final_tx.kernels.len(), 1);
final_tx.kernels[0].excess = final_excess.clone();
final_tx.kernels[0].excess_sig = excess_sig.clone();
// confirm the kernel verifies successfully before proceeding
debug!(LOGGER, "Validating final transaction");
final_tx.kernels[0]
.verify()
.context(ErrorKind::Transaction)?;
// confirm the overall transaction is valid (including the updated kernel)
let _ = final_tx.validate().context(ErrorKind::Transaction)?;
debug!(
LOGGER,
"Finalized transaction and built output - {:?}, {:?}, {}",
root_key_id.clone(),
output_key_id.clone(),
output_key_derivation,
);
Ok(final_tx)
}

View file

@ -25,14 +25,15 @@ use std::sync::{Arc, RwLock};
use api;
use core::consensus::reward;
use core::core::{amount_to_hr_string, Committed, Output, Transaction, TxKernel};
use libwallet::{aggsig, build, reward};
use core::core::{Output, Transaction, TxKernel};
use libwallet::{aggsig, reward, transaction};
use grinwallet::keys;
use core::{global, ser};
use failure::{Fail, ResultExt};
use keychain::{BlindingFactor, Identifier, Keychain};
use keychain::Keychain;
use types::*;
use urlencoded::UrlEncodedQuery;
use util::{secp, to_hex, LOGGER};
use util::{to_hex, LOGGER};
/// Dummy wrapper for the hex-encoded serialized transaction.
#[derive(Serialize, Deserialize)]
@ -46,71 +47,64 @@ lazy_static! {
= Arc::new(RwLock::new(aggsig::ContextManager::new()));
}
/// Receive Part 1 of interactive transactions from sender, Sender Initiation
/// Return result of part 2, Recipient Initation, to sender
/// -Receiver receives inputs, outputs xS * G and kS * G
/// -Receiver picks random blinding factors for all outputs being received,
/// computes total blinding
/// excess xR
/// -Receiver picks random nonce kR
/// -Receiver computes Schnorr challenge e = H(M | kR * G + kS * G)
/// -Receiver computes their part of signature, sR = kR + e * xR
/// -Receiver responds with sR, blinding excess xR * G, public nonce kR * G
fn handle_sender_initiation(
config: &WalletConfig,
context_manager: &mut aggsig::ContextManager,
keychain: &Keychain,
partial_tx: &PartialTx,
) -> Result<PartialTx, Error> {
let (amount, _sender_pub_blinding, sender_pub_nonce, kernel_offset, _sig, tx) =
read_partial_tx(keychain, partial_tx)?;
// Create a potential output for this transaction
let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
keys::next_available_key(&wallet_data, keychain)
})?;
let partial_tx =
transaction::recipient_initiation(keychain, context_manager, partial_tx, &key_id)?;
let mut context = context_manager.get_context(&partial_tx.id);
context.add_output(&key_id);
// Add the output to our wallet
let _ = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
wallet_data.add_output(OutputData {
root_key_id: keychain.root_key_id(),
key_id: key_id.clone(),
n_child: derivation,
value: partial_tx.amount - context.fee,
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
is_coinbase: false,
block: None,
merkle_proof: None,
});
})?;
context_manager.save_context(context);
Ok(partial_tx)
}
fn handle_sender_confirmation(
config: &WalletConfig,
context_manager: &mut aggsig::ContextManager,
keychain: &Keychain,
partial_tx: &PartialTx,
fluff: bool,
) -> Result<Transaction, Error> {
let context = context_manager.get_context(&partial_tx.id);
// Get output we created in earlier step
// TODO: will just be one for now, support multiple later
let output_vec = context.get_outputs();
let root_key_id = keychain.root_key_id();
// double check the fee amount included in the partial tx
// we don't necessarily want to just trust the sender
// we could just overwrite the fee here (but we won't) due to the ecdsa sig
let fee = tx_fee(
tx.inputs.len(),
tx.outputs.len() + 1,
tx.input_proofs_count(),
None,
);
if fee > tx.fee() {
return Err(ErrorKind::FeeDispute {
sender_fee: tx.fee(),
recipient_fee: fee,
})?;
}
if fee > amount {
info!(
LOGGER,
"Rejected the transfer because transaction fee ({}) exceeds received amount ({}).",
amount_to_hr_string(fee),
amount_to_hr_string(amount)
);
return Err(ErrorKind::FeeExceedsAmount {
sender_amount: amount,
recipient_fee: fee,
})?;
}
let out_amount = amount - tx.fee();
// First step is just to get the excess sum of the outputs we're participating
// in Output and key needs to be stored until transaction finalisation time,
// somehow
let key_id = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
let (key_id, derivation) = next_available_key(&wallet_data, keychain);
// operate within a lock on wallet data
let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
let (key_id, derivation) = keys::retrieve_existing_key(&wallet_data, output_vec[0].clone());
wallet_data.add_output(OutputData {
root_key_id: root_key_id.clone(),
key_id: key_id.clone(),
n_child: derivation,
value: out_amount,
value: partial_tx.amount - context.fee,
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
@ -119,135 +113,17 @@ fn handle_sender_initiation(
merkle_proof: None,
});
key_id
(key_id, derivation)
})?;
// Still handy for getting the blinding sum
let (_, blind_sum) =
build::partial_transaction(vec![build::output(out_amount, key_id.clone())], keychain)
.context(ErrorKind::Keychain)?;
warn!(LOGGER, "Creating new aggsig context");
// Create a new aggsig context
// this will create a new blinding sum and nonce, and store them
let blind = blind_sum
.secret_key(&keychain.secp())
.context(ErrorKind::Keychain)?;
let mut context = context_manager.create_context(keychain.secp(), &partial_tx.id, blind);
context.add_output(&key_id);
let sig_part = context
.calculate_partial_sig(
keychain.secp(),
&sender_pub_nonce,
tx.fee(),
tx.lock_height(),
)
.unwrap();
// Build the response, which should contain sR, blinding excess xR * G, public
// nonce kR * G
let mut partial_tx = build_partial_tx(
&context,
// In this case partial_tx contains other party's pubkey info
let final_tx = transaction::finalize_transaction(
keychain,
amount,
kernel_offset,
Some(sig_part),
tx,
);
partial_tx.phase = PartialTxPhase::ReceiverInitiation;
context_manager.save_context(context);
Ok(partial_tx)
}
/// Receive Part 3 of interactive transactions from sender, Sender Confirmation
/// Return Ok/Error
/// -Receiver receives sS
/// -Receiver verifies sender's sig, by verifying that
/// kS * G + e *xS * G = sS* G
/// -Receiver calculates final sig as s=(sS+sR, kS * G+kR * G)
/// -Receiver puts into TX kernel:
///
/// Signature S
/// pubkey xR * G+xS * G
/// fee (= M)
/// -Receiver sends completed TX to mempool. responds OK to sender
fn handle_sender_confirmation(
config: &WalletConfig,
context_manager: &mut aggsig::ContextManager,
keychain: &Keychain,
partial_tx: &PartialTx,
fluff: bool,
) -> Result<PartialTx, Error> {
let (amount, sender_pub_blinding, sender_pub_nonce, kernel_offset, sender_sig_part, tx) =
read_partial_tx(keychain, partial_tx)?;
let mut context = context_manager.get_context(&partial_tx.id);
let sender_sig_part = sender_sig_part.unwrap();
let res = context.verify_partial_sig(
&keychain.secp(),
&sender_sig_part,
&sender_pub_nonce,
&sender_pub_blinding,
tx.fee(),
tx.lock_height(),
);
if !res {
error!(LOGGER, "Partial Sig from sender invalid.");
return Err(ErrorKind::Signature("Partial Sig from sender invalid."))?;
}
// Just calculate our sig part again instead of storing
let our_sig_part = context
.calculate_partial_sig(
&keychain.secp(),
&sender_pub_nonce,
tx.fee(),
tx.lock_height(),
)
.unwrap();
// And the final signature
let final_sig = context
.calculate_final_sig(
&keychain.secp(),
&sender_sig_part,
&our_sig_part,
&sender_pub_nonce,
)
.unwrap();
// Calculate the final public key (for our own sanity check)
let final_pubkey = context
.calculate_final_pubkey(&keychain.secp(), &sender_pub_blinding)
.unwrap();
// Check our final sig verifies
let res = context.verify_final_sig_build_msg(
&keychain.secp(),
&final_sig,
&final_pubkey,
tx.fee(),
tx.lock_height(),
);
if !res {
error!(LOGGER, "Final aggregated signature invalid.");
return Err(ErrorKind::Signature("Final aggregated signature invalid."))?;
}
let final_tx = build_final_transaction(
&mut context,
config,
keychain,
amount,
kernel_offset,
&final_sig,
tx.clone(),
context_manager,
partial_tx,
partial_tx,
&key_id,
derivation,
)?;
let tx_hex = to_hex(ser::ser_vec(&final_tx).unwrap());
@ -263,20 +139,7 @@ fn handle_sender_confirmation(
}
api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }).context(ErrorKind::Node)?;
// Return what we've actually posted
// TODO - why build_partial_tx here? Just a naming issue?
let mut partial_tx = build_partial_tx(
&context,
keychain,
amount,
kernel_offset,
Some(final_sig),
tx,
);
context_manager.save_context(context);
partial_tx.phase = PartialTxPhase::ReceiverConfirmation;
Ok(partial_tx)
Ok(final_tx)
}
/// Component used to receive coins, implements all the receiving end of the
@ -345,23 +208,7 @@ impl Handler for WalletReceiver {
}
}
fn retrieve_existing_key(wallet_data: &WalletData, key_id: Identifier) -> (Identifier, u32) {
if let Some(existing) = wallet_data.get_output(&key_id) {
let key_id = existing.key_id.clone();
let derivation = existing.n_child;
(key_id, derivation)
} else {
panic!("should never happen");
}
}
fn next_available_key(wallet_data: &WalletData, keychain: &Keychain) -> (Identifier, u32) {
let root_key_id = keychain.root_key_id();
let derivation = wallet_data.next_child(root_key_id.clone());
let key_id = keychain.derive_key_id(derivation).unwrap();
(key_id, derivation)
}
//TODO: Split up the output creation and the wallet insertion
/// Build a coinbase output and the corresponding kernel
pub fn receive_coinbase(
config: &WalletConfig,
@ -377,8 +224,8 @@ pub fn receive_coinbase(
let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
let key_id = block_fees.key_id();
let (key_id, derivation) = match key_id {
Some(key_id) => retrieve_existing_key(&wallet_data, key_id),
None => next_available_key(&wallet_data, keychain),
Some(key_id) => keys::retrieve_existing_key(&wallet_data, key_id),
None => keys::next_available_key(&wallet_data, keychain),
};
// track the new output and return the stuff needed for reward
@ -415,129 +262,3 @@ pub fn receive_coinbase(
/* .context(ErrorKind::Keychain)?; */
Ok((out, kern, block_fees))
}
/// builds a final transaction after the aggregated sig exchange
fn build_final_transaction(
context: &mut aggsig::Context,
config: &WalletConfig,
keychain: &Keychain,
amount: u64,
kernel_offset: BlindingFactor,
excess_sig: &secp::Signature,
tx: Transaction,
) -> Result<Transaction, Error> {
let root_key_id = keychain.root_key_id();
// double check the fee amount included in the partial tx
// we don't necessarily want to just trust the sender
// we could just overwrite the fee here (but we won't) due to the ecdsa sig
let fee = tx_fee(
tx.inputs.len(),
tx.outputs.len() + 1,
tx.input_proofs_count(),
None,
);
if fee > tx.fee() {
return Err(ErrorKind::FeeDispute {
sender_fee: tx.fee(),
recipient_fee: fee,
})?;
}
if fee > amount {
info!(
LOGGER,
"Rejected the transfer because transaction fee ({}) exceeds received amount ({}).",
amount_to_hr_string(fee),
amount_to_hr_string(amount)
);
return Err(ErrorKind::FeeExceedsAmount {
sender_amount: amount,
recipient_fee: fee,
})?;
}
let out_amount = amount - tx.fee();
// Get output we created in earlier step
// TODO: will just be one for now, support multiple later
let output_vec = context.get_outputs();
// operate within a lock on wallet data
let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
let (key_id, derivation) = retrieve_existing_key(&wallet_data, output_vec[0].clone());
wallet_data.add_output(OutputData {
root_key_id: root_key_id.clone(),
key_id: key_id.clone(),
n_child: derivation,
value: out_amount,
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
is_coinbase: false,
block: None,
merkle_proof: None,
});
(key_id, derivation)
})?;
// Build final transaction, the sum of which should
// be the same as the exchanged excess values
let mut final_tx = build::transaction(
vec![
build::initial_tx(tx),
build::output(out_amount, key_id.clone()),
build::with_offset(kernel_offset),
],
keychain,
).context(ErrorKind::Keychain)?;
// build the final excess based on final tx and offset
let final_excess = {
// TODO - do we need to verify rangeproofs here?
for x in &final_tx.outputs {
x.verify_proof().context(ErrorKind::Transaction)?;
}
// sum the input/output commitments on the final tx
let overage = final_tx.fee() as i64;
let tx_excess = final_tx
.sum_commitments(overage, None)
.context(ErrorKind::Transaction)?;
// subtract the kernel_excess (built from kernel_offset)
let offset_excess = keychain
.secp()
.commit(0, kernel_offset.secret_key(&keychain.secp()).unwrap())
.unwrap();
keychain
.secp()
.commit_sum(vec![tx_excess], vec![offset_excess])
.context(ErrorKind::Transaction)?
};
// update the tx kernel to reflect the offset excess and sig
assert_eq!(final_tx.kernels.len(), 1);
final_tx.kernels[0].excess = final_excess.clone();
final_tx.kernels[0].excess_sig = excess_sig.clone();
// confirm the kernel verifies successfully before proceeding
final_tx.kernels[0]
.verify()
.context(ErrorKind::Transaction)?;
// confirm the overall transaction is valid (including the updated kernel)
let _ = final_tx.validate().context(ErrorKind::Transaction)?;
debug!(
LOGGER,
"Finalized transaction and built output - {:?}, {:?}, {}",
root_key_id.clone(),
key_id.clone(),
derivation,
);
Ok(final_tx)
}

View file

@ -12,20 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use rand::thread_rng;
use uuid::Uuid;
use api;
use client;
use checker;
use core::core::{amount_to_hr_string, Transaction};
use libwallet::{aggsig, build};
use core::core::amount_to_hr_string;
use libwallet::{aggsig, build, transaction};
use grinwallet::selection;
use core::ser;
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
use keychain::{Identifier, Keychain};
use receiver::TxWrapper;
use types::*;
use util::LOGGER;
use util::secp::key::SecretKey;
use util;
use failure::ResultExt;
@ -45,13 +44,19 @@ pub fn issue_send_tx(
) -> Result<(), Error> {
checker::refresh_outputs(config, keychain)?;
// Create a new aggsig context
let mut context_manager = aggsig::ContextManager::new();
let tx_id = Uuid::new_v4();
// Get lock height
let chain_tip = checker::get_tip_from_node(config)?;
let current_height = chain_tip.height;
// ensure outputs we're selecting are up to date
checker::refresh_outputs(config, keychain)?;
// proof of concept - set lock_height on the tx
let lock_height = chain_tip.height;
let lock_height = current_height;
let (tx, blind, coins, change_key, amount_with_fee) = build_send_tx(
let tx_data = selection::build_send_tx(
config,
keychain,
amount,
@ -62,40 +67,22 @@ pub fn issue_send_tx(
selection_strategy_is_use_all,
)?;
// TODO - wrap this up in build_send_tx or even the build() call?
// Generate a random kernel offset here
// and subtract it from the blind_sum so we create
// the aggsig context with the "split" key
let kernel_offset =
BlindingFactor::from_secret_key(SecretKey::new(&keychain.secp(), &mut thread_rng()));
let partial_tx = transaction::sender_initiation(
keychain,
&tx_id,
&mut context_manager,
current_height,
tx_data,
)?;
let blind_offset = keychain
.blind_sum(&BlindSum::new()
.add_blinding_factor(blind)
.sub_blinding_factor(kernel_offset))
.unwrap();
//
// -Sender picks random blinding factors for all outputs it participates in,
// computes total blinding excess xS -Sender picks random nonce kS
// -Sender posts inputs, outputs, Message M=fee, xS * G and kS * G to Receiver
//
let tx_id = Uuid::new_v4();
let skey = blind_offset
.secret_key(&keychain.secp())
.context(ErrorKind::Keychain)?;
// Create a new aggsig context
let mut context_manager = aggsig::ContextManager::new();
let context = context_manager.create_context(keychain.secp(), &tx_id, skey);
let partial_tx = build_partial_tx(&context, keychain, amount_with_fee, kernel_offset, None, tx);
let context = context_manager.get_context(&tx_id);
// Closure to acquire wallet lock and lock the coins being spent
// so we avoid accidental double spend attempt.
let update_wallet = || {
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
for coin in coins {
for id in context.get_outputs().clone() {
let coin = wallet_data.get_output(&id).unwrap().clone();
wallet_data.lock_output(&coin);
}
})
@ -105,7 +92,7 @@ pub fn issue_send_tx(
// failure.
let rollback_wallet = || {
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
match change_key.clone() {
match context.change_key.clone() {
Some(change) => {
info!(LOGGER, "cleaning up unused change output from wallet");
wallet_data.delete_output(&change);
@ -126,7 +113,7 @@ pub fn issue_send_tx(
if &dest[..4] != "http" {
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
match change_key.clone() {
match context.change_key.clone() {
Some(change) => {
info!(LOGGER, "cleaning up unused change output from wallet");
wallet_data.delete_output(&change);
@ -163,51 +150,11 @@ pub fn issue_send_tx(
return Err(e);
}
/* -Sender receives xR * G, kR * G, sR
* -Sender computes Schnorr challenge e = H(M | kR * G + kS * G)
* -Sender verifies receivers sig, by verifying that kR * G + e * xR * G =
* sR * G· -Sender computes their part of signature, sS = kS + e * xS
* -Sender posts sS to receiver
*/
let (_amount, recp_pub_blinding, recp_pub_nonce, kernel_offset, sig, tx) =
read_partial_tx(keychain, &res.unwrap())?;
let res = context.verify_partial_sig(
&keychain.secp(),
&sig.unwrap(),
&recp_pub_nonce,
&recp_pub_blinding,
tx.fee(),
lock_height,
);
if !res {
error!(LOGGER, "Partial Sig from recipient invalid.");
return Err(ErrorKind::Signature("Partial Sig from recipient invalid."))?;
}
let partial_tx =
transaction::sender_confirmation(keychain, &mut context_manager, res.unwrap())?;
let sig_part = context
.calculate_partial_sig(
&keychain.secp(),
&recp_pub_nonce,
tx.fee(),
tx.lock_height(),
)
.unwrap();
// Build the next stage, containing sS (and our pubkeys again, for the
// recipient's convenience) offset has not been modified during tx building,
// so pass it back in
let mut partial_tx = build_partial_tx(
&context,
keychain,
amount_with_fee,
kernel_offset,
Some(sig_part),
tx,
);
partial_tx.phase = PartialTxPhase::SenderConfirmation;
// And send again
let res = client::send_partial_tx(&url, &partial_tx, fluff);
// And send again, expecting completed transaction as result this time
let res = client::send_partial_tx_final(&url, &partial_tx, fluff);
if let Err(e) = res {
match e.kind() {
ErrorKind::FeeExceedsAmount {sender_amount, recipient_fee} =>
@ -224,120 +171,13 @@ pub fn issue_send_tx(
}
// Not really necessary here
context_manager.save_context(context);
context_manager.save_context(context.clone());
// All good so
update_wallet()?;
Ok(())
}
/// Builds a transaction to send to someone from the HD seed associated with the
/// wallet and the amount to send. Handles reading through the wallet data file,
/// selecting outputs to spend and building the change.
fn build_send_tx(
config: &WalletConfig,
keychain: &Keychain,
amount: u64,
current_height: u64,
minimum_confirmations: u64,
lock_height: u64,
max_outputs: usize,
selection_strategy_is_use_all: bool,
) -> Result<
(
Transaction,
BlindingFactor,
Vec<OutputData>,
Option<Identifier>,
u64,
),
Error,
> {
let key_id = keychain.clone().root_key_id();
// select some spendable coins from the wallet
let mut coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
Ok(wallet_data.select_coins(
key_id.clone(),
amount,
current_height,
minimum_confirmations,
max_outputs,
selection_strategy_is_use_all,
))
})?;
// Get the maximum number of outputs in the wallet
let max_outputs = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
Ok(wallet_data.select_coins(
key_id.clone(),
amount,
current_height,
minimum_confirmations,
max_outputs,
true,
))
})?.len();
// sender is responsible for setting the fee on the partial tx
// recipient should double check the fee calculation and not blindly trust the
// sender
let mut fee;
// First attempt to spend without change
fee = tx_fee(coins.len(), 1, coins_proof_count(&coins), None);
let mut total: u64 = coins.iter().map(|c| c.value).sum();
let mut amount_with_fee = amount + fee;
if total == 0 {
return Err(ErrorKind::NotEnoughFunds(total as u64))?;
}
// Check if we need to use a change address
if total > amount_with_fee {
fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
amount_with_fee = amount + fee;
// Here check if we have enough outputs for the amount including fee otherwise
// look for other outputs and check again
while total < amount_with_fee {
// End the loop if we have selected all the outputs and still not enough funds
if coins.len() == max_outputs {
return Err(ErrorKind::NotEnoughFunds(total as u64))?;
}
// select some spendable coins from the wallet
coins = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
Ok(wallet_data.select_coins(
key_id.clone(),
amount_with_fee,
current_height,
minimum_confirmations,
max_outputs,
selection_strategy_is_use_all,
))
})?;
fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
total = coins.iter().map(|c| c.value).sum();
amount_with_fee = amount + fee;
}
}
// build transaction skeleton with inputs and change
let (mut parts, change_key) = inputs_and_change(&coins, config, keychain, amount, fee)?;
// This is more proof of concept than anything but here we set lock_height
// on tx being sent (based on current chain height via api).
parts.push(build::with_lock_height(lock_height));
let (tx, blind) = build::partial_transaction(parts, &keychain).context(ErrorKind::Keychain)?;
Ok((tx, blind, coins, change_key, amount_with_fee))
}
fn coins_proof_count(coins: &Vec<OutputData>) -> usize {
coins.iter().filter(|c| c.merkle_proof.is_some()).count()
}
pub fn issue_burn_tx(
config: &WalletConfig,
keychain: &Keychain,
@ -368,8 +208,8 @@ pub fn issue_burn_tx(
debug!(LOGGER, "selected some coins - {}", coins.len());
let fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
let (mut parts, _) = inputs_and_change(&coins, config, keychain, amount, fee)?;
let fee = tx_fee(coins.len(), 2, selection::coins_proof_count(&coins), None);
let (mut parts, _) = selection::inputs_and_change(&coins, config, keychain, amount, fee)?;
// add burn output and fees
parts.push(build::output(amount - fee, Identifier::zero()));
@ -385,77 +225,6 @@ pub fn issue_burn_tx(
Ok(())
}
fn inputs_and_change(
coins: &Vec<OutputData>,
config: &WalletConfig,
keychain: &Keychain,
amount: u64,
fee: u64,
) -> Result<(Vec<Box<build::Append>>, Option<Identifier>), Error> {
let mut parts = vec![];
// calculate the total across all inputs, and how much is left
let total: u64 = coins.iter().map(|c| c.value).sum();
parts.push(build::with_fee(fee));
// if we are spending 10,000 coins to send 1,000 then our change will be 9,000
// if the fee is 80 then the recipient will receive 1000 and our change will be
// 8,920
let change = total - amount - fee;
// build inputs using the appropriate derived key_ids
for coin in coins {
let key_id = keychain
.derive_key_id(coin.n_child)
.context(ErrorKind::Keychain)?;
if coin.is_coinbase {
let block = coin.block.clone();
let merkle_proof = coin.merkle_proof.clone();
let merkle_proof = merkle_proof.unwrap().merkle_proof();
parts.push(build::coinbase_input(
coin.value,
block.unwrap().hash(),
merkle_proof,
key_id,
));
} else {
parts.push(build::input(coin.value, key_id));
}
}
let change_key;
if change != 0 {
// track the output representing our change
change_key = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
let root_key_id = keychain.root_key_id();
let change_derivation = wallet_data.next_child(root_key_id.clone());
let change_key = keychain.derive_key_id(change_derivation).unwrap();
wallet_data.add_output(OutputData {
root_key_id: root_key_id.clone(),
key_id: change_key.clone(),
n_child: change_derivation,
value: change as u64,
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
is_coinbase: false,
block: None,
merkle_proof: None,
});
Some(change_key)
})?;
parts.push(build::output(change, change_key.clone().unwrap()));
} else {
change_key = None
}
Ok((parts, change_key))
}
#[cfg(test)]
mod test {
use libwallet::build;

View file

@ -276,7 +276,7 @@ impl<'de> serde::de::Visitor<'de> for MerkleProofWrapperVisitor {
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct BlockIdentifier(Hash);
pub struct BlockIdentifier(pub Hash);
impl BlockIdentifier {
pub fn hash(&self) -> Hash {
@ -767,6 +767,7 @@ pub struct PartialTx {
pub phase: PartialTxPhase,
pub id: Uuid,
pub amount: u64,
pub lock_height: u64,
pub public_blind_excess: String,
pub public_nonce: String,
pub kernel_offset: String,
@ -781,6 +782,7 @@ pub fn build_partial_tx(
context: &aggsig::Context,
keychain: &keychain::Keychain,
receive_amount: u64,
lock_height: u64,
kernel_offset: BlindingFactor,
part_sig: Option<secp::Signature>,
tx: Transaction,
@ -798,6 +800,7 @@ pub fn build_partial_tx(
phase: PartialTxPhase::SenderInitiation,
id: context.transaction_id,
amount: receive_amount,
lock_height: lock_height,
public_blind_excess: util::to_hex(pub_excess),
public_nonce: util::to_hex(pub_nonce),
kernel_offset: kernel_offset.to_hex(),
@ -816,6 +819,7 @@ pub fn read_partial_tx(
partial_tx: &PartialTx,
) -> Result<
(
u64,
u64,
PublicKey,
PublicKey,
@ -850,7 +854,15 @@ pub fn read_partial_tx(
let tx = ser::deserialize(&mut &tx_bin[..]).context(ErrorKind::GenericError(
"Could not deserialize transaction, invalid format.",
))?;
Ok((partial_tx.amount, blinding, nonce, kernel_offset, sig, tx))
Ok((
partial_tx.amount,
partial_tx.lock_height,
blinding,
nonce,
kernel_offset,
sig,
tx,
))
}
/// Amount in request to build a coinbase output.

197
wallet/tests/common/mod.rs Normal file
View file

@ -0,0 +1,197 @@
// Copyright 2018 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.
//! Common functions to facilitate wallet, walletlib and transaction testing
use std::collections::hash_map::Entry;
use std::collections::HashMap;
extern crate grin_api as api;
extern crate grin_chain as chain;
extern crate grin_core as core;
extern crate grin_keychain as keychain;
extern crate grin_wallet as wallet;
extern crate time;
use chain::Chain;
use core::core::{Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel};
use core::core::hash::Hashed;
use core::{consensus, global, pow};
use wallet::types::{BlockIdentifier, Error, ErrorKind, MerkleProofWrapper, OutputStatus,
WalletConfig, WalletData};
use wallet::{checker, BlockFees};
use keychain::Keychain;
use util::secp::pedersen;
/// Mostly for testing, refreshes output state against a local chain instance instead of
/// via an http API call
pub fn refresh_output_state_local(
config: &WalletConfig,
keychain: &Keychain,
chain: &chain::Chain,
) -> Result<(), Error> {
let wallet_outputs = checker::map_wallet_outputs(config, keychain)?;
let chain_outputs: Vec<api::Output> = wallet_outputs
.keys()
.map(|k| match get_output_local(chain, &k) {
Err(e) => panic!(e),
Ok(k) => k,
})
.collect();
let mut api_outputs: HashMap<pedersen::Commitment, api::Output> = HashMap::new();
for out in chain_outputs {
api_outputs.insert(out.commit.commit(), out);
}
checker::apply_api_outputs(config, &wallet_outputs, &api_outputs)?;
Ok(())
}
/// Return the spendable wallet balance from the local chain
/// (0:total, 1:amount_awaiting_confirmation, 2:confirmed but locked, 3:currently_spendable,
/// 4:locked total) TODO: Should be a wallet lib function with nicer return values
pub fn get_wallet_balances(
config: &WalletConfig,
keychain: &Keychain,
height: u64,
) -> Result<(u64, u64, u64, u64, u64), Error> {
let ret_val = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
let mut unspent_total = 0;
let mut unspent_but_locked_total = 0;
let mut unconfirmed_total = 0;
let mut locked_total = 0;
for out in wallet_data
.outputs
.values()
.filter(|out| out.root_key_id == keychain.root_key_id())
{
if out.status == OutputStatus::Unspent {
unspent_total += out.value;
if out.lock_height > height {
unspent_but_locked_total += out.value;
}
}
if out.status == OutputStatus::Unconfirmed && !out.is_coinbase {
unconfirmed_total += out.value;
}
if out.status == OutputStatus::Locked {
locked_total += out.value;
}
}
Ok((
unspent_total + unconfirmed_total, //total
unconfirmed_total, //amount_awaiting_confirmation
unspent_but_locked_total, // confirmed but locked
unspent_total - unspent_but_locked_total, // currently spendable
locked_total, // locked total
))
});
ret_val
}
/// Get an output from the chain locally and present it back as an API output
fn get_output_local(
chain: &chain::Chain,
commit: &pedersen::Commitment,
) -> Result<api::Output, Error> {
let outputs = [
OutputIdentifier::new(OutputFeatures::DEFAULT_OUTPUT, commit),
OutputIdentifier::new(OutputFeatures::COINBASE_OUTPUT, commit),
];
for x in outputs.iter() {
if let Ok(_) = chain.is_unspent(&x) {
return Ok(api::Output::new(&commit));
}
}
Err(ErrorKind::Transaction)?
}
/// Adds a block with a given reward to the chain and mines it
pub fn add_block_with_reward(chain: &Chain, txs: Vec<&Transaction>, reward: (Output, TxKernel)) {
let prev = chain.head_header().unwrap();
let difficulty = consensus::next_difficulty(chain.difficulty_iter()).unwrap();
let mut b = core::core::Block::new(&prev, txs, difficulty.clone(), reward).unwrap();
b.header.timestamp = prev.timestamp + time::Duration::seconds(60);
chain.set_txhashset_roots(&mut b, false).unwrap();
pow::pow_size(
&mut b.header,
difficulty,
global::proofsize(),
global::sizeshift(),
).unwrap();
chain.process_block(b, chain::Options::MINE).unwrap();
chain.validate(false).unwrap();
}
/// adds a reward output to a wallet, includes that reward in a block, mines the block
/// and adds it to the chain, with option transactions included.
/// Helpful for building up precise wallet balances for testing.
pub fn award_block_to_wallet(
chain: &Chain,
txs: Vec<&Transaction>,
wallet: &(WalletConfig, Keychain),
) {
let prev = chain.head_header().unwrap();
let fee_amt = txs.iter().map(|tx| tx.fee()).sum();
let fees = BlockFees {
fees: fee_amt,
key_id: None,
height: prev.height + 1,
};
let coinbase_tx = wallet::receiver::receive_coinbase(&wallet.0, &wallet.1, &fees);
let (coinbase_tx, fees) = match coinbase_tx {
Ok(t) => ((t.0, t.1), t.2),
Err(e) => {
panic!("Unable to create block reward: {:?}", e);
}
};
add_block_with_reward(chain, txs, coinbase_tx.clone());
// build merkle proof and block identifier and save in wallet
let output_id = OutputIdentifier::from_output(&coinbase_tx.0.clone());
let m_proof = chain.get_merkle_proof(&output_id, &chain.head_header().unwrap());
let block_id = Some(BlockIdentifier(chain.head_header().unwrap().hash()));
let _ = WalletData::with_wallet(&wallet.0.data_file_dir, |wallet_data| {
if let Entry::Occupied(mut output) = wallet_data
.outputs
.entry(fees.key_id.as_ref().unwrap().to_hex())
{
let output = output.get_mut();
output.block = block_id;
output.merkle_proof = Some(MerkleProofWrapper(m_proof.unwrap()));
}
});
}
/// adds many block rewards to a wallet, no transactions
pub fn award_blocks_to_wallet(
chain: &Chain,
wallet: &(WalletConfig, Keychain),
num_rewards: usize,
) {
for _ in 0..num_rewards {
award_block_to_wallet(chain, vec![], wallet);
}
}
/// Create a new wallet in a particular directory
pub fn create_wallet(dir: &str) -> (WalletConfig, Keychain) {
let mut wallet_config = WalletConfig::default();
wallet_config.data_file_dir = String::from(dir);
let wallet_seed = wallet::WalletSeed::init_file(&wallet_config).unwrap();
let keychain = wallet_seed
.derive_keychain("")
.expect("Failed to derive keychain from seed file and passphrase.");
(wallet_config, keychain)
}

235
wallet/tests/transaction.rs Normal file
View file

@ -0,0 +1,235 @@
// Copyright 2018 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.
//! tests for transactions building within libwallet
extern crate grin_chain as chain;
extern crate grin_core as core;
extern crate grin_keychain as keychain;
extern crate grin_util as util;
extern crate grin_wallet as wallet;
extern crate rand;
#[macro_use]
extern crate slog;
extern crate time;
extern crate uuid;
mod common;
use std::fs;
use std::sync::Arc;
use uuid::Uuid;
use chain::Chain;
use chain::types::*;
use core::{global, pow};
use core::global::ChainTypes;
use wallet::libwallet::{aggsig, transaction};
use wallet::grinwallet::{keys, selection};
use wallet::types::{OutputData, OutputStatus, WalletData};
use util::LOGGER;
fn clean_output_dir(test_dir: &str) {
let _ = fs::remove_dir_all(test_dir);
}
fn setup(test_dir: &str, chain_dir: &str) -> Chain {
util::init_test_logger();
clean_output_dir(test_dir);
global::set_mining_mode(ChainTypes::AutomatedTesting);
let genesis_block = pow::mine_genesis_block().unwrap();
let dir_name = format!("{}/{}", test_dir, chain_dir);
chain::Chain::init(
dir_name.to_string(),
Arc::new(NoopAdapter {}),
genesis_block,
pow::verify_size,
).unwrap()
}
/// Build a transaction between 2 parties
#[cfg(test)]
#[test]
fn build_transaction() {
let chain = setup("test_output", "build_transaction/.grin");
let wallet1 = common::create_wallet("test_output/build_transaction/wallet1");
let wallet2 = common::create_wallet("test_output/build_transaction/wallet2");
common::award_blocks_to_wallet(&chain, &wallet1, 10);
// Wallet 1 has 600 Grins, wallet 2 has 0. Create a transaction that sends
// 300 Grins from wallet 1 to wallet 2, using libwallet
// Sender creates a new aggsig context
// SENDER (create sender initiation)
let mut sender_context_manager = aggsig::ContextManager::new();
let tx_id = Uuid::new_v4();
// Get lock height
let chain_tip = chain.head().unwrap();
// ensure outputs we're selecting are up to date
let res = common::refresh_output_state_local(&wallet1.0, &wallet1.1, &chain);
let amount = 300_000_000_000;
// Select our outputs
let tx_data = selection::build_send_tx(
&wallet1.0,
&wallet1.1,
amount,
chain_tip.height,
3,
chain_tip.height,
1000,
true,
).unwrap();
if let Err(e) = res {
panic!("Unable to refresh sender wallet outputs: {}", e);
}
let partial_tx = transaction::sender_initiation(
&wallet1.1,
&tx_id,
&mut sender_context_manager,
chain_tip.height,
tx_data,
).unwrap();
let sender_context = sender_context_manager.get_context(&tx_id);
// TODO: Might make more sense to do this before the transaction
// building call
// Closure to acquire wallet lock and lock the coins being spent
// so we avoid accidental double spend attempt.
let update_sender_wallet = || {
WalletData::with_wallet(&wallet1.0.data_file_dir, |wallet_data| {
for id in sender_context.get_outputs().clone() {
let coin = wallet_data.get_output(&id).unwrap().clone();
wallet_data.lock_output(&coin);
}
})
};
debug!(LOGGER, "PartialTx after step 1: sender initiation");
debug!(LOGGER, "-----------------------------------------");
debug!(LOGGER, "{:?}", partial_tx);
// RECIPIENT (Handle sender initiation)
let mut recipient_context_manager = aggsig::ContextManager::new();
// Create a potential output for this transaction
let (key_id, derivation) = WalletData::with_wallet(&wallet2.0.data_file_dir, |wallet_data| {
keys::next_available_key(&wallet_data, &wallet2.1)
}).unwrap();
let partial_tx = transaction::recipient_initiation(
&wallet2.1,
&mut recipient_context_manager,
&partial_tx,
&key_id,
).unwrap();
let mut context = recipient_context_manager.get_context(&partial_tx.id);
// Add the output to recipient's wallet
let _ = WalletData::with_wallet(&wallet2.0.data_file_dir, |wallet_data| {
wallet_data.add_output(OutputData {
root_key_id: wallet2.1.root_key_id(),
key_id: key_id.clone(),
n_child: derivation,
value: partial_tx.amount - context.fee,
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
is_coinbase: false,
block: None,
merkle_proof: None,
});
}).unwrap();
context.add_output(&key_id);
recipient_context_manager.save_context(context);
debug!(LOGGER, "PartialTx after step 2: recipient initiation");
debug!(LOGGER, "--------------------------------------------");
debug!(LOGGER, "{:?}", partial_tx);
// TODO: We want to allow the sender to be able to calculate this, but also need
// the recipient's output information available, and the recipient needs to know
// whether to finalize the output in their wallet
let _tx_with_recipients_pubkeys = partial_tx.clone();
// SENDER Part 3: Sender confirmation
let partial_tx =
transaction::sender_confirmation(&wallet1.1, &mut sender_context_manager, partial_tx)
.unwrap();
debug!(LOGGER, "PartialTx after step 3: sender confirmation");
debug!(LOGGER, "--------------------------------------------");
debug!(LOGGER, "{:?}", partial_tx);
// RECIPIENT Part 4: Recipient confirmation
// Get output we created in earlier step
let context = recipient_context_manager.get_context(&partial_tx.id);
let output_vec = context.get_outputs();
let root_key_id = &wallet2.1.root_key_id();
// operate within a lock on wallet data
let (key_id, derivation) = WalletData::with_wallet(&wallet2.0.data_file_dir, |wallet_data| {
let (key_id, derivation) = keys::retrieve_existing_key(&wallet_data, output_vec[0].clone());
wallet_data.add_output(OutputData {
root_key_id: root_key_id.clone(),
key_id: key_id.clone(),
n_child: derivation,
value: partial_tx.amount - context.fee,
status: OutputStatus::Unconfirmed,
height: 0,
lock_height: 0,
is_coinbase: false,
block: None,
merkle_proof: None,
});
(key_id, derivation)
}).unwrap();
let final_tx_recipient = transaction::finalize_transaction(
&wallet2.1,
&mut recipient_context_manager,
&partial_tx,
&partial_tx,
&key_id,
derivation,
);
if let Err(e) = final_tx_recipient {
panic!("Error creating final tx: {:?}", e);
}
debug!(LOGGER, "Recipient calculates final transaction as:");
debug!(LOGGER, "--------------------------------------------");
debug!(LOGGER, "{:?}", final_tx_recipient);
let _ = update_sender_wallet();
// Insert this transaction into a new block, then mine till confirmation
common::award_block_to_wallet(&chain, vec![&final_tx_recipient.unwrap()], &wallet1);
common::award_blocks_to_wallet(&chain, &wallet1, 3);
// Refresh wallets
let res = common::refresh_output_state_local(&wallet2.0, &wallet2.1, &chain);
if let Err(e) = res {
panic!("Error refreshing output state for wallet: {:?}", e);
}
let chain_tip = chain.head().unwrap();
let balances = common::get_wallet_balances(&wallet2.0, &wallet2.1, chain_tip.height).unwrap();
assert_eq!(balances.3, 300_000_000_000);
}