mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-01-21 03:21:08 +03:00
[WIP] [Contracts] Early payment proofs (#681)
* add types and beginnings of signature utils * add proof serialization * serialisation of proof data + signature operation * add serialization type for invoice proof + separate bin wrapper version * add witness data + serializion to invoice payment proof, insert verfication functions in place in order to begin verification testing * tests and infrastructure in place for validation * verification of promise sig * added verification of promise signature, infrastructure up to the point where a signature must be subtracted * attempting to figure out differences between recipient nonce that's getting stored and calculated recipient nonce * implementation of witness verification function, retrieve relevant values and re-validate derived recipient partial signature * move stored portion of invoice proof into core types for storage, need to rename invoice proof * define/refine the stored portion of payment proofs type 2? * Folding all proof data into tx log entry storage * back to importing master * remove cargo files from diffs * remove a lot of extra debug output * return proof witness as part of proof retrieval, define json serialization of invoice proof + witness fields * finish adding verification steps to foreign API * remove redundant promise sig field * move lcation of sign/verify calls * Replace Azure Pipelines with Github Actions (#688) * Update CI Badge on README.MD (#690) * Trigger CI on push and pull request (#693) * Update versioning to 5.2.0-beta.1 against grin 5.2.0-beta.3 (#691) * update versioning to 5.2.0-beta.1 against grin 5.2.0-beta.3 * tweak for CI trigger --------- Co-authored-by: Quentin Le Sceller <q.lesceller@gmail.com> --------- Co-authored-by: Quentin Le Sceller <q.lesceller@gmail.com>
This commit is contained in:
parent
58659d85fe
commit
e3148d0305
21 changed files with 1126 additions and 24 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -747,6 +747,8 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.13.4"
|
version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
@ -782,6 +784,7 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
>>>>>>> contracts
|
||||||
name = "dashmap"
|
name = "dashmap"
|
||||||
version = "5.5.0"
|
version = "5.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
@ -1703,7 +1706,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
|
||||||
"sha2 0.8.2",
|
"sha2 0.8.2",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
|
@ -2035,12 +2037,6 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ident_case"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -3796,6 +3792,8 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "1.14.0"
|
version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
@ -3819,6 +3817,7 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
>>>>>>> contracts
|
||||||
name = "serde_yaml"
|
name = "serde_yaml"
|
||||||
version = "0.8.26"
|
version = "0.8.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
use crate::config::TorConfig;
|
use crate::config::TorConfig;
|
||||||
use crate::keychain::Keychain;
|
use crate::keychain::Keychain;
|
||||||
use crate::libwallet::api_impl::foreign;
|
use crate::libwallet::api_impl::foreign;
|
||||||
|
use crate::libwallet::contract::proofs::InvoiceProof;
|
||||||
use crate::libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
use crate::libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||||
use crate::libwallet::{
|
use crate::libwallet::{
|
||||||
BlockFees, CbData, Error, NodeClient, NodeVersionInfo, Slate, VersionInfo, WalletInst,
|
BlockFees, CbData, Error, NodeClient, NodeVersionInfo, Slate, VersionInfo, WalletInst,
|
||||||
|
@ -25,6 +26,7 @@ use crate::libwallet::{
|
||||||
use crate::try_slatepack_sync_workflow;
|
use crate::try_slatepack_sync_workflow;
|
||||||
use crate::util::secp::key::SecretKey;
|
use crate::util::secp::key::SecretKey;
|
||||||
use crate::util::Mutex;
|
use crate::util::Mutex;
|
||||||
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// ForeignAPI Middleware Check callback
|
/// ForeignAPI Middleware Check callback
|
||||||
|
@ -477,6 +479,18 @@ where
|
||||||
let w = w_lock.lc_provider()?.wallet_inst()?;
|
let w = w_lock.lc_provider()?.wallet_inst()?;
|
||||||
foreign::contract_sign(&mut **w, keychain_mask, &args, &slate)
|
foreign::contract_sign(&mut **w, keychain_mask, &args, &slate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
pub fn verify_payment_proof_invoice(
|
||||||
|
&self,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
recipient_address: &DalekPublicKey,
|
||||||
|
proof: &InvoiceProof,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut w_lock = self.wallet_inst.lock();
|
||||||
|
let w = w_lock.lc_provider()?.wallet_inst()?;
|
||||||
|
foreign::verify_payment_proof_invoice(&mut **w, keychain_mask, recipient_address, proof)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use ed25519_dalek::SecretKey as DalekSecretKey;
|
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||||
|
use grin_wallet_libwallet::contract::proofs::InvoiceProof;
|
||||||
use grin_wallet_libwallet::RetrieveTxQueryArgs;
|
use grin_wallet_libwallet::RetrieveTxQueryArgs;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -806,6 +807,17 @@ where
|
||||||
owner::contract_sign(&mut **w, keychain_mask, &args, &slate)
|
owner::contract_sign(&mut **w, keychain_mask, &args, &slate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
pub fn get_slate_index_matching_my_context(
|
||||||
|
&self,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
slate: &Slate,
|
||||||
|
) -> Result<usize, Error> {
|
||||||
|
let mut w_lock = self.wallet_inst.lock();
|
||||||
|
let w = w_lock.lc_provider()?.wallet_inst()?;
|
||||||
|
owner::get_slate_index_matching_my_context(&mut **w, keychain_mask, &slate)
|
||||||
|
}
|
||||||
|
|
||||||
/// TODO
|
/// TODO
|
||||||
pub fn contract_revoke(
|
pub fn contract_revoke(
|
||||||
&self,
|
&self,
|
||||||
|
@ -2406,6 +2418,32 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TODO: Temporary, likely should merge with above
|
||||||
|
pub fn retrieve_payment_proof_invoice(
|
||||||
|
&self,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
refresh_from_node: bool,
|
||||||
|
tx_id: Option<u32>,
|
||||||
|
tx_slate_id: Option<Uuid>,
|
||||||
|
) -> Result<InvoiceProof, Error> {
|
||||||
|
let tx = {
|
||||||
|
let t = self.status_tx.lock();
|
||||||
|
t.clone()
|
||||||
|
};
|
||||||
|
let refresh_from_node = match self.updater_running.load(Ordering::Relaxed) {
|
||||||
|
true => false,
|
||||||
|
false => refresh_from_node,
|
||||||
|
};
|
||||||
|
owner::retrieve_payment_proof_invoice(
|
||||||
|
self.wallet_inst.clone(),
|
||||||
|
keychain_mask,
|
||||||
|
&tx,
|
||||||
|
refresh_from_node,
|
||||||
|
tx_id,
|
||||||
|
tx_slate_id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Verifies a [PaymentProof](../grin_wallet_libwallet/api_impl/types/struct.PaymentProof.html)
|
/// Verifies a [PaymentProof](../grin_wallet_libwallet/api_impl/types/struct.PaymentProof.html)
|
||||||
/// This process entails:
|
/// This process entails:
|
||||||
///
|
///
|
||||||
|
|
|
@ -1682,6 +1682,7 @@ impl ContractNewArgs {
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
proof_args: Default::default(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
|
189
controller/tests/contract_early_proofs.rs
Normal file
189
controller/tests/contract_early_proofs.rs
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
// Copyright 2023 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.
|
||||||
|
|
||||||
|
//! Development and testing of early payment proofs, restricted at the moment
|
||||||
|
//! to contract-style transactions for experimental purposes
|
||||||
|
//!
|
||||||
|
//! https://github.com/mimblewimble/grin-rfcs/pull/70
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
|
||||||
|
extern crate grin_wallet_controller as wallet;
|
||||||
|
extern crate grin_wallet_impls as impls;
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
use grin_core::consensus::KERNEL_WEIGHT;
|
||||||
|
use grin_wallet_libwallet as libwallet;
|
||||||
|
|
||||||
|
use grin_util::static_secp_instance;
|
||||||
|
use impls::test_framework::{self};
|
||||||
|
use libwallet::contract::my_fee_contribution;
|
||||||
|
use libwallet::contract::proofs::{InvoiceProof, ProofWitness};
|
||||||
|
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||||
|
use libwallet::{Slate, SlateState, TxLogEntryType};
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod common;
|
||||||
|
use common::{clean_output_dir, create_wallets, setup};
|
||||||
|
|
||||||
|
/// Development + Tests of early payment proof functionality
|
||||||
|
fn contract_early_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||||
|
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
|
||||||
|
let (wallets, chain, stopper, mut bh) =
|
||||||
|
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
|
||||||
|
let send_wallet = wallets[0].0.clone();
|
||||||
|
let send_mask = wallets[0].1.as_ref();
|
||||||
|
let recv_wallet = wallets[1].0.clone();
|
||||||
|
let recv_mask = wallets[1].1.as_ref();
|
||||||
|
|
||||||
|
let mut slate = Slate::blank(0, true); // this gets overriden below
|
||||||
|
|
||||||
|
let mut sender_address = None;
|
||||||
|
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||||
|
// Send wallet inititates a standard transaction with --send=5
|
||||||
|
let args = &ContractNewArgsAPI {
|
||||||
|
setup_args: ContractSetupArgsAPI {
|
||||||
|
net_change: Some(-5_000_000_000),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
slate = api.contract_new(m, args)?;
|
||||||
|
sender_address = Some(api.get_slatepack_address(send_mask, 0)?.pub_key);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
assert_eq!(slate.state, SlateState::Standard1);
|
||||||
|
|
||||||
|
let mut recipient_address = None;
|
||||||
|
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||||
|
// Receive wallet calls --receive=5
|
||||||
|
let mut args = &mut ContractSetupArgsAPI {
|
||||||
|
net_change: Some(5_000_000_000),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
// Note sender address explicity added here
|
||||||
|
args.proof_args.sender_address = sender_address;
|
||||||
|
slate = api.contract_sign(m, &slate, args)?;
|
||||||
|
recipient_address = Some(api.get_slatepack_address(recv_mask, 0)?.pub_key);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
assert_eq!(slate.state, SlateState::Standard2);
|
||||||
|
|
||||||
|
// Send wallet finalizes and posts
|
||||||
|
//let mut sender_part_sig = None;
|
||||||
|
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||||
|
let args = &ContractSetupArgsAPI {
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
slate = api.contract_sign(m, &slate, args)?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
assert_eq!(slate.state, SlateState::Standard3);
|
||||||
|
|
||||||
|
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||||
|
api.post_tx(m, &slate, false)?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
bh += 1;
|
||||||
|
|
||||||
|
let _ =
|
||||||
|
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
|
||||||
|
bh += 3;
|
||||||
|
|
||||||
|
// Assert changes in receive wallet
|
||||||
|
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||||
|
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||||
|
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||||
|
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||||
|
assert!(refreshed);
|
||||||
|
assert_eq!(txs.len(), 5); // 4 mined and 1 received
|
||||||
|
let tx_log = txs[4].clone();
|
||||||
|
assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived);
|
||||||
|
assert_eq!(tx_log.amount_credited, 5_000_000_000);
|
||||||
|
assert_eq!(tx_log.amount_debited, 0);
|
||||||
|
assert_eq!(tx_log.num_inputs, 1);
|
||||||
|
assert_eq!(tx_log.num_outputs, 1);
|
||||||
|
let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?);
|
||||||
|
assert_eq!(tx_log.fee, expected_fees_paid);
|
||||||
|
assert_eq!(
|
||||||
|
wallet_info.amount_currently_spendable,
|
||||||
|
4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Assert changes in send wallet
|
||||||
|
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||||
|
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||||
|
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||||
|
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||||
|
assert!(refreshed);
|
||||||
|
assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx
|
||||||
|
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
|
||||||
|
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent);
|
||||||
|
assert_eq!(tx_log.amount_credited, 0);
|
||||||
|
assert_eq!(tx_log.amount_debited, 5_000_000_000);
|
||||||
|
assert_eq!(tx_log.num_inputs, 1);
|
||||||
|
assert_eq!(tx_log.num_outputs, 1);
|
||||||
|
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?));
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut invoice_proof = None;
|
||||||
|
// Now some time has passed, sender retrieves and verify the payment proof
|
||||||
|
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||||
|
// Extract the stored data as an invoice proof
|
||||||
|
invoice_proof =
|
||||||
|
Some(api.retrieve_payment_proof_invoice(send_mask, true, None, Some(slate.id))?);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let invoice_proof = invoice_proof.unwrap();
|
||||||
|
let invoice_proof_json = serde_json::to_string(&invoice_proof).unwrap();
|
||||||
|
|
||||||
|
// Should have all proof fields filled out
|
||||||
|
println!("INVOICE PROOF: {}", invoice_proof_json);
|
||||||
|
|
||||||
|
wallet::controller::foreign_single_use(recv_wallet.clone(), recv_mask.cloned(), |api| {
|
||||||
|
let mut proof = serde_json::from_str(&invoice_proof_json).unwrap();
|
||||||
|
api.verify_payment_proof_invoice(recv_mask, recipient_address.as_ref().unwrap(), &proof)?;
|
||||||
|
// tweak something and it shouldn't verify
|
||||||
|
proof.amount = 400000;
|
||||||
|
let retval = api.verify_payment_proof_invoice(
|
||||||
|
recv_mask,
|
||||||
|
recipient_address.as_ref().unwrap(),
|
||||||
|
&proof,
|
||||||
|
);
|
||||||
|
assert!(retval.is_err());
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// let logging finish
|
||||||
|
stopper.store(false, Ordering::Relaxed);
|
||||||
|
thread::sleep(Duration::from_millis(200));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn contract_early_proofs() -> Result<(), libwallet::Error> {
|
||||||
|
let test_dir = "test_output/contract_early_proofs";
|
||||||
|
setup(test_dir);
|
||||||
|
contract_early_proofs_test_impl(test_dir)?;
|
||||||
|
clean_output_dir(test_dir);
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -13,12 +13,14 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//! Generic implementation of owner API functions
|
//! Generic implementation of owner API functions
|
||||||
|
use grin_util::secp::pedersen::Commitment;
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use crate::api_impl::owner::contract_new as owner_contract_new;
|
use crate::api_impl::owner::contract_new as owner_contract_new;
|
||||||
use crate::api_impl::owner::contract_sign as owner_contract_sign;
|
use crate::api_impl::owner::contract_sign as owner_contract_sign;
|
||||||
use crate::api_impl::owner::finalize_tx as owner_finalize;
|
use crate::api_impl::owner::finalize_tx as owner_finalize;
|
||||||
use crate::api_impl::owner::{check_ttl, post_tx};
|
use crate::api_impl::owner::{check_ttl, post_tx};
|
||||||
|
use crate::contract::proofs::InvoiceProof;
|
||||||
use crate::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
use crate::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||||
use crate::grin_core::core::FeeFields;
|
use crate::grin_core::core::FeeFields;
|
||||||
use crate::grin_keychain::Keychain;
|
use crate::grin_keychain::Keychain;
|
||||||
|
@ -29,6 +31,7 @@ use crate::{
|
||||||
address, BlockFees, CbData, Error, NodeClient, Slate, SlateState, TxLogEntryType, VersionInfo,
|
address, BlockFees, CbData, Error, NodeClient, Slate, SlateState, TxLogEntryType, VersionInfo,
|
||||||
WalletBackend,
|
WalletBackend,
|
||||||
};
|
};
|
||||||
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
|
|
||||||
const FOREIGN_API_VERSION: u16 = 2;
|
const FOREIGN_API_VERSION: u16 = 2;
|
||||||
|
|
||||||
|
@ -223,3 +226,56 @@ where
|
||||||
}
|
}
|
||||||
owner_contract_sign(&mut *w, keychain_mask, args, slate)
|
owner_contract_sign(&mut *w, keychain_mask, args, slate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify an invoice payment proof
|
||||||
|
pub fn verify_payment_proof_invoice<'a, T: ?Sized, C, K>(
|
||||||
|
w: &mut T,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
recipient_address: &DalekPublicKey,
|
||||||
|
proof: &InvoiceProof,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: WalletBackend<'a, C, K>,
|
||||||
|
C: NodeClient + 'a,
|
||||||
|
K: Keychain + 'a,
|
||||||
|
{
|
||||||
|
let (mut client, parent_key_id, keychain) = {
|
||||||
|
(
|
||||||
|
w.w2n_client().clone(),
|
||||||
|
w.parent_key_id(),
|
||||||
|
w.keychain(keychain_mask)?,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let wd = match proof.witness_data.clone() {
|
||||||
|
Some(w) => w,
|
||||||
|
None => {
|
||||||
|
return Err(Error::PaymentProof(format!(
|
||||||
|
"Cannot verify invoice proof with no witness data",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (retrieved_kernel, index) = match client.get_kernel(&wd.kernel_commitment, None, None) {
|
||||||
|
Err(e) => {
|
||||||
|
return Err(Error::PaymentProof(format!(
|
||||||
|
"Error retrieving kernel from chain: {}",
|
||||||
|
e
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
return Err(Error::PaymentProof(format!(
|
||||||
|
"Transaction kernel with excess {:?} not found on chain",
|
||||||
|
wd.kernel_commitment
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(Some((k, _, index))) => (k, index),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now verify with retrieved data
|
||||||
|
proof.verify_witness(
|
||||||
|
recipient_address,
|
||||||
|
&retrieved_kernel.excess_sig,
|
||||||
|
&retrieved_kernel.msg_to_sign()?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::contract::proofs::{InvoiceProof, ProofWitness};
|
||||||
use crate::grin_core::core::hash::Hashed;
|
use crate::grin_core::core::hash::Hashed;
|
||||||
use crate::grin_core::core::{Output, OutputFeatures, Transaction};
|
use crate::grin_core::core::{Output, OutputFeatures, Transaction};
|
||||||
use crate::grin_core::libtx::proof;
|
use crate::grin_core::libtx::proof;
|
||||||
|
@ -44,6 +45,7 @@ use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||||
use ed25519_dalek::Verifier;
|
use ed25519_dalek::Verifier;
|
||||||
|
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
use std::ops::Index;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -474,6 +476,145 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve invoice payment proof
|
||||||
|
/// TODO: Need to unify with legacy above
|
||||||
|
pub fn retrieve_payment_proof_invoice<'a, L, C, K>(
|
||||||
|
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
status_send_channel: &Option<Sender<StatusMessage>>,
|
||||||
|
refresh_from_node: bool,
|
||||||
|
tx_id: Option<u32>,
|
||||||
|
tx_slate_id: Option<Uuid>,
|
||||||
|
) -> Result<InvoiceProof, Error>
|
||||||
|
where
|
||||||
|
L: WalletLCProvider<'a, C, K>,
|
||||||
|
C: NodeClient + 'a,
|
||||||
|
K: Keychain + 'a,
|
||||||
|
{
|
||||||
|
if tx_id.is_none() && tx_slate_id.is_none() {
|
||||||
|
return Err(Error::PaymentProofRetrieval(
|
||||||
|
"Transaction ID or Slate UUID must be specified".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if refresh_from_node {
|
||||||
|
update_wallet_state(
|
||||||
|
wallet_inst.clone(),
|
||||||
|
keychain_mask,
|
||||||
|
status_send_channel,
|
||||||
|
false,
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let txs = retrieve_txs(
|
||||||
|
wallet_inst.clone(),
|
||||||
|
keychain_mask,
|
||||||
|
status_send_channel,
|
||||||
|
refresh_from_node,
|
||||||
|
tx_id,
|
||||||
|
tx_slate_id,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
if txs.1.len() != 1 {
|
||||||
|
return Err(Error::PaymentProofRetrieval(
|
||||||
|
"Transaction doesn't exist".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// Pull out all needed fields, returning an error if they're not present
|
||||||
|
let tx = txs.1[0].clone();
|
||||||
|
let amount = if tx.amount_credited >= tx.amount_debited {
|
||||||
|
tx.amount_credited - tx.amount_debited
|
||||||
|
} else {
|
||||||
|
// TODO: Invoice proof not expecting fee included here
|
||||||
|
tx.amount_debited - tx.amount_credited
|
||||||
|
};
|
||||||
|
|
||||||
|
let (mut proof, sender_part_sig) = match tx.payment_proof {
|
||||||
|
Some(p) => {
|
||||||
|
if p.receiver_public_nonce.is_none() {
|
||||||
|
return Err(Error::PaymentProofRetrieval(
|
||||||
|
"Invoice Proof requires stored receiver public nonce".into(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
if p.receiver_public_excess.is_none() {
|
||||||
|
return Err(Error::PaymentProofRetrieval(
|
||||||
|
"Invoice Proof requires stored receiver public excess".into(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
if p.timestamp.is_none() {
|
||||||
|
return Err(Error::PaymentProofRetrieval(
|
||||||
|
"Invoice Proof requires stored timestamp".into(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
if p.sender_part_sig.is_none() {
|
||||||
|
return Err(Error::PaymentProofRetrieval(
|
||||||
|
"Invoice Proof requires stored sender partial signature".into(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
InvoiceProof {
|
||||||
|
proof_type: if let Some(t) = p.proof_type { t } else { 1u8 },
|
||||||
|
amount,
|
||||||
|
receiver_public_nonce: p.receiver_public_nonce.unwrap(),
|
||||||
|
receiver_public_excess: p.receiver_public_excess.unwrap(),
|
||||||
|
sender_address: p.sender_address,
|
||||||
|
timestamp: p.timestamp.unwrap().timestamp(),
|
||||||
|
memo: p.memo,
|
||||||
|
promise_signature: p.promise_signature,
|
||||||
|
witness_data: None,
|
||||||
|
},
|
||||||
|
p.sender_part_sig.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(Error::PaymentProofRetrieval(
|
||||||
|
"Transaction does not contain a payment proof".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now to kernel lookup, to fill in the witness data
|
||||||
|
// Check kernel exists
|
||||||
|
let mut client = {
|
||||||
|
wallet_lock!(wallet_inst, w);
|
||||||
|
w.w2n_client().clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let kernel_excess = match tx.kernel_excess {
|
||||||
|
Some(k) => k,
|
||||||
|
None => {
|
||||||
|
return Err(Error::PaymentProofRetrieval(format!(
|
||||||
|
"Invoice proof transaction kernel excess missing",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (retrieved_kernel, index) = match client.get_kernel(&kernel_excess, None, None) {
|
||||||
|
Err(e) => {
|
||||||
|
return Err(Error::PaymentProof(format!(
|
||||||
|
"Error retrieving kernel from chain: {}",
|
||||||
|
e
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
return Err(Error::PaymentProof(format!(
|
||||||
|
"Transaction kernel with excess {:?} not found on chain",
|
||||||
|
kernel_excess
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(Some((k, _, index))) => (k, index),
|
||||||
|
};
|
||||||
|
|
||||||
|
proof.witness_data = Some(ProofWitness {
|
||||||
|
kernel_index: index,
|
||||||
|
kernel_commitment: retrieved_kernel.excess,
|
||||||
|
sender_partial_sig: sender_part_sig,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(proof)
|
||||||
|
}
|
||||||
|
|
||||||
/// Initiate tx as sender
|
/// Initiate tx as sender
|
||||||
pub fn init_send_tx<'a, T: ?Sized, C, K>(
|
pub fn init_send_tx<'a, T: ?Sized, C, K>(
|
||||||
w: &mut T,
|
w: &mut T,
|
||||||
|
@ -1494,3 +1635,20 @@ where
|
||||||
{
|
{
|
||||||
contract::revoke(&mut *w, keychain_mask, &args)
|
contract::revoke(&mut *w, keychain_mask, &args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Revoke transaction contract
|
||||||
|
pub fn get_slate_index_matching_my_context<'a, T: ?Sized, C, K>(
|
||||||
|
w: &mut T,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
slate: &Slate,
|
||||||
|
// use_test_rng: bool,
|
||||||
|
) -> Result<usize, Error>
|
||||||
|
where
|
||||||
|
T: WalletBackend<'a, C, K>,
|
||||||
|
C: NodeClient + 'a,
|
||||||
|
K: Keychain + 'a,
|
||||||
|
{
|
||||||
|
let keychain = w.keychain(keychain_mask)?;
|
||||||
|
let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?;
|
||||||
|
slate.find_index_matching_context(&keychain, &context)
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
//! Implementation of contract revoke
|
//! Implementation of contract revoke
|
||||||
|
|
||||||
|
use std::default;
|
||||||
|
|
||||||
use crate::contract::types::{ContractRevokeArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs};
|
use crate::contract::types::{ContractRevokeArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs};
|
||||||
use crate::contract::{new, sign};
|
use crate::contract::{new, sign};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
@ -78,6 +80,7 @@ where
|
||||||
use_inputs: Some(String::from(input_commit)),
|
use_inputs: Some(String::from(input_commit)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
proof_args: Default::default(),
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
let finished_slate = sign(
|
let finished_slate = sign(
|
||||||
|
@ -94,6 +97,7 @@ where
|
||||||
use_inputs: Some(String::from(input_commit)),
|
use_inputs: Some(String::from(input_commit)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
proof_args: Default::default(),
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
// TODO: Think about what to do with transaction context of the cancelled slate. It should probably get deleted.
|
// TODO: Think about what to do with transaction context of the cancelled slate. It should probably get deleted.
|
||||||
|
|
|
@ -75,7 +75,14 @@ where
|
||||||
|
|
||||||
// Add keys and payment proof to slate (both are idempotent operations)
|
// Add keys and payment proof to slate (both are idempotent operations)
|
||||||
contract::slate::add_keys(&mut sl, &w.keychain(keychain_mask)?, &mut context)?;
|
contract::slate::add_keys(&mut sl, &w.keychain(keychain_mask)?, &mut context)?;
|
||||||
contract::slate::add_payment_proof(&mut sl)?; // noop for the sender
|
contract::slate::add_payment_proof(
|
||||||
|
w,
|
||||||
|
&mut sl,
|
||||||
|
keychain_mask,
|
||||||
|
&mut context,
|
||||||
|
&setup_args.net_change,
|
||||||
|
&setup_args.proof_args,
|
||||||
|
)?; // noop for the sender
|
||||||
|
|
||||||
// Add inputs/outputs to the Context if needed. No locking is done here. This happens at save_step.
|
// Add inputs/outputs to the Context if needed. No locking is done here. This happens at save_step.
|
||||||
if setup_args.add_outputs {
|
if setup_args.add_outputs {
|
||||||
|
|
|
@ -78,7 +78,10 @@ where
|
||||||
let (mut sl, mut context) = setup::compute(w, keychain_mask, &mut sl, &setup_args)?;
|
let (mut sl, mut context) = setup::compute(w, keychain_mask, &mut sl, &setup_args)?;
|
||||||
// Add outputs to the slate, verify the payment proof and sign the slate
|
// Add outputs to the slate, verify the payment proof and sign the slate
|
||||||
contract::slate::add_outputs(w, keychain_mask, &mut sl, &context)?;
|
contract::slate::add_outputs(w, keychain_mask, &mut sl, &context)?;
|
||||||
contract::slate::verify_payment_proof(&sl)?; // noop for the receiver
|
if let Some(ref p) = sl.payment_proof {
|
||||||
|
contract::slate::verify_payment_proof(&sl, expected_net_change, &p.receiver_address)?;
|
||||||
|
// noop for the receiver
|
||||||
|
}
|
||||||
contract::slate::sign(w, keychain_mask, &mut sl, &mut context)?;
|
contract::slate::sign(w, keychain_mask, &mut sl, &mut context)?;
|
||||||
contract::slate::transition_state(&mut sl)?;
|
contract::slate::transition_state(&mut sl)?;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
mod actions;
|
mod actions;
|
||||||
mod context;
|
mod context;
|
||||||
|
pub mod proofs;
|
||||||
mod selection;
|
mod selection;
|
||||||
mod slate;
|
mod slate;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
443
libwallet/src/contract/proofs.rs
Normal file
443
libwallet/src/contract/proofs.rs
Normal file
|
@ -0,0 +1,443 @@
|
||||||
|
// Copyright 2023 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.
|
||||||
|
|
||||||
|
//! Experimental early payment proof functionality, currently only used
|
||||||
|
//! with contracts. Can move outside of this module if early proofs are adopted
|
||||||
|
//! by legacy transactions
|
||||||
|
|
||||||
|
use crate::contract::types::ProofArgs;
|
||||||
|
use crate::grin_core::libtx::aggsig;
|
||||||
|
use crate::grin_core::libtx::secp_ser;
|
||||||
|
use crate::grin_core::ser as grin_ser;
|
||||||
|
use crate::grin_core::ser::{Readable, Reader, Writeable, Writer};
|
||||||
|
use crate::grin_keychain::Keychain;
|
||||||
|
use crate::grin_util::secp::key::{PublicKey, SecretKey};
|
||||||
|
use crate::grin_util::secp::pedersen::Commitment;
|
||||||
|
use crate::grin_util::secp::Signature;
|
||||||
|
use crate::grin_util::static_secp_instance;
|
||||||
|
use crate::slate::{PaymentInfo, PaymentMemo, Slate};
|
||||||
|
use crate::slate_versions::ser as dalek_ser;
|
||||||
|
use crate::types::{Context, NodeClient, WalletBackend};
|
||||||
|
use crate::{address, Error};
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
|
use ed25519_dalek::Keypair as DalekKeypair;
|
||||||
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
|
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||||
|
use ed25519_dalek::Signature as DalekSignature;
|
||||||
|
use ed25519_dalek::{Signer, Verifier};
|
||||||
|
use grin_util::secp::Message;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ProofWitness {
|
||||||
|
/// Kernel index, supplied so verifiers can look up kernel
|
||||||
|
/// without an expensive lookup operation
|
||||||
|
#[serde(with = "secp_ser::string_or_u64")]
|
||||||
|
pub kernel_index: u64,
|
||||||
|
/// Kernel commitment, supplied so prover can recompute index
|
||||||
|
/// if required after a reorg
|
||||||
|
#[serde(
|
||||||
|
serialize_with = "secp_ser::as_hex",
|
||||||
|
deserialize_with = "secp_ser::commitment_from_hex"
|
||||||
|
)]
|
||||||
|
pub kernel_commitment: Commitment,
|
||||||
|
/// sender partial signature, used to recover receiver partial signature
|
||||||
|
#[serde(with = "secp_ser::sig_serde")]
|
||||||
|
pub sender_partial_sig: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payment proof, to be extracted from slates for
|
||||||
|
// signing (when wrapped as PaymentProofBin) or json export from stored tx data
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct InvoiceProof {
|
||||||
|
/// Proof type, 0x00 legacy (though this will use StoredProofInfo above, 1 invoice, 2 Sender nonce)
|
||||||
|
pub proof_type: u8,
|
||||||
|
/// amount
|
||||||
|
#[serde(with = "secp_ser::string_or_u64")]
|
||||||
|
pub amount: u64,
|
||||||
|
/// receiver's public nonce from signing
|
||||||
|
#[serde(with = "secp_ser::pubkey_serde")]
|
||||||
|
pub receiver_public_nonce: PublicKey,
|
||||||
|
/// receiver's public excess from signing
|
||||||
|
#[serde(with = "secp_ser::pubkey_serde")]
|
||||||
|
pub receiver_public_excess: PublicKey,
|
||||||
|
/// Sender's address
|
||||||
|
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
|
||||||
|
pub sender_address: DalekPublicKey,
|
||||||
|
/// Timestamp provided by recipient when signing
|
||||||
|
pub timestamp: i64,
|
||||||
|
/// Optional payment memo
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub memo: Option<PaymentMemo>,
|
||||||
|
/// Not serialized in binary format
|
||||||
|
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
|
||||||
|
pub promise_signature: Option<DalekSignature>,
|
||||||
|
/// Not serialized in binary format, just a convenient place to insert
|
||||||
|
/// the witness kernel commitment index
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub witness_data: Option<ProofWitness>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InvoiceProofBin(InvoiceProof);
|
||||||
|
|
||||||
|
impl Writeable for InvoiceProofBin {
|
||||||
|
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
|
||||||
|
writer.write_u8(1)?;
|
||||||
|
|
||||||
|
// Amount field is 7 bytes, throw error if value is greater
|
||||||
|
let mut amount_bytes = [0; 8];
|
||||||
|
BigEndian::write_u64(&mut amount_bytes, self.0.amount);
|
||||||
|
|
||||||
|
if amount_bytes[0] > 0 {
|
||||||
|
return Err(grin_ser::Error::UnexpectedData {
|
||||||
|
expected: [0u8].to_vec(),
|
||||||
|
received: [amount_bytes[0]].to_vec(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
writer.write_fixed_bytes(amount_bytes[1..].to_vec())?;
|
||||||
|
{
|
||||||
|
let static_secp = static_secp_instance();
|
||||||
|
let static_secp = static_secp.lock();
|
||||||
|
writer.write_fixed_bytes(
|
||||||
|
self.0
|
||||||
|
.receiver_public_nonce
|
||||||
|
.serialize_vec(&static_secp, true),
|
||||||
|
)?;
|
||||||
|
writer.write_fixed_bytes(
|
||||||
|
self.0
|
||||||
|
.receiver_public_excess
|
||||||
|
.serialize_vec(&static_secp, true),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
writer.write_fixed_bytes(self.0.sender_address.as_bytes())?;
|
||||||
|
writer.write_i64(self.0.timestamp)?;
|
||||||
|
match &self.0.memo {
|
||||||
|
Some(s) => {
|
||||||
|
writer.write_u8(s.memo_type)?;
|
||||||
|
writer.write_fixed_bytes(&s.memo.to_vec())?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
writer.write_u8(0)?;
|
||||||
|
writer.write_fixed_bytes([0u8; 32].to_vec())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Not strictly necessary, but useful for tests
|
||||||
|
impl Readable for InvoiceProofBin {
|
||||||
|
fn read<R: Reader>(reader: &mut R) -> Result<InvoiceProofBin, grin_ser::Error> {
|
||||||
|
// first 8 bytes are proof type + 7 bytes worth of amount
|
||||||
|
let mut amount = reader.read_u64()?;
|
||||||
|
let proof_type: u8 = ((amount & 0xFF00_0000_0000_0000) >> 56).try_into().unwrap();
|
||||||
|
amount &= 0x00FF_FFFF_FFFF_FFFF;
|
||||||
|
|
||||||
|
let receiver_public_nonce;
|
||||||
|
let receiver_public_excess;
|
||||||
|
{
|
||||||
|
let static_secp = static_secp_instance();
|
||||||
|
let static_secp = static_secp.lock();
|
||||||
|
receiver_public_nonce =
|
||||||
|
PublicKey::from_slice(&static_secp, &reader.read_fixed_bytes(33)?).unwrap();
|
||||||
|
receiver_public_excess =
|
||||||
|
PublicKey::from_slice(&static_secp, &reader.read_fixed_bytes(33)?).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let sender_address_vec = reader.read_fixed_bytes(32)?;
|
||||||
|
let sender_address = DalekPublicKey::from_bytes(&sender_address_vec).unwrap();
|
||||||
|
|
||||||
|
let timestamp = reader.read_i64()?;
|
||||||
|
|
||||||
|
let memo_type = reader.read_u8()?;
|
||||||
|
let memo = reader.read_fixed_bytes(32)?;
|
||||||
|
let mut memo_bytes: [u8; 32] = [0u8; 32];
|
||||||
|
memo_bytes.copy_from_slice(&memo);
|
||||||
|
|
||||||
|
let res = InvoiceProof {
|
||||||
|
proof_type,
|
||||||
|
amount,
|
||||||
|
receiver_public_nonce,
|
||||||
|
receiver_public_excess,
|
||||||
|
sender_address,
|
||||||
|
timestamp,
|
||||||
|
memo: match memo_type {
|
||||||
|
0 => None,
|
||||||
|
_ => Some(PaymentMemo {
|
||||||
|
memo_type,
|
||||||
|
memo: memo_bytes,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
promise_signature: None,
|
||||||
|
witness_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(InvoiceProofBin(res))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InvoiceProof {
|
||||||
|
/// Extracts as much data as possible from the slate to create an invoice proof
|
||||||
|
pub fn from_slate(
|
||||||
|
slate: &Slate,
|
||||||
|
participant_index: usize,
|
||||||
|
sender_address: Option<DalekPublicKey>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
// Sender address is either provided or in slate (or error)
|
||||||
|
let sender_address = match sender_address {
|
||||||
|
Some(a) => a,
|
||||||
|
None => {
|
||||||
|
if let Some(ref p) = slate.payment_proof {
|
||||||
|
if let Some(a) = p.sender_address {
|
||||||
|
a
|
||||||
|
} else {
|
||||||
|
return Err(Error::NoSenderAddressProvided);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::NoSenderAddressProvided);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let timestamp = match slate.payment_proof.as_ref() {
|
||||||
|
Some(p) => NaiveDateTime::from_timestamp(p.timestamp.timestamp(), 0).timestamp(),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let memo = match slate.payment_proof.as_ref() {
|
||||||
|
Some(p) => p.memo.clone(),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let promise_signature = match slate.payment_proof.as_ref() {
|
||||||
|
Some(p) => p.promise_signature.clone(),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
proof_type: 1u8,
|
||||||
|
amount: slate.amount,
|
||||||
|
receiver_public_nonce: slate.participant_data[participant_index].public_nonce,
|
||||||
|
receiver_public_excess: slate.participant_data[participant_index].public_blind_excess,
|
||||||
|
sender_address,
|
||||||
|
timestamp,
|
||||||
|
memo,
|
||||||
|
promise_signature,
|
||||||
|
witness_data: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign(&self, sec_key: &SecretKey) -> Result<(DalekSignature, DalekPublicKey), Error> {
|
||||||
|
let d_skey = match DalekSecretKey::from_bytes(&sec_key.0) {
|
||||||
|
Ok(k) => k,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(Error::ED25519Key(format!("{}", e)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let pub_key: DalekPublicKey = (&d_skey).into();
|
||||||
|
let keypair = DalekKeypair {
|
||||||
|
public: pub_key,
|
||||||
|
secret: d_skey,
|
||||||
|
};
|
||||||
|
let mut sig_data_bin = Vec::new();
|
||||||
|
let _ = grin_ser::serialize_default(&mut sig_data_bin, &InvoiceProofBin(self.clone()))
|
||||||
|
.expect("serialization failed");
|
||||||
|
|
||||||
|
Ok((keypair.sign(&sig_data_bin), pub_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_promise_signature(
|
||||||
|
&self,
|
||||||
|
recipient_address: &DalekPublicKey,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if self.promise_signature.is_none() {
|
||||||
|
return Err(Error::PaymentProofValidation(
|
||||||
|
"Missing promise signature".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild message
|
||||||
|
let mut sig_data_bin = Vec::new();
|
||||||
|
let _ = grin_ser::serialize_default(&mut sig_data_bin, &InvoiceProofBin(self.clone()))
|
||||||
|
.expect("serialization failed");
|
||||||
|
|
||||||
|
if recipient_address
|
||||||
|
.verify(&sig_data_bin, self.promise_signature.as_ref().unwrap())
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Err(Error::PaymentProof(
|
||||||
|
"Invalid recipient signature".to_owned(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_witness(
|
||||||
|
&self,
|
||||||
|
recipient_address: &DalekPublicKey,
|
||||||
|
excess_sig: &Signature,
|
||||||
|
msg: &Message,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if self.witness_data.is_none() {
|
||||||
|
return Err(Error::PaymentProofValidation("Missing witness data".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.verify_promise_signature(recipient_address)?;
|
||||||
|
|
||||||
|
let wd = self.witness_data.as_ref().unwrap().clone();
|
||||||
|
{
|
||||||
|
let static_secp = static_secp_instance();
|
||||||
|
let static_secp = static_secp.lock();
|
||||||
|
|
||||||
|
let receiver_part_sig =
|
||||||
|
aggsig::subtract_signature(&static_secp, &excess_sig, &wd.sender_partial_sig)?;
|
||||||
|
|
||||||
|
// Retrieve the public nonce sum from the kernel excess signature
|
||||||
|
let mut pub_nonce_sum_bytes = [3u8; 33];
|
||||||
|
pub_nonce_sum_bytes[1..33].copy_from_slice(&excess_sig[0..32]);
|
||||||
|
let pub_nonce_sum = PublicKey::from_slice(&static_secp, &pub_nonce_sum_bytes)?;
|
||||||
|
|
||||||
|
// Retrieve the public key sum from the kernel excess
|
||||||
|
let pub_blind_sum = wd.kernel_commitment.to_pubkey(&static_secp)?;
|
||||||
|
|
||||||
|
if let Err(_) = aggsig::verify_partial_sig(
|
||||||
|
&static_secp,
|
||||||
|
&receiver_part_sig.0,
|
||||||
|
&pub_nonce_sum,
|
||||||
|
&self.receiver_public_excess,
|
||||||
|
Some(&pub_blind_sum),
|
||||||
|
&msg,
|
||||||
|
) {
|
||||||
|
// Try other possibility
|
||||||
|
if let Some(s) = receiver_part_sig.1 {
|
||||||
|
aggsig::verify_partial_sig(
|
||||||
|
&static_secp,
|
||||||
|
&s,
|
||||||
|
&pub_nonce_sum,
|
||||||
|
&self.receiver_public_excess,
|
||||||
|
Some(&pub_blind_sum),
|
||||||
|
&msg,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
return Err(Error::PaymentProofValidation(
|
||||||
|
"Signature subtraction failed".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for InvoiceProofBin {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let mut vec = vec![];
|
||||||
|
grin_ser::serialize(&mut vec, grin_ser::ProtocolVersion(4), self)
|
||||||
|
.map_err(|err| serde::ser::Error::custom(err.to_string()))?;
|
||||||
|
serializer.serialize_bytes(&vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds all info needed for a payment proof to a slate, complete with signed recipient data
|
||||||
|
pub fn add_payment_proof<'a, T: ?Sized, C, K>(
|
||||||
|
wallet: &mut T,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
slate: &mut Slate,
|
||||||
|
context: &Context,
|
||||||
|
proof_args: &ProofArgs,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: WalletBackend<'a, C, K>,
|
||||||
|
C: NodeClient + 'a,
|
||||||
|
K: Keychain + 'a,
|
||||||
|
{
|
||||||
|
// TODO: Just generating invoice (type 1) for now
|
||||||
|
let (invoice_proof, promise_signature, receiver_address) =
|
||||||
|
generate_invoice_signature(wallet, keychain_mask, slate, context, proof_args)?;
|
||||||
|
let timestamp = NaiveDateTime::from_timestamp(Utc::now().timestamp(), 0);
|
||||||
|
let timestamp = DateTime::<Utc>::from_utc(timestamp, Utc);
|
||||||
|
|
||||||
|
let proof = PaymentInfo {
|
||||||
|
sender_address: proof_args.sender_address.clone(),
|
||||||
|
receiver_address,
|
||||||
|
timestamp,
|
||||||
|
promise_signature: Some(promise_signature),
|
||||||
|
memo: invoice_proof.memo,
|
||||||
|
};
|
||||||
|
slate.payment_proof = Some(proof);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a signature for proof type 'Invoice'
|
||||||
|
fn generate_invoice_signature<'a, T: ?Sized, C, K>(
|
||||||
|
wallet: &mut T,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
slate: &mut Slate,
|
||||||
|
context: &Context,
|
||||||
|
proof_args: &ProofArgs,
|
||||||
|
) -> Result<(InvoiceProof, DalekSignature, DalekPublicKey), Error>
|
||||||
|
where
|
||||||
|
T: WalletBackend<'a, C, K>,
|
||||||
|
C: NodeClient + 'a,
|
||||||
|
K: Keychain + 'a,
|
||||||
|
{
|
||||||
|
let keychain = wallet.keychain(keychain_mask)?;
|
||||||
|
let index = slate.find_index_matching_context(&keychain, context)?;
|
||||||
|
let mut invoice_proof = InvoiceProof::from_slate(&slate, index, proof_args.sender_address)?;
|
||||||
|
let derivation_index = match context.payment_proof_derivation_index {
|
||||||
|
Some(i) => i,
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
let parent_key_id = wallet.parent_key_id();
|
||||||
|
let recp_key =
|
||||||
|
address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?;
|
||||||
|
|
||||||
|
invoice_proof.timestamp = NaiveDateTime::from_timestamp(Utc::now().timestamp(), 0).timestamp();
|
||||||
|
let (sig, addr) = invoice_proof.sign(&recp_key)?;
|
||||||
|
Ok((invoice_proof, sig, addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::slate_versions::tests::populate_test_slate;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ser_invoice_proof_bin() -> Result<(), Error> {
|
||||||
|
let mut slate = populate_test_slate()?;
|
||||||
|
slate.amount |= 0xFF00_0000_0000_0000;
|
||||||
|
// Bin serialization doesn't include promise sig as it's used to create signature data
|
||||||
|
slate.payment_proof.as_mut().unwrap().promise_signature = None;
|
||||||
|
|
||||||
|
// Should fail, amount too big
|
||||||
|
let invoice_proof = InvoiceProof::from_slate(&slate, 1, None)?;
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
assert!(grin_ser::serialize_default(&mut vec, &InvoiceProofBin(invoice_proof)).is_err());
|
||||||
|
|
||||||
|
// Should be okay now
|
||||||
|
slate.amount = 1234;
|
||||||
|
let invoice_proof = InvoiceProof::from_slate(&slate, 1, None)?;
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
grin_ser::serialize_default(&mut vec, &InvoiceProofBin(invoice_proof.clone()))
|
||||||
|
.expect("Serialization Failed");
|
||||||
|
|
||||||
|
let proof_deser: InvoiceProofBin = grin_ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||||
|
assert_eq!(invoice_proof, proof_deser.0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,23 +22,54 @@ use crate::slate::{Slate, SlateState};
|
||||||
use crate::types::{Context, NodeClient, WalletBackend};
|
use crate::types::{Context, NodeClient, WalletBackend};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
|
use super::types::ProofArgs;
|
||||||
|
use crate::contract::proofs::InvoiceProof;
|
||||||
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
|
|
||||||
/// The secret key we replace the actual key with after we have signed with the Context keys. This is
|
/// The secret key we replace the actual key with after we have signed with the Context keys. This is
|
||||||
/// to prevent possibility of signing with the same key twice.
|
/// to prevent possibility of signing with the same key twice.
|
||||||
pub const SEC_KEY_FAKE: [u8; 32] = [0; 32];
|
pub const SEC_KEY_FAKE: [u8; 32] = [0; 32];
|
||||||
|
|
||||||
/// Add payment proof data to slate
|
/// Add payment proof data to slate, noop for sender
|
||||||
pub fn add_payment_proof(slate: &mut Slate) -> Result<(), Error> {
|
pub fn add_payment_proof<'a, T: ?Sized, C, K>(
|
||||||
|
w: &mut T,
|
||||||
|
slate: &mut Slate,
|
||||||
|
keychain_mask: Option<&SecretKey>,
|
||||||
|
context: &Context,
|
||||||
|
net_change: &Option<i64>,
|
||||||
|
proof_args: &ProofArgs,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: WalletBackend<'a, C, K>,
|
||||||
|
C: NodeClient + 'a,
|
||||||
|
K: Keychain + 'a,
|
||||||
|
{
|
||||||
// TODO: Implement. Consider adding this function to the Slate itself so they can easily be versioned
|
// TODO: Implement. Consider adding this function to the Slate itself so they can easily be versioned
|
||||||
// e.g. slate.add_payment_proof_data()
|
// e.g. slate.add_payment_proof_data()
|
||||||
debug!("contract::slate::add_payment_proof => called (not implemented yet)");
|
trace!("contract::slate::add_payment_proof => called");
|
||||||
|
// If we're a recipient, generate proof unless explicity told not to
|
||||||
|
if let Some(ref c) = net_change {
|
||||||
|
if *c > 0 && !proof_args.suppress_proof && slate.payment_proof.is_none() {
|
||||||
|
super::proofs::add_payment_proof(w, keychain_mask, slate, &context, proof_args)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify payment proof signature
|
/// Verify payment proof signature
|
||||||
pub fn verify_payment_proof(slate: &Slate) -> Result<(), Error> {
|
pub fn verify_payment_proof(
|
||||||
|
slate: &Slate,
|
||||||
|
net_change: i64,
|
||||||
|
recipient_address: &DalekPublicKey,
|
||||||
|
) -> Result<(), Error> {
|
||||||
// TODO: Implement. Consider adding this function to the Slate itself so they can easily be versioned
|
// TODO: Implement. Consider adding this function to the Slate itself so they can easily be versioned
|
||||||
// e.g. slate.verify_payment_proof_sig()
|
// e.g. slate.verify_payment_proof_sig()
|
||||||
debug!("contract::slate::verify_payment_proof => called (not implemented yet)");
|
debug!("contract::slate::verify_payment_proof => called");
|
||||||
|
if net_change > 0 && slate.payment_proof.is_some() {
|
||||||
|
let invoice_proof = InvoiceProof::from_slate(&slate, 1, None)?;
|
||||||
|
invoice_proof.verify_promise_signature(&recipient_address)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
//! Types related to a contract
|
//! Types related to a contract
|
||||||
|
|
||||||
use crate::grin_core::consensus;
|
use crate::grin_core::consensus;
|
||||||
|
use crate::slate_versions::ser as dalek_ser;
|
||||||
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||||
|
|
||||||
/// Output selection args
|
/// Output selection args
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
|
@ -81,6 +83,42 @@ impl Default for OutputSelectionArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Types of proof that can be generated
|
||||||
|
/// as per https://github.com/tromp/grin-rfcs/blob/early-payment-proofs/text/0000-early-payment-proofs.md
|
||||||
|
/// TODO: Update when RFC is merged
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub enum ProofType {
|
||||||
|
/// Legacy (0x00)
|
||||||
|
Legacy,
|
||||||
|
/// Invoice (0x01, Default)
|
||||||
|
Invoice,
|
||||||
|
/// Sender Nonce (0x02)
|
||||||
|
SenderNonce,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Proof generation parameters that can be provided during new or sign phases
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct ProofArgs {
|
||||||
|
/// If net change is positive during this step, whether to suppress the creation of payment proof
|
||||||
|
pub suppress_proof: bool,
|
||||||
|
/// Type of proof (Default 'Invoice')
|
||||||
|
pub proof_type: ProofType,
|
||||||
|
/// Sender address (required at some stage, may not necessarily be in slate so can be provided explicitly)
|
||||||
|
#[serde(with = "dalek_ser::option_dalek_pubkey_serde")]
|
||||||
|
pub sender_address: Option<DalekPublicKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ProofArgs {
|
||||||
|
fn default() -> ProofArgs {
|
||||||
|
ProofArgs {
|
||||||
|
suppress_proof: false,
|
||||||
|
proof_type: ProofType::Legacy,
|
||||||
|
sender_address: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Contract Setup - defines how we pick inputs/outputs and what we expect from a contract. Both
|
/// Contract Setup - defines how we pick inputs/outputs and what we expect from a contract. Both
|
||||||
/// 'new' and 'sign' actions perform a setup phase which is why their endpoints take these parameters.
|
/// 'new' and 'sign' actions perform a setup phase which is why their endpoints take these parameters.
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
@ -99,6 +137,8 @@ pub struct ContractSetupArgsAPI {
|
||||||
pub add_outputs: bool,
|
pub add_outputs: bool,
|
||||||
/// Output selection arguments
|
/// Output selection arguments
|
||||||
pub selection_args: OutputSelectionArgs,
|
pub selection_args: OutputSelectionArgs,
|
||||||
|
/// Proof arguments
|
||||||
|
pub proof_args: ProofArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ContractSetupArgsAPI {
|
impl Default for ContractSetupArgsAPI {
|
||||||
|
@ -111,6 +151,7 @@ impl Default for ContractSetupArgsAPI {
|
||||||
selection_args: OutputSelectionArgs {
|
selection_args: OutputSelectionArgs {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
proof_args: ProofArgs::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,6 +180,7 @@ impl Default for ContractNewArgsAPI {
|
||||||
selection_args: OutputSelectionArgs {
|
selection_args: OutputSelectionArgs {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
proof_args: ProofArgs::default(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,14 @@ use crate::grin_core::libtx::tx_fee;
|
||||||
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::slate::Slate;
|
use crate::slate::Slate;
|
||||||
use crate::types::{Context, NodeClient, TxLogEntryType, WalletBackend};
|
use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType, WalletBackend};
|
||||||
use crate::{Error, OutputData, OutputStatus, TxLogEntry};
|
use crate::util::OnionV3Address;
|
||||||
|
use crate::{address, Error, OutputData, OutputStatus, TxLogEntry};
|
||||||
use grin_core::core::FeeFields;
|
use grin_core::core::FeeFields;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::proofs::InvoiceProof;
|
||||||
|
|
||||||
/// Creates an initial TxLogEntry without input/output or kernel information
|
/// Creates an initial TxLogEntry without input/output or kernel information
|
||||||
pub fn create_tx_log_entry(
|
pub fn create_tx_log_entry(
|
||||||
slate: &Slate,
|
slate: &Slate,
|
||||||
|
@ -69,6 +72,7 @@ where
|
||||||
{
|
{
|
||||||
// This is expected to be called when we are signing the contract and have already contributed inputs & outputs
|
// This is expected to be called when we are signing the contract and have already contributed inputs & outputs
|
||||||
let keychain = wallet.keychain(keychain_mask)?;
|
let keychain = wallet.keychain(keychain_mask)?;
|
||||||
|
let parent_key_id = context.parent_key_id.clone();
|
||||||
let current_height = wallet.w2n_client().get_chain_tip()?.0;
|
let current_height = wallet.w2n_client().get_chain_tip()?.0;
|
||||||
// We have already contributed inputs and outputs so we know how much of each we contribute
|
// We have already contributed inputs and outputs so we know how much of each we contribute
|
||||||
tx_log_entry.num_outputs = context.output_ids.len();
|
tx_log_entry.num_outputs = context.output_ids.len();
|
||||||
|
@ -81,6 +85,44 @@ where
|
||||||
};
|
};
|
||||||
tx_log_entry.kernel_lookup_min_height = Some(current_height);
|
tx_log_entry.kernel_lookup_min_height = Some(current_height);
|
||||||
|
|
||||||
|
// If we're sending and there's payment proof info in the slate added by recipient, store as well
|
||||||
|
if let Some(ref p) = slate.payment_proof {
|
||||||
|
if tx_log_entry.amount_debited > 0 {
|
||||||
|
// note we only use a single path for now
|
||||||
|
let sender_address_path = 0u32;
|
||||||
|
let sender_key = address::address_from_derivation_path(
|
||||||
|
&keychain,
|
||||||
|
&parent_key_id,
|
||||||
|
sender_address_path,
|
||||||
|
)?;
|
||||||
|
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
|
||||||
|
|
||||||
|
// We're looking for the OTHER party here, the recipient
|
||||||
|
let sender_index = slate.find_index_matching_context(&keychain, context)?;
|
||||||
|
let recipient_index = sender_index ^ 1;
|
||||||
|
|
||||||
|
tx_log_entry.payment_proof = Some(StoredProofInfo {
|
||||||
|
receiver_address: p.receiver_address,
|
||||||
|
receiver_signature: p.promise_signature,
|
||||||
|
sender_address: sender_address.to_ed25519()?,
|
||||||
|
sender_address_path,
|
||||||
|
sender_signature: None,
|
||||||
|
/// TODO: Will fill these as separate steps for now, check whether this
|
||||||
|
/// can be merged in a general case (which means knowing which nonces here belong to
|
||||||
|
/// the recipient)
|
||||||
|
proof_type: Some(1u8),
|
||||||
|
receiver_public_nonce: Some(slate.participant_data[recipient_index].public_nonce),
|
||||||
|
receiver_public_excess: Some(
|
||||||
|
slate.participant_data[recipient_index].public_blind_excess,
|
||||||
|
),
|
||||||
|
timestamp: Some(p.timestamp),
|
||||||
|
memo: p.memo.clone(),
|
||||||
|
promise_signature: p.promise_signature,
|
||||||
|
sender_part_sig: slate.participant_data[sender_index].part_sig,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,6 +271,7 @@ where
|
||||||
batch.lock_output(&mut coin)?;
|
batch.lock_output(&mut coin)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update context
|
// Update context
|
||||||
if is_signed && !is_step2 {
|
if is_signed && !is_step2 {
|
||||||
// NOTE: We MUST forget the context when we sign. Ideally, these two would be atomic or perhaps
|
// NOTE: We MUST forget the context when we sign. Ideally, these two would be atomic or perhaps
|
||||||
|
|
|
@ -249,10 +249,18 @@ pub enum Error {
|
||||||
#[error("Payment Proof parsing error: {0}")]
|
#[error("Payment Proof parsing error: {0}")]
|
||||||
PaymentProofParsing(String),
|
PaymentProofParsing(String),
|
||||||
|
|
||||||
|
/// Retrieving Payment Proof
|
||||||
|
#[error("Unable to verify payment proof: {0}")]
|
||||||
|
PaymentProofValidation(String),
|
||||||
|
|
||||||
/// Decoding OnionV3 addresses to payment proof addresses
|
/// Decoding OnionV3 addresses to payment proof addresses
|
||||||
#[error("Proof Address decoding: {0}")]
|
#[error("Proof Address decoding: {0}")]
|
||||||
AddressDecoding(String),
|
AddressDecoding(String),
|
||||||
|
|
||||||
|
// Payment proof - no sender address provided or found in slate
|
||||||
|
#[error("Sender address has not been provided")]
|
||||||
|
NoSenderAddressProvided,
|
||||||
|
|
||||||
/// Transaction has expired it's TTL
|
/// Transaction has expired it's TTL
|
||||||
#[error("Transaction Expired")]
|
#[error("Transaction Expired")]
|
||||||
TransactionExpired,
|
TransactionExpired,
|
||||||
|
@ -301,6 +309,10 @@ pub enum Error {
|
||||||
#[error("Stored Tx error: {0}")]
|
#[error("Stored Tx error: {0}")]
|
||||||
StoredTx(String),
|
StoredTx(String),
|
||||||
|
|
||||||
|
/// Trying to match index to context
|
||||||
|
#[error("Cannot match transaction context to slate index")]
|
||||||
|
ContextToIndex,
|
||||||
|
|
||||||
/// Other
|
/// Other
|
||||||
#[error("Generic error: {0}")]
|
#[error("Generic error: {0}")]
|
||||||
GenericError(String),
|
GenericError(String),
|
||||||
|
|
|
@ -203,12 +203,23 @@ where
|
||||||
sender_address_path,
|
sender_address_path,
|
||||||
)?;
|
)?;
|
||||||
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
|
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
|
||||||
|
|
||||||
t.payment_proof = Some(StoredProofInfo {
|
t.payment_proof = Some(StoredProofInfo {
|
||||||
receiver_address: p.receiver_address,
|
receiver_address: p.receiver_address,
|
||||||
receiver_signature: p.promise_signature,
|
receiver_signature: p.promise_signature,
|
||||||
sender_address: sender_address.to_ed25519()?,
|
sender_address: sender_address.to_ed25519()?,
|
||||||
sender_address_path,
|
sender_address_path,
|
||||||
sender_signature: None,
|
sender_signature: None,
|
||||||
|
/// TODO: Will fill these as separate steps for now, check whether this
|
||||||
|
/// can be merged in a general case (which means knowing which nonces here belong to
|
||||||
|
/// the recipient)
|
||||||
|
proof_type: None,
|
||||||
|
receiver_public_nonce: None,
|
||||||
|
receiver_public_excess: None,
|
||||||
|
timestamp: None,
|
||||||
|
memo: None,
|
||||||
|
promise_signature: None,
|
||||||
|
sender_part_sig: None,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -433,12 +433,21 @@ where
|
||||||
address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?;
|
address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?;
|
||||||
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
|
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
|
||||||
let sig = create_payment_proof_signature(slate.amount, &excess, saddr, sender_key)?;
|
let sig = create_payment_proof_signature(slate.amount, &excess, saddr, sender_key)?;
|
||||||
|
|
||||||
tx.payment_proof = Some(StoredProofInfo {
|
tx.payment_proof = Some(StoredProofInfo {
|
||||||
receiver_address: p.receiver_address,
|
receiver_address: p.receiver_address,
|
||||||
receiver_signature: p.promise_signature,
|
receiver_signature: p.promise_signature,
|
||||||
sender_address_path: derivation_index,
|
sender_address_path: derivation_index,
|
||||||
sender_address: sender_address.to_ed25519()?,
|
sender_address: sender_address.to_ed25519()?,
|
||||||
sender_signature: Some(sig),
|
sender_signature: Some(sig),
|
||||||
|
// Filled in during contract flow proofs for now
|
||||||
|
proof_type: None,
|
||||||
|
receiver_public_nonce: None,
|
||||||
|
receiver_public_excess: None,
|
||||||
|
timestamp: None,
|
||||||
|
memo: None,
|
||||||
|
promise_signature: None,
|
||||||
|
sender_part_sig: None,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,11 +48,12 @@ use crate::slate_versions::VersionedSlate;
|
||||||
use crate::slate_versions::{CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION};
|
use crate::slate_versions::{CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION};
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct PaymentMemo {
|
pub struct PaymentMemo {
|
||||||
// The type of memo
|
// The type of memo
|
||||||
// 0x00 is directly embedded additional payment details
|
// 0x00 is the absence of any specific memo data
|
||||||
// 0x01 represents the blake2b hash of an arbitrary invoice document
|
// 0x01 is directly embedded additional payment details
|
||||||
|
// 0x02 represents the blake2b hash of an arbitrary invoice document
|
||||||
pub memo_type: u8,
|
pub memo_type: u8,
|
||||||
// memo data itself
|
// memo data itself
|
||||||
pub memo: [u8; 32],
|
pub memo: [u8; 32],
|
||||||
|
@ -410,6 +411,28 @@ impl Slate {
|
||||||
Ok(msg)
|
Ok(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Matches a participant index on the slate with the stored context
|
||||||
|
pub fn find_index_matching_context<K>(
|
||||||
|
&self,
|
||||||
|
keychain: &K,
|
||||||
|
context: &Context,
|
||||||
|
) -> Result<usize, Error>
|
||||||
|
where
|
||||||
|
K: Keychain,
|
||||||
|
{
|
||||||
|
for i in 0..self.num_participants() as usize {
|
||||||
|
let calc_pub_excess = PublicKey::from_secret_key(keychain.secp(), &context.sec_key)?;
|
||||||
|
let calc_pub_nonce = PublicKey::from_secret_key(keychain.secp(), &context.sec_nonce)?;
|
||||||
|
|
||||||
|
// find my entry
|
||||||
|
if self.participant_data[i].public_blind_excess == calc_pub_excess
|
||||||
|
|| self.participant_data[i].public_nonce == calc_pub_nonce
|
||||||
|
{
|
||||||
|
return Ok(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(Error::ContextToIndex);
|
||||||
|
}
|
||||||
/// Completes caller's part of round 2, completing signatures
|
/// Completes caller's part of round 2, completing signatures
|
||||||
pub fn fill_round_2<K>(
|
pub fn fill_round_2<K>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -953,7 +976,7 @@ impl From<OutputFeatures> for OutputFeaturesV5 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///// V4
|
///// V5
|
||||||
impl From<SlateV5> for Slate {
|
impl From<SlateV5> for Slate {
|
||||||
fn from(slate: SlateV5) -> Slate {
|
fn from(slate: SlateV5) -> Slate {
|
||||||
let SlateV5 {
|
let SlateV5 {
|
||||||
|
|
|
@ -141,7 +141,7 @@ impl VersionedCoinbase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub mod tests {
|
||||||
use crate::grin_core::core::transaction::{FeeFields, OutputFeatures};
|
use crate::grin_core::core::transaction::{FeeFields, OutputFeatures};
|
||||||
use crate::grin_util::from_hex;
|
use crate::grin_util::from_hex;
|
||||||
use crate::grin_util::secp::key::PublicKey;
|
use crate::grin_util::secp::key::PublicKey;
|
||||||
|
@ -159,7 +159,7 @@ mod tests {
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
// Populate a test internal slate with all fields to test conversions
|
// Populate a test internal slate with all fields to test conversions
|
||||||
fn populate_test_slate() -> Result<Slate, Error> {
|
pub fn populate_test_slate() -> Result<Slate, Error> {
|
||||||
let keychain = ExtKeychain::from_random_seed(true).unwrap();
|
let keychain = ExtKeychain::from_random_seed(true).unwrap();
|
||||||
let switch = SwitchCommitmentType::Regular;
|
let switch = SwitchCommitmentType::Regular;
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ mod tests {
|
||||||
let ts = NaiveDateTime::from_timestamp(Utc::now().timestamp(), 0);
|
let ts = NaiveDateTime::from_timestamp(Utc::now().timestamp(), 0);
|
||||||
let ts = DateTime::<Utc>::from_utc(ts, Utc);
|
let ts = DateTime::<Utc>::from_utc(ts, Utc);
|
||||||
let pm = PaymentMemo {
|
let pm = PaymentMemo {
|
||||||
memo_type: 0,
|
memo_type: 1,
|
||||||
memo: [9; 32],
|
memo: [9; 32],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,9 @@ use crate::grin_core::{global, ser};
|
||||||
use crate::grin_keychain::{Identifier, Keychain};
|
use crate::grin_keychain::{Identifier, Keychain};
|
||||||
use crate::grin_util::logger::LoggingConfig;
|
use crate::grin_util::logger::LoggingConfig;
|
||||||
use crate::grin_util::secp::key::{PublicKey, SecretKey};
|
use crate::grin_util::secp::key::{PublicKey, SecretKey};
|
||||||
use crate::grin_util::secp::{self, pedersen, Secp256k1};
|
use crate::grin_util::secp::{self, pedersen, Secp256k1, Signature};
|
||||||
use crate::grin_util::{ToHex, ZeroingString};
|
use crate::grin_util::{ToHex, ZeroingString};
|
||||||
|
use crate::slate::PaymentMemo;
|
||||||
use crate::slate_versions::ser as dalek_ser;
|
use crate::slate_versions::ser as dalek_ser;
|
||||||
use crate::InitTxArgs;
|
use crate::InitTxArgs;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
|
@ -934,6 +935,23 @@ pub struct StoredProofInfo {
|
||||||
/// sender signature
|
/// sender signature
|
||||||
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
|
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
|
||||||
pub sender_signature: Option<DalekSignature>,
|
pub sender_signature: Option<DalekSignature>,
|
||||||
|
// Fields beyond here are specific to early payment proofs,
|
||||||
|
// invoice and sender nonce
|
||||||
|
/// Assumed to be 0x00 (Legacy) if missing
|
||||||
|
pub proof_type: Option<u8>,
|
||||||
|
/// receiver's public nonce from signing
|
||||||
|
pub receiver_public_nonce: Option<PublicKey>,
|
||||||
|
/// receiver's public excess from signing
|
||||||
|
pub receiver_public_excess: Option<PublicKey>,
|
||||||
|
/// Timestamp provided by recipient when signing
|
||||||
|
pub timestamp: Option<DateTime<Utc>>,
|
||||||
|
/// Optional payment memo
|
||||||
|
pub memo: Option<PaymentMemo>,
|
||||||
|
/// recipient promise signature
|
||||||
|
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
|
||||||
|
pub promise_signature: Option<DalekSignature>,
|
||||||
|
/// Original Sender partial key
|
||||||
|
pub sender_part_sig: Option<Signature>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ser::Writeable for StoredProofInfo {
|
impl ser::Writeable for StoredProofInfo {
|
||||||
|
|
Loading…
Reference in a new issue