Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ anyhow = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
rand = "0.8"
proptest = "1"

# --- internal crates ---
octo-crypto = { path = "crates/crypto" }
Expand Down
1 change: 1 addition & 0 deletions crates/wallet-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ hex.workspace = true
sha2.workspace = true

[dev-dependencies]
proptest = { workspace = true }
50 changes: 50 additions & 0 deletions crates/wallet-core/src/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ impl WalletSeed {
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
use stellar_strkey::ed25519::PublicKey;

// Official SEP-0005 Test 1 vector (no passphrase).
Expand Down Expand Up @@ -122,4 +123,53 @@ mod tests {
Err(WalletError::InvalidMnemonic)
));
}

proptest! {
#[test]
fn derivation_is_deterministic_for_any_index(
entropy in any::<[u8; 16]>(),
index in any::<u32>()
) {
let mnemonic =
bip39::Mnemonic::from_entropy(&entropy, bip39::Language::English).unwrap();
let seed_bytes = bip39::Seed::new(&mnemonic, "").as_bytes().to_vec();
let seed_a = WalletSeed::from_bytes(seed_bytes.clone());
let seed_b = WalletSeed::from_bytes(seed_bytes);
let secret_a = seed_a.derive_ed25519_secret(index);
let secret_b = seed_b.derive_ed25519_secret(index);
prop_assert_eq!(*secret_a, *secret_b);
}

#[test]
fn distinct_indices_yield_distinct_secrets(
entropy in any::<[u8; 16]>(),
index_a in any::<u32>(),
index_b in any::<u32>()
) {
prop_assume!(index_a != index_b);
let mnemonic =
bip39::Mnemonic::from_entropy(&entropy, bip39::Language::English).unwrap();
let seed =
WalletSeed::from_bytes(bip39::Seed::new(&mnemonic, "").as_bytes().to_vec());
let secret_a = seed.derive_ed25519_secret(index_a);
let secret_b = seed.derive_ed25519_secret(index_b);
prop_assert_ne!(*secret_a, *secret_b);
}
}

#[test]
fn boundary_indices_derive_without_panic() {
let seed = WalletSeed::from_phrase(VECTOR_MNEMONIC).unwrap();
// Exercises the hardened-offset OR-mask at the extreme ends of u32:
// 0, 1 (lowest valid indices), HARDENED-1 (highest non-hardened u32 value),
// and u32::MAX (wraps the OR-mask into the already-set upper bit).
for &index in &[0u32, 1, super::HARDENED - 1, u32::MAX] {
let secret = seed.derive_ed25519_secret(index);
let signing = ed25519_dalek::SigningKey::from_bytes(&secret);
let pk = PublicKey(signing.verifying_key().to_bytes());
let encoded = format!("{pk}");
let decoded = PublicKey::from_string(&encoded).unwrap();
assert_eq!(decoded.0, signing.verifying_key().to_bytes());
}
}
}
18 changes: 9 additions & 9 deletions crates/wallet-core/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ mod tests {
&sealed,
StellarNetwork::Testnet,
0,
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: 200, sequence: 0 },
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: 200 },
)
.unwrap();

Expand Down Expand Up @@ -631,7 +631,7 @@ mod tests {
&sealed,
StellarNetwork::Testnet,
0,
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: 200, sequence: 0 },
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: 200 },
)
.unwrap();

Expand All @@ -658,7 +658,7 @@ mod tests {
&sealed,
StellarNetwork::Testnet,
0,
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: 200, sequence: 0 },
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: 200 },
)
.unwrap();

Expand All @@ -682,7 +682,7 @@ mod tests {
&sealed,
StellarNetwork::Testnet,
0,
&FeeBumpRequest { inner_xdr: "", max_base_fee_stroops: 200, sequence: 0 },
&FeeBumpRequest { inner_xdr: "", max_base_fee_stroops: 200 },
),
Err(WalletError::InvalidXdr)
));
Expand All @@ -701,7 +701,7 @@ mod tests {
&sealed,
StellarNetwork::Testnet,
0,
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: 200, sequence: 0 },
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: 200 },
)
.unwrap()
.envelope_xdr;
Expand All @@ -713,7 +713,7 @@ mod tests {
&sealed,
StellarNetwork::Testnet,
0,
&FeeBumpRequest { inner_xdr: &fee_bump_xdr, max_base_fee_stroops: 200, sequence: 0 },
&FeeBumpRequest { inner_xdr: &fee_bump_xdr, max_base_fee_stroops: 200 },
),
Err(WalletError::InvalidXdr)
));
Expand All @@ -731,7 +731,7 @@ mod tests {
&mainnet_sealed,
StellarNetwork::Testnet,
0,
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: 200, sequence: 0 },
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: 200 },
),
Err(WalletError::SeedDecryption)
));
Expand All @@ -750,7 +750,7 @@ mod tests {
&sealed,
StellarNetwork::Testnet,
0,
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: max_base_fee, sequence: 0 },
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: max_base_fee },
)
.unwrap();

Expand All @@ -776,7 +776,7 @@ mod tests {
&sealed,
StellarNetwork::Testnet,
0,
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: 200, sequence: 0 },
&FeeBumpRequest { inner_xdr: &inner_xdr, max_base_fee_stroops: 200 },
);
assert!(result.is_ok());
assert_eq!(result.unwrap().source_account, MASTER_ACCOUNT_0);
Expand Down
Loading