mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-02-08 12:21:10 +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)",
|
"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)",
|
"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)",
|
"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 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)",
|
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grin_wallet_config 3.0.0-alpha.1",
|
"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 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_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)",
|
"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)",
|
"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)",
|
"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)",
|
"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"
|
version = "3.0.0-alpha.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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 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)",
|
"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 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_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)",
|
"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 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)",
|
"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)",
|
"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"] }
|
chrono = { version = "0.4.4", features = ["serde"] }
|
||||||
ring = "0.13"
|
ring = "0.13"
|
||||||
base64 = "0.9"
|
base64 = "0.9"
|
||||||
|
ed25519-dalek = "1.0.0-pre.1"
|
||||||
|
|
||||||
grin_wallet_libwallet = { path = "../libwallet", version = "3.0.0-alpha.1" }
|
grin_wallet_libwallet = { path = "../libwallet", version = "3.0.0-alpha.1" }
|
||||||
grin_wallet_config = { path = "../config", 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::foreign_rpc::run_doctest_foreign;
|
||||||
pub use crate::owner_rpc::run_doctest_owner;
|
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
|
//! Owner API External Definition
|
||||||
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::config::{TorConfig, WalletConfig};
|
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_updater::{start_updater_log_thread, StatusMessage};
|
||||||
use crate::libwallet::api_impl::{owner, owner_updater};
|
use crate::libwallet::api_impl::{owner, owner_updater};
|
||||||
use crate::libwallet::{
|
use crate::libwallet::{
|
||||||
AcctPathMapping, Error, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient,
|
address, AcctPathMapping, Error, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient,
|
||||||
NodeHeightResult, OutputCommitMapping, Slate, TxLogEntry, WalletInfo, WalletInst,
|
NodeHeightResult, OutputCommitMapping, Slate, TxLogEntry, WalletInfo, WalletInst,
|
||||||
WalletLCProvider,
|
WalletLCProvider,
|
||||||
};
|
};
|
||||||
|
@ -1886,6 +1887,109 @@ where
|
||||||
let index = q.len().saturating_sub(count);
|
let index = q.len().saturating_sub(count);
|
||||||
Ok(q.split_off(index))
|
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)]
|
#[doc(hidden)]
|
||||||
|
|
|
@ -240,6 +240,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||||
"parent_key_id": "0200000000000000000000000000000000",
|
"parent_key_id": "0200000000000000000000000000000000",
|
||||||
"stored_tx": null,
|
"stored_tx": null,
|
||||||
"tx_slate_id": null,
|
"tx_slate_id": null,
|
||||||
|
"payment_proof": null,
|
||||||
"tx_type": "ConfirmedCoinbase"
|
"tx_type": "ConfirmedCoinbase"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -258,6 +259,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||||
"parent_key_id": "0200000000000000000000000000000000",
|
"parent_key_id": "0200000000000000000000000000000000",
|
||||||
"stored_tx": null,
|
"stored_tx": null,
|
||||||
"tx_slate_id": null,
|
"tx_slate_id": null,
|
||||||
|
"payment_proof": null,
|
||||||
"tx_type": "ConfirmedCoinbase"
|
"tx_type": "ConfirmedCoinbase"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -340,6 +342,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||||
"selection_strategy_is_use_all": true,
|
"selection_strategy_is_use_all": true,
|
||||||
"message": "my message",
|
"message": "my message",
|
||||||
"target_slate_version": null,
|
"target_slate_version": null,
|
||||||
|
"payment_proof_recipient_address": null,
|
||||||
"send_args": null
|
"send_args": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -561,6 +564,7 @@ pub trait OwnerRpc: Sync + Send {
|
||||||
"selection_strategy_is_use_all": true,
|
"selection_strategy_is_use_all": true,
|
||||||
"message": "Ok, here are your grins",
|
"message": "Ok, here are your grins",
|
||||||
"target_slate_version": null,
|
"target_slate_version": null,
|
||||||
|
"payment_proof_recipient_address": null,
|
||||||
"send_args": null
|
"send_args": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -1485,6 +1489,8 @@ pub fn run_doctest_owner(
|
||||||
assert!(wallet_refreshed);
|
assert!(wallet_refreshed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//let proof_address = api_impl::owner::get_public_proof_address(wallet2.clone(), (&mask2).as_ref(), 0).unwrap();
|
||||||
|
|
||||||
if perform_tx {
|
if perform_tx {
|
||||||
let amount = 60_000_000_000;
|
let amount = 60_000_000_000;
|
||||||
let mut w_lock = wallet1.lock();
|
let mut w_lock = wallet1.lock();
|
||||||
|
|
|
@ -27,7 +27,7 @@ use crate::libwallet::{
|
||||||
};
|
};
|
||||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||||
use crate::util::{static_secp_instance, LoggingConfig, ZeroingString};
|
use crate::util::{static_secp_instance, LoggingConfig, ZeroingString};
|
||||||
use crate::{ECDHPubkey, Owner, Token};
|
use crate::{ECDHPubkey, Owner, PubAddress, Token};
|
||||||
use easy_jsonrpc_mw;
|
use easy_jsonrpc_mw;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -264,6 +264,7 @@ pub trait OwnerRpcS {
|
||||||
"parent_key_id": "0200000000000000000000000000000000",
|
"parent_key_id": "0200000000000000000000000000000000",
|
||||||
"stored_tx": null,
|
"stored_tx": null,
|
||||||
"tx_slate_id": null,
|
"tx_slate_id": null,
|
||||||
|
"payment_proof": null,
|
||||||
"tx_type": "ConfirmedCoinbase"
|
"tx_type": "ConfirmedCoinbase"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -281,6 +282,7 @@ pub trait OwnerRpcS {
|
||||||
"num_outputs": 1,
|
"num_outputs": 1,
|
||||||
"parent_key_id": "0200000000000000000000000000000000",
|
"parent_key_id": "0200000000000000000000000000000000",
|
||||||
"stored_tx": null,
|
"stored_tx": null,
|
||||||
|
"payment_proof": null,
|
||||||
"tx_slate_id": null,
|
"tx_slate_id": null,
|
||||||
"tx_type": "ConfirmedCoinbase"
|
"tx_type": "ConfirmedCoinbase"
|
||||||
}
|
}
|
||||||
|
@ -371,6 +373,7 @@ pub trait OwnerRpcS {
|
||||||
"selection_strategy_is_use_all": true,
|
"selection_strategy_is_use_all": true,
|
||||||
"message": "my message",
|
"message": "my message",
|
||||||
"target_slate_version": null,
|
"target_slate_version": null,
|
||||||
|
"payment_proof_recipient_address": "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb",
|
||||||
"send_args": null
|
"send_args": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -391,7 +394,11 @@ pub trait OwnerRpcS {
|
||||||
"lock_height": "0",
|
"lock_height": "0",
|
||||||
"ttl_cutoff_height": "0",
|
"ttl_cutoff_height": "0",
|
||||||
"num_participants": 2,
|
"num_participants": 2,
|
||||||
"payment_proof": null,
|
"payment_proof": {
|
||||||
|
"receiver_address": "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb",
|
||||||
|
"receiver_signature": null,
|
||||||
|
"sender_address": "32cdd63928854f8b2628b1dce4626ddcdf35d56cb7cfdf7d64cca5822b78d4d3"
|
||||||
|
},
|
||||||
"participant_data": [
|
"participant_data": [
|
||||||
{
|
{
|
||||||
"id": "0",
|
"id": "0",
|
||||||
|
@ -598,6 +605,7 @@ pub trait OwnerRpcS {
|
||||||
"selection_strategy_is_use_all": true,
|
"selection_strategy_is_use_all": true,
|
||||||
"message": "Ok, here are your grins",
|
"message": "Ok, here are your grins",
|
||||||
"target_slate_version": null,
|
"target_slate_version": null,
|
||||||
|
"payment_proof_recipient_address": null,
|
||||||
"send_args": null
|
"send_args": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1776,6 +1784,71 @@ pub trait OwnerRpcS {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fn get_updater_messages(&self, count: u32) -> Result<Vec<StatusMessage>, ErrorKind>;
|
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>
|
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> {
|
fn get_updater_messages(&self, count: u32) -> Result<Vec<StatusMessage>, ErrorKind> {
|
||||||
Owner::get_updater_messages(self, count as usize).map_err(|e| e.kind())
|
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.
|
// limitations under the License.
|
||||||
|
|
||||||
use crate::core::libtx::secp_ser;
|
use crate::core::libtx::secp_ser;
|
||||||
|
use crate::libwallet::dalek_ser;
|
||||||
use crate::libwallet::{Error, ErrorKind};
|
use crate::libwallet::{Error, ErrorKind};
|
||||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||||
use crate::util::{from_hex, to_hex};
|
use crate::util::{from_hex, to_hex};
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
|
|
||||||
use base64;
|
use base64;
|
||||||
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use ring::aead;
|
use ring::aead;
|
||||||
use serde_json::{self, Value};
|
use serde_json::{self, Value};
|
||||||
|
@ -32,6 +34,15 @@ pub struct Token {
|
||||||
pub keychain_mask: Option<SecretKey>,
|
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
|
/// Wrapper for ECDH Public keys
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
use crate::api::{self, ApiServer, BasicAuthMiddleware, ResponseFuture, Router, TLSConfig};
|
use crate::api::{self, ApiServer, BasicAuthMiddleware, ResponseFuture, Router, TLSConfig};
|
||||||
use crate::keychain::Keychain;
|
use crate::keychain::Keychain;
|
||||||
use crate::libwallet::{
|
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,
|
CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION,
|
||||||
};
|
};
|
||||||
use crate::util::secp::key::SecretKey;
|
use crate::util::secp::key::SecretKey;
|
||||||
|
@ -98,7 +98,7 @@ where
|
||||||
let k = w_inst.keychain((&mask).as_ref())?;
|
let k = w_inst.keychain((&mask).as_ref())?;
|
||||||
let parent_key_id = w_inst.parent_key_id();
|
let parent_key_id = w_inst.parent_key_id();
|
||||||
let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?);
|
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()))?;
|
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||||
let onion_address = tor_config::onion_address_from_seckey(&sec_key)
|
let onion_address = tor_config::onion_address_from_seckey(&sec_key)
|
||||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
.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_impls as impls;
|
||||||
extern crate grin_wallet_libwallet as libwallet;
|
extern crate grin_wallet_libwallet as libwallet;
|
||||||
|
|
||||||
use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage};
|
// use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage};
|
||||||
use grin_wallet_util::grin_core as core;
|
// use grin_wallet_util::grin_core as core;
|
||||||
|
|
||||||
use self::libwallet::{InitTxArgs, Slate};
|
|
||||||
use impls::test_framework::{self, LocalWalletClient};
|
use impls::test_framework::{self, LocalWalletClient};
|
||||||
use impls::{PathToSlate, SlateGetter as _, SlatePutter as _};
|
|
||||||
use std::sync::mpsc::channel;
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
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
|
// add some accounts
|
||||||
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
wallet::controller::owner_single_use(wallet1.clone(), mask1, |api, m| {
|
||||||
api.create_account_path(m, "mining")?;
|
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);
|
wallet_inst!(wallet1, w);
|
||||||
w.set_parent_key_id_by_name("mining")?;
|
w.set_parent_key_id_by_name("mining")?;
|
||||||
}
|
}
|
||||||
let mut bh = 10u64;
|
let bh = 10u64;
|
||||||
let _ =
|
let _ =
|
||||||
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
|
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"
|
#tokio-tls = "0.1"
|
||||||
ed25519-dalek = "1.0.0-pre.1"
|
ed25519-dalek = "1.0.0-pre.1"
|
||||||
data-encoding = "2"
|
data-encoding = "2"
|
||||||
sha3 = "0.8"
|
|
||||||
regex = "1.3"
|
regex = "1.3"
|
||||||
timer = "0.2"
|
timer = "0.2"
|
||||||
sysinfo = "0.9"
|
sysinfo = "0.9"
|
||||||
|
|
|
@ -83,8 +83,8 @@ pub enum ErrorKind {
|
||||||
ED25519Key(String),
|
ED25519Key(String),
|
||||||
|
|
||||||
/// Checking for onion address
|
/// Checking for onion address
|
||||||
#[fail(display = "Address is not an Onion v3 Address")]
|
#[fail(display = "Address is not an Onion v3 Address: {}", _0)]
|
||||||
NotOnion,
|
NotOnion(String),
|
||||||
|
|
||||||
/// Other
|
/// Other
|
||||||
#[fail(display = "Generic error: {}", _0)]
|
#[fail(display = "Generic error: {}", _0)]
|
||||||
|
|
|
@ -15,15 +15,11 @@
|
||||||
//! Tor Configuration + Onion (Hidden) Service operations
|
//! Tor Configuration + Onion (Hidden) Service operations
|
||||||
use crate::util::secp::key::SecretKey;
|
use crate::util::secp::key::SecretKey;
|
||||||
use crate::{Error, ErrorKind};
|
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::ExpandedSecretKey;
|
||||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
use ed25519_dalek::SecretKey as DalekSecretKey;
|
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||||
use sha3::{Digest, Sha3_256};
|
|
||||||
|
|
||||||
use crate::blake2::blake2b::blake2b;
|
|
||||||
|
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::Write;
|
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
|
/// helper to get address
|
||||||
pub fn onion_address_from_seckey(sec_key: &SecretKey) -> Result<String, Error> {
|
pub fn onion_address_from_seckey(sec_key: &SecretKey) -> Result<String, Error> {
|
||||||
let (_, d_pub_key) = ed25519_keypair(sec_key)?;
|
let (_, d_pub_key) = address::ed25519_keypair(sec_key)?;
|
||||||
onion_address(&d_pub_key)
|
Ok(address::onion_v3_from_pubkey(&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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_onion_service_sec_key_file(
|
pub fn create_onion_service_sec_key_file(
|
||||||
|
@ -178,8 +144,8 @@ pub fn output_onion_service_config(
|
||||||
tor_config_directory: &str,
|
tor_config_directory: &str,
|
||||||
sec_key: &SecretKey,
|
sec_key: &SecretKey,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
let (_, d_pub_key) = ed25519_keypair(&sec_key)?;
|
let (_, d_pub_key) = address::ed25519_keypair(&sec_key)?;
|
||||||
let address = onion_address(&d_pub_key)?;
|
let address = address::onion_v3_from_pubkey(&d_pub_key)?;
|
||||||
let hs_dir_file_path = format!(
|
let hs_dir_file_path = format!(
|
||||||
"{}{}{}{}{}",
|
"{}{}{}{}{}",
|
||||||
tor_config_directory, MAIN_SEPARATOR, HIDDEN_SERVICES_DIR, MAIN_SEPARATOR, address
|
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
|
// create directory if it doesn't exist
|
||||||
fs::create_dir_all(&hs_dir_file_path).context(ErrorKind::IO)?;
|
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_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_pub_key_file(&hs_dir_file_path, &d_pub_key)?;
|
||||||
create_onion_service_hostname_file(&hs_dir_file_path, &address)?;
|
create_onion_service_hostname_file(&hs_dir_file_path, &address)?;
|
||||||
|
@ -272,51 +238,14 @@ pub fn output_tor_sender_config(
|
||||||
Ok(())
|
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> {
|
pub fn is_tor_address(input: &str) -> Result<(), Error> {
|
||||||
let mut input = input.to_uppercase();
|
match address::pubkey_from_onion_v3(input) {
|
||||||
if input.starts_with("HTTP://") || input.starts_with("HTTPS://") {
|
Ok(_) => Ok(()),
|
||||||
input = input.replace("HTTP://", "");
|
Err(e) => {
|
||||||
input = input.replace("HTTPS://", "");
|
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> {
|
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 mut test_rng = StepRng::new(1234567890u64, 1);
|
||||||
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
||||||
println!("{:?}", sec_key);
|
println!("{:?}", sec_key);
|
||||||
let (_, d_pub_key) = ed25519_keypair(&sec_key)?;
|
let (_, d_pub_key) = address::ed25519_keypair(&sec_key)?;
|
||||||
println!("{:?}", d_pub_key);
|
println!("{:?}", d_pub_key);
|
||||||
// some randoms
|
// some randoms
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
let sec_key = secp::key::SecretKey::new(&secp, &mut thread_rng());
|
let sec_key = secp::key::SecretKey::new(&secp, &mut thread_rng());
|
||||||
let (_, _) = ed25519_keypair(&sec_key)?;
|
let (_, _) = address::ed25519_keypair(&sec_key)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -373,8 +302,8 @@ mod tests {
|
||||||
let mut test_rng = StepRng::new(1234567890u64, 1);
|
let mut test_rng = StepRng::new(1234567890u64, 1);
|
||||||
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
||||||
println!("{:?}", sec_key);
|
println!("{:?}", sec_key);
|
||||||
let (_, d_pub_key) = ed25519_keypair(&sec_key)?;
|
let (_, d_pub_key) = address::ed25519_keypair(&sec_key)?;
|
||||||
let address = onion_address(&d_pub_key)?;
|
let address = address::onion_v3_from_pubkey(&d_pub_key)?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid",
|
"kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid",
|
||||||
address
|
address
|
||||||
|
@ -412,6 +341,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_tor_address() -> Result<(), Error> {
|
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("2a6at2obto3uvkpkitqp4wxcg6u36qf534eucbskqciturczzc5suyid").is_ok());
|
||||||
|
assert!(is_tor_address("kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid").is_ok());
|
||||||
assert!(is_tor_address(
|
assert!(is_tor_address(
|
||||||
"http://kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid.onion"
|
"http://kcgiy5g6m76nzlzz4vyqmgdv34f6yokdqwfhdhaafanpo5p4fceibyid.onion"
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,6 +25,9 @@ lazy_static = "1"
|
||||||
strum = "0.15"
|
strum = "0.15"
|
||||||
strum_macros = "0.15"
|
strum_macros = "0.15"
|
||||||
ed25519-dalek = "1.0.0-pre.1"
|
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_util = { path = "../util", version = "3.0.0-alpha.1" }
|
||||||
grin_wallet_config = { path = "../config", 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::internal::{tx, updater};
|
||||||
use crate::slate_versions::SlateVersion;
|
use crate::slate_versions::SlateVersion;
|
||||||
use crate::{
|
use crate::{
|
||||||
BlockFees, CbData, Error, ErrorKind, NodeClient, Slate, TxLogEntryType, VersionInfo,
|
address, BlockFees, CbData, Error, ErrorKind, NodeClient, Slate, TxLogEntryType, VersionInfo,
|
||||||
WalletBackend,
|
WalletBackend,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -113,6 +113,21 @@ where
|
||||||
use_test_rng,
|
use_test_rng,
|
||||||
)?;
|
)?;
|
||||||
tx::update_message(&mut *w, keychain_mask, &mut ret_slate)?;
|
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)
|
Ok(ret_slate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +145,7 @@ where
|
||||||
let mut sl = slate.clone();
|
let mut sl = slate.clone();
|
||||||
let context = w.get_private_context(keychain_mask, sl.id.as_bytes(), 1)?;
|
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::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)?;
|
tx::update_message(&mut *w, keychain_mask, &mut sl)?;
|
||||||
{
|
{
|
||||||
let mut batch = w.batch(keychain_mask)?;
|
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::api_impl::owner_updater::StatusMessage;
|
||||||
use crate::grin_keychain::{Identifier, Keychain};
|
use crate::grin_keychain::{Identifier, Keychain};
|
||||||
use crate::internal::{keys, scan, selection, tx, updater};
|
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::types::{AcctPathMapping, NodeClient, TxLogEntry, TxWrapper, WalletBackend, WalletInfo};
|
||||||
use crate::{
|
use crate::{
|
||||||
wallet_lock, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping,
|
address, wallet_lock, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping,
|
||||||
ScannedBlockInfo, TxLogEntryType, WalletInitStatus, WalletInst, WalletLCProvider,
|
ScannedBlockInfo, TxLogEntryType, WalletInitStatus, WalletInst, WalletLCProvider,
|
||||||
};
|
};
|
||||||
use crate::{Error, ErrorKind};
|
use crate::{Error, ErrorKind};
|
||||||
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
|
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -72,6 +74,26 @@ where
|
||||||
w.set_parent_key_id_by_name(label)
|
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
|
/// retrieve outputs
|
||||||
pub fn retrieve_outputs<'a, L, C, K>(
|
pub fn retrieve_outputs<'a, L, C, K>(
|
||||||
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
||||||
|
@ -222,7 +244,7 @@ where
|
||||||
return Ok(slate);
|
return Ok(slate);
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = tx::add_inputs_to_slate(
|
let mut context = tx::add_inputs_to_slate(
|
||||||
&mut *w,
|
&mut *w,
|
||||||
keychain_mask,
|
keychain_mask,
|
||||||
&mut slate,
|
&mut slate,
|
||||||
|
@ -237,6 +259,26 @@ where
|
||||||
use_test_rng,
|
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
|
// Save the aggsig context in our DB for when we
|
||||||
// recieve the transaction back
|
// recieve the transaction back
|
||||||
{
|
{
|
||||||
|
@ -247,6 +289,7 @@ where
|
||||||
if let Some(v) = args.target_slate_version {
|
if let Some(v) = args.target_slate_version {
|
||||||
slate.version_info.orig_version = v;
|
slate.version_info.orig_version = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(slate)
|
Ok(slate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,8 +460,10 @@ where
|
||||||
{
|
{
|
||||||
let mut sl = slate.clone();
|
let mut sl = slate.clone();
|
||||||
let context = w.get_private_context(keychain_mask, sl.id.as_bytes(), 0)?;
|
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::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)?;
|
tx::update_message(&mut *w, keychain_mask, &mut sl)?;
|
||||||
{
|
{
|
||||||
let mut batch = w.batch(keychain_mask)?;
|
let mut batch = w.batch(keychain_mask)?;
|
||||||
|
|
|
@ -17,9 +17,12 @@
|
||||||
use crate::grin_core::libtx::secp_ser;
|
use crate::grin_core::libtx::secp_ser;
|
||||||
use crate::grin_keychain::Identifier;
|
use crate::grin_keychain::Identifier;
|
||||||
use crate::grin_util::secp::pedersen;
|
use crate::grin_util::secp::pedersen;
|
||||||
|
use crate::slate_versions::ser as dalek_ser;
|
||||||
use crate::slate_versions::SlateVersion;
|
use crate::slate_versions::SlateVersion;
|
||||||
use crate::types::OutputData;
|
use crate::types::OutputData;
|
||||||
|
|
||||||
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
|
|
||||||
/// Send TX API Args
|
/// Send TX API Args
|
||||||
// TODO: This is here to ensure the legacy V1 API remains intact
|
// TODO: This is here to ensure the legacy V1 API remains intact
|
||||||
// remove this when v1 api is removed
|
// 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
|
/// down to the minimum slate version compatible with the current. If `None` the slate
|
||||||
/// is generated with the latest version.
|
/// is generated with the latest version.
|
||||||
pub target_slate_version: Option<u16>,
|
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
|
/// 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
|
/// 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
|
/// '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,
|
message: None,
|
||||||
target_slate_version: None,
|
target_slate_version: None,
|
||||||
estimate_only: Some(false),
|
estimate_only: Some(false),
|
||||||
|
payment_proof_recipient_address: None,
|
||||||
send_args: None,
|
send_args: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,6 +221,18 @@ pub enum ErrorKind {
|
||||||
#[fail(display = "Tor Config Error: {}", _0)]
|
#[fail(display = "Tor Config Error: {}", _0)]
|
||||||
TorConfig(String),
|
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
|
/// Other
|
||||||
#[fail(display = "Generic error: {}", _0)]
|
#[fail(display = "Generic error: {}", _0)]
|
||||||
GenericError(String),
|
GenericError(String),
|
||||||
|
|
|
@ -160,6 +160,23 @@ where
|
||||||
t.amount_debited = amount_debited;
|
t.amount_debited = amount_debited;
|
||||||
t.messages = messages;
|
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
|
// write the output representing our change
|
||||||
for (id, _, _) in &context.get_outputs() {
|
for (id, _, _) in &context.get_outputs() {
|
||||||
t.num_outputs += 1;
|
t.num_outputs += 1;
|
||||||
|
|
|
@ -14,17 +14,24 @@
|
||||||
|
|
||||||
//! Transaction building functions
|
//! Transaction building functions
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use std::io::Cursor;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::grin_core::consensus::valid_header_version;
|
use crate::grin_core::consensus::valid_header_version;
|
||||||
use crate::grin_core::core::HeaderVersion;
|
use crate::grin_core::core::HeaderVersion;
|
||||||
use crate::grin_keychain::{Identifier, Keychain};
|
use crate::grin_keychain::{Identifier, Keychain};
|
||||||
use crate::grin_util::secp::key::SecretKey;
|
use crate::grin_util::secp::key::SecretKey;
|
||||||
|
use crate::grin_util::secp::pedersen;
|
||||||
use crate::grin_util::Mutex;
|
use crate::grin_util::Mutex;
|
||||||
use crate::internal::{selection, updater};
|
use crate::internal::{selection, updater};
|
||||||
use crate::slate::Slate;
|
use crate::slate::Slate;
|
||||||
use crate::types::{Context, NodeClient, TxLogEntryType, WalletBackend};
|
use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType, WalletBackend};
|
||||||
use crate::{Error, ErrorKind};
|
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
|
// static for incrementing test UUIDs
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -303,6 +310,7 @@ where
|
||||||
pub fn update_stored_tx<'a, T: ?Sized, C, K>(
|
pub fn update_stored_tx<'a, T: ?Sized, C, K>(
|
||||||
wallet: &mut T,
|
wallet: &mut T,
|
||||||
keychain_mask: Option<&SecretKey>,
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
context: &Context,
|
||||||
slate: &Slate,
|
slate: &Slate,
|
||||||
is_invoiced: bool,
|
is_invoiced: bool,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
|
@ -332,6 +340,29 @@ where
|
||||||
wallet.store_tx(&format!("{}", tx.tx_slate_id.unwrap()), &slate.tx)?;
|
wallet.store_tx(&format!("{}", tx.tx_slate_id.unwrap()), &slate.tx)?;
|
||||||
let parent_key = tx.parent_key_id.clone();
|
let parent_key = tx.parent_key_id.clone();
|
||||||
tx.kernel_excess = Some(slate.tx.body.kernels[0].excess);
|
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)?;
|
let mut batch = wallet.batch(keychain_mask)?;
|
||||||
batch.save_tx_log_entry(tx, &parent_key)?;
|
batch.save_tx_log_entry(tx, &parent_key)?;
|
||||||
batch.commit()?;
|
batch.commit()?;
|
||||||
|
@ -363,11 +394,146 @@ where
|
||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use rand::rngs::mock::StepRng;
|
||||||
|
|
||||||
use crate::grin_core::core::KernelFeatures;
|
use crate::grin_core::core::KernelFeatures;
|
||||||
use crate::grin_core::libtx::{build, ProofBuilder};
|
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]
|
#[test]
|
||||||
// demonstrate that input.commitment == referenced output.commitment
|
// 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].features, tx2.inputs()[0].features);
|
||||||
assert_eq!(tx1.outputs()[0].commitment(), tx2.inputs()[0].commitment());
|
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]
|
#[macro_use]
|
||||||
extern crate strum_macros;
|
extern crate strum_macros;
|
||||||
|
|
||||||
|
pub mod address;
|
||||||
pub mod api_impl;
|
pub mod api_impl;
|
||||||
mod error;
|
mod error;
|
||||||
mod internal;
|
mod internal;
|
||||||
|
@ -63,10 +64,11 @@ pub use api_impl::types::{
|
||||||
OutputCommitMapping, SendTXArgs, VersionInfo,
|
OutputCommitMapping, SendTXArgs, VersionInfo,
|
||||||
};
|
};
|
||||||
pub use internal::scan::scan;
|
pub use internal::scan::scan;
|
||||||
|
pub use slate_versions::ser as dalek_ser;
|
||||||
pub use types::{
|
pub use types::{
|
||||||
AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData,
|
AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData,
|
||||||
OutputStatus, ScannedBlockInfo, TxLogEntry, TxLogEntryType, TxWrapper, WalletBackend,
|
OutputStatus, ScannedBlockInfo, StoredProofInfo, TxLogEntry, TxLogEntryType, TxWrapper,
|
||||||
WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider, WalletOutputBatch,
|
WalletBackend, WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider, WalletOutputBatch,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Helper for taking a lock on the wallet instance
|
/// 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::grin_util::{self, secp, RwLock};
|
||||||
use crate::slate_versions::ser as dalek_ser;
|
use crate::slate_versions::ser as dalek_ser;
|
||||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
|
use ed25519_dalek::Signature as DalekSignature;
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
use rand::rngs::mock::StepRng;
|
use rand::rngs::mock::StepRng;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
|
@ -52,10 +53,10 @@ use crate::types::CbData;
|
||||||
pub struct PaymentInfo {
|
pub struct PaymentInfo {
|
||||||
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
||||||
pub sender_address: DalekPublicKey,
|
pub sender_address: DalekPublicKey,
|
||||||
#[serde(with = "dalek_ser::option_dalek_pubkey_serde")]
|
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
||||||
pub receiver_address: Option<DalekPublicKey>,
|
pub receiver_address: DalekPublicKey,
|
||||||
#[serde(with = "secp_ser::option_sig_serde")]
|
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
|
||||||
pub receiver_signature: Option<Signature>,
|
pub receiver_signature: Option<DalekSignature>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Public data for each participant in the slate
|
/// 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
|
// Test serialization methods of components that are being used
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
@ -87,8 +127,10 @@ mod test {
|
||||||
use rand::rngs::mock::StepRng;
|
use rand::rngs::mock::StepRng;
|
||||||
|
|
||||||
use crate::grin_util::{secp, static_secp_instance};
|
use crate::grin_util::{secp, static_secp_instance};
|
||||||
|
use ed25519_dalek::Keypair;
|
||||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
use ed25519_dalek::SecretKey as DalekSecretKey;
|
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||||
|
use ed25519_dalek::Signature as DalekSignature;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
@ -99,6 +141,8 @@ mod test {
|
||||||
pub pub_key: DalekPublicKey,
|
pub pub_key: DalekPublicKey,
|
||||||
#[serde(with = "option_dalek_pubkey_serde")]
|
#[serde(with = "option_dalek_pubkey_serde")]
|
||||||
pub pub_key_opt: Option<DalekPublicKey>,
|
pub pub_key_opt: Option<DalekPublicKey>,
|
||||||
|
#[serde(with = "option_dalek_sig_serde")]
|
||||||
|
pub sig_opt: Option<DalekSignature>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SerTest {
|
impl SerTest {
|
||||||
|
@ -109,9 +153,19 @@ mod test {
|
||||||
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
||||||
let d_skey = DalekSecretKey::from_bytes(&sec_key.0).unwrap();
|
let d_skey = DalekSecretKey::from_bytes(&sec_key.0).unwrap();
|
||||||
let d_pub_key: DalekPublicKey = (&d_skey).into();
|
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 {
|
SerTest {
|
||||||
pub_key: d_pub_key.clone(),
|
pub_key: d_pub_key.clone(),
|
||||||
pub_key_opt: Some(d_pub_key),
|
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::CompatKernelFeatures;
|
||||||
use crate::slate_versions::ser as dalek_ser;
|
use crate::slate_versions::ser as dalek_ser;
|
||||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
|
use ed25519_dalek::Signature as DalekSignature;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
@ -105,10 +106,10 @@ pub struct ParticipantDataV3 {
|
||||||
pub struct PaymentInfoV3 {
|
pub struct PaymentInfoV3 {
|
||||||
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
||||||
pub sender_address: DalekPublicKey,
|
pub sender_address: DalekPublicKey,
|
||||||
#[serde(with = "dalek_ser::option_dalek_pubkey_serde")]
|
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
||||||
pub receiver_address: Option<DalekPublicKey>,
|
pub receiver_address: DalekPublicKey,
|
||||||
#[serde(with = "secp_ser::option_sig_serde")]
|
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
|
||||||
pub receiver_signature: Option<Signature>,
|
pub receiver_signature: Option<DalekSignature>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A transaction
|
/// 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::secp::{self, pedersen, Secp256k1};
|
||||||
use crate::grin_util::{LoggingConfig, ZeroingString};
|
use crate::grin_util::{LoggingConfig, ZeroingString};
|
||||||
use crate::slate::ParticipantMessages;
|
use crate::slate::ParticipantMessages;
|
||||||
|
use crate::slate_versions::ser as dalek_ser;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
|
use ed25519_dalek::Signature as DalekSignature;
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
use serde;
|
use serde;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
@ -546,6 +549,8 @@ pub struct Context {
|
||||||
pub fee: u64,
|
pub fee: u64,
|
||||||
/// keep track of the participant id
|
/// keep track of the participant id
|
||||||
pub participant_id: usize,
|
pub participant_id: usize,
|
||||||
|
/// Payment proof sender address derivation path, if needed
|
||||||
|
pub payment_proof_derivation_index: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
|
@ -569,6 +574,7 @@ impl Context {
|
||||||
output_ids: vec![],
|
output_ids: vec![],
|
||||||
fee: 0,
|
fee: 0,
|
||||||
participant_id: participant_id,
|
participant_id: participant_id,
|
||||||
|
payment_proof_derivation_index: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -784,6 +790,9 @@ pub struct TxLogEntry {
|
||||||
/// of kernel is necessary
|
/// of kernel is necessary
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub kernel_lookup_min_height: Option<u64>,
|
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 {
|
impl ser::Writeable for TxLogEntry {
|
||||||
|
@ -819,6 +828,7 @@ impl TxLogEntry {
|
||||||
stored_tx: None,
|
stored_tx: None,
|
||||||
kernel_excess: None,
|
kernel_excess: None,
|
||||||
kernel_lookup_min_height: 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
|
/// Map of named accounts to BIP32 paths
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct AcctPathMapping {
|
pub struct AcctPathMapping {
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"selection_strategy_is_use_all": true,
|
"selection_strategy_is_use_all": true,
|
||||||
"message": "my message",
|
"message": "my message",
|
||||||
"target_slate_version": null,
|
"target_slate_version": null,
|
||||||
|
"payment_proof_recipient_address": null,
|
||||||
"send_args": null
|
"send_args": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
"selection_strategy_is_use_all": true,
|
"selection_strategy_is_use_all": true,
|
||||||
"message": "my message",
|
"message": "my message",
|
||||||
"target_slate_version": null,
|
"target_slate_version": null,
|
||||||
|
"payment_proof_recipient_address": null,
|
||||||
"send_args": null
|
"send_args": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue