darkfi_money_contract/client/transfer_v1/
proof.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::*, pedersen_commitment_u64, poseidon_hash, BaseBlind, FuncId, MerkleNode,
28        PublicKey, ScalarBlind, SecretKey,
29    },
30    pasta::pallas,
31};
32use log::debug;
33use rand::rngs::OsRng;
34
35use super::{TransferCallInput, TransferCallOutput};
36use crate::model::{Coin, CoinAttributes, Nullifier};
37
38pub struct TransferMintRevealed {
39    pub coin: Coin,
40    pub value_commit: pallas::Point,
41    pub token_commit: pallas::Base,
42}
43
44impl TransferMintRevealed {
45    pub fn to_vec(&self) -> Vec<pallas::Base> {
46        let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
47
48        // NOTE: It's important to keep these in the same order
49        // as the `constrain_instance` calls in the zkas code.
50        vec![self.coin.inner(), *valcom_coords.x(), *valcom_coords.y(), self.token_commit]
51    }
52}
53
54pub struct TransferBurnRevealed {
55    pub value_commit: pallas::Point,
56    pub token_commit: pallas::Base,
57    pub nullifier: Nullifier,
58    pub merkle_root: MerkleNode,
59    pub spend_hook: FuncId,
60    pub user_data_enc: pallas::Base,
61    pub signature_public: PublicKey,
62}
63
64impl TransferBurnRevealed {
65    pub fn to_vec(&self) -> Vec<pallas::Base> {
66        let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
67
68        // NOTE: It's important to keep these in the same order
69        // as the `constrain_instance` calls in the zkas code.
70        vec![
71            self.nullifier.inner(),
72            *valcom_coords.x(),
73            *valcom_coords.y(),
74            self.token_commit,
75            self.merkle_root.inner(),
76            self.user_data_enc,
77            self.spend_hook.inner(),
78            self.signature_public.x(),
79            self.signature_public.y(),
80        ]
81    }
82}
83
84pub fn create_transfer_burn_proof(
85    zkbin: &ZkBinary,
86    pk: &ProvingKey,
87    input: &TransferCallInput,
88    value_blind: ScalarBlind,
89    token_blind: BaseBlind,
90    signature_secret: SecretKey,
91) -> Result<(Proof, TransferBurnRevealed)> {
92    let public_key = PublicKey::from_secret(input.coin.secret);
93    let signature_public = PublicKey::from_secret(signature_secret);
94
95    let coin = CoinAttributes {
96        public_key,
97        value: input.coin.note.value,
98        token_id: input.coin.note.token_id,
99        spend_hook: input.coin.note.spend_hook,
100        user_data: input.coin.note.user_data,
101        blind: input.coin.note.coin_blind,
102    }
103    .to_coin();
104
105    let merkle_root = {
106        let position: u64 = input.coin.leaf_position.into();
107        let mut current = MerkleNode::from(coin.inner());
108        for (level, sibling) in input.merkle_path.iter().enumerate() {
109            let level = level as u8;
110            current = if position & (1 << level) == 0 {
111                MerkleNode::combine(level.into(), &current, sibling)
112            } else {
113                MerkleNode::combine(level.into(), sibling, &current)
114            };
115        }
116        current
117    };
118
119    let user_data_enc = poseidon_hash([input.coin.note.user_data, input.user_data_blind.inner()]);
120    let value_commit = pedersen_commitment_u64(input.coin.note.value, value_blind);
121    let token_commit = poseidon_hash([input.coin.note.token_id.inner(), token_blind.inner()]);
122
123    let public_inputs = TransferBurnRevealed {
124        value_commit,
125        token_commit,
126        nullifier: input.coin.nullifier(),
127        merkle_root,
128        spend_hook: input.coin.note.spend_hook,
129        user_data_enc,
130        signature_public,
131    };
132
133    let prover_witnesses = vec![
134        Witness::Base(Value::known(input.coin.secret.inner())),
135        Witness::Base(Value::known(pallas::Base::from(input.coin.note.value))),
136        Witness::Base(Value::known(input.coin.note.token_id.inner())),
137        Witness::Base(Value::known(input.coin.note.spend_hook.inner())),
138        Witness::Base(Value::known(input.coin.note.user_data)),
139        Witness::Base(Value::known(input.coin.note.coin_blind.inner())),
140        Witness::Scalar(Value::known(value_blind.inner())),
141        Witness::Base(Value::known(token_blind.inner())),
142        Witness::Base(Value::known(input.user_data_blind.inner())),
143        Witness::Uint32(Value::known(u64::from(input.coin.leaf_position).try_into().unwrap())),
144        Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
145        Witness::Base(Value::known(signature_secret.inner())),
146    ];
147
148    //darkfi::zk::export_witness_json("proof/witness/burn_v1.json", &prover_witnesses, &public_inputs.to_vec());
149    let circuit = ZkCircuit::new(prover_witnesses, zkbin);
150    let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
151
152    Ok((proof, public_inputs))
153}
154
155#[allow(clippy::too_many_arguments)]
156pub fn create_transfer_mint_proof(
157    zkbin: &ZkBinary,
158    pk: &ProvingKey,
159    output: &TransferCallOutput,
160    value_blind: ScalarBlind,
161    token_blind: BaseBlind,
162    spend_hook: FuncId,
163    user_data: pallas::Base,
164    coin_blind: BaseBlind,
165) -> Result<(Proof, TransferMintRevealed)> {
166    let value_commit = pedersen_commitment_u64(output.value, value_blind);
167    let token_commit = poseidon_hash([output.token_id.inner(), token_blind.inner()]);
168    let (pub_x, pub_y) = output.public_key.xy();
169
170    let coin = CoinAttributes {
171        public_key: output.public_key,
172        value: output.value,
173        token_id: output.token_id,
174        spend_hook,
175        user_data,
176        blind: coin_blind,
177    };
178    debug!(target: "contract::money::client::transfer::proof", "Created coin: {:?}", coin);
179    let coin = coin.to_coin();
180
181    let public_inputs = TransferMintRevealed { coin, value_commit, token_commit };
182
183    let prover_witnesses = vec![
184        Witness::Base(Value::known(pub_x)),
185        Witness::Base(Value::known(pub_y)),
186        Witness::Base(Value::known(pallas::Base::from(output.value))),
187        Witness::Base(Value::known(output.token_id.inner())),
188        Witness::Base(Value::known(spend_hook.inner())),
189        Witness::Base(Value::known(user_data)),
190        Witness::Base(Value::known(coin_blind.inner())),
191        Witness::Scalar(Value::known(value_blind.inner())),
192        Witness::Base(Value::known(token_blind.inner())),
193    ];
194
195    //darkfi::zk::export_witness_json("proof/witness/mint_v1.json", &prover_witnesses, &public_inputs.to_vec());
196    let circuit = ZkCircuit::new(prover_witnesses, zkbin);
197    let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
198
199    Ok((proof, public_inputs))
200}