mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-02-01 08:51:09 +03:00
Payment Proof Implementation (#259)
* refactor address generation code into libwallet, bool to flag whether to include proof, add sender address in init_send_tx * rustfmt * require payment proof addr as part of init_tx * rustfmt * store payment proof on sender transaction side * rustfmt * change sig to ed25519 sig * rustfmt * add message creation and signature * rustfmt * add payment proof verification function * rustfmt * validate proof on sender side, store proof * rustfmt * fix json tests * fixes and updates to tests * added API functions for converting and retrieving proof addresses * rustfmt * add payment proof to init_send_tx example * rustfmt * incorrect comment
This commit is contained in:
parent
9c2177e3d9
commit
7293ca99c3
28 changed files with 988 additions and 125 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -918,6 +918,7 @@ dependencies = [
|
|||
"base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"easy-jsonrpc-mw 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"grin_wallet_config 3.0.0-alpha.1",
|
||||
|
@ -1005,7 +1006,6 @@ dependencies = [
|
|||
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sysinfo 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"timer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1021,7 +1021,9 @@ name = "grin_wallet_libwallet"
|
|||
version = "3.0.0-alpha.1"
|
||||
dependencies = [
|
||||
"blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1033,6 +1035,7 @@ dependencies = [
|
|||
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -22,6 +22,7 @@ easy-jsonrpc-mw = "0.5.3"
|
|||
chrono = { version = "0.4.4", features = ["serde"] }
|
||||
ring = "0.13"
|
||||
base64 = "0.9"
|
||||
ed25519-dalek = "1.0.0-pre.1"
|
||||
|
||||
grin_wallet_libwallet = { path = "../libwallet", version = "3.0.0-alpha.1" }
|
||||
grin_wallet_config = { path = "../config", version = "3.0.0-alpha.1" }
|
||||
|
|
|
@ -56,4 +56,6 @@ pub use crate::foreign_rpc::foreign_rpc as foreign_rpc_client;
|
|||
pub use crate::foreign_rpc::run_doctest_foreign;
|
||||
pub use crate::owner_rpc::run_doctest_owner;
|
||||
|
||||
pub use types::{ECDHPubkey, EncryptedRequest, EncryptedResponse, EncryptionErrorResponse, Token};
|
||||
pub use types::{
|
||||
ECDHPubkey, EncryptedRequest, EncryptedResponse, EncryptionErrorResponse, PubAddress, Token,
|
||||
};
|
||||
|
|
106
api/src/owner.rs
106
api/src/owner.rs
|
@ -15,6 +15,7 @@
|
|||
//! Owner API External Definition
|
||||
|
||||
use chrono::prelude::*;
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::{TorConfig, WalletConfig};
|
||||
|
@ -25,7 +26,7 @@ use crate::keychain::{Identifier, Keychain};
|
|||
use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage};
|
||||
use crate::libwallet::api_impl::{owner, owner_updater};
|
||||
use crate::libwallet::{
|
||||
AcctPathMapping, Error, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient,
|
||||
address, AcctPathMapping, Error, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient,
|
||||
NodeHeightResult, OutputCommitMapping, Slate, TxLogEntry, WalletInfo, WalletInst,
|
||||
WalletLCProvider,
|
||||
};
|
||||
|
@ -1886,6 +1887,109 @@ where
|
|||
let index = q.len().saturating_sub(count);
|
||||
Ok(q.split_off(index))
|
||||
}
|
||||
|
||||
/// Retrieve the public proof "addresses" associated with the active account at the
|
||||
/// given derivation path.
|
||||
///
|
||||
/// In this case, an "address" means a Dalek ed25519 public key corresponding to
|
||||
/// a private key derived as follows:
|
||||
///
|
||||
/// e.g. The default parent account is at
|
||||
///
|
||||
/// `m/0/0`
|
||||
///
|
||||
/// With output blinding factors created as
|
||||
///
|
||||
/// `m/0/0/0`
|
||||
/// `m/0/0/1` etc...
|
||||
///
|
||||
/// The corresponding public address derivation path would be at:
|
||||
///
|
||||
/// `m/0/1`
|
||||
///
|
||||
/// With addresses created as:
|
||||
///
|
||||
/// `m/0/1/0`
|
||||
/// `m/0/1/1` etc...
|
||||
///
|
||||
/// Note that these addresses correspond to the public keys used in the addresses
|
||||
/// of TOR hidden services configured by the wallet listener.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
|
||||
/// * `derivation_index` - The index along the derivation path to retrieve an address for
|
||||
///
|
||||
/// # Returns
|
||||
/// * Ok with a DalekPublicKey representing the address
|
||||
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered.
|
||||
///
|
||||
/// # Example
|
||||
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
|
||||
/// ```
|
||||
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
|
||||
///
|
||||
/// use grin_core::global::ChainTypes;
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// // Set up as above
|
||||
/// # let api_owner = Owner::new(wallet.clone());
|
||||
///
|
||||
/// let res = api_owner.get_public_proof_address(None, 0);
|
||||
///
|
||||
/// if let Ok(_) = res {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
|
||||
pub fn get_public_proof_address(
|
||||
&self,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
derivation_index: u32,
|
||||
) -> Result<DalekPublicKey, Error> {
|
||||
owner::get_public_proof_address(self.wallet_inst.clone(), keychain_mask, derivation_index)
|
||||
}
|
||||
|
||||
/// Helper function to convert an Onion v3 address to a payment proof address (essentially
|
||||
/// exctacting and verifying the public key)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `address_v3` - An V3 Onion address
|
||||
///
|
||||
/// # Returns
|
||||
/// * Ok(DalekPublicKey) representing the public key associated with the address, if successful
|
||||
/// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered
|
||||
/// or the address provided is invalid
|
||||
///
|
||||
/// # Example
|
||||
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
|
||||
/// ```
|
||||
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
|
||||
///
|
||||
/// use grin_core::global::ChainTypes;
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// // Set up as above
|
||||
/// # let api_owner = Owner::new(wallet.clone());
|
||||
///
|
||||
/// let res = api_owner.proof_address_from_onion_v3(
|
||||
/// "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid"
|
||||
/// );
|
||||
///
|
||||
/// if let Ok(_) = res {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// let res = api_owner.stop_updater();
|
||||
/// ```
|
||||
|
||||
pub fn proof_address_from_onion_v3(&self, address_v3: &str) -> Result<DalekPublicKey, Error> {
|
||||
address::pubkey_from_onion_v3(address_v3)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
|
|
@ -240,6 +240,7 @@ pub trait OwnerRpc: Sync + Send {
|
|||
"parent_key_id": "0200000000000000000000000000000000",
|
||||
"stored_tx": null,
|
||||
"tx_slate_id": null,
|
||||
"payment_proof": null,
|
||||
"tx_type": "ConfirmedCoinbase"
|
||||
},
|
||||
{
|
||||
|
@ -258,6 +259,7 @@ pub trait OwnerRpc: Sync + Send {
|
|||
"parent_key_id": "0200000000000000000000000000000000",
|
||||
"stored_tx": null,
|
||||
"tx_slate_id": null,
|
||||
"payment_proof": null,
|
||||
"tx_type": "ConfirmedCoinbase"
|
||||
}
|
||||
]
|
||||
|
@ -340,6 +342,7 @@ pub trait OwnerRpc: Sync + Send {
|
|||
"selection_strategy_is_use_all": true,
|
||||
"message": "my message",
|
||||
"target_slate_version": null,
|
||||
"payment_proof_recipient_address": null,
|
||||
"send_args": null
|
||||
}
|
||||
},
|
||||
|
@ -561,6 +564,7 @@ pub trait OwnerRpc: Sync + Send {
|
|||
"selection_strategy_is_use_all": true,
|
||||
"message": "Ok, here are your grins",
|
||||
"target_slate_version": null,
|
||||
"payment_proof_recipient_address": null,
|
||||
"send_args": null
|
||||
}
|
||||
],
|
||||
|
@ -1485,6 +1489,8 @@ pub fn run_doctest_owner(
|
|||
assert!(wallet_refreshed);
|
||||
}
|
||||
|
||||
//let proof_address = api_impl::owner::get_public_proof_address(wallet2.clone(), (&mask2).as_ref(), 0).unwrap();
|
||||
|
||||
if perform_tx {
|
||||
let amount = 60_000_000_000;
|
||||
let mut w_lock = wallet1.lock();
|
||||
|
|
|
@ -27,7 +27,7 @@ use crate::libwallet::{
|
|||
};
|
||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||
use crate::util::{static_secp_instance, LoggingConfig, ZeroingString};
|
||||
use crate::{ECDHPubkey, Owner, Token};
|
||||
use crate::{ECDHPubkey, Owner, PubAddress, Token};
|
||||
use easy_jsonrpc_mw;
|
||||
use rand::thread_rng;
|
||||
use std::time::Duration;
|
||||
|
@ -264,6 +264,7 @@ pub trait OwnerRpcS {
|
|||
"parent_key_id": "0200000000000000000000000000000000",
|
||||
"stored_tx": null,
|
||||
"tx_slate_id": null,
|
||||
"payment_proof": null,
|
||||
"tx_type": "ConfirmedCoinbase"
|
||||
},
|
||||
{
|
||||
|
@ -281,6 +282,7 @@ pub trait OwnerRpcS {
|
|||
"num_outputs": 1,
|
||||
"parent_key_id": "0200000000000000000000000000000000",
|
||||
"stored_tx": null,
|
||||
"payment_proof": null,
|
||||
"tx_slate_id": null,
|
||||
"tx_type": "ConfirmedCoinbase"
|
||||
}
|
||||
|
@ -371,6 +373,7 @@ pub trait OwnerRpcS {
|
|||
"selection_strategy_is_use_all": true,
|
||||
"message": "my message",
|
||||
"target_slate_version": null,
|
||||
"payment_proof_recipient_address": "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb",
|
||||
"send_args": null
|
||||
}
|
||||
},
|
||||
|
@ -391,7 +394,11 @@ pub trait OwnerRpcS {
|
|||
"lock_height": "0",
|
||||
"ttl_cutoff_height": "0",
|
||||
"num_participants": 2,
|
||||
"payment_proof": null,
|
||||
"payment_proof": {
|
||||
"receiver_address": "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb",
|
||||
"receiver_signature": null,
|
||||
"sender_address": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3"
|
||||
},
|
||||
"participant_data": [
|
||||
{
|
||||
"id": "0",
|
||||
|
@ -598,6 +605,7 @@ pub trait OwnerRpcS {
|
|||
"selection_strategy_is_use_all": true,
|
||||
"message": "Ok, here are your grins",
|
||||
"target_slate_version": null,
|
||||
"payment_proof_recipient_address": null,
|
||||
"send_args": null
|
||||
}
|
||||
},
|
||||
|
@ -1776,6 +1784,71 @@ pub trait OwnerRpcS {
|
|||
*/
|
||||
|
||||
fn get_updater_messages(&self, count: u32) -> Result<Vec<StatusMessage>, ErrorKind>;
|
||||
|
||||
/**
|
||||
Networked version of [Owner::get_public_proof_address](struct.Owner.html#method.get_public_proof_address).
|
||||
```
|
||||
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
|
||||
# r#"
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "get_public_proof_address",
|
||||
"params": {
|
||||
"token": "d202964900000000d302964900000000d402964900000000d502964900000000",
|
||||
"derivation_index": 0
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
# "#
|
||||
# ,
|
||||
# r#"
|
||||
{
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"Ok": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3"
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
fn get_public_proof_address(
|
||||
&self,
|
||||
token: Token,
|
||||
derivation_index: u32,
|
||||
) -> Result<PubAddress, ErrorKind>;
|
||||
|
||||
/**
|
||||
Networked version of [Owner::proof_address_from_onion_v3](struct.Owner.html#method.proof_address_from_onion_v3).
|
||||
```
|
||||
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
|
||||
# r#"
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "proof_address_from_onion_v3",
|
||||
"params": {
|
||||
"address_v3": "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid"
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
# "#
|
||||
# ,
|
||||
# r#"
|
||||
{
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"Ok": "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb"
|
||||
}
|
||||
}
|
||||
# "#
|
||||
# , true, 0, false, false, false);
|
||||
```
|
||||
*/
|
||||
|
||||
fn proof_address_from_onion_v3(&self, address_v3: String) -> Result<PubAddress, ErrorKind>;
|
||||
}
|
||||
|
||||
impl<L, C, K> OwnerRpcS for Owner<L, C, K>
|
||||
|
@ -2080,4 +2153,24 @@ where
|
|||
fn get_updater_messages(&self, count: u32) -> Result<Vec<StatusMessage>, ErrorKind> {
|
||||
Owner::get_updater_messages(self, count as usize).map_err(|e| e.kind())
|
||||
}
|
||||
|
||||
fn get_public_proof_address(
|
||||
&self,
|
||||
token: Token,
|
||||
derivation_index: u32,
|
||||
) -> Result<PubAddress, ErrorKind> {
|
||||
let address = Owner::get_public_proof_address(
|
||||
self,
|
||||
(&token.keychain_mask).as_ref(),
|
||||
derivation_index,
|
||||
)
|
||||
.map_err(|e| e.kind())?;
|
||||
Ok(PubAddress { address })
|
||||
}
|
||||
|
||||
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())?;
|
||||
Ok(PubAddress { address })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,14 @@
|
|||
// limitations under the License.
|
||||
|
||||
use crate::core::libtx::secp_ser;
|
||||
use crate::libwallet::dalek_ser;
|
||||
use crate::libwallet::{Error, ErrorKind};
|
||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||
use crate::util::{from_hex, to_hex};
|
||||
use failure::ResultExt;
|
||||
|
||||
use base64;
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use rand::{thread_rng, Rng};
|
||||
use ring::aead;
|
||||
use serde_json::{self, Value};
|
||||
|
@ -32,6 +34,15 @@ pub struct Token {
|
|||
pub keychain_mask: Option<SecretKey>,
|
||||
}
|
||||
|
||||
/// Wrapper for dalek public keys, used as addresses
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(transparent)]
|
||||
pub struct PubAddress {
|
||||
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
||||
/// Public address
|
||||
pub address: DalekPublicKey,
|
||||
}
|
||||
|
||||
/// Wrapper for ECDH Public keys
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(transparent)]
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
use crate::api::{self, ApiServer, BasicAuthMiddleware, ResponseFuture, Router, TLSConfig};
|
||||
use crate::keychain::Keychain;
|
||||
use crate::libwallet::{
|
||||
Error, ErrorKind, NodeClient, NodeVersionInfo, Slate, WalletInst, WalletLCProvider,
|
||||
address, Error, ErrorKind, NodeClient, NodeVersionInfo, Slate, WalletInst, WalletLCProvider,
|
||||
CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION,
|
||||
};
|
||||
use crate::util::secp::key::SecretKey;
|
||||
|
@ -98,7 +98,7 @@ where
|
|||
let k = w_inst.keychain((&mask).as_ref())?;
|
||||
let parent_key_id = w_inst.parent_key_id();
|
||||
let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?);
|
||||
let sec_key = tor_config::address_derivation_path(&k, &parent_key_id, 0)
|
||||
let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0)
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
let onion_address = tor_config::onion_address_from_seckey(&sec_key)
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
|
|
153
controller/tests/payment_proofs.rs
Normal file
153
controller/tests/payment_proofs.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
// Copyright 2019 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! tests differing accounts in the same wallet
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate grin_wallet_util;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
use impls::test_framework::{self, LocalWalletClient};
|
||||
use libwallet::{InitTxArgs, Slate};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallet_proxy, setup};
|
||||
|
||||
/// Various tests on accounts within the same wallet
|
||||
fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// Create a new proxy to simulate server and wallet responses
|
||||
let mut wallet_proxy = create_wallet_proxy(test_dir);
|
||||
let chain = wallet_proxy.chain.clone();
|
||||
|
||||
create_wallet_and_add!(
|
||||
client1,
|
||||
wallet1,
|
||||
mask1_i,
|
||||
test_dir,
|
||||
"wallet1",
|
||||
None,
|
||||
&mut wallet_proxy,
|
||||
false
|
||||
);
|
||||
|
||||
let mask1 = (&mask1_i).as_ref();
|
||||
|
||||
create_wallet_and_add!(
|
||||
client2,
|
||||
wallet2,
|
||||
mask2_i,
|
||||
test_dir,
|
||||
"wallet2",
|
||||
None,
|
||||
&mut wallet_proxy,
|
||||
false
|
||||
);
|
||||
|
||||
let mask2 = (&mask2_i).as_ref();
|
||||
|
||||
// Set the wallet proxy listener running
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = wallet_proxy.run() {
|
||||
error!("Wallet Proxy error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
// few values to keep things shorter
|
||||
|
||||
// Do some mining
|
||||
let bh = 10u64;
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
|
||||
|
||||
let mut address = None;
|
||||
wallet::controller::owner_single_use(wallet2.clone(), mask2, |api, m| {
|
||||
address = Some(api.get_public_proof_address(m, 0)?);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let address = address.unwrap();
|
||||
println!("Public address is: {:?}", address);
|
||||
let amount = 60_000_000_000;
|
||||
let mut slate = Slate::blank(1);
|
||||
wallet::controller::owner_single_use(wallet1.clone(), mask1, |sender_api, m| {
|
||||
// note this will increment the block count as part of the transaction "Posting"
|
||||
let args = InitTxArgs {
|
||||
src_acct_name: None,
|
||||
amount: amount,
|
||||
minimum_confirmations: 2,
|
||||
max_outputs: 500,
|
||||
num_change_outputs: 1,
|
||||
selection_strategy_is_use_all: true,
|
||||
payment_proof_recipient_address: Some(address),
|
||||
..Default::default()
|
||||
};
|
||||
let slate_i = sender_api.init_send_tx(m, args)?;
|
||||
|
||||
assert_eq!(
|
||||
slate_i.payment_proof.as_ref().unwrap().receiver_address,
|
||||
address
|
||||
);
|
||||
println!(
|
||||
"Sender addr: {:?}",
|
||||
slate_i.payment_proof.as_ref().unwrap().sender_address
|
||||
);
|
||||
|
||||
// Check we are creating a tx with the expected lock_height of 0.
|
||||
// We will check this produces a Plain kernel later.
|
||||
assert_eq!(0, slate.lock_height);
|
||||
|
||||
slate = client1.send_tx_slate_direct("wallet2", &slate_i)?;
|
||||
sender_api.tx_lock_outputs(m, &slate, 0)?;
|
||||
|
||||
// Ensure what's stored in TX log for payment proof is correct
|
||||
let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?;
|
||||
assert!(txs[0].payment_proof.is_some());
|
||||
let pp = txs[0].clone().payment_proof.unwrap();
|
||||
assert_eq!(
|
||||
pp.receiver_address,
|
||||
slate_i.payment_proof.as_ref().unwrap().receiver_address
|
||||
);
|
||||
assert!(pp.receiver_signature.is_some());
|
||||
assert_eq!(pp.sender_address_path, 0);
|
||||
assert_eq!(pp.sender_signature, None);
|
||||
|
||||
slate = sender_api.finalize_tx(m, &slate)?;
|
||||
|
||||
// Check payment proof here
|
||||
let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?;
|
||||
let tx = txs[0].clone();
|
||||
|
||||
println!("{:?}", tx);
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payment_proofs() {
|
||||
let test_dir = "test_output/payment_proofs";
|
||||
setup(test_dir);
|
||||
if let Err(e) = payment_proofs_test_impl(test_dir) {
|
||||
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
|
||||
}
|
||||
clean_output_dir(test_dir);
|
||||
}
|
|
@ -19,13 +19,10 @@ extern crate grin_wallet_controller as wallet;
|
|||
extern crate grin_wallet_impls as impls;
|
||||
extern crate grin_wallet_libwallet as libwallet;
|
||||
|
||||
use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage};
|
||||
use grin_wallet_util::grin_core as core;
|
||||
// use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage};
|
||||
// use grin_wallet_util::grin_core as core;
|
||||
|
||||
use self::libwallet::{InitTxArgs, Slate};
|
||||
use impls::test_framework::{self, LocalWalletClient};
|
||||
use impls::{PathToSlate, SlateGetter as _, SlatePutter as _};
|
||||
use std::sync::mpsc::channel;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -71,9 +68,6 @@ fn updater_thread_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err
|
|||
}
|
||||
});
|
||||
|
||||
// few values to keep things shorter
|
||||
let reward = core::consensus::REWARD;
|
||||
|
||||
// add some accounts
|
||||
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
||||
api.create_account_path(m, "mining")?;
|
||||
|
@ -93,7 +87,7 @@ fn updater_thread_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err
|
|||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("mining")?;
|
||||
}
|
||||
let mut bh = 10u64;
|
||||
let bh = 10u64;
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
|
||||
|
||||
|
|
|
@ -42,7 +42,6 @@ tokio-io = "0.1"
|
|||
#tokio-tls = "0.1"
|
||||
ed25519-dalek = "1.0.0-pre.1"
|
||||
data-encoding = "2"
|
||||
sha3 = "0.8"
|
||||
regex = "1.3"
|
||||
timer = "0.2"
|
||||
sysinfo = "0.9"
|
||||
|
|
|
@ -83,8 +83,8 @@ pub enum ErrorKind {
|
|||
ED25519Key(String),
|
||||
|
||||
/// Checking for onion address
|
||||
#[fail(display = "Address is not an Onion v3 Address")]
|
||||
NotOnion,
|
||||
#[fail(display = "Address is not an Onion v3 Address: {}", _0)]
|
||||
NotOnion(String),
|
||||
|
||||
/// Other
|
||||
#[fail(display = "Generic error: {}", _0)]
|
||||
|
|
|
@ -15,15 +15,11 @@
|
|||
//! Tor Configuration + Onion (Hidden) Service operations
|
||||
use crate::util::secp::key::SecretKey;
|
||||
use crate::{Error, ErrorKind};
|
||||
use grin_wallet_util::grin_keychain::{ChildNumber, Identifier, Keychain, SwitchCommitmentType};
|
||||
use grin_wallet_libwallet::address;
|
||||
|
||||
use data_encoding::BASE32;
|
||||
use ed25519_dalek::ExpandedSecretKey;
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||
use sha3::{Digest, Sha3_256};
|
||||
|
||||
use crate::blake2::blake2b::blake2b;
|
||||
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
|
@ -96,40 +92,10 @@ impl TorRcConfig {
|
|||
}
|
||||
}
|
||||
|
||||
/// Output ed25519 keypair given an rust_secp256k1 SecretKey
|
||||
pub fn ed25519_keypair(sec_key: &SecretKey) -> Result<(DalekSecretKey, DalekPublicKey), Error> {
|
||||
let d_skey = match DalekSecretKey::from_bytes(&sec_key.0) {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
return Err(ErrorKind::ED25519Key(format!("{}", e)).to_owned())?;
|
||||
}
|
||||
};
|
||||
let d_pub_key: DalekPublicKey = (&d_skey).into();
|
||||
Ok((d_skey, d_pub_key))
|
||||
}
|
||||
|
||||
/// helper to get address
|
||||
pub fn onion_address_from_seckey(sec_key: &SecretKey) -> Result<String, Error> {
|
||||
let (_, d_pub_key) = ed25519_keypair(sec_key)?;
|
||||
onion_address(&d_pub_key)
|
||||
}
|
||||
|
||||
/// Generate an onion address from an ed25519_dalek public key
|
||||
pub fn onion_address(pub_key: &DalekPublicKey) -> Result<String, Error> {
|
||||
// calculate checksum
|
||||
let mut hasher = Sha3_256::new();
|
||||
hasher.input(b".onion checksum");
|
||||
hasher.input(pub_key.as_bytes());
|
||||
hasher.input([0x03u8]);
|
||||
let checksum = hasher.result();
|
||||
|
||||
let mut address_bytes = pub_key.as_bytes().to_vec();
|
||||
address_bytes.push(checksum[0]);
|
||||
address_bytes.push(checksum[1]);
|
||||
address_bytes.push(0x03u8);
|
||||
|
||||
let ret = BASE32.encode(&address_bytes);
|
||||
Ok(ret.to_lowercase())
|
||||
let (_, d_pub_key) = address::ed25519_keypair(sec_key)?;
|
||||
Ok(address::onion_v3_from_pubkey(&d_pub_key)?)
|
||||
}
|
||||
|
||||
pub fn create_onion_service_sec_key_file(
|
||||
|
@ -178,8 +144,8 @@ pub fn output_onion_service_config(
|
|||
tor_config_directory: &str,
|
||||
sec_key: &SecretKey,
|
||||
) -> Result<String, Error> {
|
||||
let (_, d_pub_key) = ed25519_keypair(&sec_key)?;
|
||||
let address = onion_address(&d_pub_key)?;
|
||||
let (_, d_pub_key) = address::ed25519_keypair(&sec_key)?;
|
||||
let address = address::onion_v3_from_pubkey(&d_pub_key)?;
|
||||
let hs_dir_file_path = format!(
|
||||
"{}{}{}{}{}",
|
||||
tor_config_directory, MAIN_SEPARATOR, HIDDEN_SERVICES_DIR, MAIN_SEPARATOR, address
|
||||
|
@ -193,7 +159,7 @@ pub fn output_onion_service_config(
|
|||
// create directory if it doesn't exist
|
||||
fs::create_dir_all(&hs_dir_file_path).context(ErrorKind::IO)?;
|
||||
|
||||
let (d_sec_key, d_pub_key) = ed25519_keypair(&sec_key)?;
|
||||
let (d_sec_key, d_pub_key) = address::ed25519_keypair(&sec_key)?;
|
||||
create_onion_service_sec_key_file(&hs_dir_file_path, &d_sec_key)?;
|
||||
create_onion_service_pub_key_file(&hs_dir_file_path, &d_pub_key)?;
|
||||
create_onion_service_hostname_file(&hs_dir_file_path, &address)?;
|
||||
|
@ -272,51 +238,14 @@ pub fn output_tor_sender_config(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Derive a secret key given a derivation path and index
|
||||
pub fn address_derivation_path<K>(
|
||||
keychain: &K,
|
||||
parent_key_id: &Identifier,
|
||||
index: u32,
|
||||
) -> Result<SecretKey, Error>
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
let mut key_path = parent_key_id.to_path();
|
||||
// An output derivation for acct m/0
|
||||
// is m/0/0/0, m/0/0/1 (for instance), m/1 is m/1/0/0, m/1/0/1
|
||||
// Address generation path should be
|
||||
// for m/0: m/0/1/0, m/0/1/1
|
||||
// for m/1: m/1/1/0, m/1/1/1
|
||||
key_path.path[1] = ChildNumber::from(1);
|
||||
key_path.depth = key_path.depth + 1;
|
||||
key_path.path[key_path.depth as usize - 1] = ChildNumber::from(index);
|
||||
let key_id = Identifier::from_path(&key_path);
|
||||
debug!("Onion Address derivation path is: {}", key_id);
|
||||
let sec_key = keychain.derive_key(0, &key_id, &SwitchCommitmentType::None)?;
|
||||
let hashed = blake2b(32, &[], &sec_key.0[..]);
|
||||
Ok(SecretKey::from_slice(
|
||||
&keychain.secp(),
|
||||
&hashed.as_bytes()[..],
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn is_tor_address(input: &str) -> Result<(), Error> {
|
||||
let mut input = input.to_uppercase();
|
||||
if input.starts_with("HTTP://") || input.starts_with("HTTPS://") {
|
||||
input = input.replace("HTTP://", "");
|
||||
input = input.replace("HTTPS://", "");
|
||||
match address::pubkey_from_onion_v3(input) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
let msg = format!("{}", e);
|
||||
Err(ErrorKind::NotOnion(msg).to_owned())?
|
||||
}
|
||||
}
|
||||
if input.ends_with(".ONION") {
|
||||
input = input.replace(".ONION", "");
|
||||
}
|
||||
// for now, just check input is the right length and is base32
|
||||
if input.len() != 56 {
|
||||
return Err(ErrorKind::NotOnion.to_owned())?;
|
||||
}
|
||||
let _ = BASE32
|
||||
.decode(input.as_bytes())
|
||||
.context(ErrorKind::NotOnion)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn complete_tor_address(input: &str) -> Result<String, Error> {
|
||||
|
@ -356,12 +285,12 @@ mod tests {
|
|||
let mut test_rng = StepRng::new(1234567890u64, 1);
|
||||
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
||||
println!("{:?}", sec_key);
|
||||
let (_, d_pub_key) = ed25519_keypair(&sec_key)?;
|
||||
let (_, d_pub_key) = address::ed25519_keypair(&sec_key)?;
|
||||
println!("{:?}", d_pub_key);
|
||||
// some randoms
|
||||
for _ in 0..1000 {
|
||||
let sec_key = secp::key::SecretKey::new(&secp, &mut thread_rng());
|
||||
let (_, _) = ed25519_keypair(&sec_key)?;
|
||||
let (_, _) = address::ed25519_keypair(&sec_key)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -373,8 +302,8 @@ mod tests {
|
|||
let mut test_rng = StepRng::new(1234567890u64, 1);
|
||||
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
||||
println!("{:?}", sec_key);
|
||||
let (_, d_pub_key) = ed25519_keypair(&sec_key)?;
|
||||
let address = onion_address(&d_pub_key)?;
|
||||
let (_, d_pub_key) = address::ed25519_keypair(&sec_key)?;
|
||||
let address = address::onion_v3_from_pubkey(&d_pub_key)?;
|
||||
assert_eq!(
|
||||
"kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid",
|
||||
address
|
||||
|
@ -412,6 +341,8 @@ mod tests {
|
|||
#[test]
|
||||
fn test_is_tor_address() -> Result<(), Error> {
|
||||
assert!(is_tor_address("2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid").is_ok());
|
||||
assert!(is_tor_address("2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid").is_ok());
|
||||
assert!(is_tor_address("kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid").is_ok());
|
||||
assert!(is_tor_address(
|
||||
"http://kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid.onion"
|
||||
)
|
||||
|
|
|
@ -25,6 +25,9 @@ lazy_static = "1"
|
|||
strum = "0.15"
|
||||
strum_macros = "0.15"
|
||||
ed25519-dalek = "1.0.0-pre.1"
|
||||
sha3 = "0.8"
|
||||
byteorder = "1"
|
||||
data-encoding = "2"
|
||||
|
||||
grin_wallet_util = { path = "../util", version = "3.0.0-alpha.1" }
|
||||
grin_wallet_config = { path = "../config", version = "3.0.0-alpha.1" }
|
||||
|
|
156
libwallet/src/address.rs
Normal file
156
libwallet/src/address.rs
Normal file
|
@ -0,0 +1,156 @@
|
|||
// Copyright 2019 The Grin Develope;
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Functions defining wallet 'addresses', i.e. ed2559 keys based on
|
||||
//! a derivation path
|
||||
|
||||
use crate::grin_util::secp::key::SecretKey;
|
||||
use crate::{Error, ErrorKind};
|
||||
use grin_wallet_util::grin_keychain::{ChildNumber, Identifier, Keychain, SwitchCommitmentType};
|
||||
|
||||
use data_encoding::BASE32;
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||
use failure::ResultExt;
|
||||
use sha3::{Digest, Sha3_256};
|
||||
|
||||
use crate::blake2::blake2b::blake2b;
|
||||
|
||||
/// Derive a secret key given a derivation path and index
|
||||
pub fn address_from_derivation_path<K>(
|
||||
keychain: &K,
|
||||
parent_key_id: &Identifier,
|
||||
index: u32,
|
||||
) -> Result<SecretKey, Error>
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
let mut key_path = parent_key_id.to_path();
|
||||
// An output derivation for acct m/0
|
||||
// is m/0/0/0, m/0/0/1 (for instance), m/1 is m/1/0/0, m/1/0/1
|
||||
// Address generation path should be
|
||||
// for m/0: m/0/1/0, m/0/1/1
|
||||
// for m/1: m/1/1/0, m/1/1/1
|
||||
key_path.path[1] = ChildNumber::from(1);
|
||||
key_path.depth = key_path.depth + 1;
|
||||
key_path.path[key_path.depth as usize - 1] = ChildNumber::from(index);
|
||||
let key_id = Identifier::from_path(&key_path);
|
||||
let sec_key = keychain.derive_key(0, &key_id, &SwitchCommitmentType::None)?;
|
||||
let hashed = blake2b(32, &[], &sec_key.0[..]);
|
||||
Ok(SecretKey::from_slice(
|
||||
&keychain.secp(),
|
||||
&hashed.as_bytes()[..],
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Output ed25519 keypair given an rust_secp256k1 SecretKey
|
||||
pub fn ed25519_keypair(sec_key: &SecretKey) -> Result<(DalekSecretKey, DalekPublicKey), Error> {
|
||||
let d_skey = match DalekSecretKey::from_bytes(&sec_key.0) {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
return Err(ErrorKind::ED25519Key(format!("{}", e)).to_owned())?;
|
||||
}
|
||||
};
|
||||
let d_pub_key: DalekPublicKey = (&d_skey).into();
|
||||
Ok((d_skey, d_pub_key))
|
||||
}
|
||||
|
||||
/// Return the ed25519 public key represented in an onion address
|
||||
pub fn pubkey_from_onion_v3(onion_address: &str) -> Result<DalekPublicKey, Error> {
|
||||
let mut input = onion_address.to_uppercase();
|
||||
if input.starts_with("HTTP://") || input.starts_with("HTTPS://") {
|
||||
input = input.replace("HTTP://", "");
|
||||
input = input.replace("HTTPS://", "");
|
||||
}
|
||||
if input.ends_with(".ONION") {
|
||||
input = input.replace(".ONION", "");
|
||||
}
|
||||
let orig_address_raw = input.clone();
|
||||
// for now, just check input is the right length and try and decode from base32
|
||||
if input.len() != 56 {
|
||||
return Err(
|
||||
ErrorKind::AddressDecoding("Input address is wrong length".to_owned()).to_owned(),
|
||||
)?;
|
||||
}
|
||||
let mut address = BASE32
|
||||
.decode(input.as_bytes())
|
||||
.context(ErrorKind::AddressDecoding(
|
||||
"Input address is not base 32".to_owned(),
|
||||
))?
|
||||
.to_vec();
|
||||
|
||||
address.split_off(32);
|
||||
let key = match DalekPublicKey::from_bytes(&address) {
|
||||
Ok(k) => k,
|
||||
Err(_) => {
|
||||
return Err(ErrorKind::AddressDecoding(
|
||||
"Provided onion V3 address is invalid (parsing key)".to_owned(),
|
||||
)
|
||||
.to_owned())?;
|
||||
}
|
||||
};
|
||||
let test_v3 = match onion_v3_from_pubkey(&key) {
|
||||
Ok(k) => k,
|
||||
Err(_) => {
|
||||
return Err(ErrorKind::AddressDecoding(
|
||||
"Provided onion V3 address is invalid (converting from pubkey)".to_owned(),
|
||||
)
|
||||
.to_owned())?;
|
||||
}
|
||||
};
|
||||
|
||||
if test_v3.to_uppercase() != orig_address_raw.to_uppercase() {
|
||||
return Err(ErrorKind::AddressDecoding(
|
||||
"Provided onion V3 address is invalid (no match)".to_owned(),
|
||||
)
|
||||
.to_owned())?;
|
||||
}
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
/// Generate an onion address from an ed25519_dalek public key
|
||||
pub fn onion_v3_from_pubkey(pub_key: &DalekPublicKey) -> Result<String, Error> {
|
||||
// calculate checksum
|
||||
let mut hasher = Sha3_256::new();
|
||||
hasher.input(b".onion checksum");
|
||||
hasher.input(pub_key.as_bytes());
|
||||
hasher.input([0x03u8]);
|
||||
let checksum = hasher.result();
|
||||
|
||||
let mut address_bytes = pub_key.as_bytes().to_vec();
|
||||
address_bytes.push(checksum[0]);
|
||||
address_bytes.push(checksum[1]);
|
||||
address_bytes.push(0x03u8);
|
||||
|
||||
let ret = BASE32.encode(&address_bytes);
|
||||
Ok(ret.to_lowercase())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn onion_v3_conversion() {
|
||||
let onion_address = "2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid";
|
||||
|
||||
let key = pubkey_from_onion_v3(onion_address).unwrap();
|
||||
println!("Key: {:?}", &key);
|
||||
|
||||
let out_address = onion_v3_from_pubkey(&key).unwrap();
|
||||
println!("Address: {:?}", &out_address);
|
||||
|
||||
assert_eq!(onion_address, out_address);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ use crate::grin_util::secp::key::SecretKey;
|
|||
use crate::internal::{tx, updater};
|
||||
use crate::slate_versions::SlateVersion;
|
||||
use crate::{
|
||||
BlockFees, CbData, Error, ErrorKind, NodeClient, Slate, TxLogEntryType, VersionInfo,
|
||||
address, BlockFees, CbData, Error, ErrorKind, NodeClient, Slate, TxLogEntryType, VersionInfo,
|
||||
WalletBackend,
|
||||
};
|
||||
|
||||
|
@ -113,6 +113,21 @@ where
|
|||
use_test_rng,
|
||||
)?;
|
||||
tx::update_message(&mut *w, keychain_mask, &mut ret_slate)?;
|
||||
|
||||
let keychain = w.keychain(keychain_mask)?;
|
||||
let excess = ret_slate.calc_excess(&keychain)?;
|
||||
|
||||
if let Some(ref mut p) = ret_slate.payment_proof {
|
||||
let sig = tx::create_payment_proof_signature(
|
||||
ret_slate.amount,
|
||||
&excess,
|
||||
p.sender_address,
|
||||
address::address_from_derivation_path(&keychain, &parent_key_id, 0)?,
|
||||
)?;
|
||||
|
||||
p.receiver_signature = Some(sig);
|
||||
}
|
||||
|
||||
Ok(ret_slate)
|
||||
}
|
||||
|
||||
|
@ -130,7 +145,7 @@ where
|
|||
let mut sl = slate.clone();
|
||||
let context = w.get_private_context(keychain_mask, sl.id.as_bytes(), 1)?;
|
||||
tx::complete_tx(&mut *w, keychain_mask, &mut sl, 1, &context)?;
|
||||
tx::update_stored_tx(&mut *w, keychain_mask, &mut sl, true)?;
|
||||
tx::update_stored_tx(&mut *w, keychain_mask, &context, &mut sl, true)?;
|
||||
tx::update_message(&mut *w, keychain_mask, &mut sl)?;
|
||||
{
|
||||
let mut batch = w.batch(keychain_mask)?;
|
||||
|
|
|
@ -26,13 +26,15 @@ use crate::grin_util::Mutex;
|
|||
use crate::api_impl::owner_updater::StatusMessage;
|
||||
use crate::grin_keychain::{Identifier, Keychain};
|
||||
use crate::internal::{keys, scan, selection, tx, updater};
|
||||
use crate::slate::Slate;
|
||||
use crate::slate::{PaymentInfo, Slate};
|
||||
use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, TxWrapper, WalletBackend, WalletInfo};
|
||||
use crate::{
|
||||
wallet_lock, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping,
|
||||
address, wallet_lock, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping,
|
||||
ScannedBlockInfo, TxLogEntryType, WalletInitStatus, WalletInst, WalletLCProvider,
|
||||
};
|
||||
use crate::{Error, ErrorKind};
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -72,6 +74,26 @@ where
|
|||
w.set_parent_key_id_by_name(label)
|
||||
}
|
||||
|
||||
/// Retrieve the payment proof address for the current parent key at
|
||||
/// the given index
|
||||
/// set active account
|
||||
pub fn get_public_proof_address<'a, L, C, K>(
|
||||
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
index: u32,
|
||||
) -> Result<DalekPublicKey, Error>
|
||||
where
|
||||
L: WalletLCProvider<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
wallet_lock!(wallet_inst, w);
|
||||
let parent_key_id = w.parent_key_id();
|
||||
let k = w.keychain(keychain_mask)?;
|
||||
let sec_addr_key = address::address_from_derivation_path(&k, &parent_key_id, index)?;
|
||||
Ok(address::ed25519_keypair(&sec_addr_key)?.1)
|
||||
}
|
||||
|
||||
/// retrieve outputs
|
||||
pub fn retrieve_outputs<'a, L, C, K>(
|
||||
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
||||
|
@ -222,7 +244,7 @@ where
|
|||
return Ok(slate);
|
||||
}
|
||||
|
||||
let context = tx::add_inputs_to_slate(
|
||||
let mut context = tx::add_inputs_to_slate(
|
||||
&mut *w,
|
||||
keychain_mask,
|
||||
&mut slate,
|
||||
|
@ -237,6 +259,26 @@ where
|
|||
use_test_rng,
|
||||
)?;
|
||||
|
||||
// Payment Proof, add addresses to slate and save address
|
||||
// TODO: Note we only use single derivation path for now,
|
||||
// probably want to allow sender to specify which one
|
||||
let deriv_path = 0u32;
|
||||
|
||||
if let Some(a) = args.payment_proof_recipient_address {
|
||||
let k = w.keychain(keychain_mask)?;
|
||||
|
||||
let sec_addr_key = address::address_from_derivation_path(&k, &parent_key_id, deriv_path)?;
|
||||
let sender_address = address::ed25519_keypair(&sec_addr_key)?.1;
|
||||
|
||||
slate.payment_proof = Some(PaymentInfo {
|
||||
sender_address,
|
||||
receiver_address: a,
|
||||
receiver_signature: None,
|
||||
});
|
||||
|
||||
context.payment_proof_derivation_index = Some(deriv_path);
|
||||
}
|
||||
|
||||
// Save the aggsig context in our DB for when we
|
||||
// recieve the transaction back
|
||||
{
|
||||
|
@ -247,6 +289,7 @@ where
|
|||
if let Some(v) = args.target_slate_version {
|
||||
slate.version_info.orig_version = v;
|
||||
}
|
||||
|
||||
Ok(slate)
|
||||
}
|
||||
|
||||
|
@ -417,8 +460,10 @@ where
|
|||
{
|
||||
let mut sl = slate.clone();
|
||||
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::update_stored_tx(&mut *w, keychain_mask, &mut sl, false)?;
|
||||
tx::verify_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)?;
|
||||
{
|
||||
let mut batch = w.batch(keychain_mask)?;
|
||||
|
|
|
@ -17,9 +17,12 @@
|
|||
use crate::grin_core::libtx::secp_ser;
|
||||
use crate::grin_keychain::Identifier;
|
||||
use crate::grin_util::secp::pedersen;
|
||||
use crate::slate_versions::ser as dalek_ser;
|
||||
use crate::slate_versions::SlateVersion;
|
||||
use crate::types::OutputData;
|
||||
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
|
||||
/// Send TX API Args
|
||||
// TODO: This is here to ensure the legacy V1 API remains intact
|
||||
// remove this when v1 api is removed
|
||||
|
@ -86,6 +89,9 @@ pub struct InitTxArgs {
|
|||
/// down to the minimum slate version compatible with the current. If `None` the slate
|
||||
/// is generated with the latest version.
|
||||
pub target_slate_version: Option<u16>,
|
||||
/// If set, require a payment proof for the particular recipient
|
||||
#[serde(with = "dalek_ser::option_dalek_pubkey_serde")]
|
||||
pub payment_proof_recipient_address: Option<DalekPublicKey>,
|
||||
/// If true, just return an estimate of the resulting slate, containing fees and amounts
|
||||
/// locked without actually locking outputs or creating the transaction. Note if this is set to
|
||||
/// 'true', the amount field in the slate will contain the total amount locked, not the provided
|
||||
|
@ -124,6 +130,7 @@ impl Default for InitTxArgs {
|
|||
message: None,
|
||||
target_slate_version: None,
|
||||
estimate_only: Some(false),
|
||||
payment_proof_recipient_address: None,
|
||||
send_args: None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -221,6 +221,18 @@ pub enum ErrorKind {
|
|||
#[fail(display = "Tor Config Error: {}", _0)]
|
||||
TorConfig(String),
|
||||
|
||||
/// Generating ED25519 Public Key
|
||||
#[fail(display = "Error generating ed25519 secret key: {}", _0)]
|
||||
ED25519Key(String),
|
||||
|
||||
/// Generating Payment Proof
|
||||
#[fail(display = "Payment Proof generation error: {}", _0)]
|
||||
PaymentProof(String),
|
||||
|
||||
/// Decoding OnionV3 addresses to payment proof addresses
|
||||
#[fail(display = "Proof Address decoding: {}", _0)]
|
||||
AddressDecoding(String),
|
||||
|
||||
/// Other
|
||||
#[fail(display = "Generic error: {}", _0)]
|
||||
GenericError(String),
|
||||
|
|
|
@ -160,6 +160,23 @@ where
|
|||
t.amount_debited = amount_debited;
|
||||
t.messages = messages;
|
||||
|
||||
// store extra payment proof info, if required
|
||||
if let Some(ref p) = slate.payment_proof {
|
||||
t.payment_proof = Some(StoredProofInfo {
|
||||
receiver_address: p.receiver_address.clone(),
|
||||
receiver_signature: p.receiver_signature.clone(),
|
||||
sender_address_path: match context.payment_proof_derivation_index {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
return Err(ErrorKind::PaymentProof(
|
||||
"Payment proof derivation index required".to_owned(),
|
||||
))?;
|
||||
}
|
||||
},
|
||||
sender_signature: None,
|
||||
});
|
||||
};
|
||||
|
||||
// write the output representing our change
|
||||
for (id, _, _) in &context.get_outputs() {
|
||||
t.num_outputs += 1;
|
||||
|
|
|
@ -14,17 +14,24 @@
|
|||
|
||||
//! Transaction building functions
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::Cursor;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::grin_core::consensus::valid_header_version;
|
||||
use crate::grin_core::core::HeaderVersion;
|
||||
use crate::grin_keychain::{Identifier, Keychain};
|
||||
use crate::grin_util::secp::key::SecretKey;
|
||||
use crate::grin_util::secp::pedersen;
|
||||
use crate::grin_util::Mutex;
|
||||
use crate::internal::{selection, updater};
|
||||
use crate::slate::Slate;
|
||||
use crate::types::{Context, NodeClient, TxLogEntryType, WalletBackend};
|
||||
use crate::{Error, ErrorKind};
|
||||
use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType, WalletBackend};
|
||||
use crate::{address, Error, ErrorKind};
|
||||
use ed25519_dalek::Keypair as DalekKeypair;
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
|
||||
// static for incrementing test UUIDs
|
||||
lazy_static! {
|
||||
|
@ -303,6 +310,7 @@ where
|
|||
pub fn update_stored_tx<'a, T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
context: &Context,
|
||||
slate: &Slate,
|
||||
is_invoiced: bool,
|
||||
) -> Result<(), Error>
|
||||
|
@ -332,6 +340,29 @@ where
|
|||
wallet.store_tx(&format!("{}", tx.tx_slate_id.unwrap()), &slate.tx)?;
|
||||
let parent_key = tx.parent_key_id.clone();
|
||||
tx.kernel_excess = Some(slate.tx.body.kernels[0].excess);
|
||||
|
||||
if let Some(ref p) = slate.payment_proof {
|
||||
let derivation_index = match context.payment_proof_derivation_index {
|
||||
Some(i) => i,
|
||||
None => 0,
|
||||
};
|
||||
let keychain = wallet.keychain(keychain_mask)?;
|
||||
let parent_key_id = wallet.parent_key_id();
|
||||
let excess = slate.calc_excess(&keychain)?;
|
||||
let sig = create_payment_proof_signature(
|
||||
slate.amount,
|
||||
&excess,
|
||||
p.sender_address,
|
||||
address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?,
|
||||
)?;
|
||||
tx.payment_proof = Some(StoredProofInfo {
|
||||
receiver_address: p.receiver_address,
|
||||
receiver_signature: p.receiver_signature,
|
||||
sender_address_path: derivation_index,
|
||||
sender_signature: Some(sig),
|
||||
})
|
||||
}
|
||||
|
||||
let mut batch = wallet.batch(keychain_mask)?;
|
||||
batch.save_tx_log_entry(tx, &parent_key)?;
|
||||
batch.commit()?;
|
||||
|
@ -363,11 +394,146 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn payment_proof_message(
|
||||
amount: u64,
|
||||
kernel_commitment: &pedersen::Commitment,
|
||||
sender_address: DalekPublicKey,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let mut msg = Vec::new();
|
||||
msg.write_u64::<BigEndian>(amount)?;
|
||||
msg.append(&mut kernel_commitment.0.to_vec());
|
||||
msg.append(&mut sender_address.to_bytes().to_vec());
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
pub fn _decode_payment_proof_message(
|
||||
msg: &Vec<u8>,
|
||||
) -> Result<(u64, pedersen::Commitment, DalekPublicKey), Error> {
|
||||
let mut rdr = Cursor::new(msg);
|
||||
let amount = rdr.read_u64::<BigEndian>()?;
|
||||
let mut commit_bytes = [0u8; 33];
|
||||
for i in 0..33 {
|
||||
commit_bytes[i] = rdr.read_u8()?;
|
||||
}
|
||||
let mut sender_address_bytes = [0u8; 32];
|
||||
for i in 0..32 {
|
||||
sender_address_bytes[i] = rdr.read_u8()?;
|
||||
}
|
||||
|
||||
Ok((
|
||||
amount,
|
||||
pedersen::Commitment::from_vec(commit_bytes.to_vec()),
|
||||
DalekPublicKey::from_bytes(&sender_address_bytes).unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// create a payment proof
|
||||
pub fn create_payment_proof_signature(
|
||||
amount: u64,
|
||||
kernel_commitment: &pedersen::Commitment,
|
||||
sender_address: DalekPublicKey,
|
||||
sec_key: SecretKey,
|
||||
) -> Result<DalekSignature, Error> {
|
||||
let msg = payment_proof_message(amount, kernel_commitment, sender_address)?;
|
||||
let d_skey = match DalekSecretKey::from_bytes(&sec_key.0) {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
return Err(ErrorKind::ED25519Key(format!("{}", e)).to_owned())?;
|
||||
}
|
||||
};
|
||||
let pub_key: DalekPublicKey = (&d_skey).into();
|
||||
let keypair = DalekKeypair {
|
||||
public: pub_key,
|
||||
secret: d_skey,
|
||||
};
|
||||
Ok(keypair.sign(&msg))
|
||||
}
|
||||
|
||||
/// Verify all aspects of a completed payment proof
|
||||
pub fn verify_payment_proof<'a, T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
parent_key_id: &Identifier,
|
||||
context: &Context,
|
||||
slate: &Slate,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
if let Some(ref p) = slate.payment_proof {
|
||||
let keychain = wallet.keychain(keychain_mask)?;
|
||||
let index = match context.payment_proof_derivation_index {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
return Err(ErrorKind::PaymentProof(
|
||||
"Payment proof derivation index required".to_owned(),
|
||||
))?;
|
||||
}
|
||||
};
|
||||
let orig_sender_sk =
|
||||
address::address_from_derivation_path(&keychain, parent_key_id, index)?;
|
||||
let orig_sender_address = address::ed25519_keypair(&orig_sender_sk)?.1;
|
||||
if p.sender_address != orig_sender_address {
|
||||
return Err(ErrorKind::PaymentProof(
|
||||
"Sender address on slate does not match original sender address".to_owned(),
|
||||
))?;
|
||||
}
|
||||
let tx_vec =
|
||||
updater::retrieve_txs(wallet, None, Some(slate.id), Some(&parent_key_id), false)?;
|
||||
if tx_vec.len() != 1 {
|
||||
return Err(ErrorKind::PaymentProof(
|
||||
"TxLogEntry with original proof info not found".to_owned(),
|
||||
))?;
|
||||
}
|
||||
let orig_proof_info = match tx_vec[0].clone().payment_proof {
|
||||
Some(o) => o,
|
||||
None => {
|
||||
return Err(ErrorKind::PaymentProof(
|
||||
"Original proof info not stored in tx".to_owned(),
|
||||
))?;
|
||||
}
|
||||
};
|
||||
if orig_proof_info.receiver_address != p.receiver_address {
|
||||
return Err(ErrorKind::PaymentProof(
|
||||
"Recipient address on slate does not match original recipient address".to_owned(),
|
||||
))?;
|
||||
}
|
||||
let msg = payment_proof_message(
|
||||
slate.amount,
|
||||
&slate.calc_excess(&keychain)?,
|
||||
orig_sender_address,
|
||||
)?;
|
||||
let sig = match p.receiver_signature {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return Err(ErrorKind::PaymentProof(
|
||||
"Recipient did not provide requested proof signature".to_owned(),
|
||||
))?;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(_) = p.receiver_address.verify(&msg, &sig) {
|
||||
return Err(ErrorKind::PaymentProof(
|
||||
"Invalid proof signature".to_owned(),
|
||||
))?;
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use rand::rngs::mock::StepRng;
|
||||
|
||||
use crate::grin_core::core::KernelFeatures;
|
||||
use crate::grin_core::libtx::{build, ProofBuilder};
|
||||
use crate::grin_keychain::{ExtKeychain, ExtKeychainPath, Keychain};
|
||||
use crate::grin_keychain::{
|
||||
BlindSum, BlindingFactor, ExtKeychain, ExtKeychainPath, Keychain, SwitchCommitmentType,
|
||||
};
|
||||
use crate::grin_util::{secp, static_secp_instance};
|
||||
|
||||
#[test]
|
||||
// demonstrate that input.commitment == referenced output.commitment
|
||||
|
@ -395,4 +561,49 @@ mod test {
|
|||
assert_eq!(tx1.outputs()[0].features, tx2.inputs()[0].features);
|
||||
assert_eq!(tx1.outputs()[0].commitment(), tx2.inputs()[0].commitment());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payment_proof_construction() {
|
||||
let secp_inst = static_secp_instance();
|
||||
let secp = secp_inst.lock();
|
||||
let mut test_rng = StepRng::new(1234567890u64, 1);
|
||||
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
||||
let d_skey = DalekSecretKey::from_bytes(&sec_key.0).unwrap();
|
||||
|
||||
let address: DalekPublicKey = (&d_skey).into();
|
||||
|
||||
let kernel_excess = {
|
||||
ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
|
||||
let keychain = ExtKeychain::from_random_seed(true).unwrap();
|
||||
let switch = &SwitchCommitmentType::Regular;
|
||||
let id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
||||
let id2 = ExtKeychain::derive_key_id(1, 2, 0, 0, 0);
|
||||
let skey1 = keychain.derive_key(0, &id1, switch).unwrap();
|
||||
let skey2 = keychain.derive_key(0, &id2, switch).unwrap();
|
||||
let blinding_factor = keychain
|
||||
.blind_sum(
|
||||
&BlindSum::new()
|
||||
.sub_blinding_factor(BlindingFactor::from_secret_key(skey1))
|
||||
.add_blinding_factor(BlindingFactor::from_secret_key(skey2)),
|
||||
)
|
||||
.unwrap();
|
||||
keychain
|
||||
.secp()
|
||||
.commit(0, blinding_factor.secret_key(&keychain.secp()).unwrap())
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let amount = 12345678u64;
|
||||
let msg = payment_proof_message(amount, &kernel_excess, address).unwrap();
|
||||
println!("payment proof message is (len {}): {:?}", msg.len(), msg);
|
||||
|
||||
let decoded = _decode_payment_proof_message(&msg).unwrap();
|
||||
assert_eq!(decoded.0, amount);
|
||||
assert_eq!(decoded.1, kernel_excess);
|
||||
assert_eq!(decoded.2, address);
|
||||
|
||||
let sig = create_payment_proof_signature(amount, &kernel_excess, address, sec_key).unwrap();
|
||||
|
||||
assert!(address.verify(&msg, &sig).is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ extern crate strum;
|
|||
#[macro_use]
|
||||
extern crate strum_macros;
|
||||
|
||||
pub mod address;
|
||||
pub mod api_impl;
|
||||
mod error;
|
||||
mod internal;
|
||||
|
@ -63,10 +64,11 @@ pub use api_impl::types::{
|
|||
OutputCommitMapping, SendTXArgs, VersionInfo,
|
||||
};
|
||||
pub use internal::scan::scan;
|
||||
pub use slate_versions::ser as dalek_ser;
|
||||
pub use types::{
|
||||
AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData,
|
||||
OutputStatus, ScannedBlockInfo, TxLogEntry, TxLogEntryType, TxWrapper, WalletBackend,
|
||||
WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider, WalletOutputBatch,
|
||||
OutputStatus, ScannedBlockInfo, StoredProofInfo, TxLogEntry, TxLogEntryType, TxWrapper,
|
||||
WalletBackend, WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider, WalletOutputBatch,
|
||||
};
|
||||
|
||||
/// Helper for taking a lock on the wallet instance
|
||||
|
|
|
@ -32,6 +32,7 @@ use crate::grin_util::secp::Signature;
|
|||
use crate::grin_util::{self, secp, RwLock};
|
||||
use crate::slate_versions::ser as dalek_ser;
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
use failure::ResultExt;
|
||||
use rand::rngs::mock::StepRng;
|
||||
use rand::thread_rng;
|
||||
|
@ -52,10 +53,10 @@ use crate::types::CbData;
|
|||
pub struct PaymentInfo {
|
||||
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
||||
pub sender_address: DalekPublicKey,
|
||||
#[serde(with = "dalek_ser::option_dalek_pubkey_serde")]
|
||||
pub receiver_address: Option<DalekPublicKey>,
|
||||
#[serde(with = "secp_ser::option_sig_serde")]
|
||||
pub receiver_signature: Option<Signature>,
|
||||
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
||||
pub receiver_address: DalekPublicKey,
|
||||
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
|
||||
pub receiver_signature: Option<DalekSignature>,
|
||||
}
|
||||
|
||||
/// Public data for each participant in the slate
|
||||
|
|
|
@ -80,6 +80,46 @@ pub mod option_dalek_pubkey_serde {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes an Option<ed25519_dalek::PublicKey> to and from hex
|
||||
pub mod option_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: &Option<DalekSignature>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match key {
|
||||
Some(key) => serializer.serialize_str(&to_hex(key.to_bytes().to_vec())),
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DalekSignature>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Option::<String>::deserialize(deserializer).and_then(|res| match res {
|
||||
Some(string) => from_hex(string.to_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(|val| Some(val))
|
||||
.map_err(|err| Error::custom(err.to_string()))
|
||||
}),
|
||||
None => Ok(None),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test serialization methods of components that are being used
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
@ -87,8 +127,10 @@ mod test {
|
|||
use rand::rngs::mock::StepRng;
|
||||
|
||||
use crate::grin_util::{secp, static_secp_instance};
|
||||
use ed25519_dalek::Keypair;
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
use serde::Deserialize;
|
||||
|
||||
use serde_json;
|
||||
|
@ -99,6 +141,8 @@ mod test {
|
|||
pub pub_key: DalekPublicKey,
|
||||
#[serde(with = "option_dalek_pubkey_serde")]
|
||||
pub pub_key_opt: Option<DalekPublicKey>,
|
||||
#[serde(with = "option_dalek_sig_serde")]
|
||||
pub sig_opt: Option<DalekSignature>,
|
||||
}
|
||||
|
||||
impl SerTest {
|
||||
|
@ -109,9 +153,19 @@ mod test {
|
|||
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
||||
let d_skey = DalekSecretKey::from_bytes(&sec_key.0).unwrap();
|
||||
let d_pub_key: DalekPublicKey = (&d_skey).into();
|
||||
|
||||
let keypair = Keypair {
|
||||
public: d_pub_key,
|
||||
secret: d_skey,
|
||||
};
|
||||
|
||||
let d_sig = keypair.sign("test sig".as_bytes());
|
||||
println!("D sig: {:?}", d_sig);
|
||||
|
||||
SerTest {
|
||||
pub_key: d_pub_key.clone(),
|
||||
pub_key_opt: Some(d_pub_key),
|
||||
sig_opt: Some(d_sig),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ use crate::grin_util::secp::Signature;
|
|||
use crate::slate::CompatKernelFeatures;
|
||||
use crate::slate_versions::ser as dalek_ser;
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
|
@ -105,10 +106,10 @@ pub struct ParticipantDataV3 {
|
|||
pub struct PaymentInfoV3 {
|
||||
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
||||
pub sender_address: DalekPublicKey,
|
||||
#[serde(with = "dalek_ser::option_dalek_pubkey_serde")]
|
||||
pub receiver_address: Option<DalekPublicKey>,
|
||||
#[serde(with = "secp_ser::option_sig_serde")]
|
||||
pub receiver_signature: Option<Signature>,
|
||||
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
||||
pub receiver_address: DalekPublicKey,
|
||||
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
|
||||
pub receiver_signature: Option<DalekSignature>,
|
||||
}
|
||||
|
||||
/// A transaction
|
||||
|
|
|
@ -26,7 +26,10 @@ use crate::grin_util::secp::key::{PublicKey, SecretKey};
|
|||
use crate::grin_util::secp::{self, pedersen, Secp256k1};
|
||||
use crate::grin_util::{LoggingConfig, ZeroingString};
|
||||
use crate::slate::ParticipantMessages;
|
||||
use crate::slate_versions::ser as dalek_ser;
|
||||
use chrono::prelude::*;
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
use failure::ResultExt;
|
||||
use serde;
|
||||
use serde_json;
|
||||
|
@ -546,6 +549,8 @@ pub struct Context {
|
|||
pub fee: u64,
|
||||
/// keep track of the participant id
|
||||
pub participant_id: usize,
|
||||
/// Payment proof sender address derivation path, if needed
|
||||
pub payment_proof_derivation_index: Option<u32>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
|
@ -569,6 +574,7 @@ impl Context {
|
|||
output_ids: vec![],
|
||||
fee: 0,
|
||||
participant_id: participant_id,
|
||||
payment_proof_derivation_index: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -784,6 +790,9 @@ pub struct TxLogEntry {
|
|||
/// of kernel is necessary
|
||||
#[serde(default)]
|
||||
pub kernel_lookup_min_height: Option<u64>,
|
||||
/// Additional info needed to stored payment proof
|
||||
#[serde(default)]
|
||||
pub payment_proof: Option<StoredProofInfo>,
|
||||
}
|
||||
|
||||
impl ser::Writeable for TxLogEntry {
|
||||
|
@ -819,6 +828,7 @@ impl TxLogEntry {
|
|||
stored_tx: None,
|
||||
kernel_excess: None,
|
||||
kernel_lookup_min_height: None,
|
||||
payment_proof: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -836,6 +846,36 @@ impl TxLogEntry {
|
|||
}
|
||||
}
|
||||
|
||||
/// Payment proof information. Differs from what is sent via
|
||||
/// the slate
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct StoredProofInfo {
|
||||
/// receiver address
|
||||
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
||||
pub receiver_address: DalekPublicKey,
|
||||
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
|
||||
/// receiver signature
|
||||
pub receiver_signature: Option<DalekSignature>,
|
||||
/// sender address derivation path index
|
||||
pub sender_address_path: u32,
|
||||
/// sender signature
|
||||
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
|
||||
pub sender_signature: Option<DalekSignature>,
|
||||
}
|
||||
|
||||
impl ser::Writeable for StoredProofInfo {
|
||||
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Readable for StoredProofInfo {
|
||||
fn read(reader: &mut dyn ser::Reader) -> Result<StoredProofInfo, ser::Error> {
|
||||
let data = reader.read_bytes_len_prefix()?;
|
||||
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
|
||||
}
|
||||
}
|
||||
|
||||
/// Map of named accounts to BIP32 paths
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AcctPathMapping {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"selection_strategy_is_use_all": true,
|
||||
"message": "my message",
|
||||
"target_slate_version": null,
|
||||
"payment_proof_recipient_address": null,
|
||||
"send_args": null
|
||||
}
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"selection_strategy_is_use_all": true,
|
||||
"message": "my message",
|
||||
"target_slate_version": null,
|
||||
"payment_proof_recipient_address": null,
|
||||
"send_args": null
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue