Repository: ergoplatform/sigma-rust (language: rust)

Issue:
BIP-32 support for `Wallet::from_mnemonic`
from ergo discord https://discord.com/channels/668903786361651200/729692906209673257/922992039954632744

>I've deployed a version with the sign from mnemonic buttons at the bottom: http://transaction-builder.ergo.ga/
>The code to sign is visible at: https://github.com/ThierryM1212/transaction-builder/blob/main/src/ergo-related/serializer.js#L163
>Here how to reproduce (34s): https://youtu.be/y6KluUvUsfQ
>The wallet is a 15 words mnemonic created in Yoroi


Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-lib-c-core/src/wallet.rs b/bindings/ergo-lib-c-core/src/wallet.rs
index 67ff21c8c..e01d845c3 100644
--- a/bindings/ergo-lib-c-core/src/wallet.rs
+++ b/bindings/ergo-lib-c-core/src/wallet.rs
@@ -51,6 +51,17 @@ pub unsafe fn wallet_from_secrets(
     Ok(())
 }
 
+/// Add a new secret to the wallets prover
+pub unsafe fn wallet_add_secret(
+    wallet_ptr: WalletPtr,
+    secret_key_ptr: *mut SecretKey,
+) -> Result<(), Error> {
+    let wallet = mut_ptr_as_mut(wallet_ptr, "wallet_ptr")?;
+    let sk = mut_ptr_as_mut(secret_key_ptr, "secret_key_ptr")?;
+    wallet.0.add_secret(sk.0.clone());
+    Ok(())
+}
+
 /// Signs a transaction
 pub unsafe fn wallet_sign_transaction(
     wallet_ptr: ConstWalletPtr,
diff --git a/bindings/ergo-lib-c/src/wallet.rs b/bindings/ergo-lib-c/src/wallet.rs
index ae749f77f..3cedae2df 100644
--- a/bindings/ergo-lib-c/src/wallet.rs
+++ b/bindings/ergo-lib-c/src/wallet.rs
@@ -39,6 +39,15 @@ pub unsafe extern "C" fn ergo_lib_wallet_from_secrets(
     wallet_from_secrets(secret_keys_ptr, wallet_out).unwrap();
 }
 
+#[no_mangle]
+pub unsafe extern "C" fn ergo_lib_wallet_add_secret(
+    wallet_ptr: WalletPtr,
+    secret_key_ptr: *mut SecretKey,
+) -> ErrorPtr {
+    let res = wallet_add_secret(wallet_ptr, secret_key_ptr);
+    Error::c_api_from(res)
+}
+
 /// Signs a transaction
 #[no_mangle]
 pub unsafe extern "C" fn ergo_lib_wallet_sign_transaction(
diff --git a/bindings/ergo-lib-ios/Sources/ErgoLib/Wallet.swift b/bindings/ergo-lib-ios/Sources/ErgoLib/Wallet.swift
index 33e7f89a1..e1e6a99f1 100644
--- a/bindings/ergo-lib-ios/Sources/ErgoLib/Wallet.swift
+++ b/bindings/ergo-lib-ios/Sources/ErgoLib/Wallet.swift
@@ -24,6 +24,12 @@ class Wallet {
         ergo_lib_wallet_from_secrets(secrets.pointer, &ptr)
         self.pointer = ptr!
     }
+
+    /// Add a secret to the wallets prover
+    func addSecret(secret: SecretKey) throws {
+        let error = ergo_lib_wallet_add_secret(self.pointer, secret.pointer)
+        try checkError(error)
+    }
     
     /// Sign a transaction
     func signTransaction(
diff --git a/bindings/ergo-lib-wasm/src/wallet.rs b/bindings/ergo-lib-wasm/src/wallet.rs
index d240862c3..da0151cdd 100644
--- a/bindings/ergo-lib-wasm/src/wallet.rs
+++ b/bindings/ergo-lib-wasm/src/wallet.rs
@@ -9,8 +9,12 @@ pub mod ext_secret_key;
 pub mod mnemonic;
 
 use crate::{
-    box_coll::ErgoBoxes, ergo_state_ctx::ErgoStateContext, error_conversion::to_js,
-    secret_key::SecretKeys, transaction::reduced::ReducedTransaction, transaction::Transaction,
+    box_coll::ErgoBoxes,
+    ergo_state_ctx::ErgoStateContext,
+    error_conversion::to_js,
+    secret_key::{SecretKey, SecretKeys},
+    transaction::reduced::ReducedTransaction,
+    transaction::Transaction,
     transaction::UnsignedTransaction,
 };
 
@@ -34,6 +38,12 @@ impl Wallet {
         Wallet(ergo_lib::wallet::Wallet::from_secrets(secret.into()))
     }
 
+    /// Add a secret to the wallets prover
+    #[wasm_bindgen]
+    pub fn add_secret(&mut self, secret: &SecretKey) {
+        self.0.add_secret(secret.clone().into());
+    }
+
     /// Sign a transaction:
     /// `tx` - transaction to sign
     /// `boxes_to_spend` - boxes corresponding to [`UnsignedTransaction::inputs`]
diff --git a/ergo-lib/src/wallet.rs b/ergo-lib/src/wallet.rs
index ade47d843..ab5940929 100644
--- a/ergo-lib/src/wallet.rs
+++ b/ergo-lib/src/wallet.rs
@@ -23,6 +23,7 @@ use crate::chain::transaction::reduced::ReducedTransaction;
 use crate::chain::transaction::Transaction;
 use crate::wallet::mnemonic::Mnemonic;
 
+use self::ext_secret_key::ExtSecretKey;
 use self::signing::sign_reduced_transaction;
 use self::signing::TransactionContext;
 
@@ -50,9 +51,8 @@ impl Wallet {
     /// Returns None if a DlogSecretKey cannot be parsed from the provided phrase
     pub fn from_mnemonic(mnemonic_phrase: &str, mnemonic_pass: &str) -> Option<Wallet> {
         let seed = Mnemonic::to_seed(mnemonic_phrase, mnemonic_pass);
-        let mut dlog_bytes: [u8; 32] = Default::default();
-        dlog_bytes.copy_from_slice(&seed[..32]);
-        let secret = SecretKey::dlog_from_bytes(&dlog_bytes)?;
+        let ext_sk = ExtSecretKey::derive_master(seed).ok()?;
+        let secret = SecretKey::dlog_from_bytes(&ext_sk.secret_key_bytes())?;
 
         Some(Wallet::from_secrets(vec![secret]))
     }
@@ -67,6 +67,11 @@ impl Wallet {
         }
     }
 
+    /// Add a new secret to the wallet prover
+    pub fn add_secret(&mut self, secret: SecretKey) {
+        self.prover.append_secret(secret.into())
+    }
+
     /// Signs a transaction
     pub fn sign_transaction(
         &self,
diff --git a/ergotree-interpreter/src/sigma_protocol/prover.rs b/ergotree-interpreter/src/sigma_protocol/prover.rs
index 46e39140e..c943ebc12 100644
--- a/ergotree-interpreter/src/sigma_protocol/prover.rs
+++ b/ergotree-interpreter/src/sigma_protocol/prover.rs
@@ -108,6 +108,9 @@ pub trait Prover {
     /// Secrets of the prover
     fn secrets(&self) -> &[PrivateInput];
 
+    /// Add an extra secret to the prover
+    fn append_secret(&mut self, input: PrivateInput);
+
     /// The comments in this section are taken from the algorithm for the
     /// Sigma-protocol prover as described in the ErgoScript white-paper
     /// <https://ergoplatform.org/docs/ErgoScript.pdf>, Appendix A
@@ -1131,6 +1134,10 @@ impl Prover for TestProver {
     fn secrets(&self) -> &[PrivateInput] {
         self.secrets.as_ref()
     }
+
+    fn append_secret(&mut self, input: PrivateInput) {
+        self.secrets.push(input)
+    }
 }
 
 #[allow(clippy::unwrap_used)]


Test patch: