contract/money/client: Reworked Swap API

This commit is contained in:
parazyd
2023-02-23 12:01:54 +01:00
parent 6fe6cf9819
commit 86a66dae86
3 changed files with 210 additions and 2 deletions

View File

@@ -35,6 +35,9 @@ use darkfi_serial::{SerialDecodable, SerialEncodable};
/// `Money::TransferV1` API
pub mod transfer_v1;
/// `Money::OtcSwapV1` API
pub mod swap_v1;
// Wallet SQL table constant names. These have to represent the `wallet.sql`
// SQL schema.
// TODO: They should also be prefixed with the contract ID to avoid collisions.

View File

@@ -0,0 +1,205 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! This API is crufty. Please rework it into something nice to read and nice to use.
use darkfi::{
zk::{Proof, ProvingKey},
zkas::ZkBinary,
ClientFailed, Result,
};
use darkfi_sdk::{
crypto::{
note::AeadEncryptedNote, pasta_prelude::*, MerkleTree, PublicKey, SecretKey, TokenId,
},
incrementalmerkletree::Tree,
pasta::pallas,
};
use darkfi_serial::serialize;
use log::{debug, info};
use rand::rngs::OsRng;
use crate::{
client::{
transfer_v1::{
create_transfer_burn_proof, create_transfer_mint_proof, TransactionBuilderInputInfo,
TransactionBuilderOutputInfo,
},
MoneyNote, OwnCoin,
},
model::{Input, MoneyTransferParamsV1, Output},
};
pub struct SwapCallDebris {
pub params: MoneyTransferParamsV1,
pub proofs: Vec<Proof>,
}
/// Struct holding necessary information to build a `Money::OtcSwapV1` contract call.
/// This is used to build half of the swap transaction, so both parties have to build
/// their halves and combine them.
pub struct SwapCallBuilder {
/// Party's public key for receiving the output
pub pubkey: PublicKey,
/// The value of the party's input to swap (send)
pub value_send: u64,
/// The token ID of the party's input to swap (send)
pub token_id_send: TokenId,
/// The value of the party's output to receive
pub value_recv: u64,
/// The token ID of the party's output to receive
pub token_id_recv: TokenId,
/// User data blind for the party's input
pub user_data_blind_send: pallas::Base,
/// Spend hook for the party's output
pub spend_hook_recv: pallas::Base,
/// User data for the party's output
pub user_data_recv: pallas::Base,
/// The blinds to be used for value pedersen commitments
/// `[0]` is used for input 0 and output 1, and `[1]` is
/// used for input 1 and output 0. The same applies to
/// `token_blinds`.
pub value_blinds: [pallas::Scalar; 2],
/// The blinds to be used for token ID pedersen commitments
pub token_blinds: [pallas::Scalar; 2],
/// The coin to be used as the input to the swap
pub coin: OwnCoin,
/// Merkle tree of coins used to create inclusion proofs
pub tree: MerkleTree,
/// `Mint_V1` zkas circuit ZkBinary
pub mint_zkbin: ZkBinary,
/// Proving key for the `Mint_V1` zk circuit
pub mint_pk: ProvingKey,
/// `Burn_V1` zkas circuit ZkBinary
pub burn_zkbin: ZkBinary,
/// Proving key for the `Burn_V1` zk circuit
pub burn_pk: ProvingKey,
}
impl SwapCallBuilder {
pub fn build(&self) -> Result<SwapCallDebris> {
debug!("Building half of Money::OtcSwapV1 contract call");
assert!(self.value_send != 0);
assert!(self.value_recv != 0);
assert!(self.token_id_send.inner() != pallas::Base::zero());
assert!(self.token_id_recv.inner() != pallas::Base::zero());
if self.coin.note.value != self.value_send {
return Err(ClientFailed::InvalidAmount(self.coin.note.value).into())
}
if self.coin.note.token_id != self.token_id_send {
return Err(ClientFailed::InvalidTokenId.into())
}
let leaf_position = self.coin.leaf_position;
let root = self.tree.root(0).unwrap();
let merkle_path = self.tree.authentication_path(leaf_position, &root).unwrap();
let input = TransactionBuilderInputInfo {
leaf_position,
merkle_path,
secret: self.coin.secret,
note: self.coin.note.clone(),
};
let output = TransactionBuilderOutputInfo {
value: self.value_recv,
token_id: self.token_id_recv,
public_key: self.pubkey,
};
// Now we fill this with necessary stuff
let mut params =
MoneyTransferParamsV1 { clear_inputs: vec![], inputs: vec![], outputs: vec![] };
// Create a new ephemeral secret key
let signature_secret = SecretKey::random(&mut OsRng);
let mut proofs = vec![];
info!("Creating burn proof for input");
let (proof, public_inputs) = create_transfer_burn_proof(
&self.burn_zkbin,
&self.burn_pk,
&input,
self.value_blinds[0],
self.token_blinds[0],
self.user_data_blind_send,
signature_secret,
)?;
params.inputs.push(Input {
value_commit: public_inputs.value_commit,
token_commit: public_inputs.token_commit,
nullifier: public_inputs.nullifier,
merkle_root: public_inputs.merkle_root,
spend_hook: public_inputs.spend_hook,
user_data_enc: public_inputs.user_data_enc,
signature_public: public_inputs.signature_public,
});
proofs.push(proof);
// For the output, we create a new serial and coin blind
let serial = pallas::Base::random(&mut OsRng);
let coin_blind = pallas::Base::random(&mut OsRng);
info!("Creating mint proof for output");
let (proof, public_inputs) = create_transfer_mint_proof(
&self.mint_zkbin,
&self.mint_pk,
&output,
self.value_blinds[1],
self.token_blinds[1],
serial,
self.spend_hook_recv,
self.user_data_recv,
coin_blind,
)?;
proofs.push(proof);
// Encrypted note
let note = MoneyNote {
serial,
value: output.value,
token_id: output.token_id,
spend_hook: self.spend_hook_recv,
user_data: self.user_data_recv,
coin_blind,
value_blind: self.value_blinds[1],
token_blind: self.token_blinds[1],
// Here we store our secret key we use for signing
memo: serialize(&signature_secret),
};
let encrypted_note = AeadEncryptedNote::encrypt(&note, &self.pubkey, &mut OsRng)?;
params.outputs.push(Output {
value_commit: public_inputs.value_commit,
token_commit: public_inputs.token_commit,
coin: public_inputs.coin,
note: encrypted_note,
});
// Now we should have all the params, zk proofs, and signature secrets.
// We return it all and let the caller deal with it.
let debris = SwapCallDebris { params, proofs };
Ok(debris)
}
}

View File

@@ -363,7 +363,7 @@ impl TransferCallBuilder {
}
}
fn create_transfer_burn_proof(
pub(crate) fn create_transfer_burn_proof(
zkbin: &ZkBinary,
pk: &ProvingKey,
input: &TransactionBuilderInputInfo,
@@ -439,7 +439,7 @@ fn create_transfer_burn_proof(
Ok((proof, public_inputs))
}
fn create_transfer_mint_proof(
pub(crate) fn create_transfer_mint_proof(
zkbin: &ZkBinary,
pk: &ProvingKey,
output: &TransactionBuilderOutputInfo,