Skip to content

Commit 6152304

Browse files
fix(key-wallet): refuse DIP-14 256-bit children in BLS and SLIP-10 derivation (#1051)
ExtendedBLSPrivKey / ExtendedBLSPubKey and ExtendedEd25519PrivKey hash the child index as u32::from(child), which maps every Normal256 / Hardened256 child to u32::MAX. A path with a 256-bit level therefore derived one key for every identifier at that level, with no error. DIP-14 defines 256-bit children for secp256k1 only, and dashbls / SLIP-10 have 32-bit indices, so these derivers now refuse such a child: InvalidDerivationPath for BLS (private and public), InvalidChildNumberFormat for Ed25519. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent adca78d commit 6152304

2 files changed

Lines changed: 50 additions & 0 deletions

File tree

‎key-wallet/src/derivation_bls_bip32.rs‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,12 @@ impl ExtendedBLSPrivKey {
224224
child: ChildNumber,
225225
format: BlsScheme,
226226
) -> Result<Self, Error> {
227+
// DIP-14 256-bit children are defined for secp256k1 only; dashbls has
228+
// no 256-bit index, and `u32::from` would collapse every one to the same key.
229+
if child.is_256_bits() {
230+
return Err(Error::InvalidDerivationPath);
231+
}
232+
227233
// Build the input data for HMAC, following dashbls
228234
// `ExtendedPrivateKey::PrivateChild` (extendedprivatekey.cpp)
229235
let mut input_data = Vec::new();
@@ -429,6 +435,10 @@ impl ExtendedBLSPubKey {
429435
if child.is_hardened() {
430436
return Err(Error::CannotDeriveFromHardenedPublic);
431437
}
438+
// See `derive_priv_with_mode`.
439+
if child.is_256_bits() {
440+
return Err(Error::InvalidDerivationPath);
441+
}
432442

433443
// Build the input data for HMAC: public_key || index — matches
434444
// dashbls `ExtendedPublicKey::PublicChild`, whose fLegacy flag
@@ -1743,4 +1753,22 @@ mod tests {
17431753
assert_eq!(key.depth, 0);
17441754
assert_eq!(key.parent_fingerprint, Fingerprint::default());
17451755
}
1756+
1757+
/// A 32-bit index cannot hold a DIP-14 256-bit child, so derivation refuses one instead of
1758+
/// deriving every such child to the same key.
1759+
#[test]
1760+
fn test_256_bit_children_are_refused() {
1761+
let master = master_from_seed64();
1762+
let hardened = ChildNumber::from_hardened_idx_256([0x35; 32]);
1763+
let normal = ChildNumber::from_normal_idx_256([0x35; 32]);
1764+
1765+
assert!(matches!(master.derive_priv(hardened), Err(Error::InvalidDerivationPath)));
1766+
assert!(matches!(master.derive_priv(normal), Err(Error::InvalidDerivationPath)));
1767+
assert!(matches!(
1768+
master.to_extended_pub_key().unwrap().derive_pub(normal),
1769+
Err(Error::InvalidDerivationPath)
1770+
));
1771+
let path = DerivationPath::from(vec![ChildNumber::from_hardened_idx(9).unwrap(), hardened]);
1772+
assert!(matches!(master.derive_path(&path), Err(Error::InvalidDerivationPath)));
1773+
}
17461774
}

‎key-wallet/src/derivation_slip10.rs‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ impl ExtendedEd25519PrivKey {
150150
if !child.is_hardened() {
151151
return Err(Error::NonHardenedNotSupported);
152152
}
153+
// SLIP-10 indices are 32-bit; `u32::from` would collapse every DIP-14
154+
// 256-bit child to the same key.
155+
if child.is_256_bits() {
156+
return Err(Error::InvalidChildNumberFormat);
157+
}
153158

154159
let mut hmac_engine: HmacEngine<sha512::Hash> = HmacEngine::new(self.chain_code.as_ref());
155160

@@ -835,4 +840,21 @@ mod test {
835840
other_network.network = Network::Testnet;
836841
assert_ne!(master, other_network);
837842
}
843+
844+
/// SLIP-10 indices are 32-bit, so a DIP-14 256-bit child is refused instead of deriving every
845+
/// such child to the same key.
846+
#[test]
847+
fn test_256_bit_children_are_refused() {
848+
let master = ExtendedEd25519PrivKey::new_master(
849+
Network::Mainnet,
850+
&hex::decode(CASE_1_SEED).unwrap(),
851+
)
852+
.unwrap();
853+
let child = ChildNumber::from_hardened_idx_256([0x35; 32]);
854+
assert!(matches!(master.ckd_priv(child), Err(Error::InvalidChildNumberFormat)));
855+
assert!(matches!(
856+
master.derive_priv(&[ChildNumber::from_hardened_idx(9).unwrap(), child]),
857+
Err(Error::InvalidChildNumberFormat)
858+
));
859+
}
838860
}

0 commit comments

Comments
 (0)