Back to Blog

Beyond Secure Storage, Part 2: Implementing Wallet Cryptography Securely

Code Auditing
November 11, 2026
16 min read
Key Insights
  • A private key can be recoverable without its stored copy being read.

  • Six incidents reveal how weak randomness, entropy loss and signing nonce flaws undermine protected keys.

  • Payment institutions need to verify that their implementations preserve unpredictability during generation and secrecy during signing.

Part 1 of Beyond Secure Storage examined misuse of a payment system's own signing capability. This second part examines a different route to unauthorized signatures: cryptographic implementation flaws that let an attacker recover a private key and sign independently. The question is: can an attacker reconstruct a key without reading its protected copy?

Section 1 establishes the security requirements for key generation, storage and signing. Sections 2 and 3 examine four key-generation incidents and two signing incidents, explaining the implementation errors and their consequences. Section 4 draws design and implementation principles from those cases, then explains how to verify the required properties.

1. Security Properties Across the Key Lifecycle

A key must be generated, stored and used to sign. Each stage has a different security requirement, as the diagram shows.

In a hierarchical deterministic wallet using BIP-39 mnemonics and BIP-32 key derivation, cryptographically secure random data is encoded into a mnemonic phrase and converted into a seed. That seed is used to derive private keys, public keys and wallet addresses [1, 2].

Once generated, seeds and private keys need protection through encryption or hardware isolation. Signing then uses the private key to produce a publicly verifiable signature. Depending on the scheme, it may also require a securely generated random nonce or one derived deterministically from secret material.

The security requirements differ across these stages:

  1. Unpredictability at creation. An attacker must not be able to predict or feasibly enumerate the secret that controls the wallet.
  2. Confidentiality in storage. Unauthorized parties must not be able to read secret material through software, physical interfaces, backups, logs or remote services.
  3. Non-leakage during use. Signing must not disclose enough information to reconstruct the secret, even after many publicly observable signatures.

Hardware wallets, secure elements and trusted execution environments (TEEs) can strengthen confidentiality. Their presence alone does not establish the other two properties. A predictable key remains predictable after it is placed in secure storage; mathematical information exposed by signatures remains available even when the private-key bytes are isolated from the host. An attacker may therefore recover an affected key and access the assets it controls without reading its stored copy.

The next two sections examine how implementations violate these requirements: generation flaws can make keys predictable, while signing flaws can make them recoverable from public signatures. These cases provide the basis for the implementation principles and verification objectives in Section 4.

2. Flawed Key Generation: Predictable Private Keys

Key generation must preserve unpredictability at every step. The entropy source must be suitable for cryptographic use, its state must not be practically enumerable, and conversions from random bytes to a seed or private key must preserve the intended uncertainty. A 128-bit security target requires a secret space that cannot be reduced to a feasible search through implementation flaws.

The table summarizes four ways that generation can lose this property: a weak library generator, an incorrect random-number generator (RNG) binding, a lossy input conversion and a generator with a limited initial state. The impact figures come from reports covering different periods; they are not totals measured at the same date.

Incident Relevant period Reported impact (approx.) Failure mode explored below
Ill Bloom May-July 2026 ~$5M lower bound as of July 13 Weak randomness in mnemonic generation
Coldcard From July 30, 2026; tally August 14 $112.70M Incorrect RNG path in firmware
XRPH Wallet September 3, 2026 $0.45M Predictable, severely restricted wallet-generation inputs
Qubic Wallet October 17-18, 2026 $1.3M Insecure seed generation using Math.random()

2.1 Ill Bloom: Weak Randomness in a Wallet Dependency

In the Ill Bloom incident, some software wallets generated BIP-39 recovery phrases using vulnerable versions of crypto-js. The library's CryptoJS.lib.WordArray.random() used a custom Multiply-With-Carry (MWC) generator initialized with Math.random() instead of drawing entropy from a cryptographically secure source. Wallets that use this function as a security-sensitive entropy source inherited the generator's weaknesses, even if the later mnemonic and key-derivation steps followed their standards correctly [3].

The vulnerable crypto-js v3.1.9-1 implementation [4] contains this characteristic logic:

  random: function (nBytes) {
      var words = [];

      var r = (function (m_w) {
          var m_w = m_w;
          var m_z = 0x3ade68b1;
          var mask = 0xffffffff;

          return function () {
              m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask;
              m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask;
              var result = ((m_z << 0x10) + m_w) & mask;
              result /= 0x100000000;
              result += 0.5;
              return result * (Math.random() > .5 ? 1 : -1);
          }
      });

      for (var i = 0, rcache; i < nBytes; i += 4) {
          var _r = r((rcache || Math.random()) * 0x100000000);

          rcache = _r() * 0x3ade67b7;
          words.push((_r() * 0x100000000) | 0);
      }

      return new WordArray.init(words, nBytes);
  }

Here, r() is the custom MWC-based generator, while Math.random() helps initialize or influence its state. This pseudorandom number generator (PRNG) produces bytes that look like ordinary entropy, but the implementation did not offer cryptographic unpredictability. The effective candidate spaces were approximately 2392^{39} for nominal 128-bit output and 2472^{47} for nominal 256-bit output [3].

Those smaller spaces made it feasible to reconstruct candidate recovery phrases, derive the corresponding wallet addresses, and match them against public blockchain activity. A secret could thus be recovered without extracting anything from a victim's device. Following the mnemonic standard could not correct the weak randomness inherited from the older dependency.

2.2 Coldcard: A Hardware RNG That the Code Did Not Use

Coldcard shows why having a secure randomness source is insufficient if the application calls a different generator. Although the device had a hardware random-number generator, a build-time integration error caused rng_get() in the seed-generation path to resolve to MicroPython's software PRNG, Yasmarang, instead. This non-cryptographic PRNG was seeded using the device's fixed unique ID and limited timing information, making its initial state predictable enough to drastically reduce the effective entropy of generated wallet seeds [5].

The seed-generation routine also mixed in a second Yasmarang generator. On older Mk2/Mk3 devices, that second generator started from public constants and contributed no additional entropy. On newer models, although randomness from secure elements was introduced, only 32 bits were retained during reseeding. Consequently, despite producing 256-bit wallet seeds, the effective search space was dramatically reduced. Coinkite preliminarily estimated about 2402^{40} candidates on Mk2/Mk3 and 2722^{72} on Mk4/Q/Mk5 under its stated attack assumptions [6, 7].

The weakness turned seed recovery into an offline search: an attacker could derive addresses from candidate seeds and compare them with public blockchain data. The practical cost depends on the model and attack assumptions; the 2722^{72} estimate does not imply the same feasibility as the 2402^{40} case. A successful match exposes the corresponding private keys.

2.3 XRPH Wallet: An Input-Format Bug That Shrank the Key Space

The generation path also includes the conversion of random output into the input expected by a wallet API. In XRPH Wallet, that conversion discarded most of the generated input. The Android application generated decimal blocks with JavaScript Math.random(), concatenated them into a text string, and passed that string to Wallet.fromEntropy(). The receiving code interpreted characters as small integer byte values and retained only the first 16 positions. Because two positions contained fixed space delimiters, only 14 variable decimal digits contributed entropy to the resulting wallet [8].

padlock = { A, B, C, D, E, F, G, H };     // each = 6 digits from the line above
r6 =  A.join('') + ' ' + B.join('') + ' ' + C.join('') + ' ' + D.join('')
    + ' ' + E.join('') + ' ' + F.join('') + ' ' + G.join('') + ' ' + H.join('');
r5 = Wallet.fromEntropy;
r3 = r5.bind(Wallet)(r6);                 // a 55-char STRING is passed; fromEntropy wants 16 bytes

// compute entropy be like
const input = "528875 921568 528239 ...";  // decimal blocks
const entropy = Uint8Array.from(input).slice(0, 16);
// Result: [5,2,8,8,7,5,0,9,2,1,5,6,8,0,5,2]

The resulting transformation left approximately 1014≈24610^{14}\approx 2^{46} reachable wallet inputs, versus the intended roughly 21282^{128} space for uniformly sampled 16-byte entropy. That figure describes reachable candidates under the observed transformation, not a cryptographic proof of the statistical entropy of Math.random().

Two failures compounded each other: an inappropriate random source and a representation/encoding mistake at the API boundary. Both can escape functional tests because the wallet still generates plausible addresses and signs valid transactions. The public investigation establishes a recoverable key space; it does not by itself prove which key-acquisition route the attacker used.

2.4 Qubic Wallet: Weak Randomness Behind a 55-Character Seed

The generator's initial state can also limit the possible seeds, however long the output string appears. Qubic illustrates this problem: its reported wallet compromise involved seeds generated with JavaScript's non-cryptographic Math.random(). The affected environments included Apple browsers and certain older iOS app builds based on JavaScriptCore [9, 10].

The vulnerable generation logic and its fix can be summarized as follows [11]:

// Vulnerable seed generation (abridged)
seed += letters[Math.floor(Math.random() * 26)];

// Corrected approach (schematic excerpt of the fix)
crypto.getRandomValues(bytes);
if (byte < 234) seed += letters[byte % 26];

Calling Math.random() 55 times does not provide 55 independent sources of entropy. Between reseeding events, each PRNG call advances its internal state deterministically. For a fixed call position, if the initial seed is SS, the entire 55-character wallet seed is a deterministic function W=F(S)W=F(S). Knowing SS and that call position is enough to reproduce all 55 characters and calling the PRNG more times does not create additional entropy.

The range of possible initial states depends on the runtime's initialization. The JavaScriptCore source snapshot retrieved on October 29, 2026 initializes its PRNG from a 32-bit seed, giving at most 2322^{32} sequences for a fixed call position. The V8 source reviewed here uses a 64-bit seed, giving at most 2642^{64} such sequences. These bounds apply to the inspected initialization paths at a fixed call position. The snapshots do not establish the exact browser builds used in the incident or the cost of exploiting them. The fix instead uses crypto.getRandomValues() to obtain cryptographically secure randomness, with rejection sampling (byte < 234) to avoid bias when mapping bytes to 26 letters [12, 13, 14].

3. Flawed Signing: Private Key Recovery from Public Signatures

Once a key has been generated securely, it must also remain secret when used to sign. Each signature makes data available to observers, and implementation flaws can turn that public data into enough information to recover the private key. ECDSA and Schnorr use a per-signature secret scalar, called a nonce, to mask the private key's contribution to the public signature.

The nonce must remain secret, avoid reuse across distinct messages under the same key, and meet the scheme's distribution requirements. Deterministic derivation is compatible with these requirements: knowing the public key and signed message must not be enough to predict the nonce.

Take the simplified Schnorr relation as an example:

s=k−rd(modn),s=k-rd\pmod n,

where dd is the private key, kk the nonce, rr a public challenge computed with public information (e.g., public key and message), ss the public signature scalar, and nn the subgroup order. A known nonce immediately exposes d=(k−s)r−1(modn)d=(k-s)r^{-1}\pmod n when rr is invertible.

In addition, the nonce does not have to be fully known for the key to leak. Constraints or bias across multiple nonces may provide enough information for key recovery using lattice-based methods. The feasibility depends on the information exposed and the signatures available.

The table distinguishes the two nonce failures examined in this section: Zilliqa's nonces were partially constrained, while SecondFi's nonces became publicly computable. Reported impacts use separate historical reporting windows.

Incident Relevant period Reported impact (approx.) Failure mode explored below
Zilliqa Ledger Wallet March-July 2026 683,130,969.66 ZIL Biased EC-Schnorr signing nonces
SecondFi June 2026 Not established by the cited code evidence Broken Ed25519 nonce derivation

3.1 Zilliqa Ledger Wallet: 64 Missing Bits in Every Schnorr Nonce

The Zilliqa Ledger Wallet incident involved a flaw in the application used for legacy Zilliqa EC-Schnorr transactions [15]. The application generated nonce material, but copied the wrong portion into its final signing buffer. It kept eight bytes of zero padding and discarded eight bytes of actual randomness. As a result, every affected nonce satisfied 0≤k<2192,0 \le k < 2^{192}, rather than ranging properly across the roughly 256-bit secp256k1 group order.

The signature equations show why restricting the nonce matters:

P=dG,Q=kG,r=H(compress⁡(Q)∥compress⁡(P)∥m)  n,s=k−rd(modn).\begin{aligned} P &= dG,\\ Q &= kG,\\ r &= H(\operatorname{compress}(Q)\|\operatorname{compress}(P)\|m)\bmod n,\\ s &= k-rd\pmod n. \end{aligned}

Here, GG is the group generator. Rearranging gives

k=s+rd(modn).k=s+rd\pmod n.

Each signature therefore provides a modular relation in the unknown dd, with a nonce bounded by 21922^{192}. Combining several independent relations produces an instance of the Hidden Number Problem (HNP), which can be approached through lattice reduction. The official post-mortem reports private-key recovery from four or more affected signatures.

The secure element never needed to reveal the private-key bytes. The biased nonces made that key recoverable from signatures already published on-chain.

3.2 SecondFi: A Signing Nonce Computable from Public Data

In SecondFi, incorrect Ed25519 nonce derivation exposed more than a partial constraint: it made the nonce computable from public data. This allowed recovery of private signing keys from a single publicly available transaction signature [16].

For an Ed25519-style signature, the intended construction derives a secret scalar aa and a private nonce prefix from secret key material. Here, A=aBA=aB is the public key, BB is the Ed25519 base point, MM is the signed message, LL is the subgroup order, and HH denotes SHA-512. In schematic form:

r=H(secret nonce prefix∥M)  L,R=rB,h=H(R∥A∥M)  L,S=r+ha(modL).\begin{aligned} r &= H(\text{secret nonce prefix}\|M)\bmod L,\\ R &= rB,\\ h &= H(R\|A\|M)\bmod L,\\ S &= r+ha\pmod L. \end{aligned}

The broken implementation instead sets r=H(M)  Lr=H(M)\bmod L. Anyone who knows the signed message can derive rr. Since RR, AA, and SS are public, an observer can then compute

a=(S−r)h−1(modL),a=(S-r)h^{-1}\pmod L,

provided hh is nonzero modulo LL.

Unlike Zilliqa's partial nonce leakage, one affected signature was enough to recover the signing scalar. This does not automatically prove recovery of the original mnemonic or every sibling key in a hierarchical wallet. The failure was at the interface between key derivation and signing: the required secret was available but never passed to the nonce derivation routine.

4. Secure Implementation Principles and Verification

Taken together, the cases show why randomness sources and signing algorithms must be reviewed along with the code that connects them. An unpredictable source must produce unpredictable keys, and those keys must remain secret during signing. Errors in conversion routines or library interfaces can break either requirement.

Sections 4.1 and 4.2 state the design and implementation principles for generation and signing. Section 4.3 explains what evidence reviews and tests should provide to assess those principles in the deployed system.

4.1 Preserve Security During Key Generation

The generation cases point to three implementation requirements:

  1. Use cryptographically secure randomness and stop on failure. Seeds and private keys require a suitable cryptographically secure source, such as a cryptographically secure pseudorandom number generator (CSPRNG). Ill Bloom and Qubic show why a general-purpose generator cannot serve that role. If the intended source is unavailable or fails, generation must stop rather than silently use a weaker substitute.

  2. Preserve entropy through data conversion. The output of a secure source must retain the required uncertainty as it is encoded, sampled or passed between interfaces. The XRPH Wallet case shows why a valid-looking output is insufficient. Type, length and encoding constraints must prevent unintended truncation or biased mappings from reducing the effective key space.

  3. Ensure the deployed code uses the intended source. Coldcard shows that the presence of a secure random source does not establish which source the application actually calls. Source selection must remain correct through integration, compilation and deployment, including error and fallback paths. A design assumption about the RNG must hold in the executable running on the target platform.

4.2 Preserve Secrecy During Signing

The generation requirements protect how a key is created. Keeping it secret during use also requires correct nonce handling. The requirements depend on the scheme; the list includes ECDSA alongside the Schnorr and Ed25519 schemes examined in the cases.

  1. ECDSA: Preserve nonce secrecy and unpredictability, and prevent reuse under the same key across different messages. Deterministic generation following RFC 6979 [17] avoids dependence on fresh external randomness for each signature, but implementations still need protection against side-channel leakage and fault attacks.

  2. EdDSA: Preserve all secret inputs required by nonce derivation. SecondFi demonstrates the consequence of omitting the private prefix and using public data alone. Wrappers and key adapters must pass the complete secret inputs required by RFC 8032 [18], rather than treating a working signature as evidence that the inputs are correct.

  3. Schnorr: Preserve the nonce distribution and representation required by the scheme. As Zilliqa shows, avoiding repetition and fully predictable nonces is insufficient if part of the randomness is lost. Data handling and scalar conversion must not introduce exploitable constraints or bias.

Well-reviewed libraries reduce implementation risk, but they do not eliminate integration errors. A correct signing primitive remains unsafe if its caller supplies incomplete or corrupted inputs.

4.3 Test Security Properties

To assess whether these requirements hold, reviews must follow the inputs through the implementation that actually runs. A valid output may conceal an integration error, so verification also needs to cover paths that normal functional tests miss. The table translates the generation and signing requirements into specific review objectives:

Review Area Verification Objective
Entropy unpredictability Establish whether the source, initialization and effective seed space resist feasible prediction or enumeration
Entropy preservation Verify that conversions and sampling preserve the required uncertainty without unintended truncation or bias
RNG integration Confirm that the deployed executable uses the intended source, including error and fallback paths
Nonce secrecy Verify that nonce generation or derivation preserves secrecy, including all required secret inputs, and cannot be reproduced from public data alone
Nonce correctness Compare nonce handling with the scheme requirements, using known-answer tests and independent implementations where applicable
Dependency security Review the versions and integration of cryptographic dependencies, and check that updates preserve the required behavior

Assessing these objectives requires combining several methods. Code review traces how secret and random inputs reach the deployed generation and signing routines. Negative-path tests and fault injection examine behavior when normal assumptions fail; known-answer tests and comparisons with independent implementations can reveal scheme deviations. Statistical tests can detect obvious bias, but successful address generation, signature verification or statistical testing alone cannot establish cryptographic security. Assess the combined evidence against the source, input handling and scheme requirements.

Third-party security teams or experts can complement internal verification through code and integration audits, particularly where randomness sources, key derivation and signing libraries meet. The scope should cover dependency versions, build configuration and the executed code path. Findings should identify the affected security property and provide reproducible evidence and regression checks. Changes to the implementation or its dependencies still require verification.

Best Security Auditor for Web3

Validate design, code, and business logic before launch

5. Conclusion

The six incidents reveal two ways implementation errors can undermine protected keys: reducing the possible keys during generation, or exposing information about an existing key through signatures. The analysis turns those failures into generation and signing requirements, with review and testing objectives for the code deployed in payment infrastructure.

For payment institutions, secure storage protects the stored secret; it cannot repair predictable generation or information leaked by signing. The required properties must survive dependencies, adapters and deployment. Reviews and tests need to examine that complete implementation, rather than infer security from valid addresses and signatures.

These implementation requirements complement the workflow controls examined in Part 1 of Beyond Secure Storage. Part 1 examines workflow risks, preventive checks and loss containment when those checks fail to stop unauthorized operations. Part 2 addresses implementation flaws that can give an attacker independent signing capability. Correct cryptographic execution does not prove business authorization; valid business approval does not establish that the signing key is unpredictable or cannot be recovered. Secure payment and signing systems require both the authorization workflow and the underlying cryptographic implementation to be designed and verified.

References

Sign up for the latest updates
Beyond Secure Storage, Part 1: Designing Secure Payment and Signing Workflows
Security Insights

Beyond Secure Storage, Part 1: Designing Secure Payment and Signing Workflows

How can an unauthorized operation obtain a valid signature without the attacker stealing the private key? Part 1 of Beyond Secure Storage uses incidents including Bitget, Bybit and Binance to examine authorization failures across payment requests, transaction construction, signing, execution and settlement. It derives design principles for preventing unauthorized operations and limiting losses, then explains how reviews, audits, penetration tests and exercises can verify those protections.

NEAR Intents Exploit: Refund Flaw and Security Lessons
Security Insights

NEAR Intents Exploit: Refund Flaw and Security Lessons

The NEAR Intents exploit used oversized refund events to preserve internal balances after the deposited assets were returned. This analysis reconstructs the receipt flow and seven BNB Chain withdrawals totaling 3,865,021 USDT, distinguishes those outflows from the project's reported full recovery, and outlines security considerations for developing and auditing NEAR smart contracts.

~$5.57M Lost: X100 Freezer, NEM Exploits | BlockSec Weekly
Security Insights

~$5.57M Lost: X100 Freezer, NEM Exploits | BlockSec Weekly

This weekly report covers two security incidents on Ethereum and BNB Chain from October 19 to October 25, 2026. It examines a flash-loan-funded governance takeover of X100 Freezer and a tax-settlement flaw affecting a NEM token liquidity pool on BNB Chain. Estimated losses within the report's scope total approximately $5.57M: ~$4.75M from the representative X100 transaction and ~$820K from the NEM pool incident. The total excludes separately reported follow-on X100 extractions.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit