Back to Blog

Beyond Secure Storage, Part 2: Implementing Wallet Cryptography Securely

Code Auditing
2026년 11월 11일
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
안전한 보관을 넘어, 제1부: 안전한 결제 및 서명 워크플로 설계
Security Insights

안전한 보관을 넘어, 제1부: 안전한 결제 및 서명 워크플로 설계

공격자가 개인 키를 탈취하지 않고도 승인되지 않은 작업이 어떻게 유효한 서명을 얻을 수 있을까요? Beyond Secure Storage의 파트 1에서는 Bitget, Bybit, Binance 등의 사고를 통해 결제 요청, 트랜잭션 구성, 서명, 실행, 정산에 걸친 승인 절차의 실패를 살펴봅니다. 이어서 승인되지 않은 작업을 방지하고 손실을 제한하기 위한 설계 원칙을 도출하고, 검토, 감사, 침투 테스트, 훈련을 통해 이러한 보호 조치를 어떻게 검증할 수 있는지 설명합니다.

NEAR Intents 익스플로잇: 환불 처리 결함과 보안 교훈
Security Insights

NEAR Intents 익스플로잇: 환불 처리 결함과 보안 교훈

NEAR Intents 익스플로잇에서는 과도한 금액의 환불 이벤트를 이용해 예치 자산이 반환된 후에도 내부 잔액을 유지했습니다. 이 분석은 리시트 처리 흐름과 BNB Chain에서 발생한 총 3,865,021 USDT 규모의 출금 7건을 재구성하고, 이러한 자금 유출을 프로젝트 측이 보고한 전액 회수와 구분하며, NEAR 스마트 계약의 개발 및 감사에 필요한 보안 고려사항을 설명합니다.

약 557만 USD 손실: X100 Freezer·NEM 공격 | BlockSec 주간 보고서
Security Insights

약 557만 USD 손실: X100 Freezer·NEM 공격 | BlockSec 주간 보고서

이 주간 보고서는 2026년 10월 19일–2026년 10월 25일에 Ethereum과 BNB Chain에서 발생한 보안 사고 두 건을 다룹니다. 플래시론으로 자금을 조달해 X100 Freezer의 거버넌스를 장악한 사건과 BNB Chain의 NEM 토큰 유동성 풀에 영향을 미친 세금 정산 결함을 살펴봅니다. 보고서 범위에 포함된 추정 손실액은 X100의 대표 거래에서 발생한 약 475만 USD와 NEM 풀 사고에서 발생한 약 82.0만 USD를 합쳐 총 약 557만 USD입니다. 이 총액에는 별도로 보고된 후속 X100 자금 유출액은 포함되지 않습니다.

Best Security Auditor for Web3

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

BlockSec Audit
Beyond Secure Storage, Part 2: Implementing Wallet Cryptography Securely