mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21:08 +03:00
[WIP] Wallet refactor - part 3 (#1072)
* Beginning to rework aggsig library workflow * more refactoring of transaction api * whoever does round 1 first creates offset * slate finalisation now context-free, so anyone can do it * remove concept of transaction phase * remove slate phase enum * update actual send/receive code with new transaction lib workflow
This commit is contained in:
parent
4bbaa8d05f
commit
85285473bd
15 changed files with 901 additions and 1102 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -778,7 +778,7 @@ dependencies = [
|
||||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"secp256k1zkp 0.7.1 (git+https://github.com/mimblewimble/rust-secp256k1-zkp?tag=grin_integration_16)",
|
"secp256k1zkp 0.7.1 (git+https://github.com/mimblewimble/rust-secp256k1-zkp?tag=grin_integration_19)",
|
||||||
"serde 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"slog 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"slog 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -1584,7 +1584,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "secp256k1zkp"
|
name = "secp256k1zkp"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
source = "git+https://github.com/mimblewimble/rust-secp256k1-zkp?tag=grin_integration_16#792660cb47acc1dafd509ff068275f86b57564d6"
|
source = "git+https://github.com/mimblewimble/rust-secp256k1-zkp?tag=grin_integration_19#800e9b3ea4a8b2df7b999980ae78b224a6ad07ce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
"arrayvec 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
|
"gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -2472,7 +2472,7 @@ dependencies = [
|
||||||
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
|
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
|
||||||
"checksum scoped-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8674d439c964889e2476f474a3bf198cc9e199e77499960893bac5de7e9218a4"
|
"checksum scoped-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8674d439c964889e2476f474a3bf198cc9e199e77499960893bac5de7e9218a4"
|
||||||
"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
|
"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
|
||||||
"checksum secp256k1zkp 0.7.1 (git+https://github.com/mimblewimble/rust-secp256k1-zkp?tag=grin_integration_16)" = "<none>"
|
"checksum secp256k1zkp 0.7.1 (git+https://github.com/mimblewimble/rust-secp256k1-zkp?tag=grin_integration_19)" = "<none>"
|
||||||
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||||
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
"checksum sequence_trie 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c915714ca833b1d4d6b8f6a9d72a3ff632fe45b40a8d184ef79c81bec6327eed"
|
"checksum sequence_trie 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c915714ca833b1d4d6b8f6a9d72a3ff632fe45b40a8d184ef79c81bec6327eed"
|
||||||
|
|
|
@ -13,20 +13,20 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//! Transactions
|
//! Transactions
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::cmp::max;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::{error, fmt};
|
||||||
|
use util::secp::pedersen::{Commitment, ProofMessage, RangeProof};
|
||||||
use util::secp::{self, Message, Signature};
|
use util::secp::{self, Message, Signature};
|
||||||
use util::{kernel_sig_msg, static_secp_instance};
|
use util::{kernel_sig_msg, static_secp_instance};
|
||||||
use util::secp::pedersen::{Commitment, ProofMessage, RangeProof};
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::cmp::max;
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::{error, fmt};
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
use consensus;
|
use consensus;
|
||||||
use consensus::VerifySortOrder;
|
use consensus::VerifySortOrder;
|
||||||
|
use core::BlockHeader;
|
||||||
use core::Committed;
|
use core::Committed;
|
||||||
use core::global;
|
use core::global;
|
||||||
use core::BlockHeader;
|
|
||||||
use core::hash::{Hash, Hashed, ZERO_HASH};
|
use core::hash::{Hash, Hashed, ZERO_HASH};
|
||||||
use core::pmmr::MerkleProof;
|
use core::pmmr::MerkleProof;
|
||||||
use keychain;
|
use keychain;
|
||||||
|
@ -188,15 +188,8 @@ impl TxKernel {
|
||||||
let secp = secp.lock().unwrap();
|
let secp = secp.lock().unwrap();
|
||||||
let sig = &self.excess_sig;
|
let sig = &self.excess_sig;
|
||||||
// Verify aggsig directly in libsecp
|
// Verify aggsig directly in libsecp
|
||||||
let pubkeys = &self.excess.to_two_pubkeys(&secp);
|
let pubkey = &self.excess.to_pubkey(&secp)?;
|
||||||
let mut valid = false;
|
if !secp::aggsig::verify_single(&secp, &sig, &msg, None, &pubkey, false) {
|
||||||
for i in 0..pubkeys.len() {
|
|
||||||
valid = secp::aggsig::verify_single(&secp, &sig, &msg, None, &pubkeys[i], false);
|
|
||||||
if valid {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !valid {
|
|
||||||
return Err(secp::Error::IncorrectSignature);
|
return Err(secp::Error::IncorrectSignature);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -400,8 +393,9 @@ impl Transaction {
|
||||||
|
|
||||||
/// To verify transaction kernels we check that -
|
/// To verify transaction kernels we check that -
|
||||||
/// * all kernels have an even fee
|
/// * all kernels have an even fee
|
||||||
/// * sum of input/output commitments matches sum of kernel commitments after applying offset
|
/// * sum of input/output commitments matches sum of kernel commitments
|
||||||
/// * each kernel sig is valid (i.e. tx commitments sum to zero, given above is true)
|
/// after applying offset * each kernel sig is valid (i.e. tx commitments
|
||||||
|
/// sum to zero, given above is true)
|
||||||
fn verify_kernels(&self) -> Result<(), Error> {
|
fn verify_kernels(&self) -> Result<(), Error> {
|
||||||
// Verify all the output rangeproofs.
|
// Verify all the output rangeproofs.
|
||||||
// Note: this is expensive.
|
// Note: this is expensive.
|
||||||
|
@ -480,10 +474,11 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We can verify the Merkle proof (for coinbase inputs) here in isolation.
|
/// We can verify the Merkle proof (for coinbase inputs) here in isolation.
|
||||||
/// But we cannot check the following as we need data from the index and the PMMR.
|
/// But we cannot check the following as we need data from the index and
|
||||||
/// So we must be sure to check these at the appropriate point during block validation.
|
/// the PMMR. So we must be sure to check these at the appropriate point
|
||||||
/// * node is in the correct pos in the PMMR
|
/// during block validation. * node is in the correct pos in the PMMR
|
||||||
/// * block is the correct one (based on output_root from block_header via the index)
|
/// * block is the correct one (based on output_root from block_header
|
||||||
|
/// via the index)
|
||||||
fn verify_inputs(&self) -> Result<(), Error> {
|
fn verify_inputs(&self) -> Result<(), Error> {
|
||||||
let coinbase_inputs = self.inputs
|
let coinbase_inputs = self.inputs
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -704,7 +699,8 @@ pub struct Input {
|
||||||
/// Currently we only care about this for coinbase outputs.
|
/// Currently we only care about this for coinbase outputs.
|
||||||
pub block_hash: Option<Hash>,
|
pub block_hash: Option<Hash>,
|
||||||
/// The Merkle Proof that shows the output being spent by this input
|
/// The Merkle Proof that shows the output being spent by this input
|
||||||
/// existed and was unspent at the time of this block (proof of inclusion in output_root)
|
/// existed and was unspent at the time of this block (proof of inclusion
|
||||||
|
/// in output_root)
|
||||||
pub merkle_proof: Option<MerkleProof>,
|
pub merkle_proof: Option<MerkleProof>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -761,9 +757,9 @@ impl Readable for Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The input for a transaction, which spends a pre-existing unspent output.
|
/// The input for a transaction, which spends a pre-existing unspent output.
|
||||||
/// The input commitment is a reproduction of the commitment of the output being spent.
|
/// The input commitment is a reproduction of the commitment of the output
|
||||||
/// Input must also provide the original output features and the hash of the block
|
/// being spent. Input must also provide the original output features and the
|
||||||
/// the output originated from.
|
/// hash of the block the output originated from.
|
||||||
impl Input {
|
impl Input {
|
||||||
/// Build a new input from the data required to identify and verify an
|
/// Build a new input from the data required to identify and verify an
|
||||||
/// output being spent.
|
/// output being spent.
|
||||||
|
@ -781,9 +777,10 @@ impl Input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The input commitment which _partially_ identifies the output being spent.
|
/// The input commitment which _partially_ identifies the output being
|
||||||
/// In the presence of a fork we need additional info to uniquely identify the output.
|
/// spent. In the presence of a fork we need additional info to uniquely
|
||||||
/// Specifically the block hash (to correctly calculate lock_height for coinbase outputs).
|
/// identify the output. Specifically the block hash (to correctly
|
||||||
|
/// calculate lock_height for coinbase outputs).
|
||||||
pub fn commitment(&self) -> Commitment {
|
pub fn commitment(&self) -> Commitment {
|
||||||
self.commit.clone()
|
self.commit.clone()
|
||||||
}
|
}
|
||||||
|
@ -795,29 +792,33 @@ impl Input {
|
||||||
block_hash.unwrap_or(Hash::default())
|
block_hash.unwrap_or(Hash::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function to return the (optional) merkle_proof for this input.
|
/// Convenience function to return the (optional) merkle_proof for this
|
||||||
/// Will return the "empty" Merkle proof if we do not have one.
|
/// input. Will return the "empty" Merkle proof if we do not have one.
|
||||||
/// We currently only care about the Merkle proof for inputs spending coinbase outputs.
|
/// We currently only care about the Merkle proof for inputs spending
|
||||||
|
/// coinbase outputs.
|
||||||
pub fn merkle_proof(&self) -> MerkleProof {
|
pub fn merkle_proof(&self) -> MerkleProof {
|
||||||
let merkle_proof = self.merkle_proof.clone();
|
let merkle_proof = self.merkle_proof.clone();
|
||||||
merkle_proof.unwrap_or(MerkleProof::empty())
|
merkle_proof.unwrap_or(MerkleProof::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify the maturity of an output being spent by an input.
|
/// Verify the maturity of an output being spent by an input.
|
||||||
/// Only relevant for spending coinbase outputs currently (locked for 1,000 confirmations).
|
/// Only relevant for spending coinbase outputs currently (locked for 1,000
|
||||||
|
/// confirmations).
|
||||||
///
|
///
|
||||||
/// The proof associates the output with the root by its hash (and pos) in the MMR.
|
/// The proof associates the output with the root by its hash (and pos) in
|
||||||
/// The proof shows the output existed and was unspent at the time the output_root was built.
|
/// the MMR. The proof shows the output existed and was unspent at the
|
||||||
/// The root associates the proof with a specific block header with that output_root.
|
/// time the output_root was built. The root associates the proof with a
|
||||||
/// So the proof shows the output was unspent at the time of the block
|
/// specific block header with that output_root. So the proof shows the
|
||||||
/// and is at least as old as that block (may be older).
|
/// output was unspent at the time of the block and is at least as old as
|
||||||
|
/// that block (may be older).
|
||||||
///
|
///
|
||||||
/// We can verify maturity of the output being spent by -
|
/// We can verify maturity of the output being spent by -
|
||||||
///
|
///
|
||||||
/// * verifying the Merkle Proof produces the correct root for the given hash (from MMR)
|
/// * verifying the Merkle Proof produces the correct root for the given
|
||||||
/// * verifying the root matches the output_root in the block_header
|
/// hash (from MMR) * verifying the root matches the output_root in the
|
||||||
/// * verifying the hash matches the node hash in the Merkle Proof
|
/// block_header * verifying the hash matches the node hash in the Merkle
|
||||||
/// * finally verify maturity rules based on height of the block header
|
/// Proof * finally verify maturity rules based on height of the block
|
||||||
|
/// header
|
||||||
///
|
///
|
||||||
pub fn verify_maturity(
|
pub fn verify_maturity(
|
||||||
&self,
|
&self,
|
||||||
|
@ -962,7 +963,8 @@ impl Output {
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub struct OutputIdentifier {
|
pub struct OutputIdentifier {
|
||||||
/// Output features (coinbase vs. regular transaction output)
|
/// Output features (coinbase vs. regular transaction output)
|
||||||
/// We need to include this when hashing to ensure coinbase maturity can be enforced.
|
/// We need to include this when hashing to ensure coinbase maturity can be
|
||||||
|
/// enforced.
|
||||||
pub features: OutputFeatures,
|
pub features: OutputFeatures,
|
||||||
/// Output commitment
|
/// Output commitment
|
||||||
pub commit: Commitment,
|
pub commit: Commitment,
|
||||||
|
@ -1121,8 +1123,8 @@ impl ProofMessageElements {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether our remainder is zero (as it should be if the BF and nonce used to unwind
|
/// Whether our remainder is zero (as it should be if the BF and nonce used
|
||||||
/// are correct
|
/// to unwind are correct
|
||||||
pub fn zeroes_correct(&self) -> bool {
|
pub fn zeroes_correct(&self) -> bool {
|
||||||
for i in 0..self.zeroes.len() {
|
for i in 0..self.zeroes.len() {
|
||||||
if self.zeroes[i] != 0 {
|
if self.zeroes[i] != 0 {
|
||||||
|
|
|
@ -27,15 +27,15 @@ extern crate grin_wallet as wallet;
|
||||||
|
|
||||||
mod framework;
|
mod framework;
|
||||||
|
|
||||||
use std::{thread, time};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use framework::{LocalServerContainer, LocalServerContainerConfig};
|
use framework::{LocalServerContainer, LocalServerContainerConfig};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::{thread, time};
|
||||||
|
|
||||||
use util::LOGGER;
|
use util::LOGGER;
|
||||||
|
|
||||||
/// Start 1 node mining and two wallets, then send a few
|
/// Start 1 node mining and two wallets, then send a few
|
||||||
/// transactions from one to the other
|
/// transactions from one to the other
|
||||||
//#[test]
|
#[test]
|
||||||
fn basic_wallet_transactions() {
|
fn basic_wallet_transactions() {
|
||||||
let test_name_dir = "test_servers";
|
let test_name_dir = "test_servers";
|
||||||
core::global::set_mining_mode(core::global::ChainTypes::AutomatedTesting);
|
core::global::set_mining_mode(core::global::ChainTypes::AutomatedTesting);
|
||||||
|
|
|
@ -20,6 +20,6 @@ zip = "0.2"
|
||||||
|
|
||||||
[dependencies.secp256k1zkp]
|
[dependencies.secp256k1zkp]
|
||||||
git = "https://github.com/mimblewimble/rust-secp256k1-zkp"
|
git = "https://github.com/mimblewimble/rust-secp256k1-zkp"
|
||||||
tag = "grin_integration_16"
|
tag = "grin_integration_19"
|
||||||
#path = "../../rust-secp256k1-zkp"
|
#path = "../../rust-secp256k1-zkp"
|
||||||
features = ["bullet-proof-sizing"]
|
features = ["bullet-proof-sizing"]
|
||||||
|
|
|
@ -12,18 +12,18 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
|
use futures::{Future, Stream};
|
||||||
use hyper;
|
use hyper;
|
||||||
use hyper::{Method, Request};
|
|
||||||
use hyper::header::ContentType;
|
use hyper::header::ContentType;
|
||||||
use tokio_core::reactor;
|
use hyper::{Method, Request};
|
||||||
|
use libwallet::transaction::Slate;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
use tokio_core::reactor;
|
||||||
|
|
||||||
use types::*;
|
|
||||||
use core::core::Transaction;
|
|
||||||
use util::LOGGER;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use types::*;
|
||||||
|
use util::LOGGER;
|
||||||
|
|
||||||
/// Call the wallet API to create a coinbase output for the given block_fees.
|
/// Call the wallet API to create a coinbase output for the given block_fees.
|
||||||
/// Will retry based on default "retry forever with backoff" behavior.
|
/// Will retry based on default "retry forever with backoff" behavior.
|
||||||
|
@ -40,7 +40,7 @@ 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> {
|
pub fn send_slate(url: &str, slate: &Slate, fluff: bool) -> Result<Slate, Error> {
|
||||||
let mut core = reactor::Core::new().context(ErrorKind::Hyper)?;
|
let mut core = reactor::Core::new().context(ErrorKind::Hyper)?;
|
||||||
let client = hyper::Client::new(&core.handle());
|
let client = hyper::Client::new(&core.handle());
|
||||||
|
|
||||||
|
@ -55,54 +55,20 @@ pub fn send_partial_tx(url: &str, partial_tx: &PartialTx, fluff: bool) -> Result
|
||||||
url_pool.parse::<hyper::Uri>().context(ErrorKind::Hyper)?,
|
url_pool.parse::<hyper::Uri>().context(ErrorKind::Hyper)?,
|
||||||
);
|
);
|
||||||
req.headers_mut().set(ContentType::json());
|
req.headers_mut().set(ContentType::json());
|
||||||
let json = serde_json::to_string(&partial_tx).context(ErrorKind::Hyper)?;
|
let json = serde_json::to_string(&slate).context(ErrorKind::Hyper)?;
|
||||||
req.set_body(json);
|
req.set_body(json);
|
||||||
|
|
||||||
let work = client.request(req).and_then(|res| {
|
let work = client.request(req).and_then(|res| {
|
||||||
res.body().concat2().and_then(move |body| {
|
res.body().concat2().and_then(move |body| {
|
||||||
let tx: PartialTx =
|
let slate: Slate =
|
||||||
serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
Ok(tx)
|
Ok(slate)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let res = core.run(work).context(ErrorKind::Hyper)?;
|
let res = core.run(work).context(ErrorKind::Hyper)?;
|
||||||
Ok(res)
|
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.
|
/// 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> {
|
fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||||
let mut core =
|
let mut core =
|
||||||
|
|
|
@ -17,13 +17,23 @@ use rand::thread_rng;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use core::core::{amount_to_hr_string, Committed, Transaction};
|
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;
|
use failure::ResultExt;
|
||||||
|
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
|
||||||
|
use libwallet::{aggsig, build};
|
||||||
|
use types::*;
|
||||||
|
use util::secp::Signature;
|
||||||
|
use util::secp::key::{PublicKey, SecretKey};
|
||||||
|
use util::{secp, LOGGER};
|
||||||
|
|
||||||
|
/// Get our next available key
|
||||||
|
pub fn new_output_key(
|
||||||
|
config: &WalletConfig,
|
||||||
|
keychain: &Keychain,
|
||||||
|
) -> Result<(Identifier, u32), Error> {
|
||||||
|
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||||
|
next_available_key(&wallet_data, keychain)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Get next available key in the wallet
|
/// Get next available key in the wallet
|
||||||
pub fn next_available_key(wallet_data: &WalletData, keychain: &Keychain) -> (Identifier, u32) {
|
pub fn next_available_key(wallet_data: &WalletData, keychain: &Keychain) -> (Identifier, u32) {
|
||||||
|
|
|
@ -14,19 +14,146 @@
|
||||||
|
|
||||||
//! Selection of inputs for building transactions
|
//! 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;
|
use failure::ResultExt;
|
||||||
|
use grinwallet::keys;
|
||||||
|
use keychain::{Identifier, Keychain};
|
||||||
|
use libwallet::{aggsig, build, transaction};
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Initialise a transaction on the sender side, returns a corresponding
|
||||||
|
/// libwallet transaction slate with the appropriate inputs selected,
|
||||||
|
/// and saves the private wallet identifiers of our selected outputs
|
||||||
|
/// into our transaction context
|
||||||
|
|
||||||
|
pub fn build_send_tx_slate(
|
||||||
|
config: &WalletConfig,
|
||||||
|
keychain: &Keychain,
|
||||||
|
context_manager: &mut aggsig::ContextManager,
|
||||||
|
num_participants: usize,
|
||||||
|
amount: u64,
|
||||||
|
current_height: u64,
|
||||||
|
minimum_confirmations: u64,
|
||||||
|
lock_height: u64,
|
||||||
|
max_outputs: usize,
|
||||||
|
selection_strategy_is_use_all: bool,
|
||||||
|
) -> Result<(transaction::Slate, impl FnOnce() -> Result<(), Error>), Error> {
|
||||||
|
let (elems, inputs, change_id, amount, fee) = select_send_tx(
|
||||||
|
config,
|
||||||
|
keychain,
|
||||||
|
amount,
|
||||||
|
current_height,
|
||||||
|
minimum_confirmations,
|
||||||
|
lock_height,
|
||||||
|
max_outputs,
|
||||||
|
selection_strategy_is_use_all,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Create public slate
|
||||||
|
let mut slate = transaction::Slate::blank(num_participants);
|
||||||
|
slate.amount = amount;
|
||||||
|
slate.height = current_height;
|
||||||
|
slate.lock_height = lock_height;
|
||||||
|
slate.fee = fee;
|
||||||
|
|
||||||
|
let blinding = slate.add_transaction_elements(keychain, elems)?;
|
||||||
|
// Create our own private context
|
||||||
|
let mut context = context_manager.create_context(
|
||||||
|
keychain.secp(),
|
||||||
|
&slate.id,
|
||||||
|
blinding.secret_key(keychain.secp()).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Store our private identifiers for each input
|
||||||
|
for input in inputs {
|
||||||
|
context.add_input(&input.key_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store change output
|
||||||
|
if change_id.is_some() {
|
||||||
|
context.add_output(&change_id.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let lock_inputs = context.get_inputs().clone();
|
||||||
|
let _lock_outputs = context.get_outputs().clone();
|
||||||
|
let data_file_dir = config.data_file_dir.clone();
|
||||||
|
|
||||||
|
// Return a closure to acquire wallet lock and lock the coins being spent
|
||||||
|
// so we avoid accidental double spend attempt.
|
||||||
|
let update_sender_wallet_fn = move || {
|
||||||
|
WalletData::with_wallet(&data_file_dir, |wallet_data| {
|
||||||
|
for id in lock_inputs {
|
||||||
|
let coin = wallet_data.get_output(&id).unwrap().clone();
|
||||||
|
wallet_data.lock_output(&coin);
|
||||||
|
}
|
||||||
|
// probably just want to leave as unconfirmed for now
|
||||||
|
// or create a new status
|
||||||
|
/*for id in lock_outputs {
|
||||||
|
let coin = wallet_data.get_output(&id).unwrap().clone();
|
||||||
|
wallet_data.lock_output(&coin);
|
||||||
|
}*/ })
|
||||||
|
};
|
||||||
|
|
||||||
|
context_manager.save_context(context);
|
||||||
|
|
||||||
|
Ok((slate, update_sender_wallet_fn))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new output in the wallet for the recipient,
|
||||||
|
/// returning the key of the fresh output and a closure
|
||||||
|
/// that actually performs the addition of the output to the
|
||||||
|
/// wallet
|
||||||
|
pub fn build_recipient_output_with_slate(
|
||||||
|
config: &WalletConfig,
|
||||||
|
keychain: &Keychain,
|
||||||
|
context_manager: &mut aggsig::ContextManager,
|
||||||
|
slate: &mut transaction::Slate,
|
||||||
|
) -> Result<(Identifier, impl FnOnce() -> Result<(), Error>), Error> {
|
||||||
|
// Create a potential output for this transaction
|
||||||
|
let (key_id, derivation) = keys::new_output_key(config, keychain)?;
|
||||||
|
|
||||||
|
let data_file_dir = config.data_file_dir.clone();
|
||||||
|
let root_key_id = keychain.root_key_id();
|
||||||
|
let key_id_inner = key_id.clone();
|
||||||
|
let amount = slate.amount;
|
||||||
|
|
||||||
|
let blinding =
|
||||||
|
slate.add_transaction_elements(keychain, vec![build::output(amount, key_id.clone())])?;
|
||||||
|
|
||||||
|
// Add blinding sum to our context
|
||||||
|
let mut context = context_manager.create_context(
|
||||||
|
keychain.secp(),
|
||||||
|
&slate.id,
|
||||||
|
blinding.secret_key(keychain.secp()).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
context.add_output(&key_id);
|
||||||
|
|
||||||
|
// Create closure that adds the output to recipient's wallet
|
||||||
|
// (up to the caller to decide when to do)
|
||||||
|
let wallet_add_fn = move || {
|
||||||
|
WalletData::with_wallet(&data_file_dir, |wallet_data| {
|
||||||
|
wallet_data.add_output(OutputData {
|
||||||
|
root_key_id: root_key_id,
|
||||||
|
key_id: key_id_inner,
|
||||||
|
n_child: derivation,
|
||||||
|
value: amount,
|
||||||
|
status: OutputStatus::Unconfirmed,
|
||||||
|
height: 0,
|
||||||
|
lock_height: 0,
|
||||||
|
is_coinbase: false,
|
||||||
|
block: None,
|
||||||
|
merkle_proof: None,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
context_manager.save_context(context);
|
||||||
|
Ok((key_id, wallet_add_fn))
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds a transaction to send to someone from the HD seed associated with the
|
/// 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,
|
/// wallet and the amount to send. Handles reading through the wallet data file,
|
||||||
/// selecting outputs to spend and building the change.
|
/// selecting outputs to spend and building the change.
|
||||||
pub fn build_send_tx(
|
pub fn select_send_tx(
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
keychain: &Keychain,
|
keychain: &Keychain,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
|
@ -37,11 +164,11 @@ pub fn build_send_tx(
|
||||||
selection_strategy_is_use_all: bool,
|
selection_strategy_is_use_all: bool,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
Transaction,
|
Vec<Box<build::Append>>,
|
||||||
BlindingFactor,
|
|
||||||
Vec<OutputData>,
|
Vec<OutputData>,
|
||||||
Option<Identifier>,
|
Option<Identifier>,
|
||||||
u64,
|
u64, // amount
|
||||||
|
u64, // fee
|
||||||
),
|
),
|
||||||
Error,
|
Error,
|
||||||
> {
|
> {
|
||||||
|
@ -121,9 +248,7 @@ pub fn build_send_tx(
|
||||||
// on tx being sent (based on current chain height via api).
|
// on tx being sent (based on current chain height via api).
|
||||||
parts.push(build::with_lock_height(lock_height));
|
parts.push(build::with_lock_height(lock_height));
|
||||||
|
|
||||||
let (tx, blind) = build::partial_transaction(parts, &keychain).context(ErrorKind::Keychain)?;
|
Ok((parts, coins, change_key, amount, fee))
|
||||||
|
|
||||||
Ok((tx, blind, coins, change_key, amount_with_fee))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// coins proof count
|
/// coins proof count
|
||||||
|
|
|
@ -12,18 +12,17 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
/// Aggsig library definitions
|
/// Aggsig library definitions
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use util::secp::key::{PublicKey, SecretKey};
|
|
||||||
use util::secp::{self, aggsig, Message, Secp256k1, Signature};
|
|
||||||
use util::secp::pedersen::Commitment;
|
|
||||||
use util::kernel_sig_msg;
|
|
||||||
use uuid::Uuid;
|
|
||||||
use keychain::Keychain;
|
use keychain::Keychain;
|
||||||
use keychain::extkey::Identifier;
|
|
||||||
use keychain::blind::BlindingFactor;
|
use keychain::blind::BlindingFactor;
|
||||||
|
use keychain::extkey::Identifier;
|
||||||
use libwallet::error::Error;
|
use libwallet::error::Error;
|
||||||
|
use util::kernel_sig_msg;
|
||||||
|
use util::secp::key::{PublicKey, SecretKey};
|
||||||
|
use util::secp::pedersen::Commitment;
|
||||||
|
use util::secp::{self, aggsig, Message, Secp256k1, Signature};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
/// Holds the context for a single aggsig transaction
|
/// Holds the context for a single aggsig transaction
|
||||||
|
@ -36,16 +35,37 @@ pub struct Context {
|
||||||
/// (basically a SecretKey)
|
/// (basically a SecretKey)
|
||||||
pub sec_nonce: SecretKey,
|
pub sec_nonce: SecretKey,
|
||||||
/// If I'm the sender, store change key
|
/// If I'm the sender, store change key
|
||||||
|
/// TODO: remove in favor of outputs below
|
||||||
pub change_key: Option<Identifier>,
|
pub change_key: Option<Identifier>,
|
||||||
/// store my outputs between invocations
|
/// store my outputs between invocations
|
||||||
pub output_ids: Vec<Identifier>,
|
pub output_ids: Vec<Identifier>,
|
||||||
|
/// store my inputs
|
||||||
|
pub input_ids: Vec<Identifier>,
|
||||||
/// store the calculated fee
|
/// store the calculated fee
|
||||||
pub fee: u64,
|
pub fee: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*impl Context {
|
||||||
|
/// Create a new context with defaults
|
||||||
|
pub fn new(
|
||||||
|
secp: &secp::Secp256k1,
|
||||||
|
sec_key: SecretKey,
|
||||||
|
) -> Context {
|
||||||
|
Context {
|
||||||
|
sec_key: sec_key,
|
||||||
|
sec_nonce: aggsig::export_secnonce_single(secp).unwrap(),
|
||||||
|
change_key: None,
|
||||||
|
input_ids: vec![],
|
||||||
|
output_ids: vec![],
|
||||||
|
fee: 0,
|
||||||
|
},
|
||||||
|
}*/
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
/// Holds many contexts, to support multiple transactions hitting a wallet receiver
|
/// Holds many contexts, to support multiple transactions hitting a wallet
|
||||||
/// at once
|
/// receiver at once
|
||||||
|
/// TODO: Remove context manager in favour of context.. keeping multiple
|
||||||
|
/// transactions separate is a wallet-specific concern
|
||||||
pub struct ContextManager {
|
pub struct ContextManager {
|
||||||
contexts: HashMap<Uuid, Context>,
|
contexts: HashMap<Uuid, Context>,
|
||||||
}
|
}
|
||||||
|
@ -74,6 +94,7 @@ impl ContextManager {
|
||||||
transaction_id: transaction_id.clone(),
|
transaction_id: transaction_id.clone(),
|
||||||
sec_nonce: aggsig::export_secnonce_single(secp).unwrap(),
|
sec_nonce: aggsig::export_secnonce_single(secp).unwrap(),
|
||||||
change_key: None,
|
change_key: None,
|
||||||
|
input_ids: vec![],
|
||||||
output_ids: vec![],
|
output_ids: vec![],
|
||||||
fee: 0,
|
fee: 0,
|
||||||
},
|
},
|
||||||
|
@ -105,6 +126,17 @@ impl Context {
|
||||||
self.output_ids.clone()
|
self.output_ids.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tracks IDs of my inputs into the transaction
|
||||||
|
/// be kept between invocations
|
||||||
|
pub fn add_input(&mut self, input_id: &Identifier) {
|
||||||
|
self.input_ids.push(input_id.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all stored input identifiers
|
||||||
|
pub fn get_inputs(&self) -> Vec<Identifier> {
|
||||||
|
self.input_ids.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns private key, private nonce
|
/// Returns private key, private nonce
|
||||||
pub fn get_private_keys(&self) -> (SecretKey, SecretKey) {
|
pub fn get_private_keys(&self) -> (SecretKey, SecretKey) {
|
||||||
(self.sec_key.clone(), self.sec_nonce.clone())
|
(self.sec_key.clone(), self.sec_nonce.clone())
|
||||||
|
@ -118,9 +150,10 @@ impl Context {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Note 'secnonce' here is used to perform the signature, while 'pubnonce' just allows you to
|
/// Note 'secnonce' here is used to perform the signature, while 'pubnonce'
|
||||||
/// provide a custom public nonce to include while calculating e
|
/// just allows you to provide a custom public nonce to include while
|
||||||
/// nonce_sum is the sum used to decide whether secnonce should be inverted during sig time
|
/// calculating e nonce_sum is the sum used to decide whether secnonce
|
||||||
|
/// should be inverted during sig time
|
||||||
pub fn sign_single(
|
pub fn sign_single(
|
||||||
&self,
|
&self,
|
||||||
secp: &Secp256k1,
|
secp: &Secp256k1,
|
||||||
|
@ -164,6 +197,7 @@ impl Context {
|
||||||
verify_single(secp, sig, &msg, Some(&nonce_sum), pubkey, true)
|
verify_single(secp, sig, &msg, Some(&nonce_sum), pubkey, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///TODO: Remove when below is integrated
|
||||||
pub fn calculate_partial_sig(
|
pub fn calculate_partial_sig(
|
||||||
&self,
|
&self,
|
||||||
secp: &Secp256k1,
|
secp: &Secp256k1,
|
||||||
|
@ -187,19 +221,36 @@ impl Context {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn calculate_partial_sig_with_nonce_sum(
|
||||||
|
&self,
|
||||||
|
secp: &Secp256k1,
|
||||||
|
nonce_sum: &PublicKey,
|
||||||
|
fee: u64,
|
||||||
|
lock_height: u64,
|
||||||
|
) -> Result<Signature, Error> {
|
||||||
|
// Add public nonces kR*G + kS*G
|
||||||
|
let (_, sec_nonce) = self.get_private_keys();
|
||||||
|
let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height))?;
|
||||||
|
|
||||||
|
//Now calculate signature using message M=fee, nonce in e=nonce_sum
|
||||||
|
self.sign_single(
|
||||||
|
secp,
|
||||||
|
&msg,
|
||||||
|
Some(&sec_nonce),
|
||||||
|
Some(&nonce_sum),
|
||||||
|
Some(&nonce_sum),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper function to calculate final signature
|
/// Helper function to calculate final signature
|
||||||
pub fn calculate_final_sig(
|
pub fn calculate_final_sig(
|
||||||
&self,
|
&self,
|
||||||
secp: &Secp256k1,
|
secp: &Secp256k1,
|
||||||
their_sig: &Signature,
|
part_sigs: Vec<&Signature>,
|
||||||
our_sig: &Signature,
|
nonce_sum: &PublicKey,
|
||||||
their_pub_nonce: &PublicKey,
|
|
||||||
) -> Result<Signature, Error> {
|
) -> Result<Signature, Error> {
|
||||||
// Add public nonces kR*G + kS*G
|
// Add public nonces kR*G + kS*G
|
||||||
let (_, sec_nonce) = self.get_private_keys();
|
let sig = aggsig::add_signatures_single(&secp, part_sigs, &nonce_sum)?;
|
||||||
let mut nonce_sum = their_pub_nonce.clone();
|
|
||||||
let _ = nonce_sum.add_exp_assign(secp, &sec_nonce);
|
|
||||||
let sig = aggsig::add_signatures_single(&secp, their_sig, our_sig, &nonce_sum)?;
|
|
||||||
Ok(sig)
|
Ok(sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,6 +269,19 @@ impl Context {
|
||||||
|
|
||||||
// Contextless functions
|
// Contextless functions
|
||||||
|
|
||||||
|
/// Verifies a partial sig given all public nonces used in the round
|
||||||
|
pub fn verify_partial_sig(
|
||||||
|
secp: &Secp256k1,
|
||||||
|
sig: &Signature,
|
||||||
|
pub_nonce_sum: &PublicKey,
|
||||||
|
pubkey: &PublicKey,
|
||||||
|
fee: u64,
|
||||||
|
lock_height: u64,
|
||||||
|
) -> bool {
|
||||||
|
let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height)).unwrap();
|
||||||
|
verify_single(secp, sig, &msg, Some(&pub_nonce_sum), pubkey, true)
|
||||||
|
}
|
||||||
|
|
||||||
/// Just a simple sig, creates its own nonce, etc
|
/// Just a simple sig, creates its own nonce, etc
|
||||||
pub fn sign_from_key_id(
|
pub fn sign_from_key_id(
|
||||||
secp: &Secp256k1,
|
secp: &Secp256k1,
|
||||||
|
@ -238,16 +302,21 @@ pub fn verify_single_from_commit(
|
||||||
commit: &Commitment,
|
commit: &Commitment,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// Extract the pubkey, unfortunately we need this hack for now, (we just hope
|
// Extract the pubkey, unfortunately we need this hack for now, (we just hope
|
||||||
// one is valid) TODO: Create better secp256k1 API to do this
|
// one is valid)
|
||||||
let pubkeys = commit.to_two_pubkeys(secp);
|
let pubkey = commit.to_pubkey(secp).unwrap();
|
||||||
let mut valid = false;
|
aggsig::verify_single(secp, &sig, &msg, None, &pubkey, false)
|
||||||
for i in 0..pubkeys.len() {
|
}
|
||||||
valid = aggsig::verify_single(secp, &sig, &msg, None, &pubkeys[i], false);
|
|
||||||
if valid {
|
/// Verify a sig, with built message
|
||||||
break;
|
pub fn verify_sig_build_msg(
|
||||||
}
|
secp: &Secp256k1,
|
||||||
}
|
sig: &Signature,
|
||||||
valid
|
pubkey: &PublicKey,
|
||||||
|
fee: u64,
|
||||||
|
lock_height: u64,
|
||||||
|
) -> bool {
|
||||||
|
let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height)).unwrap();
|
||||||
|
verify_single(secp, sig, &msg, None, pubkey, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Verifies an aggsig signature
|
//Verifies an aggsig signature
|
||||||
|
@ -262,6 +331,17 @@ pub fn verify_single(
|
||||||
aggsig::verify_single(secp, sig, msg, pubnonce, pubkey, is_partial)
|
aggsig::verify_single(secp, sig, msg, pubnonce, pubkey, is_partial)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds signatures
|
||||||
|
pub fn add_signatures(
|
||||||
|
secp: &Secp256k1,
|
||||||
|
part_sigs: Vec<&Signature>,
|
||||||
|
nonce_sum: &PublicKey,
|
||||||
|
) -> Result<Signature, Error> {
|
||||||
|
// Add public nonces kR*G + kS*G
|
||||||
|
let sig = aggsig::add_signatures_single(&secp, part_sigs, &nonce_sum)?;
|
||||||
|
Ok(sig)
|
||||||
|
}
|
||||||
|
|
||||||
/// Just a simple sig, creates its own nonce, etc
|
/// Just a simple sig, creates its own nonce, etc
|
||||||
pub fn sign_with_blinding(
|
pub fn sign_with_blinding(
|
||||||
secp: &Secp256k1,
|
secp: &Secp256k1,
|
||||||
|
|
|
@ -18,455 +18,384 @@ use rand::thread_rng;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use core::core::{amount_to_hr_string, Committed, Transaction};
|
use core::core::{amount_to_hr_string, Committed, Transaction};
|
||||||
|
use keychain::{BlindSum, BlindingFactor, Keychain};
|
||||||
use libwallet::{aggsig, build};
|
use libwallet::{aggsig, build};
|
||||||
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
|
//TODO: Remove these from here, replace with libwallet error
|
||||||
use types::*; // TODO: Remove this?
|
use types::{tx_fee, Error, ErrorKind};
|
||||||
use util::{secp, LOGGER};
|
|
||||||
use util::secp::key::{PublicKey, SecretKey};
|
|
||||||
use util::secp::Signature;
|
use util::secp::Signature;
|
||||||
|
use util::secp::key::{PublicKey, SecretKey};
|
||||||
|
use util::{secp, LOGGER};
|
||||||
|
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
|
|
||||||
// TODO: None of these functions should care about the wallet implementation,
|
/// Public data for each participant in the slate
|
||||||
|
|
||||||
/// Initiate a transaction for the aggsig exchange
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
/// with the given transaction data
|
pub struct ParticipantData {
|
||||||
pub fn sender_initiation(
|
/// Id of participant in the transaction. (For now, 0=sender, 1=rec)
|
||||||
keychain: &Keychain,
|
pub id: u64,
|
||||||
tx_id: &Uuid,
|
/// Public key corresponding to private blinding factor
|
||||||
context_manager: &mut aggsig::ContextManager,
|
pub public_blind_excess: PublicKey,
|
||||||
current_height: u64,
|
/// Public key corresponding to private nonce
|
||||||
//TODO: Make this nicer, remove wallet-specific OutputData type
|
pub public_nonce: PublicKey,
|
||||||
tx_data: (
|
/// Public partial signature
|
||||||
Transaction,
|
pub part_sig: Option<Signature>,
|
||||||
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
|
impl ParticipantData {
|
||||||
/// Return result of part 2, Recipient Initation, to sender
|
/// A helper to return whether this paricipant
|
||||||
/// -Receiver receives inputs, outputs xS * G and kS * G
|
/// has completed round 1 and round 2;
|
||||||
/// -Receiver picks random blinding factors for all outputs being received,
|
/// Round 1 has to be completed before instantiation of this struct
|
||||||
/// computes total blinding
|
/// anyhow, and for each participant consists of:
|
||||||
/// excess xR
|
/// -Inputs added to transaction
|
||||||
/// -Receiver picks random nonce kR
|
/// -Outputs added to transaction
|
||||||
/// -Receiver computes Schnorr challenge e = H(M | kR * G + kS * G)
|
/// -Public signature nonce chosen and added
|
||||||
/// -Receiver computes their part of signature, sR = kR + e * xR
|
/// -Public contribution to blinding factor chosen and added
|
||||||
/// -Receiver responds with sR, blinding excess xR * G, public nonce kR * G
|
/// Round 2 can only be completed after all participants have
|
||||||
|
/// performed round 1, and adds:
|
||||||
|
/// -Part sig is filled out
|
||||||
|
pub fn is_complete(&self) -> bool {
|
||||||
|
self.part_sig.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn recipient_initiation(
|
/// A 'Slate' is passed around to all parties to build up all of the public
|
||||||
keychain: &Keychain,
|
/// tranaction data needed to create a finalised tranaction. Callers can pass
|
||||||
context_manager: &mut aggsig::ContextManager,
|
/// the slate around by whatever means they choose, (but we can provide some
|
||||||
partial_tx: &PartialTx,
|
/// binary or JSON serialisation helpers here).
|
||||||
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
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
// we don't necessarily want to just trust the sender
|
pub struct Slate {
|
||||||
// we could just overwrite the fee here (but we won't) due to the sig
|
/// The number of participants intended to take part in this transaction
|
||||||
let fee = tx_fee(
|
pub num_participants: usize,
|
||||||
tx.inputs.len(),
|
/// Unique transaction ID, selected by sender
|
||||||
tx.outputs.len() + 1,
|
pub id: Uuid,
|
||||||
tx.input_proofs_count(),
|
/// The core transaction data:
|
||||||
None,
|
/// inputs, outputs, kernels, kernel offset
|
||||||
);
|
pub tx: Transaction,
|
||||||
if fee > tx.fee() {
|
/// base amount (excluding fee)
|
||||||
return Err(ErrorKind::FeeDispute {
|
pub amount: u64,
|
||||||
sender_fee: tx.fee(),
|
/// fee amount
|
||||||
recipient_fee: fee,
|
pub fee: u64,
|
||||||
})?;
|
/// Block height for the transaction
|
||||||
|
pub height: u64,
|
||||||
|
/// Lock height
|
||||||
|
pub lock_height: u64,
|
||||||
|
/// Participant data, each participant in the transaction will
|
||||||
|
/// insert their public data here. For now, 0 is sender and 1
|
||||||
|
/// is receiver, though this will change for multi-party
|
||||||
|
pub participant_data: Vec<ParticipantData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Slate {
|
||||||
|
/// Create a new slate
|
||||||
|
pub fn blank(num_participants: usize) -> Slate {
|
||||||
|
Slate {
|
||||||
|
num_participants: num_participants,
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
tx: Transaction::empty(),
|
||||||
|
amount: 0,
|
||||||
|
fee: 0,
|
||||||
|
height: 0,
|
||||||
|
lock_height: 0,
|
||||||
|
participant_data: vec![],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fee > amount {
|
/// Adds selected inputs and outputs to the slate's transaction
|
||||||
info!(
|
/// Returns blinding factor
|
||||||
LOGGER,
|
pub fn add_transaction_elements(
|
||||||
"Rejected the transfer because transaction fee ({}) exceeds received amount ({}).",
|
&mut self,
|
||||||
amount_to_hr_string(fee),
|
keychain: &Keychain,
|
||||||
amount_to_hr_string(amount)
|
mut elems: Vec<Box<build::Append>>,
|
||||||
|
) -> Result<BlindingFactor, Error> {
|
||||||
|
// Append to the exiting transaction
|
||||||
|
if self.tx.kernels.len() != 0 {
|
||||||
|
elems.insert(0, build::initial_tx(self.tx.clone()));
|
||||||
|
}
|
||||||
|
let (tx, blind) =
|
||||||
|
build::partial_transaction(elems, &keychain).context(ErrorKind::Keychain)?;
|
||||||
|
self.tx = tx;
|
||||||
|
Ok(blind)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Completes callers part of round 1, adding public key info
|
||||||
|
/// to the slate
|
||||||
|
pub fn fill_round_1(
|
||||||
|
&mut self,
|
||||||
|
keychain: &Keychain,
|
||||||
|
context_manager: &mut aggsig::ContextManager,
|
||||||
|
participant_id: usize,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Whoever does this first generates the offset
|
||||||
|
if self.tx.offset == BlindingFactor::zero() {
|
||||||
|
self.generate_offset(keychain, context_manager)?;
|
||||||
|
}
|
||||||
|
self.add_participant_info(keychain, context_manager, participant_id, None)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Completes caller's part of round 2, completing signatures
|
||||||
|
pub fn fill_round_2(
|
||||||
|
&mut self,
|
||||||
|
keychain: &Keychain,
|
||||||
|
context_manager: &mut aggsig::ContextManager,
|
||||||
|
participant_id: usize,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.check_fees()?;
|
||||||
|
self.verify_part_sigs(keychain.secp())?;
|
||||||
|
let context = context_manager.get_context(&self.id);
|
||||||
|
let sig_part = context
|
||||||
|
.calculate_partial_sig_with_nonce_sum(
|
||||||
|
keychain.secp(),
|
||||||
|
&self.pub_nonce_sum(keychain.secp()),
|
||||||
|
self.fee,
|
||||||
|
self.lock_height,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
self.participant_data[participant_id].part_sig = Some(sig_part);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the final signature, callable by either the sender or recipient
|
||||||
|
/// (after phase 3: sender confirmation)
|
||||||
|
/// TODO: Only callable by receiver at the moment
|
||||||
|
pub fn finalize(&mut self, keychain: &Keychain) -> Result<(), Error> {
|
||||||
|
let final_sig = self.finalize_signature(keychain)?;
|
||||||
|
self.finalize_transaction(keychain, &final_sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the sum of public nonces
|
||||||
|
fn pub_nonce_sum(&self, secp: &secp::Secp256k1) -> PublicKey {
|
||||||
|
let pub_nonces = self.participant_data
|
||||||
|
.iter()
|
||||||
|
.map(|p| &p.public_nonce)
|
||||||
|
.collect();
|
||||||
|
PublicKey::from_combination(secp, pub_nonces).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the sum of public blinding factors
|
||||||
|
fn pub_blind_sum(&self, secp: &secp::Secp256k1) -> PublicKey {
|
||||||
|
let pub_blinds = self.participant_data
|
||||||
|
.iter()
|
||||||
|
.map(|p| &p.public_blind_excess)
|
||||||
|
.collect();
|
||||||
|
PublicKey::from_combination(secp, pub_blinds).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return vector of all partial sigs
|
||||||
|
fn part_sigs(&self) -> Vec<&Signature> {
|
||||||
|
self.participant_data
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.part_sig.as_ref().unwrap())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds participants public keys to the slate data
|
||||||
|
/// and saves participant's transaction context
|
||||||
|
/// sec_key can be overriden to replace the blinding
|
||||||
|
/// factor (by whoever split the offset)
|
||||||
|
fn add_participant_info(
|
||||||
|
&mut self,
|
||||||
|
keychain: &Keychain,
|
||||||
|
context_manager: &aggsig::ContextManager,
|
||||||
|
id: usize,
|
||||||
|
part_sig: Option<Signature>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let context = context_manager.get_context(&self.id);
|
||||||
|
|
||||||
|
// Add our public key and nonce to the slate
|
||||||
|
let (pub_key, pub_nonce) = context.get_public_keys(keychain.secp());
|
||||||
|
self.participant_data.push(ParticipantData {
|
||||||
|
id: id as u64,
|
||||||
|
public_blind_excess: pub_key,
|
||||||
|
public_nonce: pub_nonce,
|
||||||
|
part_sig: part_sig,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Somebody involved needs to generate an offset with their private key
|
||||||
|
/// For now, we'll have the transaction initiator be responsible for it
|
||||||
|
/// Return offset private key
|
||||||
|
fn generate_offset(
|
||||||
|
&mut self,
|
||||||
|
keychain: &Keychain,
|
||||||
|
context_manager: &mut aggsig::ContextManager,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Generate a random kernel offset here
|
||||||
|
// and subtract it from the blind_sum so we create
|
||||||
|
// the aggsig context with the "split" key
|
||||||
|
let mut context = context_manager.get_context(&self.id);
|
||||||
|
self.tx.offset =
|
||||||
|
BlindingFactor::from_secret_key(SecretKey::new(&keychain.secp(), &mut thread_rng()));
|
||||||
|
let blind_offset = keychain
|
||||||
|
.blind_sum(&BlindSum::new()
|
||||||
|
.add_blinding_factor(BlindingFactor::from_secret_key(context.sec_key))
|
||||||
|
.sub_blinding_factor(self.tx.offset))
|
||||||
|
.unwrap();
|
||||||
|
context.sec_key = blind_offset
|
||||||
|
.secret_key(&keychain.secp())
|
||||||
|
.context(ErrorKind::Keychain)?;
|
||||||
|
context_manager.save_context(context);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the fees in the transaction in the given slate are valid
|
||||||
|
fn check_fees(&self) -> Result<(), Error> {
|
||||||
|
// 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(
|
||||||
|
self.tx.inputs.len(),
|
||||||
|
self.tx.outputs.len(),
|
||||||
|
self.tx.input_proofs_count(),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
return Err(ErrorKind::FeeExceedsAmount {
|
if fee > self.tx.fee() {
|
||||||
sender_amount: amount,
|
return Err(ErrorKind::FeeDispute {
|
||||||
recipient_fee: fee,
|
sender_fee: self.tx.fee(),
|
||||||
})?;
|
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
|
if fee > self.amount + self.fee {
|
||||||
let overage = final_tx.fee() as i64;
|
info!(
|
||||||
let tx_excess = final_tx
|
LOGGER,
|
||||||
.sum_commitments(overage, None)
|
"Rejected the transfer because transaction fee ({}) exceeds received amount ({}).",
|
||||||
|
amount_to_hr_string(fee),
|
||||||
|
amount_to_hr_string(self.amount + self.fee)
|
||||||
|
);
|
||||||
|
return Err(ErrorKind::FeeExceedsAmount {
|
||||||
|
sender_amount: self.amount + self.fee,
|
||||||
|
recipient_fee: fee,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verifies all of the partial signatures in the Slate are valid
|
||||||
|
fn verify_part_sigs(&self, secp: &secp::Secp256k1) -> Result<(), Error> {
|
||||||
|
// collect public nonces
|
||||||
|
for p in self.participant_data.iter() {
|
||||||
|
if p.is_complete() {
|
||||||
|
if aggsig::verify_partial_sig(
|
||||||
|
secp,
|
||||||
|
p.part_sig.as_ref().unwrap(),
|
||||||
|
&self.pub_nonce_sum(secp),
|
||||||
|
&p.public_blind_excess,
|
||||||
|
self.fee,
|
||||||
|
self.lock_height,
|
||||||
|
) == false
|
||||||
|
{
|
||||||
|
error!(LOGGER, "Partial Sig invalid.");
|
||||||
|
return Err(ErrorKind::Signature("Partial Sig invalid."))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 finalize_signature(&mut self, keychain: &Keychain) -> Result<Signature, Error> {
|
||||||
|
self.verify_part_sigs(keychain.secp())?;
|
||||||
|
|
||||||
|
let part_sigs = self.part_sigs();
|
||||||
|
let pub_nonce_sum = self.pub_nonce_sum(keychain.secp());
|
||||||
|
let final_pubkey = self.pub_blind_sum(keychain.secp());
|
||||||
|
// get the final signature
|
||||||
|
let final_sig =
|
||||||
|
aggsig::add_signatures(&keychain.secp(), part_sigs, &pub_nonce_sum).unwrap();
|
||||||
|
|
||||||
|
// Calculate the final public key (for our own sanity check)
|
||||||
|
|
||||||
|
// Check our final sig verifies
|
||||||
|
let res = aggsig::verify_sig_build_msg(
|
||||||
|
&keychain.secp(),
|
||||||
|
&final_sig,
|
||||||
|
&final_pubkey,
|
||||||
|
self.fee,
|
||||||
|
self.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 finalize_transaction(
|
||||||
|
&mut self,
|
||||||
|
keychain: &Keychain,
|
||||||
|
final_sig: &secp::Signature,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let kernel_offset = self.tx.offset;
|
||||||
|
|
||||||
|
self.check_fees()?;
|
||||||
|
|
||||||
|
let mut final_tx = self.tx.clone();
|
||||||
|
|
||||||
|
// 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 = final_sig.clone();
|
||||||
|
|
||||||
|
// confirm the kernel verifies successfully before proceeding
|
||||||
|
debug!(LOGGER, "Validating final transaction");
|
||||||
|
final_tx.kernels[0]
|
||||||
|
.verify()
|
||||||
.context(ErrorKind::Transaction)?;
|
.context(ErrorKind::Transaction)?;
|
||||||
|
|
||||||
// subtract the kernel_excess (built from kernel_offset)
|
// confirm the overall transaction is valid (including the updated kernel)
|
||||||
let offset_excess = keychain
|
let _ = final_tx.validate().context(ErrorKind::Transaction)?;
|
||||||
.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
|
self.tx = final_tx;
|
||||||
assert_eq!(final_tx.kernels.len(), 1);
|
Ok(())
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,15 +25,14 @@ use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use api;
|
use api;
|
||||||
use core::consensus::reward;
|
use core::consensus::reward;
|
||||||
use core::core::{Output, Transaction, TxKernel};
|
use core::core::{Output, TxKernel};
|
||||||
use libwallet::{aggsig, reward, transaction};
|
use core::global;
|
||||||
use grinwallet::keys;
|
use failure::Fail;
|
||||||
use core::{global, ser};
|
use grinwallet::{keys, selection};
|
||||||
use failure::{Fail, ResultExt};
|
|
||||||
use keychain::Keychain;
|
use keychain::Keychain;
|
||||||
|
use libwallet::{aggsig, reward, transaction};
|
||||||
use types::*;
|
use types::*;
|
||||||
use urlencoded::UrlEncodedQuery;
|
use util::LOGGER;
|
||||||
use util::{to_hex, LOGGER};
|
|
||||||
|
|
||||||
/// Dummy wrapper for the hex-encoded serialized transaction.
|
/// Dummy wrapper for the hex-encoded serialized transaction.
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -47,99 +46,27 @@ lazy_static! {
|
||||||
= Arc::new(RwLock::new(aggsig::ContextManager::new()));
|
= Arc::new(RwLock::new(aggsig::ContextManager::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_sender_initiation(
|
fn handle_send(
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
context_manager: &mut aggsig::ContextManager,
|
|
||||||
keychain: &Keychain,
|
keychain: &Keychain,
|
||||||
partial_tx: &PartialTx,
|
|
||||||
) -> Result<PartialTx, Error> {
|
|
||||||
// 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,
|
context_manager: &mut aggsig::ContextManager,
|
||||||
keychain: &Keychain,
|
slate: &mut transaction::Slate,
|
||||||
partial_tx: &PartialTx,
|
) -> Result<(), Error> {
|
||||||
fluff: bool,
|
// create an output using the amount in the slate
|
||||||
) -> Result<Transaction, Error> {
|
let (_, receiver_create_fn) =
|
||||||
let context = context_manager.get_context(&partial_tx.id);
|
selection::build_recipient_output_with_slate(config, keychain, context_manager, slate)
|
||||||
// Get output we created in earlier step
|
.unwrap();
|
||||||
// TODO: will just be one for now, support multiple later
|
|
||||||
let output_vec = context.get_outputs();
|
|
||||||
|
|
||||||
let root_key_id = keychain.root_key_id();
|
// fill public keys
|
||||||
// operate within a lock on wallet data
|
let _ = slate.fill_round_1(&keychain, context_manager, 1)?;
|
||||||
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 {
|
// perform partial sig
|
||||||
root_key_id: root_key_id.clone(),
|
let _ = slate.fill_round_2(&keychain, context_manager, 1)?;
|
||||||
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)
|
// Save output in wallet
|
||||||
})?;
|
let _ = receiver_create_fn();
|
||||||
|
|
||||||
// In this case partial_tx contains other party's pubkey info
|
Ok(())
|
||||||
let final_tx = transaction::finalize_transaction(
|
|
||||||
keychain,
|
|
||||||
context_manager,
|
|
||||||
partial_tx,
|
|
||||||
partial_tx,
|
|
||||||
&key_id,
|
|
||||||
derivation,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let tx_hex = to_hex(ser::ser_vec(&final_tx).unwrap());
|
|
||||||
|
|
||||||
let url;
|
|
||||||
if fluff {
|
|
||||||
url = format!(
|
|
||||||
"{}/v1/pool/push?fluff",
|
|
||||||
config.check_node_api_http_addr.as_str()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str());
|
|
||||||
}
|
|
||||||
api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }).context(ErrorKind::Node)?;
|
|
||||||
|
|
||||||
Ok(final_tx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Component used to receive coins, implements all the receiving end of the
|
/// Component used to receive coins, implements all the receiving end of the
|
||||||
|
@ -152,56 +79,23 @@ pub struct WalletReceiver {
|
||||||
|
|
||||||
impl Handler for WalletReceiver {
|
impl Handler for WalletReceiver {
|
||||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||||
let struct_body = req.get::<bodyparser::Struct<PartialTx>>();
|
let struct_body = req.get::<bodyparser::Struct<transaction::Slate>>();
|
||||||
|
|
||||||
let mut fluff = false;
|
if let Ok(Some(mut slate)) = struct_body {
|
||||||
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
|
||||||
if let Some(_) = params.get("fluff") {
|
|
||||||
fluff = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(Some(partial_tx)) = struct_body {
|
|
||||||
let mut acm = AGGSIG_CONTEXT_MANAGER.write().unwrap();
|
let mut acm = AGGSIG_CONTEXT_MANAGER.write().unwrap();
|
||||||
match partial_tx.phase {
|
let _ = handle_send(&self.config, &self.keychain, &mut acm, &mut slate)
|
||||||
PartialTxPhase::SenderInitiation => {
|
.map_err(|e| {
|
||||||
let resp_tx = handle_sender_initiation(
|
error!(
|
||||||
&self.config,
|
LOGGER,
|
||||||
&mut acm,
|
"Handling send -> Problematic slate, looks like this: {:?}", slate
|
||||||
&self.keychain,
|
);
|
||||||
&partial_tx,
|
e.context(api::ErrorKind::Internal(
|
||||||
).map_err(|e| {
|
"Error processing partial transaction".to_owned(),
|
||||||
error!(LOGGER, "Phase 1 Sender Initiation -> Problematic partial tx, looks like this: {:?}", partial_tx);
|
))
|
||||||
e.context(api::ErrorKind::Internal(
|
})
|
||||||
"Error processing partial transaction".to_owned(),
|
.unwrap();
|
||||||
))
|
let json = serde_json::to_string(&slate).unwrap();
|
||||||
})
|
Ok(Response::with((status::Ok, json)))
|
||||||
.unwrap();
|
|
||||||
let json = serde_json::to_string(&resp_tx).unwrap();
|
|
||||||
Ok(Response::with((status::Ok, json)))
|
|
||||||
}
|
|
||||||
PartialTxPhase::SenderConfirmation => {
|
|
||||||
let resp_tx = handle_sender_confirmation(
|
|
||||||
&self.config,
|
|
||||||
&mut acm,
|
|
||||||
&self.keychain,
|
|
||||||
&partial_tx,
|
|
||||||
fluff,
|
|
||||||
).map_err(|e| {
|
|
||||||
error!(LOGGER, "Phase 3 Sender Confirmation -> Problematic partial tx, looks like this: {:?}", partial_tx);
|
|
||||||
e.context(api::ErrorKind::Internal(
|
|
||||||
"Error processing partial transaction".to_owned(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
let json = serde_json::to_string(&resp_tx).unwrap();
|
|
||||||
Ok(Response::with((status::Ok, json)))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
error!(LOGGER, "Unhandled Phase: {:?}", partial_tx);
|
|
||||||
Ok(Response::with((status::BadRequest, "Unhandled Phase")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Ok(Response::with((status::BadRequest, "")))
|
Ok(Response::with((status::BadRequest, "")))
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,26 +12,24 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use api;
|
use api;
|
||||||
use client;
|
|
||||||
use checker;
|
use checker;
|
||||||
|
use client;
|
||||||
use core::core::amount_to_hr_string;
|
use core::core::amount_to_hr_string;
|
||||||
use libwallet::{aggsig, build, transaction};
|
|
||||||
use grinwallet::selection;
|
|
||||||
use core::ser;
|
use core::ser;
|
||||||
|
use failure::ResultExt;
|
||||||
|
use grinwallet::selection;
|
||||||
use keychain::{Identifier, Keychain};
|
use keychain::{Identifier, Keychain};
|
||||||
|
use libwallet::{aggsig, build};
|
||||||
use receiver::TxWrapper;
|
use receiver::TxWrapper;
|
||||||
use types::*;
|
use types::*;
|
||||||
use util::LOGGER;
|
|
||||||
use util;
|
use util;
|
||||||
use failure::ResultExt;
|
use util::LOGGER;
|
||||||
|
|
||||||
/// Issue a new transaction to the provided sender by spending some of our
|
/// Issue a new transaction to the provided sender by spending some of our
|
||||||
/// wallet
|
/// wallet
|
||||||
/// Outputs. The destination can be "stdout" (for command line) (currently disabled) or a URL to the
|
/// Outputs. The destination can be "stdout" (for command line) (currently
|
||||||
/// recipients wallet receiver (to be implemented).
|
/// disabled) or a URL to the recipients wallet receiver (to be implemented).
|
||||||
pub fn issue_send_tx(
|
pub fn issue_send_tx(
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
keychain: &Keychain,
|
keychain: &Keychain,
|
||||||
|
@ -42,11 +40,18 @@ pub fn issue_send_tx(
|
||||||
selection_strategy_is_use_all: bool,
|
selection_strategy_is_use_all: bool,
|
||||||
fluff: bool,
|
fluff: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
// TODO: Stdout option, probably in a separate implementation
|
||||||
|
if &dest[..4] != "http" {
|
||||||
|
panic!(
|
||||||
|
"dest formatted as {} but send -d expected stdout or http://IP:port",
|
||||||
|
dest
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
checker::refresh_outputs(config, keychain)?;
|
checker::refresh_outputs(config, keychain)?;
|
||||||
|
|
||||||
// Create a new aggsig context
|
// Create a new aggsig context
|
||||||
let mut context_manager = aggsig::ContextManager::new();
|
let mut context_manager = aggsig::ContextManager::new();
|
||||||
let tx_id = Uuid::new_v4();
|
|
||||||
|
|
||||||
// Get lock height
|
// Get lock height
|
||||||
let chain_tip = checker::get_tip_from_node(config)?;
|
let chain_tip = checker::get_tip_from_node(config)?;
|
||||||
|
@ -56,125 +61,77 @@ pub fn issue_send_tx(
|
||||||
|
|
||||||
let lock_height = current_height;
|
let lock_height = current_height;
|
||||||
|
|
||||||
let tx_data = selection::build_send_tx(
|
// Sender selects outputs into a new slate and save our corresponding IDs in
|
||||||
|
// their transaction context. The secret key in our transaction context will be
|
||||||
|
// randomly selected. This returns the public slate, and a closure that locks
|
||||||
|
// our inputs and outputs once we're convinced the transaction exchange went
|
||||||
|
// according to plan
|
||||||
|
// This function is just a big helper to do all of that, in theory
|
||||||
|
// this process can be split up in any way
|
||||||
|
let (mut slate, sender_lock_fn) = selection::build_send_tx_slate(
|
||||||
config,
|
config,
|
||||||
keychain,
|
keychain,
|
||||||
|
&mut context_manager,
|
||||||
|
2,
|
||||||
amount,
|
amount,
|
||||||
current_height,
|
current_height,
|
||||||
minimum_confirmations,
|
minimum_confirmations,
|
||||||
lock_height,
|
lock_height,
|
||||||
max_outputs,
|
max_outputs,
|
||||||
selection_strategy_is_use_all,
|
selection_strategy_is_use_all,
|
||||||
)?;
|
).unwrap();
|
||||||
|
|
||||||
let partial_tx = transaction::sender_initiation(
|
// Generate a kernel offset and subtract from our context's secret key. Store
|
||||||
keychain,
|
// the offset in the slate's transaction kernel, and adds our public key
|
||||||
&tx_id,
|
// information to the slate
|
||||||
&mut context_manager,
|
let _ = slate
|
||||||
current_height,
|
.fill_round_1(keychain, &mut context_manager, 0)
|
||||||
tx_data,
|
.unwrap();
|
||||||
)?;
|
|
||||||
|
|
||||||
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 id in context.get_outputs().clone() {
|
|
||||||
let coin = wallet_data.get_output(&id).unwrap().clone();
|
|
||||||
wallet_data.lock_output(&coin);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// Closure to acquire wallet lock and delete the change output in case of tx
|
|
||||||
// failure.
|
|
||||||
let rollback_wallet = || {
|
|
||||||
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
|
||||||
match context.change_key.clone() {
|
|
||||||
Some(change) => {
|
|
||||||
info!(LOGGER, "cleaning up unused change output from wallet");
|
|
||||||
wallet_data.delete_output(&change);
|
|
||||||
}
|
|
||||||
None => info!(LOGGER, "No change output to clean from wallet"),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: stdout option removed for now, as it won't work very will with this
|
|
||||||
// version of aggsig exchange
|
|
||||||
|
|
||||||
/*if dest == "stdout" {
|
|
||||||
let json_tx = serde_json::to_string_pretty(&partial_tx).unwrap();
|
|
||||||
update_wallet()?;
|
|
||||||
println!("{}", json_tx);
|
|
||||||
} else */
|
|
||||||
|
|
||||||
if &dest[..4] != "http" {
|
|
||||||
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
|
||||||
match context.change_key.clone() {
|
|
||||||
Some(change) => {
|
|
||||||
info!(LOGGER, "cleaning up unused change output from wallet");
|
|
||||||
wallet_data.delete_output(&change);
|
|
||||||
}
|
|
||||||
None => info!(LOGGER, "No change output to clean from wallet"),
|
|
||||||
}
|
|
||||||
}).unwrap();
|
|
||||||
panic!(
|
|
||||||
"dest formatted as {} but send -d expected stdout or http://IP:port",
|
|
||||||
dest
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let url = format!("{}/v1/receive/transaction", &dest);
|
let url = format!("{}/v1/receive/transaction", &dest);
|
||||||
debug!(LOGGER, "Posting partial transaction to {}", url);
|
debug!(LOGGER, "Posting partial transaction to {}", url);
|
||||||
let res = client::send_partial_tx(&url, &partial_tx, fluff);
|
let mut slate = match client::send_slate(&url, &slate, fluff) {
|
||||||
if let Err(e) = res {
|
Ok(s) => s,
|
||||||
match e.kind() {
|
Err(e) => {
|
||||||
ErrorKind::FeeExceedsAmount {
|
match e.kind() {
|
||||||
sender_amount,
|
ErrorKind::FeeExceedsAmount {
|
||||||
recipient_fee,
|
sender_amount,
|
||||||
} => error!(
|
recipient_fee,
|
||||||
|
} => error!(
|
||||||
|
LOGGER,
|
||||||
|
"Recipient rejected the transfer because transaction fee ({}) exceeded amount ({}).",
|
||||||
|
amount_to_hr_string(recipient_fee),
|
||||||
|
amount_to_hr_string(sender_amount)
|
||||||
|
),
|
||||||
|
_ => error!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
"Recipient rejected the transfer because transaction fee ({}) exceeded amount ({}).",
|
"Communication with receiver failed on SenderInitiation send. Aborting transaction"
|
||||||
amount_to_hr_string(recipient_fee),
|
|
||||||
amount_to_hr_string(sender_amount)
|
|
||||||
),
|
),
|
||||||
_ => error!(
|
}
|
||||||
LOGGER,
|
return Err(e);
|
||||||
"Communication with receiver failed on SenderInitiation send. Aborting transaction"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
rollback_wallet()?;
|
};
|
||||||
return Err(e);
|
|
||||||
|
let _ = slate.fill_round_2(keychain, &mut context_manager, 0)?;
|
||||||
|
|
||||||
|
// Final transaction can be built by anyone at this stage
|
||||||
|
slate.finalize(keychain)?;
|
||||||
|
|
||||||
|
// So let's post it
|
||||||
|
let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap());
|
||||||
|
let url;
|
||||||
|
if fluff {
|
||||||
|
url = format!(
|
||||||
|
"{}/v1/pool/push?fluff",
|
||||||
|
config.check_node_api_http_addr.as_str()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str());
|
||||||
}
|
}
|
||||||
|
api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }).context(ErrorKind::Node)?;
|
||||||
|
|
||||||
let partial_tx =
|
// All good so, lock our outputs
|
||||||
transaction::sender_confirmation(keychain, &mut context_manager, res.unwrap())?;
|
sender_lock_fn()?;
|
||||||
|
|
||||||
// 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} =>
|
|
||||||
error!(
|
|
||||||
LOGGER,
|
|
||||||
"Recipient rejected the transfer because transaction fee ({}) exceeded amount ({}).",
|
|
||||||
amount_to_hr_string(recipient_fee),
|
|
||||||
amount_to_hr_string(sender_amount)
|
|
||||||
),
|
|
||||||
_ => error!(LOGGER, "Communication with receiver failed on SenderConfirmation send. Aborting transaction"),
|
|
||||||
}
|
|
||||||
rollback_wallet()?;
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not really necessary here
|
|
||||||
context_manager.save_context(context.clone());
|
|
||||||
|
|
||||||
// All good so
|
|
||||||
update_wallet()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,8 +184,8 @@ pub fn issue_burn_tx(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use libwallet::build;
|
|
||||||
use keychain::Keychain;
|
use keychain::Keychain;
|
||||||
|
use libwallet::build;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// demonstrate that input.commitment == referenced output.commitment
|
// demonstrate that input.commitment == referenced output.commitment
|
||||||
|
|
|
@ -14,17 +14,15 @@
|
||||||
|
|
||||||
use blake2;
|
use blake2;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
use std::cmp::min;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::From;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use uuid::Uuid;
|
|
||||||
use std::convert::From;
|
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::Path;
|
|
||||||
use std::path::MAIN_SEPARATOR;
|
use std::path::MAIN_SEPARATOR;
|
||||||
use std::collections::HashMap;
|
use std::path::Path;
|
||||||
use std::cmp::min;
|
|
||||||
use libwallet::aggsig;
|
|
||||||
|
|
||||||
use serde;
|
use serde;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
@ -38,13 +36,8 @@ use core::consensus;
|
||||||
use core::core::Transaction;
|
use core::core::Transaction;
|
||||||
use core::core::hash::Hash;
|
use core::core::hash::Hash;
|
||||||
use core::core::pmmr::MerkleProof;
|
use core::core::pmmr::MerkleProof;
|
||||||
use core::ser;
|
|
||||||
use keychain;
|
use keychain;
|
||||||
use keychain::BlindingFactor;
|
|
||||||
use util;
|
use util;
|
||||||
use util::secp;
|
|
||||||
use util::secp::Signature;
|
|
||||||
use util::secp::key::PublicKey;
|
|
||||||
use util::LOGGER;
|
use util::LOGGER;
|
||||||
|
|
||||||
const DAT_FILE: &'static str = "wallet.dat";
|
const DAT_FILE: &'static str = "wallet.dat";
|
||||||
|
@ -644,9 +637,10 @@ impl WalletData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select spendable coins from the wallet.
|
/// Select spendable coins from the wallet.
|
||||||
/// Default strategy is to spend the maximum number of outputs (up to max_outputs).
|
/// Default strategy is to spend the maximum number of outputs (up to
|
||||||
/// Alternative strategy is to spend smallest outputs first but only as many as necessary.
|
/// max_outputs). Alternative strategy is to spend smallest outputs first
|
||||||
/// When we introduce additional strategies we should pass something other than a bool in.
|
/// but only as many as necessary. When we introduce additional strategies
|
||||||
|
/// we should pass something other than a bool in.
|
||||||
pub fn select_coins(
|
pub fn select_coins(
|
||||||
&self,
|
&self,
|
||||||
root_key_id: keychain::Identifier,
|
root_key_id: keychain::Identifier,
|
||||||
|
@ -751,120 +745,6 @@ impl WalletData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define the stages of a transaction
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub enum PartialTxPhase {
|
|
||||||
SenderInitiation,
|
|
||||||
ReceiverInitiation,
|
|
||||||
SenderConfirmation,
|
|
||||||
ReceiverConfirmation,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper in serializing the information required during an interactive aggsig
|
|
||||||
/// transaction
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
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,
|
|
||||||
pub part_sig: String,
|
|
||||||
pub tx: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a PartialTx
|
|
||||||
/// aggsig_tx_context should contain the private key/nonce pair
|
|
||||||
/// the resulting partial tx will contain the corresponding public keys
|
|
||||||
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,
|
|
||||||
) -> PartialTx {
|
|
||||||
let (pub_excess, pub_nonce) = context.get_public_keys(keychain.secp());
|
|
||||||
let mut pub_excess = pub_excess.serialize_vec(keychain.secp(), true).clone();
|
|
||||||
let len = pub_excess.clone().len();
|
|
||||||
let pub_excess: Vec<_> = pub_excess.drain(0..len).collect();
|
|
||||||
|
|
||||||
let mut pub_nonce = pub_nonce.serialize_vec(keychain.secp(), true);
|
|
||||||
let len = pub_nonce.clone().len();
|
|
||||||
let pub_nonce: Vec<_> = pub_nonce.drain(0..len).collect();
|
|
||||||
|
|
||||||
PartialTx {
|
|
||||||
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(),
|
|
||||||
part_sig: match part_sig {
|
|
||||||
None => String::from("00"),
|
|
||||||
Some(p) => util::to_hex(p.serialize_der(&keychain.secp())),
|
|
||||||
},
|
|
||||||
tx: util::to_hex(ser::ser_vec(&tx).unwrap()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads a partial transaction into the amount, sum of blinding
|
|
||||||
/// factors and the transaction itself.
|
|
||||||
pub fn read_partial_tx(
|
|
||||||
keychain: &keychain::Keychain,
|
|
||||||
partial_tx: &PartialTx,
|
|
||||||
) -> Result<
|
|
||||||
(
|
|
||||||
u64,
|
|
||||||
u64,
|
|
||||||
PublicKey,
|
|
||||||
PublicKey,
|
|
||||||
BlindingFactor,
|
|
||||||
Option<Signature>,
|
|
||||||
Transaction,
|
|
||||||
),
|
|
||||||
Error,
|
|
||||||
> {
|
|
||||||
let blind_bin = util::from_hex(partial_tx.public_blind_excess.clone())
|
|
||||||
.context(ErrorKind::GenericError("Could not decode HEX"))?;
|
|
||||||
let blinding = PublicKey::from_slice(keychain.secp(), &blind_bin[..])
|
|
||||||
.context(ErrorKind::GenericError("Could not construct public key"))?;
|
|
||||||
|
|
||||||
let nonce_bin = util::from_hex(partial_tx.public_nonce.clone())
|
|
||||||
.context(ErrorKind::GenericError("Could not decode HEX"))?;
|
|
||||||
let nonce = PublicKey::from_slice(keychain.secp(), &nonce_bin[..])
|
|
||||||
.context(ErrorKind::GenericError("Could not construct public key"))?;
|
|
||||||
|
|
||||||
let kernel_offset = BlindingFactor::from_hex(&partial_tx.kernel_offset.clone())
|
|
||||||
.context(ErrorKind::GenericError("Could not decode HEX"))?;
|
|
||||||
|
|
||||||
let sig_bin = util::from_hex(partial_tx.part_sig.clone())
|
|
||||||
.context(ErrorKind::GenericError("Could not decode HEX"))?;
|
|
||||||
let sig = match sig_bin.len() {
|
|
||||||
1 => None,
|
|
||||||
_ => Some(Signature::from_der(keychain.secp(), &sig_bin[..])
|
|
||||||
.context(ErrorKind::GenericError("Could not create signature"))?),
|
|
||||||
};
|
|
||||||
let tx_bin = util::from_hex(partial_tx.tx.clone())
|
|
||||||
.context(ErrorKind::GenericError("Could not decode HEX"))?;
|
|
||||||
let tx = ser::deserialize(&mut &tx_bin[..]).context(ErrorKind::GenericError(
|
|
||||||
"Could not deserialize transaction, invalid format.",
|
|
||||||
))?;
|
|
||||||
Ok((
|
|
||||||
partial_tx.amount,
|
|
||||||
partial_tx.lock_height,
|
|
||||||
blinding,
|
|
||||||
nonce,
|
|
||||||
kernel_offset,
|
|
||||||
sig,
|
|
||||||
tx,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Amount in request to build a coinbase output.
|
/// Amount in request to build a coinbase output.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub enum WalletReceiveRequest {
|
pub enum WalletReceiveRequest {
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//! Common functions to facilitate wallet, walletlib and transaction testing
|
//! Common functions to facilitate wallet, walletlib and transaction testing
|
||||||
use std::collections::hash_map::Entry;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
|
||||||
extern crate grin_api as api;
|
extern crate grin_api as api;
|
||||||
extern crate grin_chain as chain;
|
extern crate grin_chain as chain;
|
||||||
|
@ -24,18 +24,18 @@ extern crate grin_wallet as wallet;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
|
|
||||||
use chain::Chain;
|
use chain::Chain;
|
||||||
use core::core::{Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel};
|
|
||||||
use core::core::hash::Hashed;
|
use core::core::hash::Hashed;
|
||||||
|
use core::core::{Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel};
|
||||||
use core::{consensus, global, pow};
|
use core::{consensus, global, pow};
|
||||||
|
use keychain::Keychain;
|
||||||
use wallet::types::{BlockIdentifier, Error, ErrorKind, MerkleProofWrapper, OutputStatus,
|
use wallet::types::{BlockIdentifier, Error, ErrorKind, MerkleProofWrapper, OutputStatus,
|
||||||
WalletConfig, WalletData};
|
WalletConfig, WalletData};
|
||||||
use wallet::{checker, BlockFees};
|
use wallet::{checker, BlockFees};
|
||||||
use keychain::Keychain;
|
|
||||||
|
|
||||||
use util::secp::pedersen;
|
use util::secp::pedersen;
|
||||||
|
|
||||||
/// Mostly for testing, refreshes output state against a local chain instance instead of
|
/// Mostly for testing, refreshes output state against a local chain instance
|
||||||
/// via an http API call
|
/// instead of via an http API call
|
||||||
pub fn refresh_output_state_local(
|
pub fn refresh_output_state_local(
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
keychain: &Keychain,
|
keychain: &Keychain,
|
||||||
|
@ -58,8 +58,9 @@ pub fn refresh_output_state_local(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the spendable wallet balance from the local chain
|
/// Return the spendable wallet balance from the local chain
|
||||||
/// (0:total, 1:amount_awaiting_confirmation, 2:confirmed but locked, 3:currently_spendable,
|
/// (0:total, 1:amount_awaiting_confirmation, 2:confirmed but locked,
|
||||||
/// 4:locked total) TODO: Should be a wallet lib function with nicer return values
|
/// 3:currently_spendable, 4:locked total) TODO: Should be a wallet lib
|
||||||
|
/// function with nicer return values
|
||||||
pub fn get_wallet_balances(
|
pub fn get_wallet_balances(
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
keychain: &Keychain,
|
keychain: &Keychain,
|
||||||
|
@ -135,8 +136,8 @@ pub fn add_block_with_reward(chain: &Chain, txs: Vec<&Transaction>, reward: (Out
|
||||||
chain.validate(false).unwrap();
|
chain.validate(false).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// adds a reward output to a wallet, includes that reward in a block, mines the block
|
/// adds a reward output to a wallet, includes that reward in a block, mines
|
||||||
/// and adds it to the chain, with option transactions included.
|
/// the block and adds it to the chain, with option transactions included.
|
||||||
/// Helpful for building up precise wallet balances for testing.
|
/// Helpful for building up precise wallet balances for testing.
|
||||||
pub fn award_block_to_wallet(
|
pub fn award_block_to_wallet(
|
||||||
chain: &Chain,
|
chain: &Chain,
|
||||||
|
|
|
@ -20,11 +20,11 @@ extern crate grin_wallet as wallet;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate uuid;
|
extern crate uuid;
|
||||||
|
|
||||||
use uuid::Uuid;
|
|
||||||
use util::{kernel_sig_msg, secp};
|
|
||||||
use util::secp::key::SecretKey;
|
|
||||||
use util::secp::pedersen::ProofMessage;
|
|
||||||
use keychain::{BlindSum, BlindingFactor, Keychain};
|
use keychain::{BlindSum, BlindingFactor, Keychain};
|
||||||
|
use util::secp::key::{PublicKey, SecretKey};
|
||||||
|
use util::secp::pedersen::ProofMessage;
|
||||||
|
use util::{kernel_sig_msg, secp};
|
||||||
|
use uuid::Uuid;
|
||||||
use wallet::libwallet::{aggsig, proof};
|
use wallet::libwallet::{aggsig, proof};
|
||||||
|
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
|
@ -151,12 +151,16 @@ fn aggsig_sender_receiver_interaction() {
|
||||||
let our_sig_part = cx.calculate_partial_sig(&keychain.secp(), &sender_pub_nonce, 0, 0)
|
let our_sig_part = cx.calculate_partial_sig(&keychain.secp(), &sender_pub_nonce, 0, 0)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let combined_nonces = PublicKey::from_combination(
|
||||||
|
keychain.secp(),
|
||||||
|
vec![&sender_pub_nonce, &cx.get_public_keys(keychain.secp()).1],
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
// Receiver now generates final signature from the two parts
|
// Receiver now generates final signature from the two parts
|
||||||
let final_sig = cx.calculate_final_sig(
|
let final_sig = cx.calculate_final_sig(
|
||||||
&keychain.secp(),
|
&keychain.secp(),
|
||||||
&sender_sig_part,
|
vec![&sender_sig_part, &our_sig_part],
|
||||||
&our_sig_part,
|
&combined_nonces,
|
||||||
&sender_pub_nonce,
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
// Receiver calculates the final public key (to verify sig later)
|
// Receiver calculates the final public key (to verify sig later)
|
||||||
|
@ -322,12 +326,16 @@ fn aggsig_sender_receiver_interaction_offset() {
|
||||||
let our_sig_part = cx.calculate_partial_sig(&keychain.secp(), &sender_pub_nonce, 0, 0)
|
let our_sig_part = cx.calculate_partial_sig(&keychain.secp(), &sender_pub_nonce, 0, 0)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let combined_nonces = PublicKey::from_combination(
|
||||||
|
keychain.secp(),
|
||||||
|
vec![&sender_pub_nonce, &cx.get_public_keys(keychain.secp()).1],
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
// Receiver now generates final signature from the two parts
|
// Receiver now generates final signature from the two parts
|
||||||
let final_sig = cx.calculate_final_sig(
|
let final_sig = cx.calculate_final_sig(
|
||||||
&keychain.secp(),
|
&keychain.secp(),
|
||||||
&sender_sig_part,
|
vec![&sender_sig_part, &our_sig_part],
|
||||||
&our_sig_part,
|
&combined_nonces,
|
||||||
&sender_pub_nonce,
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
// Receiver calculates the final public key (to verify sig later)
|
// Receiver calculates the final public key (to verify sig later)
|
||||||
|
|
|
@ -20,6 +20,7 @@ extern crate grin_wallet as wallet;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate slog;
|
extern crate slog;
|
||||||
|
extern crate serde;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
extern crate uuid;
|
extern crate uuid;
|
||||||
|
|
||||||
|
@ -28,16 +29,13 @@ mod common;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use chain::Chain;
|
use chain::Chain;
|
||||||
use chain::types::*;
|
use chain::types::*;
|
||||||
use core::{global, pow};
|
|
||||||
use core::global::ChainTypes;
|
use core::global::ChainTypes;
|
||||||
use wallet::libwallet::{aggsig, transaction};
|
use core::{global, pow};
|
||||||
use wallet::grinwallet::{keys, selection};
|
|
||||||
use wallet::types::{OutputData, OutputStatus, WalletData};
|
|
||||||
use util::LOGGER;
|
use util::LOGGER;
|
||||||
|
use wallet::grinwallet::selection;
|
||||||
|
use wallet::libwallet::aggsig;
|
||||||
|
|
||||||
fn clean_output_dir(test_dir: &str) {
|
fn clean_output_dir(test_dir: &str) {
|
||||||
let _ = fs::remove_dir_all(test_dir);
|
let _ = fs::remove_dir_all(test_dir);
|
||||||
|
@ -57,32 +55,42 @@ fn setup(test_dir: &str, chain_dir: &str) -> Chain {
|
||||||
).unwrap()
|
).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a transaction between 2 parties
|
/// Build and test new version of sending API
|
||||||
#[cfg(test)]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_transaction() {
|
fn build_transaction_2() {
|
||||||
let chain = setup("test_output", "build_transaction/.grin");
|
let chain = setup("test_output", "build_transaction_2/.grin");
|
||||||
let wallet1 = common::create_wallet("test_output/build_transaction/wallet1");
|
let wallet1 = common::create_wallet("test_output/build_transaction_2/wallet1");
|
||||||
let wallet2 = common::create_wallet("test_output/build_transaction/wallet2");
|
let wallet2 = common::create_wallet("test_output/build_transaction_2/wallet2");
|
||||||
common::award_blocks_to_wallet(&chain, &wallet1, 10);
|
common::award_blocks_to_wallet(&chain, &wallet1, 10);
|
||||||
// Wallet 1 has 600 Grins, wallet 2 has 0. Create a transaction that sends
|
// Wallet 1 has 600 Grins, wallet 2 has 0. Create a transaction that sends
|
||||||
// 300 Grins from wallet 1 to wallet 2, using libwallet
|
// 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
|
// Get lock height
|
||||||
let chain_tip = chain.head().unwrap();
|
let chain_tip = chain.head().unwrap();
|
||||||
|
let amount = 300_000_000_000;
|
||||||
|
|
||||||
// ensure outputs we're selecting are up to date
|
// ensure outputs we're selecting are up to date
|
||||||
let res = common::refresh_output_state_local(&wallet1.0, &wallet1.1, &chain);
|
let res = common::refresh_output_state_local(&wallet1.0, &wallet1.1, &chain);
|
||||||
let amount = 300_000_000_000;
|
|
||||||
|
|
||||||
// Select our outputs
|
if let Err(e) = res {
|
||||||
let tx_data = selection::build_send_tx(
|
panic!("Unable to refresh sender wallet outputs: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TRANSACTION WORKFLOW STARTS HERE
|
||||||
|
// Sender creates a new aggsig context
|
||||||
|
let mut sender_context_manager = aggsig::ContextManager::new();
|
||||||
|
// Sender selects outputs into a new slate and save our corresponding IDs in
|
||||||
|
// their transaction context. The secret key in our transaction context will be
|
||||||
|
// randomly selected. This returns the public slate, and a closure that locks
|
||||||
|
// our inputs and outputs once we're convinced the transaction exchange went
|
||||||
|
// according to plan
|
||||||
|
// This function is just a big helper to do all of that, in theory
|
||||||
|
// this process can be split up in any way
|
||||||
|
let (mut slate, sender_lock_fn) = selection::build_send_tx_slate(
|
||||||
&wallet1.0,
|
&wallet1.0,
|
||||||
&wallet1.1,
|
&wallet1.1,
|
||||||
|
&mut sender_context_manager,
|
||||||
|
2,
|
||||||
amount,
|
amount,
|
||||||
chain_tip.height,
|
chain_tip.height,
|
||||||
3,
|
3,
|
||||||
|
@ -91,135 +99,74 @@ fn build_transaction() {
|
||||||
true,
|
true,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
if let Err(e) = res {
|
// Generate a kernel offset and subtract from our context's secret key. Store
|
||||||
panic!("Unable to refresh sender wallet outputs: {}", e);
|
// the offset in the slate's transaction kernel, and adds our public key
|
||||||
}
|
// information to the slate
|
||||||
|
let _ = slate
|
||||||
|
.fill_round_1(&wallet1.1, &mut sender_context_manager, 0)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let partial_tx = transaction::sender_initiation(
|
debug!(LOGGER, "Transaction Slate after step 1: 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, "-----------------------------------------");
|
||||||
debug!(LOGGER, "{:?}", partial_tx);
|
debug!(LOGGER, "{:?}", slate);
|
||||||
|
|
||||||
// RECIPIENT (Handle sender initiation)
|
// RECIPIENT (Handle sender initiation)
|
||||||
let mut recipient_context_manager = aggsig::ContextManager::new();
|
let mut recipient_context_manager = aggsig::ContextManager::new();
|
||||||
|
|
||||||
// Create a potential output for this transaction
|
// Now, just like the sender did, recipient is going to select a target output,
|
||||||
let (key_id, derivation) = WalletData::with_wallet(&wallet2.0.data_file_dir, |wallet_data| {
|
// add it to the transaction, and keep track of the corresponding wallet
|
||||||
keys::next_available_key(&wallet_data, &wallet2.1)
|
// Identifier Again, this is a helper to do that, which returns a closure that
|
||||||
}).unwrap();
|
// creates the output when we're satisified the process was successful
|
||||||
|
let (_, receiver_create_fn) = selection::build_recipient_output_with_slate(
|
||||||
let partial_tx = transaction::recipient_initiation(
|
&wallet2.0,
|
||||||
&wallet2.1,
|
&wallet2.1,
|
||||||
&mut recipient_context_manager,
|
&mut recipient_context_manager,
|
||||||
&partial_tx,
|
&mut slate,
|
||||||
&key_id,
|
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let mut context = recipient_context_manager.get_context(&partial_tx.id);
|
|
||||||
|
|
||||||
// Add the output to recipient's wallet
|
let _ = slate
|
||||||
let _ = WalletData::with_wallet(&wallet2.0.data_file_dir, |wallet_data| {
|
.fill_round_1(&wallet2.1, &mut recipient_context_manager, 1)
|
||||||
wallet_data.add_output(OutputData {
|
.unwrap();
|
||||||
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");
|
// recipient can proceed to round 2 now
|
||||||
debug!(LOGGER, "--------------------------------------------");
|
let _ = receiver_create_fn();
|
||||||
debug!(LOGGER, "{:?}", partial_tx);
|
|
||||||
|
|
||||||
// TODO: We want to allow the sender to be able to calculate this, but also need
|
let _ = slate
|
||||||
// the recipient's output information available, and the recipient needs to know
|
.fill_round_2(&wallet2.1, &mut recipient_context_manager, 1)
|
||||||
// whether to finalize the output in their wallet
|
.unwrap();
|
||||||
let _tx_with_recipients_pubkeys = partial_tx.clone();
|
|
||||||
|
debug!(
|
||||||
|
LOGGER,
|
||||||
|
"Transaction Slate after step 2: receiver initiation"
|
||||||
|
);
|
||||||
|
debug!(LOGGER, "-----------------------------------------");
|
||||||
|
debug!(LOGGER, "{:?}", slate);
|
||||||
|
|
||||||
// SENDER Part 3: Sender confirmation
|
// SENDER Part 3: Sender confirmation
|
||||||
let partial_tx =
|
let _ = slate
|
||||||
transaction::sender_confirmation(&wallet1.1, &mut sender_context_manager, partial_tx)
|
.fill_round_2(&wallet1.1, &mut sender_context_manager, 0)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
debug!(LOGGER, "PartialTx after step 3: sender confirmation");
|
debug!(LOGGER, "PartialTx after step 3: sender confirmation");
|
||||||
debug!(LOGGER, "--------------------------------------------");
|
debug!(LOGGER, "--------------------------------------------");
|
||||||
debug!(LOGGER, "{:?}", partial_tx);
|
debug!(LOGGER, "{:?}", slate);
|
||||||
|
|
||||||
// RECIPIENT Part 4: Recipient confirmation
|
// Final transaction can be built by anyone at this stage
|
||||||
// Get output we created in earlier step
|
let res = slate.finalize(&wallet1.1);
|
||||||
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
|
if let Err(e) = res {
|
||||||
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);
|
panic!("Error creating final tx: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!(LOGGER, "Recipient calculates final transaction as:");
|
debug!(LOGGER, "Final transaction is:");
|
||||||
debug!(LOGGER, "--------------------------------------------");
|
debug!(LOGGER, "--------------------------------------------");
|
||||||
debug!(LOGGER, "{:?}", final_tx_recipient);
|
debug!(LOGGER, "{:?}", slate.tx);
|
||||||
|
|
||||||
let _ = update_sender_wallet();
|
// All okay, lock sender's outputs
|
||||||
|
let _ = sender_lock_fn();
|
||||||
|
|
||||||
// Insert this transaction into a new block, then mine till confirmation
|
// 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_block_to_wallet(&chain, vec![&slate.tx], &wallet1);
|
||||||
common::award_blocks_to_wallet(&chain, &wallet1, 3);
|
common::award_blocks_to_wallet(&chain, &wallet1, 3);
|
||||||
|
|
||||||
// Refresh wallets
|
// Refresh wallets
|
||||||
|
|
Loading…
Reference in a new issue