darkfi_money_contract/client/
pow_reward_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::{Proof, ProvingKey},
21    zkas::ZkBinary,
22    Result,
23};
24use darkfi_sdk::{
25    blockchain::expected_reward,
26    crypto::{note::AeadEncryptedNote, pasta_prelude::*, Blind, FuncId, Keypair, PublicKey},
27    pasta::pallas,
28};
29use darkfi_serial::serialize;
30use rand::rngs::OsRng;
31use tracing::debug;
32
33use crate::{
34    client::{
35        transfer_v1::{proof::create_transfer_mint_proof, TransferCallOutput},
36        MoneyNote,
37    },
38    model::{ClearInput, Coin, MoneyPoWRewardParamsV1, Output, DARK_TOKEN_ID},
39};
40
41pub struct PoWRewardCallDebris {
42    pub params: MoneyPoWRewardParamsV1,
43    pub proofs: Vec<Proof>,
44}
45
46pub struct PoWRewardRevealed {
47    pub coin: Coin,
48    pub value_commit: pallas::Point,
49    pub token_commit: pallas::Base,
50}
51
52impl PoWRewardRevealed {
53    pub fn to_vec(&self) -> Vec<pallas::Base> {
54        let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
55
56        // NOTE: It's important to keep these in the same order
57        // as the `constrain_instance` calls in the zkas code.
58        vec![self.coin.inner(), *valcom_coords.x(), *valcom_coords.y(), self.token_commit]
59    }
60}
61
62/// Struct holding necessary information to build a `Money::PoWRewardV1` contract call.
63pub struct PoWRewardCallBuilder {
64    /// Caller's keypair, corresponding to the one used in the signature
65    pub signature_keypair: Keypair,
66    /// Rewarded block height
67    pub block_height: u32,
68    /// Rewarded block transactions paid fees
69    pub fees: u64,
70    /// Optional recipient's public key, in case we want to mint to a different address
71    pub recipient: Option<PublicKey>,
72    /// Optional contract spend hook to use in the output
73    pub spend_hook: Option<FuncId>,
74    /// Optional user data to use in the output
75    pub user_data: Option<pallas::Base>,
76    /// `Mint_V1` zkas circuit ZkBinary
77    pub mint_zkbin: ZkBinary,
78    /// Proving key for the `Mint_V1` zk circuit
79    pub mint_pk: ProvingKey,
80}
81
82impl PoWRewardCallBuilder {
83    fn _build(&self, value: u64) -> Result<PoWRewardCallDebris> {
84        debug!(target: "contract::money::client::pow_reward", "Building Money::PoWRewardV1 contract call");
85
86        // In this call, we will build one clear input and one anonymous output.
87        // Only DARK_TOKEN_ID can be minted as PoW reward.
88        let token_id = *DARK_TOKEN_ID;
89
90        // Building the clear input using random blinds
91        let value_blind = Blind::random(&mut OsRng);
92        let token_blind = Blind::random(&mut OsRng);
93        let coin_blind = Blind::random(&mut OsRng);
94        let c_input = ClearInput {
95            value,
96            token_id,
97            value_blind,
98            token_blind,
99            signature_public: self.signature_keypair.public,
100        };
101
102        // Grab the spend hook and user data to use in the output
103        let spend_hook = self.spend_hook.unwrap_or(FuncId::none());
104        let user_data = self.user_data.unwrap_or(pallas::Base::ZERO);
105
106        // Building the anonymous output
107        let output = TransferCallOutput {
108            public_key: self.recipient.unwrap_or(self.signature_keypair.public),
109            value,
110            token_id,
111            spend_hook,
112            user_data,
113            blind: Blind::random(&mut OsRng),
114        };
115
116        debug!(target: "contract::money::client::pow_reward", "Creating token mint proof for output");
117        let (proof, public_inputs) = create_transfer_mint_proof(
118            &self.mint_zkbin,
119            &self.mint_pk,
120            &output,
121            value_blind,
122            token_blind,
123            spend_hook,
124            user_data,
125            coin_blind,
126        )?;
127
128        let note = MoneyNote {
129            value: output.value,
130            token_id: output.token_id,
131            spend_hook,
132            user_data,
133            coin_blind,
134            value_blind,
135            token_blind,
136            memo: serialize(&self.signature_keypair.secret),
137        };
138
139        let encrypted_note = AeadEncryptedNote::encrypt(&note, &output.public_key, &mut OsRng)?;
140
141        let c_output = Output {
142            value_commit: public_inputs.value_commit,
143            token_commit: public_inputs.token_commit,
144            coin: public_inputs.coin,
145            note: encrypted_note,
146        };
147
148        let params = MoneyPoWRewardParamsV1 { input: c_input, output: c_output };
149        let debris = PoWRewardCallDebris { params, proofs: vec![proof] };
150        Ok(debris)
151    }
152
153    pub fn build(&self) -> Result<PoWRewardCallDebris> {
154        let reward = expected_reward(self.block_height) + self.fees;
155        self._build(reward)
156    }
157
158    /// This function should only be used for testing, as PoW reward values are predefined
159    pub fn build_with_custom_reward(&self, reward: u64) -> Result<PoWRewardCallDebris> {
160        self._build(reward + self.fees)
161    }
162}