[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:
Yeastplume 2023-08-08 11:35:14 +01:00 committed by GitHub
parent 58659d85fe
commit e3148d0305
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1126 additions and 24 deletions

13
Cargo.lock generated
View file

@ -747,6 +747,8 @@ dependencies = [
]
[[package]]
<<<<<<< HEAD
=======
name = "darling"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -782,6 +784,7 @@ dependencies = [
]
[[package]]
>>>>>>> contracts
name = "dashmap"
version = "5.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -1703,7 +1706,6 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"serde_with",
"sha2 0.8.2",
"strum",
"strum_macros",
@ -2035,12 +2037,6 @@ dependencies = [
"cc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.4.0"
@ -3796,6 +3792,8 @@ dependencies = [
]
[[package]]
<<<<<<< HEAD
=======
name = "serde_with"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -3819,6 +3817,7 @@ dependencies = [
]
[[package]]
>>>>>>> contracts
name = "serde_yaml"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"

View file

@ -17,6 +17,7 @@
use crate::config::TorConfig;
use crate::keychain::Keychain;
use crate::libwallet::api_impl::foreign;
use crate::libwallet::contract::proofs::InvoiceProof;
use crate::libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use crate::libwallet::{
BlockFees, CbData, Error, NodeClient, NodeVersionInfo, Slate, VersionInfo, WalletInst,
@ -25,6 +26,7 @@ use crate::libwallet::{
use crate::try_slatepack_sync_workflow;
use crate::util::secp::key::SecretKey;
use crate::util::Mutex;
use ed25519_dalek::PublicKey as DalekPublicKey;
use std::sync::Arc;
/// ForeignAPI Middleware Check callback
@ -477,6 +479,18 @@ where
let w = w_lock.lc_provider()?.wallet_inst()?;
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)]

View file

@ -16,6 +16,7 @@
use chrono::prelude::*;
use ed25519_dalek::SecretKey as DalekSecretKey;
use grin_wallet_libwallet::contract::proofs::InvoiceProof;
use grin_wallet_libwallet::RetrieveTxQueryArgs;
use uuid::Uuid;
@ -806,6 +807,17 @@ where
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
pub fn contract_revoke(
&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)
/// This process entails:
///

View file

@ -1682,6 +1682,7 @@ impl ContractNewArgs {
},
..Default::default()
},
proof_args: Default::default(),
},
..Default::default()
}

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

View file

@ -13,12 +13,14 @@
// limitations under the License.
//! Generic implementation of owner API functions
use grin_util::secp::pedersen::Commitment;
use strum::IntoEnumIterator;
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::finalize_tx as owner_finalize;
use crate::api_impl::owner::{check_ttl, post_tx};
use crate::contract::proofs::InvoiceProof;
use crate::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
use crate::grin_core::core::FeeFields;
use crate::grin_keychain::Keychain;
@ -29,6 +31,7 @@ use crate::{
address, BlockFees, CbData, Error, NodeClient, Slate, SlateState, TxLogEntryType, VersionInfo,
WalletBackend,
};
use ed25519_dalek::PublicKey as DalekPublicKey;
const FOREIGN_API_VERSION: u16 = 2;
@ -223,3 +226,56 @@ where
}
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()?,
)
}

View file

@ -16,6 +16,7 @@
use uuid::Uuid;
use crate::contract::proofs::{InvoiceProof, ProofWitness};
use crate::grin_core::core::hash::Hashed;
use crate::grin_core::core::{Output, OutputFeatures, Transaction};
use crate::grin_core::libtx::proof;
@ -44,6 +45,7 @@ use ed25519_dalek::SecretKey as DalekSecretKey;
use ed25519_dalek::Verifier;
use std::convert::{TryFrom, TryInto};
use std::ops::Index;
use std::sync::mpsc::Sender;
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
pub fn init_send_tx<'a, T: ?Sized, C, K>(
w: &mut T,
@ -1494,3 +1635,20 @@ where
{
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)
}

View file

@ -14,6 +14,8 @@
//! Implementation of contract revoke
use std::default;
use crate::contract::types::{ContractRevokeArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs};
use crate::contract::{new, sign};
use crate::error::Error;
@ -78,6 +80,7 @@ where
use_inputs: Some(String::from(input_commit)),
..Default::default()
},
proof_args: Default::default(),
},
)?;
let finished_slate = sign(
@ -94,6 +97,7 @@ where
use_inputs: Some(String::from(input_commit)),
..Default::default()
},
proof_args: Default::default(),
},
)?;
// TODO: Think about what to do with transaction context of the cancelled slate. It should probably get deleted.

View file

@ -75,7 +75,14 @@ where
// 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_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.
if setup_args.add_outputs {

View file

@ -78,7 +78,10 @@ where
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
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::transition_state(&mut sl)?;

View file

@ -16,6 +16,7 @@
mod actions;
mod context;
pub mod proofs;
mod selection;
mod slate;
pub mod types;

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

View file

@ -22,23 +22,54 @@ use crate::slate::{Slate, SlateState};
use crate::types::{Context, NodeClient, WalletBackend};
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
/// to prevent possibility of signing with the same key twice.
pub const SEC_KEY_FAKE: [u8; 32] = [0; 32];
/// Add payment proof data to slate
pub fn add_payment_proof(slate: &mut Slate) -> Result<(), Error> {
/// Add payment proof data to slate, noop for sender
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
// 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(())
}
/// 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
// 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(())
}

View file

@ -15,6 +15,8 @@
//! Types related to a contract
use crate::grin_core::consensus;
use crate::slate_versions::ser as dalek_ser;
use ed25519_dalek::PublicKey as DalekPublicKey;
/// Output selection args
#[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
/// 'new' and 'sign' actions perform a setup phase which is why their endpoints take these parameters.
#[derive(Clone, Serialize, Deserialize, Debug)]
@ -99,6 +137,8 @@ pub struct ContractSetupArgsAPI {
pub add_outputs: bool,
/// Output selection arguments
pub selection_args: OutputSelectionArgs,
/// Proof arguments
pub proof_args: ProofArgs,
}
impl Default for ContractSetupArgsAPI {
@ -111,6 +151,7 @@ impl Default for ContractSetupArgsAPI {
selection_args: OutputSelectionArgs {
..Default::default()
},
proof_args: ProofArgs::default(),
}
}
}
@ -139,6 +180,7 @@ impl Default for ContractNewArgsAPI {
selection_args: OutputSelectionArgs {
..Default::default()
},
proof_args: ProofArgs::default(),
},
}
}

View file

@ -20,11 +20,14 @@ use crate::grin_core::libtx::tx_fee;
use crate::grin_keychain::{Identifier, Keychain};
use crate::grin_util::secp::key::SecretKey;
use crate::slate::Slate;
use crate::types::{Context, NodeClient, TxLogEntryType, WalletBackend};
use crate::{Error, OutputData, OutputStatus, TxLogEntry};
use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType, WalletBackend};
use crate::util::OnionV3Address;
use crate::{address, Error, OutputData, OutputStatus, TxLogEntry};
use grin_core::core::FeeFields;
use uuid::Uuid;
use super::proofs::InvoiceProof;
/// Creates an initial TxLogEntry without input/output or kernel information
pub fn create_tx_log_entry(
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
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;
// 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();
@ -81,6 +85,44 @@ where
};
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(())
}
@ -229,6 +271,7 @@ where
batch.lock_output(&mut coin)?;
}
}
// Update context
if is_signed && !is_step2 {
// NOTE: We MUST forget the context when we sign. Ideally, these two would be atomic or perhaps

View file

@ -249,10 +249,18 @@ pub enum Error {
#[error("Payment Proof parsing error: {0}")]
PaymentProofParsing(String),
/// Retrieving Payment Proof
#[error("Unable to verify payment proof: {0}")]
PaymentProofValidation(String),
/// Decoding OnionV3 addresses to payment proof addresses
#[error("Proof Address decoding: {0}")]
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
#[error("Transaction Expired")]
TransactionExpired,
@ -301,6 +309,10 @@ pub enum Error {
#[error("Stored Tx error: {0}")]
StoredTx(String),
/// Trying to match index to context
#[error("Cannot match transaction context to slate index")]
ContextToIndex,
/// Other
#[error("Generic error: {0}")]
GenericError(String),

View file

@ -203,12 +203,23 @@ where
sender_address_path,
)?;
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
t.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: None,
receiver_public_nonce: None,
receiver_public_excess: None,
timestamp: None,
memo: None,
promise_signature: None,
sender_part_sig: None,
});
};

View file

@ -433,12 +433,21 @@ where
address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?;
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
let sig = create_payment_proof_signature(slate.amount, &excess, saddr, sender_key)?;
tx.payment_proof = Some(StoredProofInfo {
receiver_address: p.receiver_address,
receiver_signature: p.promise_signature,
sender_address_path: derivation_index,
sender_address: sender_address.to_ed25519()?,
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 {
}

View file

@ -48,11 +48,12 @@ use crate::slate_versions::VersionedSlate;
use crate::slate_versions::{CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION};
use crate::Context;
#[derive(Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct PaymentMemo {
// The type of memo
// 0x00 is directly embedded additional payment details
// 0x01 represents the blake2b hash of an arbitrary invoice document
// 0x00 is the absence of any specific memo data
// 0x01 is directly embedded additional payment details
// 0x02 represents the blake2b hash of an arbitrary invoice document
pub memo_type: u8,
// memo data itself
pub memo: [u8; 32],
@ -410,6 +411,28 @@ impl Slate {
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
pub fn fill_round_2<K>(
&mut self,
@ -953,7 +976,7 @@ impl From<OutputFeatures> for OutputFeaturesV5 {
}
}
///// V4
///// V5
impl From<SlateV5> for Slate {
fn from(slate: SlateV5) -> Slate {
let SlateV5 {

View file

@ -141,7 +141,7 @@ impl VersionedCoinbase {
}
}
#[cfg(test)]
mod tests {
pub mod tests {
use crate::grin_core::core::transaction::{FeeFields, OutputFeatures};
use crate::grin_util::from_hex;
use crate::grin_util::secp::key::PublicKey;
@ -159,7 +159,7 @@ mod tests {
use std::convert::TryInto;
// 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 switch = SwitchCommitmentType::Regular;
@ -227,7 +227,7 @@ mod tests {
let ts = NaiveDateTime::from_timestamp(Utc::now().timestamp(), 0);
let ts = DateTime::<Utc>::from_utc(ts, Utc);
let pm = PaymentMemo {
memo_type: 0,
memo_type: 1,
memo: [9; 32],
};

View file

@ -26,8 +26,9 @@ use crate::grin_core::{global, ser};
use crate::grin_keychain::{Identifier, Keychain};
use crate::grin_util::logger::LoggingConfig;
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::slate::PaymentMemo;
use crate::slate_versions::ser as dalek_ser;
use crate::InitTxArgs;
use chrono::prelude::*;
@ -934,6 +935,23 @@ pub struct StoredProofInfo {
/// sender signature
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
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 {