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:
Yeastplume
2020-01-22 13:16:24 +00:00
committed by GitHub
parent 08a76c04ff
commit f09c91f626
14 changed files with 769 additions and 66 deletions
+129 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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(())
}
+20 -3
View File
@@ -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(())
})?;
+9 -1
View File
@@ -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,
+174 -2
View File
@@ -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>>>>,
+25
View File
@@ -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,
}
+8
View File
@@ -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),
+2 -2
View File
@@ -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,
+1 -1
View File
@@ -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;
+34
View File
@@ -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),
}
}
+22
View File
@@ -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
+46
View File
@@ -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));