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