• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ergoplatform / sigma-rust / 14311715155

07 Apr 2025 02:22PM UTC coverage: 78.24% (-0.03%) from 78.273%
14311715155

Pull #822

github

web-flow
Merge e753f1bba into f2e64bb7f
Pull Request #822: fix: updated the transactionhintsbag implementation

13 of 15 new or added lines in 1 file covered. (86.67%)

7 existing lines in 3 files now uncovered.

11517 of 14720 relevant lines covered (78.24%)

3.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

50.68
/ergo-lib/src/wallet.rs
1
//! Wallet-related features for Ergo
2

3
pub mod box_selector;
4
pub mod derivation_path;
5
mod deterministic;
6
pub mod ext_pub_key;
7
pub mod ext_secret_key;
8
pub mod miner_fee;
9
pub mod mnemonic;
10
#[cfg(feature = "mnemonic_gen")]
11
pub mod mnemonic_generator;
12
#[cfg(feature = "std")]
13
pub mod multi_sig;
14
pub mod secret_key;
15
pub mod signing;
16
pub mod tx_builder;
17
pub mod tx_context;
18

19
use crate::ergotree_interpreter::sigma_protocol::prover::hint::{Hint, HintsBag};
20
use alloc::boxed::Box;
21
use alloc::vec::Vec;
22
use ergotree_interpreter::sigma_protocol::private_input::PrivateInput;
23
use ergotree_interpreter::sigma_protocol::prover::Prover;
24
use ergotree_interpreter::sigma_protocol::prover::ProverError;
25
use ergotree_interpreter::sigma_protocol::prover::TestProver;
26
use hashbrown::HashMap;
27
use secret_key::SecretKey;
28
use thiserror::Error;
29

30
use crate::chain::ergo_state_context::ErgoStateContext;
31
use crate::chain::transaction::reduced::reduce_tx;
32
use crate::chain::transaction::reduced::ReducedTransaction;
33
use crate::chain::transaction::unsigned::UnsignedTransaction;
34
#[cfg(feature = "std")]
35
use crate::chain::transaction::Input;
36
use crate::chain::transaction::Transaction;
37
#[cfg(feature = "std")]
38
use crate::ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
39
use crate::wallet::mnemonic::Mnemonic;
40
#[cfg(feature = "std")]
41
use crate::wallet::multi_sig::{generate_commitments, generate_commitments_for};
42

43
use self::ext_secret_key::ExtSecretKey;
44
use self::ext_secret_key::ExtSecretKeyError;
45
use self::signing::sign_reduced_transaction;
46
use self::signing::TransactionContext;
47
use self::signing::TxSigningError;
48
#[cfg(feature = "std")]
49
use self::signing::{make_context, sign_message, sign_transaction, sign_tx_input};
50

51
/// Wallet
52
pub struct Wallet {
53
    prover: Box<dyn Prover + Send + Sync>,
54
}
55

56
/// Wallet errors
57
#[allow(missing_docs)]
58
#[derive(Error, Debug)]
59
pub enum WalletError {
60
    #[error("Transaction signing error: {0}")]
61
    TxSigningError(#[from] TxSigningError),
62

63
    #[error("Prover error: {0}")]
64
    ProverError(#[from] ProverError),
65

66
    #[error("ExtSecretKeyError: {0}")]
67
    ExtSecretKeyError(#[from] ExtSecretKeyError),
68

69
    #[error("error parsing SecretKey from ExtSecretKey.bytes")]
70
    SecretKeyParsingError,
71
}
72

73
impl Wallet {
74
    /// Create wallet instance loading secret key from mnemonic
75
    /// Returns None if a DlogSecretKey cannot be parsed from the provided phrase
76
    pub fn from_mnemonic(
×
77
        mnemonic_phrase: &str,
78
        mnemonic_pass: &str,
79
    ) -> Result<Wallet, WalletError> {
80
        let seed = Mnemonic::to_seed(mnemonic_phrase, mnemonic_pass);
×
81
        let ext_sk = ExtSecretKey::derive_master(seed)?;
×
82
        Ok(Wallet::from_secrets(vec![ext_sk.secret_key()]))
×
83
    }
84

85
    /// Create Wallet from secrets
86
    pub fn from_secrets(secrets: Vec<SecretKey>) -> Wallet {
1✔
87
        let prover = TestProver {
88
            secrets: secrets.into_iter().map(PrivateInput::from).collect(),
1✔
89
        };
90
        Wallet {
91
            prover: Box::new(prover),
92
        }
93
    }
94

95
    /// Add a new secret to the wallet prover
96
    pub fn add_secret(&mut self, secret: SecretKey) {
×
97
        self.prover.append_secret(secret.into())
×
98
    }
99

100
    /// Signs a transaction
101
    #[cfg(feature = "std")]
102
    pub fn sign_transaction(
1✔
103
        &self,
104
        tx_context: TransactionContext<UnsignedTransaction>,
105
        state_context: &ErgoStateContext,
106
        tx_hints: Option<&TransactionHintsBag>,
107
    ) -> Result<Transaction, WalletError> {
108
        sign_transaction(self.prover.as_ref(), tx_context, state_context, tx_hints)
2✔
109
            .map_err(WalletError::from)
110
    }
111

112
    /// Signs a reduced transaction (generating proofs for inputs)
113
    #[cfg(feature = "std")]
114
    pub fn sign_reduced_transaction(
1✔
115
        &self,
116
        reduced_tx: ReducedTransaction,
117
        tx_hints: Option<&TransactionHintsBag>,
118
    ) -> Result<Transaction, WalletError> {
119
        sign_reduced_transaction(self.prover.as_ref(), reduced_tx, tx_hints)
2✔
120
            .map_err(WalletError::from)
121
    }
122

123
    /// Generate commitments for Transaction by wallet secrets
124
    #[cfg(feature = "std")]
125
    pub fn generate_commitments(
×
126
        &self,
127
        tx_context: TransactionContext<UnsignedTransaction>,
128
        state_context: &ErgoStateContext,
129
    ) -> Result<TransactionHintsBag, TxSigningError> {
130
        let public_keys: Vec<SigmaBoolean> = self
×
131
            .prover
132
            .secrets()
133
            .iter()
134
            .map(|secret| secret.public_image())
×
135
            .collect();
136
        generate_commitments(tx_context, state_context, public_keys.as_slice())
×
137
    }
138

139
    /// Generate Commitments for reduced Transaction
140
    #[cfg(feature = "std")]
141
    pub fn generate_commitments_for_reduced_transaction(
×
142
        &self,
143
        reduced_tx: ReducedTransaction,
144
    ) -> Result<TransactionHintsBag, TxSigningError> {
145
        let mut tx_hints = TransactionHintsBag::empty();
×
146
        let public_keys: Vec<SigmaBoolean> = self
×
147
            .prover
148
            .secrets()
149
            .iter()
150
            .map(|secret| secret.public_image())
×
151
            .collect();
152
        for (index, input) in reduced_tx.reduced_inputs().iter().enumerate() {
×
153
            let sigma_prop = input.clone().sigma_prop;
×
154
            let hints = generate_commitments_for(&sigma_prop, &public_keys);
×
155
            tx_hints.add_hints_for_input(index, hints);
×
156
        }
157
        Ok(tx_hints)
×
158
    }
159

160
    /// Generate commitments for P2PK inputs using deterministic nonces. \
161
    /// See: [`Wallet::sign_transaction_deterministic`]
162
    pub fn generate_deterministic_commitments(
1✔
163
        &self,
164
        reduced_tx: &ReducedTransaction,
165
        aux_rand: &[u8],
166
    ) -> Result<TransactionHintsBag, TxSigningError> {
167
        let mut tx_hints = TransactionHintsBag::empty();
1✔
168
        let msg = reduced_tx.unsigned_tx.bytes_to_sign()?;
2✔
169
        for (index, input) in reduced_tx.reduced_inputs().iter().enumerate() {
5✔
170
            if let Some(bag) = self::deterministic::generate_commitments_for(
171
                &*self.prover,
1✔
172
                &input.sigma_prop,
1✔
173
                &msg,
1✔
174
                aux_rand,
175
            ) {
176
                tx_hints.add_hints_for_input(index, bag)
2✔
177
            };
178
        }
179
        Ok(tx_hints)
1✔
180
    }
181

182
    /// Generate signatures for P2PK inputs deterministically
183
    ///
184
    /// Schnorr signatures need an unpredictable nonce added to the signature to avoid private key leakage. Normally this is generated using 32 bytes of entropy, but on platforms where that
185
    /// is not available, `sign_transaction_deterministic` can be used to generate the nonce using a hash of the private key and message. \
186
    /// Additionally `aux_rand` can be optionally supplied with up 32 bytes of entropy.
187
    /// # Limitations
188
    /// Only inputs that reduce to a single public key can be signed. Thus proveDhTuple, n-of-n and t-of-n signatures can not be produced using this method
189
    pub fn sign_transaction_deterministic(
1✔
190
        &self,
191
        tx_context: TransactionContext<UnsignedTransaction>,
192
        state_context: &ErgoStateContext,
193
        aux_rand: &[u8],
194
    ) -> Result<Transaction, WalletError> {
195
        let reduced_tx = reduce_tx(tx_context, state_context)?;
1✔
196
        let hints = self.generate_deterministic_commitments(&reduced_tx, aux_rand)?;
2✔
197
        sign_reduced_transaction(&*self.prover, reduced_tx, Some(&hints)).map_err(From::from)
2✔
198
    }
199

200
    /// Generate signatures for P2PK inputs deterministically
201
    /// See: [`Wallet::sign_transaction_deterministic`]
202
    pub fn sign_reduced_transaction_deterministic(
×
203
        &self,
204
        reduced_tx: ReducedTransaction,
205
        aux_rand: &[u8],
206
    ) -> Result<Transaction, WalletError> {
207
        let hints = self.generate_deterministic_commitments(&reduced_tx, aux_rand)?;
×
208
        sign_reduced_transaction(&*self.prover, reduced_tx, Some(&hints)).map_err(From::from)
×
209
    }
210

211
    /// Signs a message
212
    #[cfg(feature = "std")]
213
    pub fn sign_message(
×
214
        &self,
215
        sigma_tree: SigmaBoolean,
216
        msg: &[u8],
217
    ) -> Result<Vec<u8>, WalletError> {
218
        sign_message(self.prover.as_ref(), sigma_tree, msg).map_err(WalletError::from)
×
219
    }
220

221
    /// Signs a transaction input
222
    #[cfg(feature = "std")]
223
    pub fn sign_tx_input(
×
224
        &self,
225
        input_idx: usize,
226
        tx_context: TransactionContext<UnsignedTransaction>,
227
        state_context: &ErgoStateContext,
228
        tx_hints: Option<&TransactionHintsBag>,
229
    ) -> Result<Input, WalletError> {
230
        let tx = tx_context.spending_tx.clone();
×
231
        let message_to_sign = tx.bytes_to_sign().map_err(TxSigningError::from)?;
×
232
        let mut context =
×
233
            make_context(state_context, &tx_context, input_idx).map_err(TxSigningError::from)?;
234
        Ok(sign_tx_input(
×
235
            self.prover.as_ref(),
×
236
            &tx_context,
237
            state_context,
238
            &mut context,
239
            tx_hints,
240
            input_idx,
241
            message_to_sign.as_slice(),
×
242
        )?)
243
    }
244
}
245

246
#[cfg(feature = "arbitrary")]
247
use proptest::prelude::Strategy;
248
/// TransactionHintsBag
249
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
250
#[cfg_attr(
251
    feature = "json",
252
    serde(
253
        try_from = "crate::chain::json::hint::TransactionHintsBagJson",
254
        into = "crate::chain::json::hint::TransactionHintsBagJson"
255
    )
256
)]
257
#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
258
#[derive(PartialEq, Debug, Clone)]
259
pub struct TransactionHintsBag {
260
    #[cfg_attr(
261
        feature = "arbitrary",
262
        proptest(
263
            strategy = "proptest::collection::hash_map(proptest::prelude::any::<usize>(), proptest::prelude::any::<HintsBag>(), 0..5).prop_map(HashMap::from_iter)"
264
        )
265
    )]
266
    pub(crate) secret_hints: HashMap<usize, HintsBag>,
267
    #[cfg_attr(
268
        feature = "arbitrary",
269
        proptest(
270
            strategy = "proptest::collection::hash_map(proptest::prelude::any::<usize>(), proptest::prelude::any::<HintsBag>(), 0..5).prop_map(HashMap::from_iter)"
271
        )
272
    )]
273
    pub(crate) public_hints: HashMap<usize, HintsBag>,
274
}
275

276
impl TransactionHintsBag {
277
    /// Empty TransactionHintsBag
278
    pub fn empty() -> Self {
1✔
279
        TransactionHintsBag {
280
            secret_hints: HashMap::new(),
1✔
281
            public_hints: HashMap::new(),
1✔
282
        }
283
    }
284

285
    /// Replacing Hints for an input index
286
    pub fn replace_hints_for_input(&mut self, index: usize, hints_bag: HintsBag) {
×
287
        // Separate secret commitments (CommitmentHint) from other hints
NEW
288
        let (secret, public): (Vec<Hint>, Vec<Hint>) = hints_bag.hints
×
289
            .into_iter()
NEW
290
            .partition(|hint| matches!(hint, Hint::CommitmentHint(_)));
×
291

292
        self.secret_hints.insert(index, HintsBag { hints: secret });
×
293
        self.public_hints.insert(index, HintsBag { hints: public });
×
294
    }
295

296
    /// Adding hints for a input index
297
    pub fn add_hints_for_input(&mut self, index: usize, hints_bag: HintsBag) {
1✔
298
        let (secret, public): (Vec<Hint>, Vec<Hint>) = hints_bag.hints
1✔
299
            .into_iter()
300
            .partition(|hint| matches!(hint, Hint::CommitmentHint(_)));
2✔
301

302
        // Get existing hints or empty bags
303
        let empty_bag = HintsBag::empty();
1✔
304
        let mut existing_secret = self.secret_hints.get(&index).unwrap_or(&empty_bag).hints.clone();
2✔
305
        let mut existing_public = self.public_hints.get(&index).unwrap_or(&empty_bag).hints.clone();
2✔
306

307
        // Combine with new hints
308
        existing_secret.extend(secret);
1✔
309
        existing_public.extend(public);
1✔
310

311
        self.secret_hints.insert(index, HintsBag { hints: existing_secret });
1✔
312
        self.public_hints.insert(index, HintsBag { hints: existing_public });
1✔
313
    }
314

315
    /// Outputting HintsBag corresponding for an index
316
    pub fn all_hints_for_input(&self, index: usize) -> HintsBag {
1✔
317
        let empty_bag = HintsBag::empty();
1✔
318
        let mut all_hints = Vec::new();
1✔
319

320
        all_hints.extend(self.secret_hints.get(&index).unwrap_or(&empty_bag).hints.clone());
2✔
321
        all_hints.extend(self.public_hints.get(&index).unwrap_or(&empty_bag).hints.clone());
1✔
322

323
        HintsBag { hints: all_hints }
324
    }
325
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc