Context
We use blockchain_utils (currently 7.1.0) as the signing backend for an air-gapped Ethereum cold wallet. Private key material is a high-value target, and we zeroize every buffer we own as soon as it's no longer needed. We ran into a case where we cannot do that for keys held inside this package's own objects.
Problem
ECDSAPrivateKey stores the key scalar as an immutable field:
// crypto/crypto/ec/ecdsa/private_key.dart
class ECDSAPrivateKey {
final BigInt secretMultiplier;
...
}
.raw / .toBytes() (and the same for Bip32PrivateKey.raw) compute a fresh List<int> from secretMultiplier on every call rather than exposing a live mutable buffer. This means:
- Callers can zeroize the
List<int> they get back from .raw, but that has no effect on the key material actually retained by the ECDSAPrivateKey/Bip32PrivateKey/Bip32/Bip44 object itself.
- There is currently no API to clear that retained state (confirmed via a full grep of the package source for
fillRange/zeroiz/wipe/destroy — no hits in any key/signer class).
- This also applies to signer construction:
ETHSigner.fromKeyBytes(privateKey) builds its own independent ECDSAPrivateKey internally, which is equally unclearable, even after the caller zeroizes its own input buffer.
So any application holding a Bip32/Bip44/ECDSAPrivateKey (e.g. after deriving an address-level key) has no way to remove the raw private key from memory before that object is garbage-collected, regardless of how careful it is with its own buffers.
Ask
A minimal API to explicitly clear retained private key material, e.g.:
class ECDSAPrivateKey {
BigInt secretMultiplier; // non-final, or backed by a mutable buffer
void zeroize() { secretMultiplier = BigInt.zero; /* or overwrite backing bytes */ }
}
with the equivalent exposed on Bip32PrivateKey/Bip32/Bip44 (zeroizing the whole derivation chain, since parent/master keys are also retained).
Happy to discuss the right shape for this (e.g. keeping BigInt but adding an explicit invalidation flag vs. switching the internal representation to a mutable byte buffer) — opening this primarily to confirm whether this is something upstream would accept, before proposing an implementation.
Environment
blockchain_utils 7.1.0
- Reported from a downstream security review of a cold-wallet application; full source-level analysis of
.raw/.toBytes() behavior available on request.
Context
We use
blockchain_utils(currently 7.1.0) as the signing backend for an air-gapped Ethereum cold wallet. Private key material is a high-value target, and we zeroize every buffer we own as soon as it's no longer needed. We ran into a case where we cannot do that for keys held inside this package's own objects.Problem
ECDSAPrivateKeystores the key scalar as an immutable field:.raw/.toBytes()(and the same forBip32PrivateKey.raw) compute a freshList<int>fromsecretMultiplieron every call rather than exposing a live mutable buffer. This means:List<int>they get back from.raw, but that has no effect on the key material actually retained by theECDSAPrivateKey/Bip32PrivateKey/Bip32/Bip44object itself.fillRange/zeroiz/wipe/destroy— no hits in any key/signer class).ETHSigner.fromKeyBytes(privateKey)builds its own independentECDSAPrivateKeyinternally, which is equally unclearable, even after the caller zeroizes its own input buffer.So any application holding a
Bip32/Bip44/ECDSAPrivateKey(e.g. after deriving an address-level key) has no way to remove the raw private key from memory before that object is garbage-collected, regardless of how careful it is with its own buffers.Ask
A minimal API to explicitly clear retained private key material, e.g.:
with the equivalent exposed on
Bip32PrivateKey/Bip32/Bip44(zeroizing the whole derivation chain, since parent/master keys are also retained).Happy to discuss the right shape for this (e.g. keeping
BigIntbut adding an explicit invalidation flag vs. switching the internal representation to a mutable byte buffer) — opening this primarily to confirm whether this is something upstream would accept, before proposing an implementation.Environment
blockchain_utils7.1.0.raw/.toBytes()behavior available on request.