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:
Yeastplume 2019-11-28 14:34:27 +00:00 committed by GitHub
parent 9c2177e3d9
commit 7293ca99c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 988 additions and 125 deletions

5
Cargo.lock generated
View file

@ -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)",

View file

@ -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" }

View file

@ -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,
};

View file

@ -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)]

View file

@ -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();

View file

@ -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 })
}
}

View file

@ -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)]

View file

@ -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()))?;

View 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);
}

View file

@ -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);

View file

@ -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"

View file

@ -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)]

View file

@ -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"
)

View file

@ -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
View 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);
}
}

View file

@ -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)?;

View file

@ -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)?;

View file

@ -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,
}
}

View file

@ -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),

View file

@ -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;

View file

@ -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());
}
}

View file

@ -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

View file

@ -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

View file

@ -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),
}
}
}

View file

@ -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

View file

@ -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 {

View file

@ -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
}
},

View file

@ -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
}
},