Skip to content

Commit

Permalink
Merge pull request zingolabs#474 from nerdcash/mnemonicAndPosition
Browse files Browse the repository at this point in the history
Support non-zero account indexes
  • Loading branch information
AloeareV authored Sep 28, 2023
2 parents f19a414 + c598735 commit 33daec1
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 26 deletions.
12 changes: 5 additions & 7 deletions zingocli/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1144,11 +1144,7 @@ async fn handling_of_nonregenerated_diversified_addresses_after_seed_restore() {
.to_string(),
);
let recipient_restored = client_builder
.build_newseed_client(
seed_of_recipient["seed"].as_str().unwrap().to_string(),
0,
true,
)
.build_newseed_client(seed_of_recipient.seed_phrase.clone(), 0, true)
.await;
let seed_of_recipient_restored = {
recipient_restored.do_sync(true).await.unwrap();
Expand Down Expand Up @@ -1528,10 +1524,12 @@ async fn load_wallet_from_v26_dat_file() {
.map_err(|e| format!("Cannot deserialize LightWallet version 26 file: {}", e))
.unwrap();

let expected_mnemonic = Mnemonic::from_phrase(TEST_SEED.to_string()).unwrap();
let expected_mnemonic = (Mnemonic::from_phrase(TEST_SEED.to_string()).unwrap(), 0);
assert_eq!(wallet.mnemonic(), Some(&expected_mnemonic));

let expected_wc = WalletCapability::new_from_phrase(&config, &expected_mnemonic, 0).unwrap();
let expected_wc =
WalletCapability::new_from_phrase(&config, &expected_mnemonic.0, expected_mnemonic.1)
.unwrap();
let wc = wallet.wallet_capability();

// We don't want the WalletCapability to impl. `Eq` (because it stores secret keys)
Expand Down
6 changes: 5 additions & 1 deletion zingolib/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,11 @@ impl Command for SeedCommand {
fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String {
RT.block_on(async move {
match lightclient.do_seed_phrase().await {
Ok(j) => j,
Ok(m) => object! {
"seed" => m.seed_phrase,
"birthday" => m.birthday,
"account_index" => m.account_index,
},
Err(e) => object! { "error" => e },
}
.pretty(2)
Expand Down
20 changes: 14 additions & 6 deletions zingolib/src/lightclient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,13 @@ impl PoolBalances {
}
}

#[derive(Clone, Debug, PartialEq)]
pub struct AccountBackupInfo {
pub seed_phrase: String,
pub birthday: u64,
pub account_index: u32,
}

impl LightClient {
fn add_nonchange_notes<'a, 'b, 'c>(
&'a self,
Expand Down Expand Up @@ -976,17 +983,18 @@ impl LightClient {
.block_on(async move { self.do_save_to_buffer().await })
}

pub async fn do_seed_phrase(&self) -> Result<JsonValue, &str> {
pub async fn do_seed_phrase(&self) -> Result<AccountBackupInfo, &str> {
match self.wallet.mnemonic() {
Some(m) => Ok(object! {
"seed" => m.to_string(),
"birthday" => self.wallet.get_birthday().await
Some(m) => Ok(AccountBackupInfo {
seed_phrase: m.0.phrase().to_string(),
birthday: self.wallet.get_birthday().await,
account_index: m.1,
}),
None => Err("This wallet is watch-only."),
None => Err("This wallet is watch-only or was created without a mnemonic."),
}
}

pub fn do_seed_phrase_sync(&self) -> Result<JsonValue, &str> {
pub fn do_seed_phrase_sync(&self) -> Result<AccountBackupInfo, &str> {
Runtime::new()
.unwrap()
.block_on(async move { self.do_seed_phrase().await })
Expand Down
60 changes: 48 additions & 12 deletions zingolib/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ pub enum WalletBase {
SeedBytes([u8; 32]),
MnemonicPhrase(String),
Mnemonic(Mnemonic),
SeedBytesAndIndex([u8; 32], u32),
MnemonicPhraseAndIndex(String, u32),
MnemonicAndIndex(Mnemonic, u32),
/// Unified full viewing key
Ufvk(String),
/// Unified spending key
Expand All @@ -207,9 +210,10 @@ pub struct LightWallet {
// will start from here.
birthday: AtomicU64,

/// The seed for the wallet, stored as a bip0039 Mnemonic
/// Can be `None` in case of wallet without spending capability.
mnemonic: Option<Mnemonic>,
/// The seed for the wallet, stored as a bip0039 Mnemonic, and the account index.
/// Can be `None` in case of wallet without spending capability
/// or created directly from spending keys.
mnemonic: Option<(Mnemonic, u32)>,

// The last 100 blocks, used if something gets re-orged
pub blocks: Arc<RwLock<Vec<BlockData>>>,
Expand Down Expand Up @@ -562,7 +566,7 @@ impl LightWallet {
}
}

pub fn mnemonic(&self) -> Option<&Mnemonic> {
pub fn mnemonic(&self) -> Option<&(Mnemonic, u32)> {
self.mnemonic.as_ref()
}

Expand All @@ -576,15 +580,29 @@ impl LightWallet {
return Self::new(config, WalletBase::SeedBytes(seed_bytes), height);
}
WalletBase::SeedBytes(seed_bytes) => {
return Self::new(config, WalletBase::SeedBytesAndIndex(seed_bytes, 0), height);
}
WalletBase::SeedBytesAndIndex(seed_bytes, position) => {
let mnemonic = Mnemonic::from_entropy(seed_bytes).map_err(|e| {
Error::new(
ErrorKind::InvalidData,
format!("Error parsing phrase: {}", e),
)
})?;
return Self::new(config, WalletBase::Mnemonic(mnemonic), height);
return Self::new(
config,
WalletBase::MnemonicAndIndex(mnemonic, position),
height,
);
}
WalletBase::MnemonicPhrase(phrase) => {
return Self::new(
config,
WalletBase::MnemonicPhraseAndIndex(phrase, 0),
height,
);
}
WalletBase::MnemonicPhraseAndIndex(phrase, position) => {
let mnemonic = Mnemonic::from_phrase(phrase)
.and_then(|m| Mnemonic::from_entropy(m.entropy()))
.map_err(|e| {
Expand All @@ -597,12 +615,19 @@ impl LightWallet {
// should be a no-op, but seems to be needed on android for some reason
// TODO: Test the this cfg actually works
//#[cfg(target_os = "android")]
return Self::new(config, WalletBase::Mnemonic(mnemonic), height);
return Self::new(
config,
WalletBase::MnemonicAndIndex(mnemonic, position),
height,
);
}
WalletBase::Mnemonic(mnemonic) => {
let wc = WalletCapability::new_from_phrase(&config, &mnemonic, 0)
return Self::new(config, WalletBase::MnemonicAndIndex(mnemonic, 0), height);
}
WalletBase::MnemonicAndIndex(mnemonic, position) => {
let wc = WalletCapability::new_from_phrase(&config, &mnemonic, position)
.map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
(wc, Some(mnemonic))
(wc, Some((mnemonic, position)))
}
WalletBase::Ufvk(ufvk_encoded) => {
let wc = WalletCapability::new_from_ufvk(&config, ufvk_encoded).map_err(|e| {
Expand Down Expand Up @@ -799,10 +824,16 @@ impl LightWallet {

let seed_bytes = Vector::read(&mut reader, |r| r.read_u8())?;
let mnemonic = if !seed_bytes.is_empty() {
Some(
let account_index = if external_version >= 28 {
reader.read_u32::<LittleEndian>()?
} else {
0
};
Some((
Mnemonic::from_entropy(seed_bytes)
.map_err(|e| Error::new(ErrorKind::InvalidData, e.to_string()))?,
)
account_index,
))
} else {
None
};
Expand Down Expand Up @@ -1434,7 +1465,7 @@ impl LightWallet {
}

pub const fn serialized_version() -> u64 {
27
28
}

pub async fn set_blocks(&self, new_blocks: Vec<BlockData>) {
Expand Down Expand Up @@ -1679,11 +1710,16 @@ impl LightWallet {
self.price.read().await.write(&mut writer)?;

let seed_bytes = match &self.mnemonic {
Some(m) => m.clone().into_entropy(),
Some(m) => m.0.clone().into_entropy(),
None => vec![],
};
Vector::write(&mut writer, &seed_bytes, |w, byte| w.write_u8(*byte))?;

match &self.mnemonic {
Some(m) => writer.write_u32::<LittleEndian>(m.1)?,
None => (),
}

Ok(())
}
}
Expand Down

0 comments on commit 33daec1

Please sign in to comment.