mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 07:11:36 +02:00
Payment Proof export + validation (#289)
* add payment proof struct and deser * rustfmt * adding proof export functions * rustfmt * add payment proof validation function * rustfmt * add RPC version of retrieve_payment_proof + doctest * rustfmt * add verify_proof rpc function and documentation for new functions * rustfmt * add export and verify commands * rustfmt * test + test framework fixes * rustfmt * check whether addresses belong to this wallet, output such when checking * rustfmt * remove raw pubkey address and replace with ov3 address in user-facing contexts * merge from master and rustfmt * doctests
This commit is contained in:
+129
-1
@@ -27,7 +27,7 @@ use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, Status
|
||||
use crate::libwallet::api_impl::{owner, owner_updater};
|
||||
use crate::libwallet::{
|
||||
address, AcctPathMapping, Error, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient,
|
||||
NodeHeightResult, OutputCommitMapping, Slate, TxLogEntry, WalletInfo, WalletInst,
|
||||
NodeHeightResult, OutputCommitMapping, PaymentProof, Slate, TxLogEntry, WalletInfo, WalletInst,
|
||||
WalletLCProvider,
|
||||
};
|
||||
use crate::util::logger::LoggingConfig;
|
||||
@@ -2012,6 +2012,132 @@ where
|
||||
pub fn proof_address_from_onion_v3(&self, address_v3: &str) -> Result<DalekPublicKey, Error> {
|
||||
address::pubkey_from_onion_v3(address_v3)
|
||||
}
|
||||
|
||||
/// Returns a single, exportable [PaymentProof](../grin_wallet_libwallet/api_impl/types/struct.PaymentProof.html)
|
||||
/// from a completed transaction within the wallet.
|
||||
///
|
||||
/// The transaction must have been created with a payment proof, and the transaction must be
|
||||
/// complete in order for a payment proof to be returned. Either the `tx_id` or `tx_slate_id`
|
||||
/// argument must be provided, or the function will return an error.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
|
||||
/// being used.
|
||||
/// * `refresh_from_node` - If true, the wallet will attempt to contact
|
||||
/// a node (via the [`NodeClient`](../grin_wallet_libwallet/types/trait.NodeClient.html)
|
||||
/// provided during wallet instantiation). If `false`, the results will
|
||||
/// contain transaction information that may be out-of-date (from the last time
|
||||
/// the wallet's output set was refreshed against the node).
|
||||
/// Note this setting is ignored if the updater process is running via a call to
|
||||
/// [`start_updater`](struct.Owner.html#method.start_updater)
|
||||
/// * `tx_id` - If `Some(i)` return the proof associated with the transaction with id `i`
|
||||
/// * `tx_slate_id` - If `Some(uuid)`, return the proof associated with the transaction with the
|
||||
/// given `uuid`
|
||||
///
|
||||
/// # Returns
|
||||
/// * Ok([PaymentProof](../grin_wallet_libwallet/api_impl/types/struct.PaymentProof.html)) if successful
|
||||
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered
|
||||
/// or the proof is not present or complete
|
||||
///
|
||||
/// # Example
|
||||
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
|
||||
/// ```
|
||||
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
|
||||
///
|
||||
/// let api_owner = Owner::new(wallet.clone());
|
||||
/// let update_from_node = true;
|
||||
/// let tx_id = None;
|
||||
/// let tx_slate_id = Some(Uuid::parse_str("0436430c-2b02-624c-2032-570501212b00").unwrap());
|
||||
///
|
||||
/// // Return all TxLogEntries
|
||||
/// let result = api_owner.retrieve_payment_proof(None, update_from_node, tx_id, tx_slate_id);
|
||||
///
|
||||
/// if let Ok(p) = result {
|
||||
/// //...
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub fn retrieve_payment_proof(
|
||||
&self,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
refresh_from_node: bool,
|
||||
tx_id: Option<u32>,
|
||||
tx_slate_id: Option<Uuid>,
|
||||
) -> Result<PaymentProof, Error> {
|
||||
let tx = {
|
||||
let t = self.status_tx.lock();
|
||||
t.clone()
|
||||
};
|
||||
let refresh_from_node = match self.updater_running.load(Ordering::Relaxed) {
|
||||
true => false,
|
||||
false => refresh_from_node,
|
||||
};
|
||||
owner::retrieve_payment_proof(
|
||||
self.wallet_inst.clone(),
|
||||
keychain_mask,
|
||||
&tx,
|
||||
refresh_from_node,
|
||||
tx_id,
|
||||
tx_slate_id,
|
||||
)
|
||||
}
|
||||
|
||||
/// Verifies a [PaymentProof](../grin_wallet_libwallet/api_impl/types/struct.PaymentProof.html)
|
||||
/// This process entails:
|
||||
///
|
||||
/// * Ensuring the kernel identified by the proof's stored excess commitment exists in the kernel set
|
||||
/// * Reproducing the signed message `amount|kernel_commitment|sender_address`
|
||||
/// * Validating the proof's `recipient_sig` against the message using the recipient's
|
||||
/// address as the public key and
|
||||
/// * Validating the proof's `sender_sig` against the message using the senders's
|
||||
/// address as the public key
|
||||
///
|
||||
/// This function also checks whether the sender or recipient address belongs to the currently
|
||||
/// open wallet, and returns 2 booleans indicating whether the address belongs to the sender and
|
||||
/// whether the address belongs to the recipient respectively
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
|
||||
/// being used.
|
||||
/// * `proof` A [PaymentProof](../grin_wallet_libwallet/api_impl/types/struct.PaymentProof.html))
|
||||
///
|
||||
/// # Returns
|
||||
/// * Ok((bool, bool)) if the proof is valid. The first boolean indicates whether the sender
|
||||
/// address belongs to this wallet, the second whether the recipient address belongs to this
|
||||
/// wallet
|
||||
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered
|
||||
/// or the proof is not present or complete
|
||||
///
|
||||
/// # Example
|
||||
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
|
||||
/// ```
|
||||
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
|
||||
///
|
||||
/// let api_owner = Owner::new(wallet.clone());
|
||||
/// let update_from_node = true;
|
||||
/// let tx_id = None;
|
||||
/// let tx_slate_id = Some(Uuid::parse_str("0436430c-2b02-624c-2032-570501212b00").unwrap());
|
||||
///
|
||||
/// // Return all TxLogEntries
|
||||
/// let result = api_owner.retrieve_payment_proof(None, update_from_node, tx_id, tx_slate_id);
|
||||
///
|
||||
/// // The proof will likely be exported as JSON to be provided to another party
|
||||
///
|
||||
/// if let Ok(p) = result {
|
||||
/// let valid = api_owner.verify_payment_proof(None, &p);
|
||||
/// if let Ok(_) = valid {
|
||||
/// //...
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub fn verify_payment_proof(
|
||||
&self,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
proof: &PaymentProof,
|
||||
) -> Result<(bool, bool), Error> {
|
||||
owner::verify_payment_proof(self.wallet_inst.clone(), keychain_mask, proof)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@@ -2037,6 +2163,8 @@ macro_rules! doctest_helper_setup_doc_env {
|
||||
use impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient};
|
||||
use libwallet::{BlockFees, InitTxArgs, IssueInvoiceTxArgs, Slate, WalletInst};
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap();
|
||||
let dir = dir
|
||||
.path()
|
||||
|
||||
+43
-19
@@ -23,7 +23,7 @@ use crate::libwallet::{
|
||||
OutputCommitMapping, Slate, SlateVersion, TxLogEntry, VersionedSlate, WalletInfo,
|
||||
WalletLCProvider,
|
||||
};
|
||||
use crate::util::Mutex;
|
||||
use crate::util::{from_hex, Mutex};
|
||||
use crate::{Owner, OwnerRpcS};
|
||||
use easy_jsonrpc_mw;
|
||||
use std::sync::Arc;
|
||||
@@ -64,7 +64,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
"id": 1
|
||||
}
|
||||
# "#
|
||||
# , false, 4, false, false, false);
|
||||
# , false, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
#[deprecated(
|
||||
@@ -98,7 +98,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
"id": 1
|
||||
}
|
||||
# "#
|
||||
# ,false, 4, false, false, false);
|
||||
# ,false, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn create_account_path(&self, label: &String) -> Result<Identifier, ErrorKind>;
|
||||
@@ -128,7 +128,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
"id": 1
|
||||
}
|
||||
# "#
|
||||
# , false, 4, false, false, false);
|
||||
# , false, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn set_active_account(&self, label: &String) -> Result<(), ErrorKind>;
|
||||
@@ -194,7 +194,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , false, 2, false, false, false);
|
||||
# , false, 2, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn retrieve_outputs(
|
||||
@@ -273,7 +273,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , false, 2, false, false, false);
|
||||
# , false, 2, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -319,7 +319,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# ,false, 4, false, false, false);
|
||||
# ,false, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -417,7 +417,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# ,false, 4, false, false, false);
|
||||
# ,false, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -499,7 +499,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# ,false, 4, false, false, false);
|
||||
# ,false, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -653,7 +653,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# ,false, 4, false, false, false);
|
||||
# ,false, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -739,7 +739,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# ,false, 5 ,true, false, false);
|
||||
# ,false, 5 ,true, false, false, false);
|
||||
|
||||
```
|
||||
*/
|
||||
@@ -910,7 +910,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , false, 5, true, true, false);
|
||||
# , false, 5, true, true, false, false);
|
||||
```
|
||||
*/
|
||||
fn finalize_tx(&self, slate: VersionedSlate) -> Result<VersionedSlate, ErrorKind>;
|
||||
@@ -976,7 +976,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , false, 5, true, true, true);
|
||||
# , false, 5, true, true, true, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -1006,7 +1006,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , false, 5, true, true, false);
|
||||
# , false, 5, true, true, false, false);
|
||||
```
|
||||
*/
|
||||
fn cancel_tx(&self, tx_id: Option<u32>, tx_slate_id: Option<Uuid>) -> Result<(), ErrorKind>;
|
||||
@@ -1103,7 +1103,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , false, 5, true, true, false);
|
||||
# , false, 5, true, true, false, false);
|
||||
```
|
||||
*/
|
||||
fn get_stored_tx(&self, tx: &TxLogEntry) -> Result<Option<TransactionV3>, ErrorKind>;
|
||||
@@ -1183,7 +1183,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# ,false, 0 ,false, false, false);
|
||||
# ,false, 0 ,false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn verify_slate_messages(&self, slate: VersionedSlate) -> Result<(), ErrorKind>;
|
||||
@@ -1212,7 +1212,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , false, 1, false, false, false);
|
||||
# , false, 1, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn scan(&self, start_height: Option<u64>, delete_unconfirmed: bool) -> Result<(), ErrorKind>;
|
||||
@@ -1245,7 +1245,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , false, 5, false, false, false);
|
||||
# , false, 5, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn node_height(&self) -> Result<NodeHeightResult, ErrorKind>;
|
||||
@@ -1372,6 +1372,7 @@ pub fn run_doctest_owner(
|
||||
perform_tx: bool,
|
||||
lock_tx: bool,
|
||||
finalize_tx: bool,
|
||||
payment_proof: bool,
|
||||
) -> Result<Option<serde_json::Value>, String> {
|
||||
use easy_jsonrpc_mw::Handler;
|
||||
use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy};
|
||||
@@ -1379,6 +1380,8 @@ pub fn run_doctest_owner(
|
||||
use grin_wallet_libwallet::{api_impl, WalletInst};
|
||||
use grin_wallet_util::grin_keychain::ExtKeychain;
|
||||
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
|
||||
use crate::core::global;
|
||||
use crate::core::global::ChainTypes;
|
||||
use grin_wallet_util::grin_util as util;
|
||||
@@ -1434,6 +1437,8 @@ pub fn run_doctest_owner(
|
||||
mask1.clone(),
|
||||
);
|
||||
|
||||
let mut slate_outer = Slate::blank(2);
|
||||
|
||||
let rec_phrase_2 = util::ZeroingString::from(
|
||||
"hour kingdom ripple lunch razor inquiry coyote clay stamp mean \
|
||||
sell finish magic kid tiny wage stand panther inside settle feed song hole exile",
|
||||
@@ -1503,6 +1508,18 @@ pub fn run_doctest_owner(
|
||||
let amount = 60_000_000_000;
|
||||
let mut w_lock = wallet1.lock();
|
||||
let w = w_lock.lc_provider().unwrap().wallet_inst().unwrap();
|
||||
let proof_address = match payment_proof {
|
||||
true => {
|
||||
let bytes = from_hex(
|
||||
"783f6528669742a990e0faf0a5fca5d5b3330e37bbb9cd5c628696d03ce4e810".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut b = [0u8; 32];
|
||||
b.copy_from_slice(&bytes[0..32]);
|
||||
Some(DalekPublicKey::from_bytes(&b).unwrap())
|
||||
}
|
||||
false => None,
|
||||
};
|
||||
let args = InitTxArgs {
|
||||
src_acct_name: None,
|
||||
amount,
|
||||
@@ -1510,6 +1527,7 @@ pub fn run_doctest_owner(
|
||||
max_outputs: 500,
|
||||
num_change_outputs: 1,
|
||||
selection_strategy_is_use_all: true,
|
||||
payment_proof_recipient_address: proof_address,
|
||||
..Default::default()
|
||||
};
|
||||
let mut slate =
|
||||
@@ -1541,6 +1559,11 @@ pub fn run_doctest_owner(
|
||||
error!("FINALIZED TX SLATE");
|
||||
println!("{}", serde_json::to_string_pretty(&slate).unwrap());
|
||||
}
|
||||
slate_outer = slate;
|
||||
}
|
||||
|
||||
if payment_proof {
|
||||
let _ = api_impl::owner::post_tx(&client1, &slate_outer.tx, true).unwrap();
|
||||
}
|
||||
|
||||
if perform_tx && lock_tx && finalize_tx {
|
||||
@@ -1570,7 +1593,7 @@ pub fn run_doctest_owner(
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! doctest_helper_json_rpc_owner_assert_response {
|
||||
($request:expr, $expected_response:expr, $use_token:expr, $blocks_to_mine:expr, $perform_tx:expr, $lock_tx:expr, $finalize_tx:expr) => {
|
||||
($request:expr, $expected_response:expr, $use_token:expr, $blocks_to_mine:expr, $perform_tx:expr, $lock_tx:expr, $finalize_tx:expr, $payment_proof:expr) => {
|
||||
// create temporary wallet, run jsonrpc request on owner api of wallet, delete wallet, return
|
||||
// json response.
|
||||
// In order to prevent leaking tempdirs, This function should not panic.
|
||||
@@ -1603,6 +1626,7 @@ macro_rules! doctest_helper_json_rpc_owner_assert_response {
|
||||
$perform_tx,
|
||||
$lock_tx,
|
||||
$finalize_tx,
|
||||
$payment_proof,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
+151
-34
@@ -22,8 +22,8 @@ use crate::keychain::{Identifier, Keychain};
|
||||
use crate::libwallet::slate_versions::v3::TransactionV3;
|
||||
use crate::libwallet::{
|
||||
AcctPathMapping, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult,
|
||||
OutputCommitMapping, Slate, SlateVersion, StatusMessage, TxLogEntry, VersionedSlate,
|
||||
WalletInfo, WalletLCProvider,
|
||||
OutputCommitMapping, PaymentProof, Slate, SlateVersion, StatusMessage, TxLogEntry,
|
||||
VersionedSlate, WalletInfo, WalletLCProvider,
|
||||
};
|
||||
use crate::util::logger::LoggingConfig;
|
||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||
@@ -72,7 +72,7 @@ pub trait OwnerRpcS {
|
||||
"id": 1
|
||||
}
|
||||
# "#
|
||||
# , true, 4, false, false, false);
|
||||
# , true, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn accounts(&self, token: Token) -> Result<Vec<AcctPathMapping>, ErrorKind>;
|
||||
@@ -105,7 +105,7 @@ pub trait OwnerRpcS {
|
||||
"id": 1
|
||||
}
|
||||
# "#
|
||||
# ,true, 4, false, false, false);
|
||||
# ,true, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn create_account_path(&self, token: Token, label: &String) -> Result<Identifier, ErrorKind>;
|
||||
@@ -138,7 +138,7 @@ pub trait OwnerRpcS {
|
||||
"id": 1
|
||||
}
|
||||
# "#
|
||||
# , true, 4, false, false, false);
|
||||
# , true, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn set_active_account(&self, token: Token, label: &String) -> Result<(), ErrorKind>;
|
||||
@@ -209,7 +209,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 2, false, false, false);
|
||||
# , true, 2, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn retrieve_outputs(
|
||||
@@ -294,7 +294,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 2, false, false, false);
|
||||
# , true, 2, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -345,7 +345,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# ,true, 4, false, false, false);
|
||||
# ,true, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -449,7 +449,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# ,true, 4, false, false, false);
|
||||
# ,true, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -532,7 +532,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# ,true, 4, false, false, false);
|
||||
# ,true, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -691,7 +691,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# ,true, 4, false, false, false);
|
||||
# ,true, 4, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -780,7 +780,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# ,true, 5 ,true, false, false);
|
||||
# ,true, 5 ,true, false, false, false);
|
||||
|
||||
```
|
||||
*/
|
||||
@@ -953,7 +953,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 5, true, true, false);
|
||||
# , true, 5, true, true, false, false);
|
||||
```
|
||||
*/
|
||||
fn finalize_tx(&self, token: Token, slate: VersionedSlate)
|
||||
@@ -1021,7 +1021,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 5, true, true, true);
|
||||
# , true, 5, true, true, true, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -1055,7 +1055,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 5, true, true, false);
|
||||
# , true, 5, true, true, false, false);
|
||||
```
|
||||
*/
|
||||
fn cancel_tx(
|
||||
@@ -1158,7 +1158,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 5, true, true, false);
|
||||
# , true, 5, true, true, false, false);
|
||||
```
|
||||
*/
|
||||
fn get_stored_tx(
|
||||
@@ -1244,7 +1244,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# ,true, 0 ,false, false, false);
|
||||
# ,true, 0 ,false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn verify_slate_messages(&self, token: Token, slate: VersionedSlate) -> Result<(), ErrorKind>;
|
||||
@@ -1277,7 +1277,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 1, false, false, false);
|
||||
# , true, 1, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn scan(
|
||||
@@ -1316,7 +1316,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 5, false, false, false);
|
||||
# , true, 5, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn node_height(&self, token: Token) -> Result<NodeHeightResult, ErrorKind>;
|
||||
@@ -1399,7 +1399,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 5, false, false, false);
|
||||
# , true, 5, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -1429,7 +1429,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 5, false, false, false);
|
||||
# , true, 5, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -1495,7 +1495,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 5, false, false, false);
|
||||
# , true, 5, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn create_config(
|
||||
@@ -1533,7 +1533,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
# , true, 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -1570,7 +1570,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
# , true, 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -1600,7 +1600,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
# , true, 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -1631,7 +1631,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
# , true, 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -1663,7 +1663,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
# , true, 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn change_password(
|
||||
@@ -1697,7 +1697,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
# , true, 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn delete_wallet(&self, name: Option<String>) -> Result<(), ErrorKind>;
|
||||
@@ -1727,7 +1727,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
# , true, 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -1755,7 +1755,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
# , true, 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn stop_updater(&self) -> Result<(), ErrorKind>;
|
||||
@@ -1784,7 +1784,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
# , true, 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -1815,7 +1815,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
# , true, 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
@@ -1849,12 +1849,103 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
# , true, 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
fn proof_address_from_onion_v3(&self, address_v3: String) -> Result<PubAddress, ErrorKind>;
|
||||
|
||||
/**
|
||||
Networked version of [Owner::retrieve_payment_proof](struct.Owner.html#method.retrieve_payment_proof).
|
||||
```
|
||||
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
|
||||
# r#"
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "retrieve_payment_proof",
|
||||
"params": {
|
||||
"token": "d202964900000000d302964900000000d402964900000000d502964900000000",
|
||||
"refresh_from_node": true,
|
||||
"tx_id": null,
|
||||
"tx_slate_id": "0436430c-2b02-624c-2032-570501212b00"
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
# "#
|
||||
# ,
|
||||
# r#"
|
||||
{
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"Ok": {
|
||||
"amount": "60000000000",
|
||||
"excess": "09bac6083b05a32a9d9b37710c70dd0a1ef9329fde0848558976b6f1b81d80ceed",
|
||||
"recipient_address": "pa7wkkdgs5bkteha7lykl7ff2wztgdrxxo442xdcq2lnaphe5aidd4id",
|
||||
"recipient_sig": "42b6f2bbcee432185993867d1a338e260454ead536bf728f4dcc8f535508715e92a0695486ba3c9945d8ecb2cf7f703a955780253b12d0048f02d318c8f08702",
|
||||
"sender_address": "glg5mojiqvhywjriwhooiytn3tptlvlmw7h567lezssyek3y2tjzznad",
|
||||
"sender_sig": "5e3f5596852e83f6db7152fc51c41b4ed8742eb8045fa85a6965c52d09fcb46ba67d4f86660c9f3dc55ab84faea79d11c3831aa77934f7e90695e63d523f8604"
|
||||
}
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 5, true, true, true, true);
|
||||
```
|
||||
*/
|
||||
|
||||
fn retrieve_payment_proof(
|
||||
&self,
|
||||
token: Token,
|
||||
refresh_from_node: bool,
|
||||
tx_id: Option<u32>,
|
||||
tx_slate_id: Option<Uuid>,
|
||||
) -> Result<PaymentProof, ErrorKind>;
|
||||
|
||||
/**
|
||||
Networked version of [Owner::verify_payment_proof](struct.Owner.html#method.verify_payment_proof).
|
||||
```
|
||||
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
|
||||
# r#"
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "verify_payment_proof",
|
||||
"params": {
|
||||
"token": "d202964900000000d302964900000000d402964900000000d502964900000000",
|
||||
"proof": {
|
||||
"amount": "60000000000",
|
||||
"excess": "09bac6083b05a32a9d9b37710c70dd0a1ef9329fde0848558976b6f1b81d80ceed",
|
||||
"recipient_address": "pa7wkkdgs5bkteha7lykl7ff2wztgdrxxo442xdcq2lnaphe5aidd4id",
|
||||
"recipient_sig": "42b6f2bbcee432185993867d1a338e260454ead536bf728f4dcc8f535508715e92a0695486ba3c9945d8ecb2cf7f703a955780253b12d0048f02d318c8f08702",
|
||||
"sender_address": "glg5mojiqvhywjriwhooiytn3tptlvlmw7h567lezssyek3y2tjzznad",
|
||||
"sender_sig": "5e3f5596852e83f6db7152fc51c41b4ed8742eb8045fa85a6965c52d09fcb46ba67d4f86660c9f3dc55ab84faea79d11c3831aa77934f7e90695e63d523f8604"
|
||||
}
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
# "#
|
||||
# ,
|
||||
# r#"
|
||||
{
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"Ok": [
|
||||
true,
|
||||
false
|
||||
]
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 5, true, true, true, true);
|
||||
```
|
||||
*/
|
||||
|
||||
fn verify_payment_proof(
|
||||
&self,
|
||||
token: Token,
|
||||
proof: PaymentProof,
|
||||
) -> Result<(bool, bool), ErrorKind>;
|
||||
|
||||
/**
|
||||
Networked version of [Owner::set_tor_config](struct.Owner.html#method.set_tor_config).
|
||||
```
|
||||
@@ -1883,7 +1974,7 @@ pub trait OwnerRpcS {
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
# , true, 0, false, false, false, false);
|
||||
```
|
||||
*/
|
||||
fn set_tor_config(&self, tor_config: Option<TorConfig>) -> Result<(), ErrorKind>;
|
||||
@@ -2206,6 +2297,32 @@ where
|
||||
Ok(PubAddress { address })
|
||||
}
|
||||
|
||||
fn retrieve_payment_proof(
|
||||
&self,
|
||||
token: Token,
|
||||
refresh_from_node: bool,
|
||||
tx_id: Option<u32>,
|
||||
tx_slate_id: Option<Uuid>,
|
||||
) -> Result<PaymentProof, ErrorKind> {
|
||||
Owner::retrieve_payment_proof(
|
||||
self,
|
||||
(&token.keychain_mask).as_ref(),
|
||||
refresh_from_node,
|
||||
tx_id,
|
||||
tx_slate_id,
|
||||
)
|
||||
.map_err(|e| e.kind())
|
||||
}
|
||||
|
||||
fn verify_payment_proof(
|
||||
&self,
|
||||
token: Token,
|
||||
proof: PaymentProof,
|
||||
) -> Result<(bool, bool), ErrorKind> {
|
||||
Owner::verify_payment_proof(self, (&token.keychain_mask).as_ref(), &proof)
|
||||
.map_err(|e| e.kind())
|
||||
}
|
||||
|
||||
fn proof_address_from_onion_v3(&self, address_v3: String) -> Result<PubAddress, ErrorKind> {
|
||||
let address =
|
||||
Owner::proof_address_from_onion_v3(self, &address_v3).map_err(|e| e.kind())?;
|
||||
|
||||
+105
-3
@@ -22,14 +22,15 @@ use crate::impls::{create_sender, KeybaseAllChannels, SlateGetter as _, SlateRec
|
||||
use crate::impls::{PathToSlate, SlatePutter};
|
||||
use crate::keychain;
|
||||
use crate::libwallet::{
|
||||
address, InitTxArgs, IssueInvoiceTxArgs, NodeClient, WalletInst, WalletLCProvider,
|
||||
self, address, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, WalletInst,
|
||||
WalletLCProvider,
|
||||
};
|
||||
use crate::util::secp::key::SecretKey;
|
||||
use crate::util::{to_hex, Mutex, ZeroingString};
|
||||
use crate::{controller, display};
|
||||
use serde_json as json;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
@@ -911,7 +912,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Addres retrieval failed: {}", e);
|
||||
error!("Address retrieval failed: {}", e);
|
||||
error!("Backtrace: {}", e.backtrace().unwrap());
|
||||
Err(e)
|
||||
}
|
||||
@@ -919,3 +920,104 @@ where
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Proof Export Args
|
||||
pub struct ProofExportArgs {
|
||||
pub output_file: String,
|
||||
pub id: Option<u32>,
|
||||
pub tx_slate_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
pub fn proof_export<L, C, K>(
|
||||
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
args: ProofExportArgs,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K> + 'static,
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
|
||||
let result = api.retrieve_payment_proof(m, true, args.id, args.tx_slate_id);
|
||||
match result {
|
||||
Ok(p) => {
|
||||
// actually export proof
|
||||
let mut proof_file = File::create(args.output_file.clone())?;
|
||||
proof_file.write_all(json::to_string_pretty(&p).unwrap().as_bytes())?;
|
||||
proof_file.sync_all()?;
|
||||
warn!("Payment proof exported to {}", args.output_file);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Proof export failed: {}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Proof Verify Args
|
||||
pub struct ProofVerifyArgs {
|
||||
pub input_file: String,
|
||||
}
|
||||
|
||||
pub fn proof_verify<L, C, K>(
|
||||
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
args: ProofVerifyArgs,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K> + 'static,
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| {
|
||||
let mut proof_f = match File::open(&args.input_file) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
let msg = format!("{}", e);
|
||||
error!(
|
||||
"Unable to open payment proof file at {}: {}",
|
||||
args.input_file, e
|
||||
);
|
||||
return Err(libwallet::ErrorKind::PaymentProofParsing(msg).into());
|
||||
}
|
||||
};
|
||||
let mut proof = String::new();
|
||||
proof_f.read_to_string(&mut proof)?;
|
||||
// read
|
||||
let proof: PaymentProof = match json::from_str(&proof) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
let msg = format!("{}", e);
|
||||
error!("Unable to parse payment proof file: {}", e);
|
||||
return Err(libwallet::ErrorKind::PaymentProofParsing(msg).into());
|
||||
}
|
||||
};
|
||||
let result = api.verify_payment_proof(m, &proof);
|
||||
match result {
|
||||
Ok((iam_sender, iam_recipient)) => {
|
||||
println!("Payment proof's signatures are valid.");
|
||||
if iam_sender {
|
||||
println!("The proof's sender address belongs to this wallet.");
|
||||
}
|
||||
if iam_recipient {
|
||||
println!("The proof's recipient address belongs to this wallet.");
|
||||
}
|
||||
if !iam_recipient && !iam_sender {
|
||||
println!(
|
||||
"Neither the proof's sender nor recipient address belongs to this wallet."
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Proof not valid: {}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -126,14 +126,31 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err
|
||||
assert_eq!(pp.sender_address_path, 0);
|
||||
assert_eq!(pp.sender_signature, None);
|
||||
|
||||
// check we should get an error at this point since proof is not complete
|
||||
let pp = sender_api.retrieve_payment_proof(m, true, None, Some(slate.id));
|
||||
assert!(pp.is_err());
|
||||
|
||||
slate = sender_api.finalize_tx(m, &slate)?;
|
||||
sender_api.post_tx(m, &slate.tx, true)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false);
|
||||
|
||||
wallet::controller::owner_single_use(wallet1.clone(), mask1, |sender_api, m| {
|
||||
// Check payment proof here
|
||||
let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?;
|
||||
let tx = txs[0].clone();
|
||||
let mut pp = sender_api.retrieve_payment_proof(m, true, None, Some(slate.id))?;
|
||||
|
||||
println!("{:?}", tx);
|
||||
println!("{:?}", pp);
|
||||
|
||||
// verify, should be good
|
||||
let res = sender_api.verify_payment_proof(m, &pp)?;
|
||||
assert_eq!(res, (true, false));
|
||||
|
||||
// Modify values, should not be good
|
||||
pp.amount = 20;
|
||||
let res = sender_api.verify_payment_proof(m, &pp);
|
||||
assert!(res.is_err());
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
|
||||
@@ -353,7 +353,15 @@ where
|
||||
let max = split[2].parse::<u64>().unwrap();
|
||||
let commit_bytes = util::from_hex(excess).unwrap();
|
||||
let commit = pedersen::Commitment::from_vec(commit_bytes);
|
||||
let k = super::get_kernel_local(self.chain.clone(), &commit, Some(min), Some(max));
|
||||
let min = match min {
|
||||
0 => None,
|
||||
m => Some(m),
|
||||
};
|
||||
let max = match max {
|
||||
0 => None,
|
||||
m => Some(m),
|
||||
};
|
||||
let k = super::get_kernel_local(self.chain.clone(), &commit, min, max);
|
||||
Ok(WalletProxyMessage {
|
||||
sender_id: "node".to_owned(),
|
||||
dest: m.sender_id,
|
||||
|
||||
@@ -30,10 +30,11 @@ use crate::slate::{PaymentInfo, Slate};
|
||||
use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, TxWrapper, WalletBackend, WalletInfo};
|
||||
use crate::{
|
||||
address, wallet_lock, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping,
|
||||
ScannedBlockInfo, TxLogEntryType, WalletInitStatus, WalletInst, WalletLCProvider,
|
||||
PaymentProof, ScannedBlockInfo, TxLogEntryType, WalletInitStatus, WalletInst, WalletLCProvider,
|
||||
};
|
||||
use crate::{Error, ErrorKind};
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::Arc;
|
||||
@@ -193,6 +194,103 @@ where
|
||||
Ok((validated, wallet_info))
|
||||
}
|
||||
|
||||
/// Retrieve payment proof
|
||||
pub fn retrieve_payment_proof<'a, L, C, K>(
|
||||
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
status_send_channel: &Option<Sender<StatusMessage>>,
|
||||
refresh_from_node: bool,
|
||||
tx_id: Option<u32>,
|
||||
tx_slate_id: Option<Uuid>,
|
||||
) -> Result<PaymentProof, Error>
|
||||
where
|
||||
L: WalletLCProvider<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
if tx_id.is_none() && tx_slate_id.is_none() {
|
||||
return Err(ErrorKind::PaymentProofRetrieval(
|
||||
"Transaction ID or Slate UUID must be specified".into(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let mut _validated = false;
|
||||
if refresh_from_node {
|
||||
_validated = update_wallet_state(
|
||||
wallet_inst.clone(),
|
||||
keychain_mask,
|
||||
status_send_channel,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
let txs = retrieve_txs(
|
||||
wallet_inst.clone(),
|
||||
keychain_mask,
|
||||
status_send_channel,
|
||||
refresh_from_node,
|
||||
tx_id,
|
||||
tx_slate_id,
|
||||
)?;
|
||||
if txs.1.len() != 1 {
|
||||
return Err(ErrorKind::PaymentProofRetrieval("Transaction doesn't exist".into()).into());
|
||||
}
|
||||
// Pull out all needed fields, returning an error if they're not present
|
||||
let tx = txs.1[0].clone();
|
||||
let proof = match tx.payment_proof {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
return Err(ErrorKind::PaymentProofRetrieval(
|
||||
"Transaction does not contain a payment proof".into(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
let amount = if tx.amount_credited >= tx.amount_debited {
|
||||
tx.amount_credited - tx.amount_debited
|
||||
} else {
|
||||
let fee = match tx.fee {
|
||||
Some(f) => f,
|
||||
None => 0,
|
||||
};
|
||||
tx.amount_debited - tx.amount_credited - fee
|
||||
};
|
||||
let excess = match tx.kernel_excess {
|
||||
Some(e) => e,
|
||||
None => {
|
||||
return Err(ErrorKind::PaymentProofRetrieval(
|
||||
"Transaction does not contain kernel excess".into(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
let r_sig = match proof.receiver_signature {
|
||||
Some(e) => e,
|
||||
None => {
|
||||
return Err(ErrorKind::PaymentProofRetrieval(
|
||||
"Proof does not contain receiver signature ".into(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
let s_sig = match proof.sender_signature {
|
||||
Some(e) => e,
|
||||
None => {
|
||||
return Err(ErrorKind::PaymentProofRetrieval(
|
||||
"Proof does not contain sender signature ".into(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
Ok(PaymentProof {
|
||||
amount: amount,
|
||||
excess: excess,
|
||||
recipient_address: address::onion_v3_from_pubkey(&proof.receiver_address)?,
|
||||
recipient_sig: r_sig,
|
||||
sender_address: address::onion_v3_from_pubkey(&proof.sender_address)?,
|
||||
sender_sig: s_sig,
|
||||
})
|
||||
}
|
||||
|
||||
/// Initiate tx as sender
|
||||
pub fn init_send_tx<'a, T: ?Sized, C, K>(
|
||||
w: &mut T,
|
||||
@@ -469,7 +567,7 @@ where
|
||||
let context = w.get_private_context(keychain_mask, sl.id.as_bytes(), 0)?;
|
||||
let parent_key_id = w.parent_key_id();
|
||||
tx::complete_tx(&mut *w, keychain_mask, &mut sl, 0, &context)?;
|
||||
tx::verify_payment_proof(&mut *w, keychain_mask, &parent_key_id, &context, &sl)?;
|
||||
tx::verify_slate_payment_proof(&mut *w, keychain_mask, &parent_key_id, &context, &sl)?;
|
||||
tx::update_stored_tx(&mut *w, keychain_mask, &context, &mut sl, false)?;
|
||||
tx::update_message(&mut *w, keychain_mask, &mut sl)?;
|
||||
{
|
||||
@@ -779,6 +877,80 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify/validate arbitrary payment proof
|
||||
/// Returns (whether this wallet is the sender, whether this wallet is the recipient)
|
||||
pub fn verify_payment_proof<'a, L, C, K>(
|
||||
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
proof: &PaymentProof,
|
||||
) -> Result<(bool, bool), Error>
|
||||
where
|
||||
L: WalletLCProvider<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
let sender_pubkey = address::pubkey_from_onion_v3(&proof.sender_address)?;
|
||||
let msg = tx::payment_proof_message(proof.amount, &proof.excess, sender_pubkey)?;
|
||||
|
||||
let (mut client, parent_key_id, keychain) = {
|
||||
wallet_lock!(wallet_inst, w);
|
||||
(
|
||||
w.w2n_client().clone(),
|
||||
w.parent_key_id(),
|
||||
w.keychain(keychain_mask)?,
|
||||
)
|
||||
};
|
||||
|
||||
// Check kernel exists
|
||||
match client.get_kernel(&proof.excess, None, None) {
|
||||
Err(e) => {
|
||||
return Err(ErrorKind::PaymentProof(
|
||||
format!("Error retrieving kernel from chain: {}", e).to_owned(),
|
||||
))?;
|
||||
}
|
||||
Ok(None) => {
|
||||
return Err(ErrorKind::PaymentProof(
|
||||
format!(
|
||||
"Transaction kernel with excess {:?} not found on chain",
|
||||
proof.excess
|
||||
)
|
||||
.to_owned(),
|
||||
))?;
|
||||
}
|
||||
Ok(Some(_)) => {}
|
||||
};
|
||||
|
||||
// Check Sigs
|
||||
let recipient_pubkey = address::pubkey_from_onion_v3(&proof.recipient_address)?;
|
||||
if let Err(_) = recipient_pubkey.verify(&msg, &proof.recipient_sig) {
|
||||
return Err(ErrorKind::PaymentProof(
|
||||
"Invalid recipient signature".to_owned(),
|
||||
))?;
|
||||
};
|
||||
|
||||
let sender_pubkey = address::pubkey_from_onion_v3(&proof.sender_address)?;
|
||||
if let Err(_) = sender_pubkey.verify(&msg, &proof.sender_sig) {
|
||||
return Err(ErrorKind::PaymentProof(
|
||||
"Invalid sender signature".to_owned(),
|
||||
))?;
|
||||
};
|
||||
|
||||
// for now, simple test as to whether one of the addresses belongs to this wallet
|
||||
let sec_key = address::address_from_derivation_path(&keychain, &parent_key_id, 0)?;
|
||||
let d_skey = match DalekSecretKey::from_bytes(&sec_key.0) {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
return Err(ErrorKind::ED25519Key(format!("{}", e)).to_owned())?;
|
||||
}
|
||||
};
|
||||
let my_address_pubkey: DalekPublicKey = (&d_skey).into();
|
||||
|
||||
let sender_mine = my_address_pubkey == sender_pubkey;
|
||||
let recipient_mine = my_address_pubkey == recipient_pubkey;
|
||||
|
||||
Ok((sender_mine, recipient_mine))
|
||||
}
|
||||
|
||||
/// Attempt to update outputs in wallet, return whether it was successful
|
||||
fn update_outputs<'a, L, C, K>(
|
||||
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::slate_versions::SlateVersion;
|
||||
use crate::types::OutputData;
|
||||
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
|
||||
/// Send TX API Args
|
||||
// TODO: This is here to ensure the legacy V1 API remains intact
|
||||
@@ -222,3 +223,27 @@ pub struct VersionInfo {
|
||||
/// Slate version
|
||||
pub supported_slate_versions: Vec<SlateVersion>,
|
||||
}
|
||||
|
||||
/// Packaged Payment Proof
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct PaymentProof {
|
||||
/// Amount
|
||||
#[serde(with = "secp_ser::string_or_u64")]
|
||||
pub amount: u64,
|
||||
/// Kernel Excess
|
||||
#[serde(
|
||||
serialize_with = "secp_ser::as_hex",
|
||||
deserialize_with = "secp_ser::commitment_from_hex"
|
||||
)]
|
||||
pub excess: pedersen::Commitment,
|
||||
/// Recipient Wallet Address (Onion V3)
|
||||
pub recipient_address: String,
|
||||
/// Recipient Signature
|
||||
#[serde(with = "dalek_ser::dalek_sig_serde")]
|
||||
pub recipient_sig: DalekSignature,
|
||||
/// Sender Wallet Address (Onion V3)
|
||||
pub sender_address: String,
|
||||
/// Sender Signature
|
||||
#[serde(with = "dalek_ser::dalek_sig_serde")]
|
||||
pub sender_sig: DalekSignature,
|
||||
}
|
||||
|
||||
@@ -229,6 +229,14 @@ pub enum ErrorKind {
|
||||
#[fail(display = "Payment Proof generation error: {}", _0)]
|
||||
PaymentProof(String),
|
||||
|
||||
/// Retrieving Payment Proof
|
||||
#[fail(display = "Payment Proof retrieval error: {}", _0)]
|
||||
PaymentProofRetrieval(String),
|
||||
|
||||
/// Retrieving Payment Proof
|
||||
#[fail(display = "Payment Proof parsing error: {}", _0)]
|
||||
PaymentProofParsing(String),
|
||||
|
||||
/// Decoding OnionV3 addresses to payment proof addresses
|
||||
#[fail(display = "Proof Address decoding: {}", _0)]
|
||||
AddressDecoding(String),
|
||||
|
||||
@@ -461,8 +461,8 @@ pub fn create_payment_proof_signature(
|
||||
Ok(keypair.sign(&msg))
|
||||
}
|
||||
|
||||
/// Verify all aspects of a completed payment proof
|
||||
pub fn verify_payment_proof<'a, T: ?Sized, C, K>(
|
||||
/// Verify all aspects of a completed payment proof on the current slate
|
||||
pub fn verify_slate_payment_proof<'a, T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
parent_key_id: &Identifier,
|
||||
|
||||
@@ -61,7 +61,7 @@ pub use crate::slate_versions::{
|
||||
pub use api_impl::owner_updater::StatusMessage;
|
||||
pub use api_impl::types::{
|
||||
BlockFees, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, NodeHeightResult,
|
||||
OutputCommitMapping, SendTXArgs, VersionInfo,
|
||||
OutputCommitMapping, PaymentProof, SendTXArgs, VersionInfo,
|
||||
};
|
||||
pub use internal::scan::scan;
|
||||
pub use slate_versions::ser as dalek_ser;
|
||||
|
||||
@@ -81,6 +81,37 @@ pub mod option_dalek_pubkey_serde {
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes an ed25519_dalek::Signature to and from hex
|
||||
pub mod dalek_sig_serde {
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
use crate::grin_util::{from_hex, to_hex};
|
||||
|
||||
///
|
||||
pub fn serialize<S>(key: &DalekSignature, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&to_hex(key.to_bytes().to_vec()))
|
||||
}
|
||||
|
||||
///
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<DalekSignature, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
String::deserialize(deserializer)
|
||||
.and_then(|string| from_hex(string).map_err(|err| Error::custom(err.to_string())))
|
||||
.and_then(|bytes: Vec<u8>| {
|
||||
let mut b = [0u8; 64];
|
||||
b.copy_from_slice(&bytes[0..64]);
|
||||
DalekSignature::from_bytes(&b).map_err(|err| Error::custom(err.to_string()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes an Option<ed25519_dalek::PublicKey> to and from hex
|
||||
pub mod option_dalek_sig_serde {
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
@@ -141,6 +172,8 @@ mod test {
|
||||
pub pub_key: DalekPublicKey,
|
||||
#[serde(with = "option_dalek_pubkey_serde")]
|
||||
pub pub_key_opt: Option<DalekPublicKey>,
|
||||
#[serde(with = "dalek_sig_serde")]
|
||||
pub sig: DalekSignature,
|
||||
#[serde(with = "option_dalek_sig_serde")]
|
||||
pub sig_opt: Option<DalekSignature>,
|
||||
}
|
||||
@@ -165,6 +198,7 @@ mod test {
|
||||
SerTest {
|
||||
pub_key: d_pub_key.clone(),
|
||||
pub_key_opt: Some(d_pub_key),
|
||||
sig: d_sig.clone(),
|
||||
sig_opt: Some(d_sig),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,3 +364,25 @@ subcommands:
|
||||
long: start_height
|
||||
default_value: "1"
|
||||
takes_value: true
|
||||
- export_proof:
|
||||
about: Export a payment proof from a completed transaction
|
||||
args:
|
||||
- output:
|
||||
help: Output proof file
|
||||
index: 1
|
||||
- id:
|
||||
help: If specified, retrieve the proof for the given transaction ID
|
||||
short: i
|
||||
long: id
|
||||
takes_value: true
|
||||
- txid:
|
||||
help: If specified, retrieve the proof for the given Slate ID
|
||||
short: t
|
||||
long: txid
|
||||
takes_value: true
|
||||
- verify_proof:
|
||||
about: Verify a payment proof
|
||||
args:
|
||||
- input:
|
||||
help: Filename of a proof file
|
||||
index: 1
|
||||
|
||||
@@ -776,6 +776,44 @@ pub fn parse_cancel_args(args: &ArgMatches) -> Result<command::CancelArgs, Parse
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_export_proof_args(args: &ArgMatches) -> Result<command::ProofExportArgs, ParseError> {
|
||||
let output_file = parse_required(args, "output")?;
|
||||
let tx_id = match args.value_of("id") {
|
||||
None => None,
|
||||
Some(tx) => Some(parse_u64(tx, "id")? as u32),
|
||||
};
|
||||
let tx_slate_id = match args.value_of("txid") {
|
||||
None => None,
|
||||
Some(tx) => match tx.parse() {
|
||||
Ok(t) => Some(t),
|
||||
Err(e) => {
|
||||
let msg = format!("Could not parse txid parameter. e={}", e);
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
},
|
||||
};
|
||||
if tx_id.is_some() && tx_slate_id.is_some() {
|
||||
let msg = format!("At most one of 'id' (-i) or 'txid' (-t) may be provided.");
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
if tx_id.is_none() && tx_slate_id.is_none() {
|
||||
let msg = format!("Either 'id' (-i) or 'txid' (-t) must be provided.");
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
Ok(command::ProofExportArgs {
|
||||
output_file: output_file.to_owned(),
|
||||
id: tx_id,
|
||||
tx_slate_id: tx_slate_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_verify_proof_args(args: &ArgMatches) -> Result<command::ProofVerifyArgs, ParseError> {
|
||||
let input_file = parse_required(args, "input")?;
|
||||
Ok(command::ProofVerifyArgs {
|
||||
input_file: input_file.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn wallet_command<C, F>(
|
||||
wallet_args: &ArgMatches,
|
||||
mut wallet_config: WalletConfig,
|
||||
@@ -1013,6 +1051,14 @@ where
|
||||
let a = arg_parse!(parse_cancel_args(&args));
|
||||
command::cancel(wallet, km, a)
|
||||
}
|
||||
("export_proof", Some(args)) => {
|
||||
let a = arg_parse!(parse_export_proof_args(&args));
|
||||
command::proof_export(wallet, km, a)
|
||||
}
|
||||
("verify_proof", Some(args)) => {
|
||||
let a = arg_parse!(parse_verify_proof_args(&args));
|
||||
command::proof_verify(wallet, km, a)
|
||||
}
|
||||
("address", Some(_)) => command::address(wallet, &global_wallet_args, km),
|
||||
("scan", Some(args)) => {
|
||||
let a = arg_parse!(parse_check_args(&args));
|
||||
|
||||
Reference in New Issue
Block a user