ergo_lib/
wallet.rs

1//! Wallet-related features for Ergo
2
3pub mod box_selector;
4pub mod derivation_path;
5pub mod ext_pub_key;
6pub mod ext_secret_key;
7pub mod miner_fee;
8pub mod mnemonic;
9#[cfg(feature = "mnemonic_gen")]
10pub mod mnemonic_generator;
11pub mod multi_sig;
12pub mod secret_key;
13pub mod signing;
14pub mod tx_builder;
15pub mod tx_context;
16
17use ergotree_interpreter::sigma_protocol::private_input::PrivateInput;
18use ergotree_interpreter::sigma_protocol::prover::Prover;
19use ergotree_interpreter::sigma_protocol::prover::ProverError;
20use ergotree_interpreter::sigma_protocol::prover::TestProver;
21use secret_key::SecretKey;
22use signing::{sign_transaction, TxSigningError};
23use thiserror::Error;
24
25use crate::chain::ergo_state_context::ErgoStateContext;
26use crate::chain::transaction::reduced::ReducedTransaction;
27use crate::chain::transaction::unsigned::UnsignedTransaction;
28use crate::chain::transaction::Input;
29use crate::chain::transaction::Transaction;
30use crate::ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
31use crate::wallet::mnemonic::Mnemonic;
32use crate::wallet::multi_sig::{
33    generate_commitments, generate_commitments_for, TransactionHintsBag,
34};
35
36use self::ext_secret_key::ExtSecretKey;
37use self::ext_secret_key::ExtSecretKeyError;
38use self::signing::make_context;
39use self::signing::sign_message;
40use self::signing::sign_reduced_transaction;
41use self::signing::sign_tx_input;
42use self::signing::TransactionContext;
43
44/// Wallet
45pub struct Wallet {
46    prover: Box<dyn Prover>,
47}
48
49/// Wallet errors
50#[allow(missing_docs)]
51#[derive(Error, Debug)]
52pub enum WalletError {
53    #[error("Transaction signing error: {0}")]
54    TxSigningError(#[from] TxSigningError),
55
56    #[error("Prover error: {0}")]
57    ProverError(#[from] ProverError),
58
59    #[error("ExtSecretKeyError: {0}")]
60    ExtSecretKeyError(#[from] ExtSecretKeyError),
61
62    #[error("error parsing SecretKey from ExtSecretKey.bytes")]
63    SecretKeyParsingError,
64}
65
66impl Wallet {
67    /// Create wallet instance loading secret key from mnemonic
68    /// Returns None if a DlogSecretKey cannot be parsed from the provided phrase
69    pub fn from_mnemonic(
70        mnemonic_phrase: &str,
71        mnemonic_pass: &str,
72    ) -> Result<Wallet, WalletError> {
73        let seed = Mnemonic::to_seed(mnemonic_phrase, mnemonic_pass);
74        let ext_sk = ExtSecretKey::derive_master(seed)?;
75        Ok(Wallet::from_secrets(vec![ext_sk.secret_key()]))
76    }
77
78    /// Create Wallet from secrets
79    pub fn from_secrets(secrets: Vec<SecretKey>) -> Wallet {
80        let prover = TestProver {
81            secrets: secrets.into_iter().map(PrivateInput::from).collect(),
82        };
83        Wallet {
84            prover: Box::new(prover),
85        }
86    }
87
88    /// Add a new secret to the wallet prover
89    pub fn add_secret(&mut self, secret: SecretKey) {
90        self.prover.append_secret(secret.into())
91    }
92
93    /// Signs a transaction
94    pub fn sign_transaction(
95        &self,
96        tx_context: TransactionContext<UnsignedTransaction>,
97        state_context: &ErgoStateContext,
98        tx_hints: Option<&TransactionHintsBag>,
99    ) -> Result<Transaction, WalletError> {
100        sign_transaction(self.prover.as_ref(), tx_context, state_context, tx_hints)
101            .map_err(WalletError::from)
102    }
103
104    /// Signs a reduced transaction (generating proofs for inputs)
105    pub fn sign_reduced_transaction(
106        &self,
107        reduced_tx: ReducedTransaction,
108        tx_hints: Option<&TransactionHintsBag>,
109    ) -> Result<Transaction, WalletError> {
110        sign_reduced_transaction(self.prover.as_ref(), reduced_tx, tx_hints)
111            .map_err(WalletError::from)
112    }
113
114    /// Generate commitments for Transaction by wallet secrets
115    pub fn generate_commitments(
116        &self,
117        tx_context: TransactionContext<UnsignedTransaction>,
118        state_context: &ErgoStateContext,
119    ) -> Result<TransactionHintsBag, TxSigningError> {
120        let public_keys: Vec<SigmaBoolean> = self
121            .prover
122            .secrets()
123            .iter()
124            .map(|secret| secret.public_image())
125            .collect();
126        generate_commitments(tx_context, state_context, public_keys.as_slice())
127    }
128
129    /// Generate Commitments for reduced Transaction
130    pub fn generate_commitments_for_reduced_transaction(
131        &self,
132        reduced_tx: ReducedTransaction,
133    ) -> Result<TransactionHintsBag, TxSigningError> {
134        let mut tx_hints = TransactionHintsBag::empty();
135        let public_keys: Vec<SigmaBoolean> = self
136            .prover
137            .secrets()
138            .iter()
139            .map(|secret| secret.public_image())
140            .collect();
141        for (index, input) in reduced_tx.reduced_inputs().iter().enumerate() {
142            let sigma_prop = input.clone().sigma_prop;
143            let hints = generate_commitments_for(&sigma_prop, &public_keys);
144            tx_hints.add_hints_for_input(index, hints);
145        }
146        Ok(tx_hints)
147    }
148
149    /// Signs a message
150    pub fn sign_message(
151        &self,
152        sigma_tree: SigmaBoolean,
153        msg: &[u8],
154    ) -> Result<Vec<u8>, WalletError> {
155        sign_message(self.prover.as_ref(), sigma_tree, msg).map_err(WalletError::from)
156    }
157
158    /// Signs a transaction input
159    pub fn sign_tx_input(
160        &self,
161        input_idx: usize,
162        tx_context: TransactionContext<UnsignedTransaction>,
163        state_context: &ErgoStateContext,
164        tx_hints: Option<&TransactionHintsBag>,
165    ) -> Result<Input, WalletError> {
166        let tx = tx_context.spending_tx.clone();
167        let message_to_sign = tx.bytes_to_sign().map_err(TxSigningError::from)?;
168        let mut context =
169            make_context(state_context, &tx_context, input_idx).map_err(TxSigningError::from)?;
170        Ok(sign_tx_input(
171            self.prover.as_ref(),
172            &tx_context,
173            state_context,
174            &mut context,
175            tx_hints,
176            input_idx,
177            message_to_sign.as_slice(),
178        )?)
179    }
180}