[CONTRACTS] Contracts branch update for 1.80 (#718)

* Simple contracts restructured v3 (#675)

* Add prototype contract implementation

Lacks:
- Payment proofs (add early payment proofs)
- Tests
- Better structure

* Separate contract utilities

* Simplify the main setup/sign code flow

* Remove commented initial_sec_key assignment

* Simplify sign::compute function

* Add logic for "removal" of secret keys from the context

* Further simplify a bit sign and setup

* No need for mutable context when adding outputs

* Refactor the commented code (lol)

* Refactor a bit

* tmp

---------

Co-authored-by: oryhp <gtrphyro@gmail.com>

* contract test fixes (#676)

* [Contracts] Experimental Slate v5 (#677)

* beginning to add and modify slate version 5

* completion of conversions from V5 to V4 Slate

* timestamp and memo fields

* upgrade/downgrade serialization of v5 slates

* add v5 binary slate versions + start of tests

* add bin slate ser/deser to tests

* ensure serialization of timestamp always excludes milliseconds

* start to update v5 tests, update v5 documentation

* add fn to generate populated internal slate for conversion testing

* add basic tests to convert all slate versions

* update

* commit cargo.lock

* [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>

* fix Cargo.lock

* Contracts - Test fixes + warning cleanups (#694)

* clean up warnings in libwallet crate

* clean up warnings in controller crate

* update all contract tests with awareness of new proof structure

* added explicit payment proof tests for both rsr and srs (#695)

* Basic mwmixnet send (#696)

* integrating onion library

* updates and changes to support newly included mwmixnet types

* add (incorrect) owner api function

* turn off test for now

* switch working grin branch to master

* fix doctests for build

* update cargo lock in attempt to fix croaring build on CI server

* update cargo lock with upstream thiserror crate

* update test dependency for croaring

* [Contracts] Add self spend transaction state (#697)

* Add self spend transaction state

* subtle errors with output states and tx lookups - fixes

* [Contracts] Slatepack v5 Deserialization fix (#698)

* add V5 deserialization test + fixes

* clarify comment

* upwrap fix during v4 deserialization

* further unwrap removal

* update cargo lock, use latest grin packages

* addition of self-spend cancel + tests (#699)

* add tests + legacy self send cancel (#702)

* [Contracts] Cancel self-spend TX Pt. 2 (#703)

* add tests + legacy self send cancel

* add missing file

* Update grin to latest master, change versioning label to include contracts (#712)

* Add owner_api_listen_interface as hidden configuration field (#714)

* updates for rust 1.80

* updates for rust 1.80

---------

Co-authored-by: oryhp <gtrphyro@gmail.com>
Co-authored-by: Quentin Le Sceller <q.lesceller@gmail.com>
This commit is contained in:
Yeastplume
2024-08-27 12:30:22 +01:00
committed by GitHub
co-authored by oryhp Quentin Le Sceller
parent 765f11b168
commit cdd63fb565
78 changed files with 10170 additions and 220 deletions
+105 -7
View File
@@ -15,7 +15,11 @@
//! Generic implementation of owner API functions
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::{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;
use crate::grin_util::secp::key::SecretKey;
@@ -25,6 +29,7 @@ use crate::{
address, BlockFees, CbData, Error, NodeClient, Slate, SlateState, TxLogEntryType, VersionInfo,
WalletBackend,
};
use ed25519_dalek::PublicKey as DalekPublicKey;
use super::owner::tx_lock_outputs;
@@ -114,14 +119,16 @@ where
let excess = ret_slate.calc_excess(keychain.secp())?;
if let Some(ref mut p) = ret_slate.payment_proof {
let sig = tx::create_payment_proof_signature(
ret_slate.amount,
&excess,
p.sender_address,
address::address_from_derivation_path(&keychain, &parent_key_id, 0)?,
)?;
if let Some(saddr) = p.sender_address {
let sig = tx::create_payment_proof_signature(
ret_slate.amount,
&excess,
saddr,
address::address_from_derivation_path(&keychain, &parent_key_id, 0)?,
)?;
p.receiver_signature = Some(sig);
p.promise_signature = Some(sig);
}
}
ret_slate.amount = 0;
@@ -231,3 +238,94 @@ where
}
Ok(sl)
}
/// Initialize a receive transaction contract
pub fn contract_new<'a, T: ?Sized, C, K>(
w: &mut T,
keychain_mask: Option<&SecretKey>,
args: &ContractNewArgsAPI,
// use_test_rng: bool,
) -> Result<Slate, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let net_change = args.setup_args.net_change.unwrap();
if net_change <= 0 {
return Err(Error::GenericError(
"Can't create a non-receiving contract from a foreign API.".to_string(),
)
.into());
}
owner_contract_new(&mut *w, keychain_mask, args)
}
/// Sign a receive transaction contract
pub fn contract_sign<'a, T: ?Sized, C, K>(
w: &mut T,
keychain_mask: Option<&SecretKey>,
args: &ContractSetupArgsAPI,
slate: &Slate,
// use_test_rng: bool,
) -> Result<Slate, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let net_change = args.net_change.unwrap();
if net_change <= 0 {
return Err(Error::GenericError(
"Can't sign a non-receiving contract from a foreign API.".to_string(),
)
.into());
}
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,
recipient_address: &DalekPublicKey,
proof: &InvoiceProof,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let mut client = w.w2n_client().clone();
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, _) = 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()?,
)
}
+297 -5
View File
@@ -17,27 +17,35 @@
use uuid::Uuid;
use crate::api_impl::foreign::finalize_tx as foreign_finalize;
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;
use crate::grin_keychain::ViewKey;
use crate::grin_util::secp::key::SecretKey;
use crate::grin_util::Mutex;
use crate::grin_util::ToHex;
use crate::grin_util::secp::pedersen;
use crate::grin_util::{static_secp_instance, Mutex, ToHex};
use crate::util::{OnionV3Address, OnionV3AddressError};
use crate::api_impl::owner_updater::StatusMessage;
use crate::contract::types::{ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI};
use crate::grin_keychain::{BlindingFactor, Identifier, Keychain, SwitchCommitmentType};
use crate::mwmixnet::onion::create_onion;
use crate::mwmixnet::types::{
add_excess, new_hop, random_secret, ComSignature, Hop, MixnetReqCreationParams, SwapReq,
};
use crate::internal::{keys, scan, selection, tx, updater};
use crate::slate::{PaymentInfo, Slate, SlateState};
use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletBackend, WalletInfo};
use crate::Error;
use crate::{
address, wallet_lock, BuiltOutput, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult,
address, contract, wallet_lock, BuiltOutput, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult,
OutputCommitMapping, PaymentProof, RetrieveTxQueryArgs, ScannedBlockInfo, Slatepack,
SlatepackAddress, Slatepacker, SlatepackerArgs, TxLogEntryType, ViewWallet, WalletInitStatus,
WalletInst, WalletLCProvider,
};
use chrono::prelude::{DateTime, NaiveDateTime, Utc};
use ed25519_dalek::PublicKey as DalekPublicKey;
use ed25519_dalek::SecretKey as DalekSecretKey;
use ed25519_dalek::Verifier;
@@ -473,6 +481,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,
@@ -568,9 +715,14 @@ where
let sender_address = OnionV3Address::from_private(&sec_addr_key.0)?;
slate.payment_proof = Some(PaymentInfo {
sender_address: sender_address.to_ed25519()?,
sender_address: Some(sender_address.to_ed25519()?),
receiver_address: a.pub_key,
receiver_signature: None,
promise_signature: None,
timestamp: DateTime::<Utc>::from_utc(
NaiveDateTime::from_timestamp_opt(0, 0).unwrap(),
Utc,
),
memo: None,
});
context.payment_proof_derivation_index = Some(deriv_path);
@@ -1369,3 +1521,143 @@ where
output: output,
})
}
// Contract implementation
/// Initialize transaction contract
pub fn contract_new<'a, T: ?Sized, C, K>(
w: &mut T,
keychain_mask: Option<&SecretKey>,
args: &ContractNewArgsAPI,
// use_test_rng: bool,
) -> Result<Slate, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
contract::new(&mut *w, keychain_mask, &args.setup_args)
}
// /// Setup transaction contract
// pub fn contract_setup<'a, T: ?Sized, C, K>(
// w: &mut T,
// keychain_mask: Option<&SecretKey>,
// args: &ContractSetupArgsAPI,
// slate: &Slate,
// // use_test_rng: bool,
// ) -> Result<Slate, Error>
// where
// T: WalletBackend<'a, C, K>,
// C: NodeClient + 'a,
// K: Keychain + 'a,
// {
// contract::setup(&mut *w, keychain_mask, slate, &args)
// }
/// Sign transaction contract
pub fn contract_sign<'a, T: ?Sized, C, K>(
w: &mut T,
keychain_mask: Option<&SecretKey>,
args: &ContractSetupArgsAPI,
slate: &Slate,
// use_test_rng: bool,
) -> Result<Slate, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
contract::sign(&mut *w, keychain_mask, slate, &args)
}
/// Revoke transaction contract
pub fn contract_revoke<'a, T: ?Sized, C, K>(
w: &mut T,
keychain_mask: Option<&SecretKey>,
args: &ContractRevokeArgsAPI,
// use_test_rng: bool,
) -> Result<Option<Slate>, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
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)
}
/// Create MXMixnet request
pub fn create_mwmixnet_req<'a, T: ?Sized, C, K>(
w: &mut T,
keychain_mask: Option<&SecretKey>,
params: &MixnetReqCreationParams,
slate: &Slate,
// use_test_rng: bool,
) -> Result<SwapReq, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?;
let my_keys = context.get_private_keys();
let kernel = slate.tx_or_err()?.kernels()[0];
let msg = kernel.msg_to_sign()?;
let comsig = ComSignature::sign(slate.amount, &my_keys.0, &msg.to_hex().as_bytes().to_vec())?;
let mut hops: Vec<Hop> = Vec::new();
let mut final_commit = kernel.excess.clone();
let mut final_blind = my_keys.0.clone();
for i in 0..params.server_keys.len() {
let excess = params.server_keys[i].clone();
let secp = secp256k1zkp::Secp256k1::with_caps(secp256k1zkp::ContextFlag::Commit);
final_blind.add_assign(&secp, &excess).unwrap();
final_commit = add_excess(&final_commit, &excess).unwrap();
let proof = if i == params.server_keys.len() - 1 {
let n1 = random_secret();
let rp = secp.bullet_proof(
slate.amount - (params.fee_per_hop * params.server_keys.len() as u32) as u64,
final_blind.clone(),
n1.clone(),
n1.clone(),
None,
None,
);
assert!(secp.verify_bullet_proof(final_commit, rp, None).is_ok());
Some(rp)
} else {
None
};
let hop = new_hop(&params.server_keys[i], &excess, params.fee_per_hop, proof);
hops.push(hop);
}
let onion = create_onion(&kernel.excess, &hops)?;
Ok(SwapReq { comsig, onion })
//slate.find_index_matching_context(&keychain, &context)
}
+3
View File
@@ -200,6 +200,8 @@ pub struct RetrieveTxQueryArgs {
pub include_received_only: Option<bool>,
/// whether to only consider coinbase transactions
pub include_coinbase_only: Option<bool>,
/// whether to only consider self spend transactions
pub include_self_spend_only: Option<bool>,
/// whether to only consider reverted transactions
pub include_reverted_only: Option<bool>,
/// lower bound on the total amount (amount_credited - amount_debited), inclusive
@@ -237,6 +239,7 @@ impl Default for RetrieveTxQueryArgs {
include_sent_only: Some(false),
include_received_only: Some(false),
include_coinbase_only: Some(false),
include_self_spend_only: Some(false),
include_reverted_only: Some(false),
min_amount: None,
max_amount: None,