Solana Hot Wallet Private Key Extraction via Ed25519 Shared-R Vulnerability

세인·2026년 2월 5일
post-thumbnail

Disclaimer: This article is not intended to identify or attribute cause to any specific exchange or incident. It provides a technical explanation of a nonce-reuse vulnerability class that can arise in publicly available cryptographic signature structures, and summarizes implications from a defensive and verification perspective. This article does not include details that could excessively increase reproducibility (such as identifiable accounts, transactions, raw data, or immediately exploitable scripts), and all experiments were conducted solely in a controlled test environment.

Acknowledgment: Special thanks to Dr. Suhyeon Lee for the analytical direction that inspired this article and for the guidance provided throughout the writing process.

1. Incident Overview

In 2025, cryptocurrency assets worth several hundred million USD were drained from the Solana hot wallets of Exchange A. Multiple Solana-based tokens (estimated at over tens of millions USD) were affected; the service has since been restored. In relation to this incident, the possibility of private key exposure was noted.

According to recent research (Jacquot and Donnet, "Short Paper: Oops...I Did It Again. I Reused my Nonce," to appear in Financial Cryptography and Data Security 2026 [link]), nonce reuse alone across six chains including Bitcoin and Ethereum has exposed 3,620 private keys, placing assets equivalent to €101M at risk. This article explains the technical mechanism of a nonce-reuse vulnerability, corresponding to Section 5.2 of that research where the same nonce is reused across different keys, in the context of Solana, and presents the results of reproducing this on the Solana devnet.


2. Background

2.1 Elliptic Curve Digital Signatures

Digital signatures are used in blockchains to prove that the sender of a transaction is the legitimate owner of the corresponding wallet. Most major blockchains rely on digital signatures based on Elliptic Curve Cryptography (ECC).

The core principle of elliptic curve cryptography is straightforward. Given a fixed base point G on an elliptic curve, multiplying G by a secret scalar a yields the public key A:

A=aGA = a \cdot G

Computing A from a is efficient, but recovering a from A alone is computationally infeasible with current hardware. This one-way property is the foundation of digital signature security.

The objective of an elliptic curve digital signature is to prove knowledge of the secret key a without revealing it. To achieve this, a fresh ephemeral nonce r is generated for each signature and incorporated into the signing equation alongside a.

Because the nonce r introduces a second unknown, a single signature equation with two unknowns (a and r) does not permit recovery of a.

The general structure of an elliptic curve digital signature scheme is as follows.

Signing

  1. Generate an ephemeral nonce rr.

  2. Compute R=rGR = r \cdot G (the elliptic curve point corresponding to the nonce).

  3. Compute SS using the hash of message MM, the secret key aa, and the nonce rr.

    e.g., S=r+H(R,A,M)aS = r + H(R, A, M) \cdot a

  4. Publish the pair (R,S)(R, S) as the signature.

Verification

Since S=r+H(R,A,M)aS = r + H(R, A, M) \cdot a during signing, multiplying both sides by G yields:

SG=rG+H(R,A,M)aG=R+H(R,A,M)AS \cdot G = r \cdot G + H(R, A, M) \cdot a \cdot G = R + H(R, A, M) \cdot A

The verifier checks whether SGS \cdot G equals R+H(R,A,M)AR + H(R, A, M) \cdot A.

The right-hand side of this equation can be computed using only the signature component RR, the public key AA, and the message MM. Consequently, anyone can determine whether a signature was produced by someone who knows aa, without learning aa itself.

While all elliptic curve signature schemes share this sign/verify structure, they differ in how the nonce is generated.


**ECDSA** (Bitcoin, Ethereum, etc.)
r ← external random number generator (e.g., OS /dev/urandom)
R = r * G
S = (H(M) + R.x * a) / r   (mod n)

The nonce r is drawn from an external source for each signature. The advantage is that a different nonce is produced each time. The disadvantage is that if the random number generator fails or degrades such that the same r is used twice, the difference between the two S values allows recovery of the secret key a. In other words, the security of the signature depends on the quality of the random number generator.

Ed25519 (Solana, SSH, TLS, etc.)

#a fixed value derived from the secret key
nonce_prefix ← lower 32 bytes of SHA-512(seed)

#nonce generated by hashing the fixed value with the message
r = SHA-512(nonce_prefix || M)

R = r * G
S = r + H(R, A, M) * a   (mod L)

Rather than drawing r from an external source, Ed25519 concatenates a secret-key-derived component called the nonce_prefix with the message M and hashes the result to produce the nonce.

The nonce_prefix is a fixed value derived once from the secret key; because a different message yields a different hash output, a distinct nonce is produced each time. This eliminates dependence on an external random number generator, but it requires that the nonce_prefix be unique to each signer. If two signers share the same nonce_prefix, signing the same message will produce the same nonce r.


2.2 Ed25519 Secret Key Structure

In Ed25519, a single secret key (seed, 32 bytes) is hashed with SHA-512 to produce 64 bytes, which are then split into two halves:

SHA-512(seed) → 64 bytes

First 32 bytes → scalar (a)     : the actual secret scalar used in signature computation.
Last 32 bytes  → nonce prefix   : the seed value used to derive the per-signature nonce.

The scalar a is the secret number used directly in the mathematical operations of the signature, while the nonce prefix fulfills the role that the operating system's random number generator plays in ECDSA. In other words, deriving the nonce from the secret key itself is the central design principle of Ed25519.


2.3 How a Signature (R, S) Is Produced

When Ed25519 signs a message M (e.g., transaction data), it produces a pair (R, S):

r = SHA-512(nonce_prefix || M)   ... nonce generation
R = r * G                        ... elliptic curve point corresponding to the nonce
S = r + H(R, A, M) * a           ... computation involving the secret key a

The meaning of each symbol is summarized below:

SymbolDescriptionPublic?
rNonce. The hash of nonce_prefix concatenated with message MNo (intermediate value)
RElliptic curve point corresponding to rYes (included in signature)
aSecret key scalarNo (protected)
SComputation result involving the secret key aYes (included in signature)
H(R, A, M)Hash of R, public key A, and message M combinedComputable by anyone
GFixed base point known to all participantsYes (constant)

The critical property here is that if nonce_prefix differs across signers, then r differs even for the same message, and consequently R(=rG)R (= r \cdot G) also differs. The uniqueness of nonce_prefix per signer is the property that upholds Ed25519 security.

In Solana, the signature is published as a 64-byte value consisting of R (32 bytes) concatenated with S (32 bytes).


2.4 Why Shared-R Is Dangerous

What happens if two signers possess the same nonce_prefix?

Consider a scenario in which two signers sign the same transaction (the same message MM):

signer A:rA=SHA-512(prefixAM)RA=rAGsigner B:rB=SHA-512(prefixBM)RB=rBG\text{signer A} : r_A = \text{SHA-512}(\text{prefix}_A || M)\quad R_A = r_A * G \\ \text{signer B} : r_B = \text{SHA-512}(\text{prefix}_B || M) \quad R_B = r_B * G

If the nonce_prefix is different for each signer, then rArBr_A \neq r_B and consequently RARBR_A \neq R_B.


However, if prefixA=prefixB\text{prefix}_A = \text{prefix}_B, then for the same message MM we get rA=rBr_A = r_B, and therefore RA=RBR_A = R_B.

This phenomenon, in which two distinct signers produce the same RR, is called Shared-R. Since both RR and SS are publicly recorded on the blockchain, the occurrence of Shared-R gives rise to a system of equations that enables recovery of the secret keys from the difference of the two SS values (the concrete derivation is presented in Section 4).


2.5 Root Cause of Shared-R: Differences in Key Derivation Implementation

Solana wallets typically derive multiple child keys from a single master seed. This approach is known as HD (Hierarchical Deterministic) key derivation, and it allows all child keys to be restored from a single seed backup. Solana uses the SLIP-0010 standard for this process.

The SLIP-0010 specification defines Ed25519 child key derivation as follows:

"the returned child key kik_i is ILI_L"
: SLIP-0010, Private parent key → private child key

Here kik_i is the derived child key and ILI_L is the left 32 bytes of the 64-byte SHA-512 output. That is, the specification only defines how to produce the 32-byte child key.

However, as discussed in Section 2.2, Ed25519 signing requires both a scalar (32 bytes) and a nonce_prefix (32 bytes), totaling 64 bytes. The 32-byte child key must therefore be passed through SHA-512 again to produce 64 bytes. This step is referred to as re-expansion.


In a correct implementation, re-expansion is performed so that each child obtains a unique nonce_prefix:

Correct:  child_key → SHA-512(child_key) → [new scalar | new prefix]  (unique per child)

If, however, re-expansion is omitted and the nonce prefix derived from the master seed is reused for all children:
Incorrect: master_seed → SHA-512 → [master_scalar | master_prefix]
           child_0: [child_scalar_0 | master_prefix]    ← same prefix
           child_1: [child_scalar_1 | master_prefix]    ← same prefix

All children share the same prefix, and signing the same message produces identical R values.

Because re-expansion is not an explicitly mandated step in the specification, custom implementations that optimize or simplify the key derivation process may omit it, thereby introducing the Shared-R vulnerability.

Vulnerability patterns similar to the one described here have been reported and registered in several prior disclosures:

SourceDescription
CVE-2022-50237 / RUSTSEC-2022-0093A flaw in the ed25519-dalek library (Rust) that allows separate manipulation of the scalar and nonce_prefix, enabling private key extraction from two signatures
orlp/ed25519 Issue #3The ed25519_add_scalar function fails to update the nonce prefix, causing R reuse
MystenLabs/ed25519-unsafe-libsA catalog of over 40 Ed25519 libraries affected by the same vulnerability class

3. Private Key Extraction Method

Given two transactions exhibiting Shared-R, the private keys can be extracted through the following procedure.

3.1 Signature Equations

Applying the signature formula S=r+H(R,A,M)aS = r + H(R, A, M) \cdot a from Section 2 to two signers (signer 0, signer 1) across two transactions (TX1, TX2):

TX1:s01=r1+h01a0(modL)(1)TX1:s11=r1+h11a1(modL)(2)TX2:s02=r2+h02a0(modL)(3)TX2:s12=r2+h12a1(modL)(4)\text{TX}1: \quad s_{01} = r_1 + h_{01} \cdot a_0 \pmod{L} \quad \cdots (1) \\ \phantom{\text{TX}1:} \quad s_{11} = r_1 + h_{11} \cdot a_1 \pmod{L} \quad \cdots (2) \\ \text{TX}2: \quad s_{02} = r_2 + h_{02} \cdot a_0 \pmod{L} \quad \cdots (3) \\ \phantom{\text{TX}2:} \quad s_{12} = r_2 + h_{12} \cdot a_1 \pmod{L} \quad \cdots (4)
  • sijs_{ij} : the S component of the signature (publicly available on-chain)
  • rjr_j : nonce (identical for both signers within the same TX due to Shared-R)
  • hij=SHA-512(RAiMj)modLh_{ij} = \text{SHA-512}(R \mathbin\| A_i \mathbin\| M_j) \mod L : challenge hash (computable from on-chain data)
  • aia_i : secret key scalar (the extraction target)
  • LL : the order of the Ed25519 group (a fixed constant)

3.2 Nonce Elimination

Subtracting (1)-(2) and (3)-(4) eliminates the nonce rr:

d1=s01s11=h01a0h11a1(modL)d2=s02s12=h02a0h12a1(modL)d_1 = s_{01} - s_{11} = h_{01} \cdot a_0 - h_{11} \cdot a_1 \pmod{L} \\ d_2 = s_{02} - s_{12} = h_{02} \cdot a_0 - h_{12} \cdot a_1 \pmod{L}

With two unknowns (a0a_0, a1a_1) and two equations, the system is solvable.

3.3 Application of Cramer's Rule

Expressed in matrix form:

(h01h11h02h12)(a0a1)=(d1d2)det=h01(h12)(h11)h02(modL)\begin{pmatrix} h_{01} & -h_{11} \\ h_{02} & -h_{12} \end{pmatrix} \begin{pmatrix} a_0 \\ a_1 \end{pmatrix} = \begin{pmatrix} d_1 \\ d_2 \end{pmatrix} \det = h_{01} \cdot (-h_{12}) - (-h_{11}) \cdot h_{02} \pmod{L} \\
a0=d1(h12)(h11)d2det(modL)a1=h01d2d1h02det(modL)a_0 = \frac{d_1 \cdot (-h_{12}) - (-h_{11}) \cdot d_2}{\det} \pmod{L} \quad a_1 = \frac{h_{01} \cdot d_2 - d_1 \cdot h_{02}}{\det} \pmod{L}

All input values (ss, hh, dd) can be computed from publicly available on-chain data.

The secret key scalars a0a_0 and a1a_1 are therefore fully determined.

3.4 Verification

The extracted scalars are verified by recomputing the public keys and comparing them against the originals:

aG=A(Ed25519 base point multiplication)a \cdot G = A \quad \text{(Ed25519 base point multiplication)}

A match confirms successful private key extraction.


4. Devnet Reproduction

To confirm that the vulnerability is exploitable in practice, the same conditions were reproduced on the Solana devnet.

4.1 Reproduction Procedure

  1. Derive two child keys from a random master seed using SLIP-0010.
  2. Derive the child scalars normally, but reuse the master's nonce prefix for both children (reproducing the vulnerable implementation).
  3. Have both signers sign the same transaction message and confirm that R is identical.
  4. Submit both transactions to the devnet.
  5. Apply Cramer's Rule using only publicly available on-chain data to extract the private keys.
  6. Recompute the public keys from the extracted private keys and verify agreement with the originals.

4.2 Execution Output

========================================================================
  Shared-R PoC — Ed25519 nonce reuse vulnerability reproduction (DEVNET)
========================================================================

[Phase 1] Vulnerable keypair generation (SLIP-0010 + master prefix reuse)
  Signer 0: GxKGiE5sGmgAs3QrLFEkrmwYtMV8crxqq5Zo2ZaqAe4J
  Signer 1: EXrP8x4UWSvWZJ9GjhumL21bodu2qz69D6ZC6nr34bQ1
  Shared prefix (hex): 51338c9a28c92c134555dbfc913a634a...
  Key generation verification: OK

[Phase 3] Transaction 1 construction and submission
  Sig 0 R: affb089cfd802e72b1d4d04373a8cefd...
  Sig 1 R: affb089cfd802e72b1d4d04373a8cefd...
  Shared-R: YES
  TX1 submitted

[Phase 4] Transaction 2 construction and submission
  Sig 0 R: bee13c44cbb4b9f044cc990080ce375b...
  Sig 1 R: bee13c44cbb4b9f044cc990080ce375b...
  Shared-R: YES
  TX2 submitted

[Phase 5] On-chain data retrieval
  TX1 Shared-R on-chain confirmation: YES
  TX2 Shared-R on-chain confirmation: YES

[Phase 6] Private key extraction (Cramer's rule — using on-chain data only)
  Input: Signatures (R, S), messages, and public keys from TX1/TX2 (all publicly on-chain)
  Extracted Scalar 0 (hex LE): a0cf256a02afea8cce5ad165f20502ab...
  Extracted Scalar 1 (hex LE): c0003bf2194765209bf14844564c25b1...

4.3 Verification Summary

Verification ItemResult
Does Signer 0's R match Signer 1's R within TX1 (Shared-R)?YES
Does Signer 0's R match Signer 1's R within TX2 (Shared-R)?YES
Does Signer 0's R in TX1 match Signer 0's R in TX2?NO (different messages)
Does the extracted scalar * G equal Signer 0's public key?YES
Does the extracted scalar * G equal Signer 1's public key?YES
When recovering the nonce for TX1, is the same r computed for both signers?YES
When recovering the nonce for TX2, is the same r computed for both signers?YES
Does the extracted scalar match the original scalar in a direct comparison?YES

4.4 On-Chain Evidence

The Shared-R phenomenon can be verified directly in the following Solana devnet transactions.

Signer Addresses:

RoleAddress
Signer 0GxKGiE5sGmgAs3QrLFEkrmwYtMV8crxqq5Zo2ZaqAe4J
Signer 1EXrP8x4UWSvWZJ9GjhumL21bodu2qz69D6ZC6nr34bQ1

Transactions:

Decoding the raw bytes of the signatures reveals the R value in the first 32 bytes. Inspecting the raw signature bytes of the first transaction on Solscan confirms that the leading portions of both signatures (the base58-encoded R values) are identical.

Both private keys were extracted using only the publicly available data from these two transactions.


5. References

ResourceLink
RFC 8032 (Ed25519 specification)https://datatracker.ietf.org/doc/html/rfc8032#section-5.1
SLIP-0010 (HD key derivation standard)https://github.com/satoshilabs/slips/blob/master/slip-0010.md
CVE-2022-50237 (NVD)https://nvd.nist.gov/vuln/detail/CVE-2022-50237
RUSTSEC-2022-0093 (Rust security advisory)https://rustsec.org/advisories/RUSTSEC-2022-0093
ed25519-unsafe-libs (vulnerable library catalog)https://github.com/MystenLabs/ed25519-unsafe-libs
orlp/ed25519 Issue #3https://github.com/orlp/ed25519/issues/3
Double Public Key Signing Attack paperhttps://arxiv.org/abs/2308.1500
Jacquot and Donnet, "Short Paper: Oops...I Did It Again. I Reused my Nonce." (FC 2026)https://fc26.ifca.ai/preproceedings/55.pdf
profile
세종과학기지 세인지부

0개의 댓글