darkfi_money_contract/client/
fee_v1.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use darkfi::{
20    zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit},
21    zkas::ZkBinary,
22    Result,
23};
24use darkfi_sdk::{
25    bridgetree::Hashable,
26    crypto::{
27        pasta_prelude::{Curve, CurveAffine},
28        pedersen_commitment_u64, poseidon_hash, BaseBlind, FuncId, MerkleNode, PublicKey,
29        ScalarBlind, SecretKey,
30    },
31    pasta::pallas,
32};
33use rand::rngs::OsRng;
34
35use crate::{
36    client::{Coin, MoneyNote, OwnCoin},
37    model::{CoinAttributes, Nullifier},
38};
39
40/// Fixed gas used by the fee call.
41/// This is the minimum gas any fee-paying transaction will use.
42pub const FEE_CALL_GAS: u64 = 42_000_000;
43
44/// Private values related to the Fee call
45pub struct FeeCallSecrets {
46    /// The ZK proof created in this builder
47    pub proof: Proof,
48    /// The ephemeral secret key created for tx signining
49    pub signature_secret: SecretKey,
50    /// Decrypted note associated with the output
51    pub note: MoneyNote,
52    /// The value blind created for the input
53    pub input_value_blind: ScalarBlind,
54    /// The value blind created for the output
55    pub output_value_blind: ScalarBlind,
56}
57
58/// Revealed public inputs of the `Fee_V1` ZK proof
59pub struct FeeRevealed {
60    /// Input's Nullifier
61    pub nullifier: Nullifier,
62    /// Input's value commitment
63    pub input_value_commit: pallas::Point,
64    /// Token commitment
65    pub token_commit: pallas::Base,
66    /// Merkle root for input coin
67    pub merkle_root: MerkleNode,
68    /// Encrypted user data for input coin
69    pub input_user_data_enc: pallas::Base,
70    /// Public key used to sign transaction
71    pub signature_public: PublicKey,
72    /// Output coin commitment
73    pub output_coin: Coin,
74    /// Output value commitment
75    pub output_value_commit: pallas::Point,
76}
77
78impl FeeRevealed {
79    /// Transform the struct into a `Vec<pallas::Base>` ready for
80    /// proof verification.
81    pub fn to_vec(&self) -> Vec<pallas::Base> {
82        let input_vc_coords = self.input_value_commit.to_affine().coordinates().unwrap();
83        let output_vc_coords = self.output_value_commit.to_affine().coordinates().unwrap();
84        let sigpub_coords = self.signature_public.inner().to_affine().coordinates().unwrap();
85
86        // NOTE: It's important to keep these in the same order
87        // as the `constrain_instance` calls in the zkas code.
88        vec![
89            self.nullifier.inner(),
90            *input_vc_coords.x(),
91            *input_vc_coords.y(),
92            self.token_commit,
93            self.merkle_root.inner(),
94            self.input_user_data_enc,
95            *sigpub_coords.x(),
96            *sigpub_coords.y(),
97            self.output_coin.inner(),
98            *output_vc_coords.x(),
99            *output_vc_coords.y(),
100        ]
101    }
102}
103
104pub struct FeeCallInput {
105    /// The [`OwnCoin`] containing necessary metadata to create an input
106    pub coin: OwnCoin,
107    /// Merkle path in the Money Merkle tree for `coin`
108    pub merkle_path: Vec<MerkleNode>,
109    /// The blinding factor for user_data
110    pub user_data_blind: BaseBlind,
111}
112
113pub type FeeCallOutput = CoinAttributes;
114
115/// Create the `Fee_V1` ZK proof given parameters
116#[allow(clippy::too_many_arguments)]
117pub fn create_fee_proof(
118    zkbin: &ZkBinary,
119    pk: &ProvingKey,
120    input: &FeeCallInput,
121    input_value_blind: ScalarBlind,
122    output: &FeeCallOutput,
123    output_value_blind: ScalarBlind,
124    output_spend_hook: FuncId,
125    output_user_data: pallas::Base,
126    output_coin_blind: BaseBlind,
127    token_blind: BaseBlind,
128    signature_secret: SecretKey,
129) -> Result<(Proof, FeeRevealed)> {
130    let public_key = PublicKey::from_secret(input.coin.secret);
131    let signature_public = PublicKey::from_secret(signature_secret);
132
133    // Create input coin
134    let input_coin = CoinAttributes {
135        public_key,
136        value: input.coin.note.value,
137        token_id: input.coin.note.token_id,
138        spend_hook: input.coin.note.spend_hook,
139        user_data: input.coin.note.user_data,
140        blind: input.coin.note.coin_blind,
141    }
142    .to_coin();
143
144    let merkle_root = {
145        let position: u64 = input.coin.leaf_position.into();
146        let mut current = MerkleNode::from(input_coin.inner());
147        for (level, sibling) in input.merkle_path.iter().enumerate() {
148            let level = level as u8;
149            current = if position & (1 << level) == 0 {
150                MerkleNode::combine(level.into(), &current, sibling)
151            } else {
152                MerkleNode::combine(level.into(), sibling, &current)
153            };
154        }
155        current
156    };
157
158    let input_user_data_enc =
159        poseidon_hash([input.coin.note.user_data, input.user_data_blind.inner()]);
160    let input_value_commit = pedersen_commitment_u64(input.coin.note.value, input_value_blind);
161    let output_value_commit = pedersen_commitment_u64(output.value, output_value_blind);
162    let token_commit = poseidon_hash([input.coin.note.token_id.inner(), token_blind.inner()]);
163
164    // Create output coin
165    let output_coin = CoinAttributes {
166        public_key: output.public_key,
167        value: output.value,
168        token_id: output.token_id,
169        spend_hook: output_spend_hook,
170        user_data: output_user_data,
171        blind: output_coin_blind,
172    }
173    .to_coin();
174
175    let public_inputs = FeeRevealed {
176        nullifier: input.coin.nullifier(),
177        input_value_commit,
178        token_commit,
179        merkle_root,
180        input_user_data_enc,
181        signature_public,
182        output_coin,
183        output_value_commit,
184    };
185
186    let prover_witnesses = vec![
187        Witness::Base(Value::known(input.coin.secret.inner())),
188        Witness::Uint32(Value::known(u64::from(input.coin.leaf_position).try_into().unwrap())),
189        Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
190        Witness::Base(Value::known(signature_secret.inner())),
191        Witness::Base(Value::known(pallas::Base::from(input.coin.note.value))),
192        Witness::Scalar(Value::known(input_value_blind.inner())),
193        Witness::Base(Value::known(input.coin.note.spend_hook.inner())),
194        Witness::Base(Value::known(input.coin.note.user_data)),
195        Witness::Base(Value::known(input.coin.note.coin_blind.inner())),
196        Witness::Base(Value::known(input.user_data_blind.inner())),
197        Witness::Base(Value::known(pallas::Base::from(output.value))),
198        Witness::Base(Value::known(output_spend_hook.inner())),
199        Witness::Base(Value::known(output_user_data)),
200        Witness::Scalar(Value::known(output_value_blind.inner())),
201        Witness::Base(Value::known(output_coin_blind.inner())),
202        Witness::Base(Value::known(input.coin.note.token_id.inner())),
203        Witness::Base(Value::known(token_blind.inner())),
204    ];
205
206    //darkfi::zk::export_witness_json("proof/witness/fee_v1.json", &prover_witnesses, &public_inputs.to_vec());
207    let circuit = ZkCircuit::new(prover_witnesses, zkbin);
208    let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
209
210    Ok((proof, public_inputs))
211}