Skip to content

Commit 9751c3a

Browse files
committed
ckd: reduce IL modulo N before range check
Restore pre-v3.0.0 behavior of applying ilNum.Mod(N) before the range check in DeriveChildKey. Required for non-hardened CKD to work on Edwards curves where N ≈ 2^252 and ~94%% of raw 256-bit HMAC outputs exceed N. v3.0.0 made the check strict, matching BIP-32 spec, which mandates retrying with the next index when IL >= N. This library does not implement retry (non-hardened Ed25519 CKD is non-standard anyway per SLIP-0010), so the strict check would make EdDSA CKD effectively unusable. Trade-off: introduces a modular bias of ~2^-4 on derived child public keys. Bias is washed out on private shares by the uniform parent share so does not enable key recovery or forgery. Preserves address compatibility with production wallets derived under prior library versions. Tests verified: - crypto/ckd: PASS (BIP-32 test vectors for secp256k1) - eddsa/signing TestE2EConcurrentWithHDDerive: PASS - ecdsa/signing TestE2EWithHDKeyDerivation: PASS
1 parent 2f5ab80 commit 9751c3a

1 file changed

Lines changed: 10 additions & 0 deletions

File tree

crypto/ckd/child_key_derivation.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,16 @@ func DeriveChildKey(index uint32, pk *ExtendedKey, curve elliptic.Curve) (*big.I
227227
il := ilr[:32]
228228
childChainCode := ilr[32:]
229229
ilNum := new(big.Int).SetBytes(il)
230+
// Reduce IL modulo N before the range check. On Edwards curves (Ed25519)
231+
// N ≈ 2^252, so ~94% of raw 256-bit HMAC outputs exceed N; strict rejection
232+
// would make non-hardened CKD effectively unusable. BIP-32 spec says to
233+
// retry with the next index, but non-hardened derivation on Ed25519 is
234+
// non-standard to begin with (SLIP-0010 specifies hardened-only) and the
235+
// retry semantics would also change addresses vs. prior library versions.
236+
// The modular bias introduced here is ~2^-4 on the child public key but
237+
// is washed out on private shares by the uniform parent share, so it does
238+
// not enable key recovery or forgery
239+
ilNum = ilNum.Mod(ilNum, curve.Params().N)
230240

231241
if ilNum.Cmp(curve.Params().N) >= 0 || ilNum.Sign() == 0 {
232242
// falling outside of the valid range for curve private keys

0 commit comments

Comments
 (0)