The Security of the Threshold EdDSA Protocol

Paylaş
Öznur MUT SAĞDIÇOĞLU, PhD, Cryptography Researcher/Consultant at TÜBİTAK BİLGEM Blockchain Research Laboratory.
 

Edwards-curve Digital Signature Algorithm (EdDSA) is a signature algorithm scheme using a variant of the Schnorr signature based on twisted Edwards curves. It aims to have a design to deliver higher performance than existing digital signature schemes while maintaining robust security. This scheme was created by a team including Bernstein et al. in 2011, [Ber+12]. EdDSA is a deterministic signature meaning that we always get the same signature for the same private key and message, whereas ECDSA is non-deterministic which was proposed around 2001 by Johnson et al. [Jhon+01].

A threshold EdDSA is a signature scheme where a group of parties collectively compute a signature without learning the private key. In a (t, n)-threshold signature scheme, parties hold key shares, and any subset of parties can generate a signature, but no group smaller than t parties can perform this task. The parties all want to sign the same message m, naturally, they agree on an elliptic curve over a finite field, the same base point on the curve, and a cryptographic hash function Hash.

Threshold key generation for EdDSA consists of the secret sharing of both the private signing key and a random secret nonce across multiple parties, enhancing security and eliminating single points of failure. Thus, a threshold EdDSA signing can be based on a DKG-based (Distributed key generation) approach. A verifiable DKG allows each party to see the commitments of all shares and confirm their validity.

As a result, the commitment to publicly shared nonces and their verification by all parties is particularly crucial. Because there is an attack that exploits the absence of nonce commitment within EdDSA threshold protocol. The vulnerability was first demonstrated through an attack on the CoSig algorithm by Drijvers et al. in 2018 [Dri+19]. As a result, it is observed that this attack can also be applied to Schnorr, EdDSA, and Musig signature schemes. This writing provides a detailed analysis of the application of this attack to the threshold EdDSA signature scheme.

In a general view of the attack, due to the absence of commitment and verification of nonces, an attacker can wait for nonces from other parties and then he find an evil/malicious nonce. As a result, using this evil nonce, he can sign any message he wants, and according to the attack, this message need not be known by the other parties. In other words, the attacker can perform signature forgery without other parties’ knowledge. To find such an evil nonce relies on Wagner’s Generalized Birthday Attack, as described in [Wag02].

The Attack

If the commitment of shared nonces and their verification is missing, then an adversary has the potential to lead other parties into signing something they didn’t intend.
Let’s assume there are three parties in the protocol to facilitate the explanation of the attack. The naming of these parties shall be as follows: Alice, Bob, and Carol. Let’s also assume that Carol is the attacker. Let (d, D = dG) represent the aggregated private and public key pairs.

The attack works as follows.

  1. Carol opens concurrent signing sessions with Alice and Bob. She pro-
    poses messages {m1, m2, …, mk} which Alice and Bob would be perfectly agreeable to sign. The number k will be discussed in Wagner Generalized Birthday Algorithm section.
  2. Carol waits for Alice and Bob to share their public nonces
 

with Carol in every one of the signing sessions.

1*cqbMptbEQBck5Cyo6M9ZTA
 

where rai and rbi denote Alice and Bob’s secret nonces for signing session i.

3. Carol chooses randomly a nonce rc ∈ Zn and generates its point on the
curve Rc = rcG.

4. Carol uses Wagner’s algorithm (see Wagner Generalized Birthday Algorithm section) to find different curve points {X1, X2, …, Xk} such that for the evil challenge e′ for its evil message m′ can be written as

1*CureYhifgdbMGKbhFWtyIw
 

and is the aggregated public key. The evil nonce R′ is the sum of Alice
and Bob’s public nonces and Carol’s nonce Rc.

5. Each Xi is hashed with the message mi and produces a challenge

1*zFKA5PD g5Vv5gVM rCHgQ
 

for each signing session i.

6. Due to Wagner’s algorithm, the sum of all ei hashes equals another hash
which Carol has chosen based on the evil message m′ which Alice and Bob
never see.

7. Since there is no validation of public nonces in the protocol, Carol can see Alice and Bob nonces and can produce evil nonces Rci. Carol calculates
a set of nonces {Rc1, …, Rck} one for each signing session

1*HZqGvKekJcFXovWf7XHAUQ
 

8. Alice and Bob upon receiving Rci, will compute the aggregate nonce

1*zyUNM6ZRd51vWeOuRR6oLA
 

9. Carol can convince Alice and Bob to sign {m1, m2, …, mk} with the aggregated nonces {X1, X2, …, Xk} chosen by Carol. I know it is not an easy
job, but it is not impossible!

10. After that, Carol awaits pairs of partial signatures

1*9XtsZ24LUvU2QuL1vCTViQ
 

where wa = λa.da and wb = λb.db, and da and db are secret shares of Alice
and Bob, respectively [Int]is the aggregated public key.

12. Carol sums Alice and Bob’s signature shares from signing sessions to
create a forged signature s′ which is valid for the evil message m′, D and
the evil nonce R′.

Forged Signature:

 

13. Carol adds her signature share

1*kM2Ycz 44CMd3rVayC5RFw
 

14. Since

 

15. Thus s′ can be written as

1*tDnt
 

16. Recall that the evil nonce is defined as

1*6nsftuQHMkHF5fJwd 3sUg
 

Although Carol does not know

 

she assumes that it is a discrete logarithm of R′. Now the secret nonce
is

 

17. Now substitute r′ into the signature equation to show the aggregated
signature is

 

and call wa + wb + wc as d, [Int].
Thus, (R′, s′) is a forged signature of the evil message m′ with the public
key D and

1*MrEffuf P44LT3h7 zr HQ

Verification:

The signature for the evil message m′ and the public key D is (R′, s′).
18. Calculate H(R′||D||m′) which is exactly e′.
19. Calculate s′G and R′ + e′D. If they are equal then the signature is valid.

Wagner Generalized Birthday Algorithm

It is time to explain how we use Wagner’s Generalized Birthday Algorithm in the attack above.
An adversary can adaptively choose its shared R-value to determine the
aggregated R-value after seeing shared nonce values from all other signing parties. Exploiting the attack involves solving the Generalized Birthday Problem, which can be done with subexponential complexity using Wagner’s algorithm, [Wag02].

1*VCcib L8 njCdNRK2idZzQ
 

Carol finds k distinct points (some set of nonces) X1, X2, .., Xk on the curve
such that for each signing session they combined with D and {m1, m2, .., mk} hash into the challenges e1, e2, .., ek. Their sum will be the evil challenge e′ = e1 + e2 + .. + ek, see the Attack section in Step 4.

For Wagner’s algorithm, some lists are defined to create the evil challenge as wanted:
1. Carol generates k − 1 lists {L1, L2, .., Lk−1}.

2. Each list Li, for i = 1, 2, .., k − 1, contains say λ hashes as follows

 

Namely, Li = {ei1, ei2, …, eiλ} (see Table 1) We will consider λ later.

3. Carol generates the last list Lk = {ek1, ek2, , …, ekλ} in the same way, but
this time she subtracts e′ from each hash ekj .

 

4. Carol stores Xij values in each list because she needs to define Rci, for
i = 1, 2, .., k (see the Attack section in Step 8).

As a result, Carol can find a hash value in each list and

 

However, how many hash values does Carol need to obtain this sum? In summary, what should the value of λ be?

The complexity

 

How many hashes do we need to find the following equation holds

 

where ej,i_j ∈ Lj?
This problem is also called the k-sum problem: Given lists L1, L2, …, Lk
of elements drawn uniformly and independently at random from {0, 1}^n, find x1 ∈ L1, x2 ∈ L2, .., xk ∈ Lk such that x1 ⊕ x2 ⊕ … ⊕ xk = 0.
For k = 2 case, it is simply the Birthday problem. Let’s have two lists L1
and L2, each of length λ. Carol wants to find a hash chosen from each list
e1 ∈ L1 and e2 ∈ L2 so that

1* vbQai9RBA08 Y2FgW F g
 

This equality holds with the probability

1*XzK IpZzkvp2lLW9 niINw
 

It is because the expected number of solutions in L1 and L2 will be the number of possible pairs (e1i, e2j ) times the probability that each pair could be a solution (namely, 1/n).
As a result, if λ^2 ≥ n, our expected number of solutions is at least 1, namely there is about a 50% chance that the sum of two values in the two lists will be zero. In other words, the length of each list must be at least

1*GeQFbCDIPtqaw GCKMv2Qw
 

i.e.

1*niP q9MUsNvNKqFHeQRn0g
 

For the k > 2 case, it is easy to see that a solution to the k-sum problem
exists with good probability so long as

1* 0RFrO9MbQXhBQE8rG Y0w
 

Since there is no good algorithm with complexity

1*VP6eQqIsh8LZG
 

the challenge is to find a solution x1, …, xk explicitly and efficiently, [Wag02]. However, Wagner proposes an idea in [Wag02] to find a solution explicitly and efficiently. He considers the 4-list birthday problem, see Figure 1.

1*f7WNlz fFyvDJR1VO8Qcgw
 

Given L1, .., L4 we find values x1, .., x4 that XOR is zero.

1*bs7ECsf8OqDgFuCLVD0ovA
 

contains all pairs from L1 × L2 that agree in their least l significant bits. Each list contains about

1*wq2 VIaz8zvgEE N1rBLQg
 

elements.

1*eHCo8aeHoj6LbBWuiSwVrw
 

We search matches between L12 and L34 so that x1 ⊕ x2 ⊕ x3 ⊕ x4 = 0 where lowl(x) represents the least significant bit of x. This leads to a solution to the 4-sum problem.
By the linearity of expectation

1*xMtXAZm6 qM06fhefFeA8w
 

Similarly,

1*Fm9gfN2rYjGKMfB19yTdVw
 

Any pair of elements from L12 ×L34 yields a match with probability

1*ZXh0 tl90Cv XKRP4oHUoA
 

The expected number of elements in common between L12 and
L34 is about

1*lQm8tu5eyqbuKa0Mgeu4GA
 

To have one solution, we need

1*QTYWP 5 s5O9PSjcHz4gZA
 

Thus, if we set l = n/3, we expect to have one solution to the 4-sum problem
with non-trivial probability. Since the size of all lists is around

1*3yEwXg kO30VbaEaYETK7w
 

the resulting algorithm can be implemented with

 

time and space. Wagner generalizes this idea to a k-list tree algorithm, [Wag02]. First, assume that is a power of two. Now to solve the k-sum problem even faster than cube-root time for larger values of k, it can be extended the idea of the 4-list tree algorithm.
This will be a complete binary tree of depth lgk. At internal nodes of height
h, we use the join operator

1*uAG2gSLDUsLb1vR7gbaa7w
 

except that at the root we use the full join operator. Each element x of an internal list Li…j contains back-pointers to elements x′ and x′′ of the two child lists used to form Li…j , such that x = x′ ⊕ x′′.

In this way, we will obtain an algorithm for the k-sum problem that requires

1*JFMT0kpkM1ighSjlKWJs5w
 

time and space and uses lists of size

1*GrHShjMfCflA2kunpweNOA
 

If is not a power of two, we work with ⌊k⌋, the largest power of two less
than k, and we use the list-elimination trick above. However, the algorithm
obtained in this way runs essentially no faster for k = 2^i + j than for k = 2^i.

Attack implementation

 

For the implementation, we choose an elliptic curve defined by

1*1T7ZbP7TaZazE RBCJQrbA
 

over finite field of size 101, F_101 is used. G = (1, 2) is the base point of order 17. We use SHA 256 as a cryptographic hash function.
The code is written for k = 2. According to Wagner’s Generalized Birthday algorithm, it will be enough to have λ ≥ 10. The code is written in Python.

import math
from random import randint
from ecpy.curves     import Curve,Point
from ecpy.keys       import ECPublicKey, ECPrivateKey
from ecpy.ecdsa      import ECDSA
import random
import hashlib
import array as arr 
import math

#k-sum attack on EdDSA for k=2

#y^2=x^3+3 over Z_101; G=(1,2) base point

def double(x, y, a, p):
    lambd = (((3 * x**2) % p ) *  pow(2 * y, -1, p)) % p
    newx = (lambd**2 - 2 * x) % p
    newy = (-lambd * newx + lambd * x - y) % p
    return (newx, newy)

def add_points(P, Q, p):
    xp, yp = P
    xq, yq = Q
    a=0
    if xq == yq == None:
        return xp, yp
    if xp == yp == None:
        return xq, yq
    
    assert (xq**3 + 3) % p == (yq ** 2) % p, "q not on curve"
    assert (xp**3 + 3) % p == (yp ** 2) % p, "p not on curve"
    
    if xq == xp and yq == yp:
        return double(xq, yq, a, p)
    elif xq == xp:
        return None, None
    
    lambd = ((yq - yp) * pow((xq - xp), -1, p) ) % p
    xr = (lambd**2 - xp - xq) % p
    yr = (lambd*(xp - xr) - yp) % p
    return xr, yr


def apply_double_and_add_method(G, k, p):
    target_point = G   
    k_binary = bin(k)[2:] 
    for i in range(1, len(k_binary)):
        current_bit = k_binary[i: i+1]
        target_point = add_points(target_point, target_point, p)
        if current_bit == "1":
            target_point = add_points(target_point, G, p)
    return target_point

def find_hash(n):
    m = str.encode(str(n))
    hash_value = hashlib.sha256(m).digest()
    # Convert the hash value to an integer
    c=int.from_bytes(hash_value, 'big')
    return c

G=(1,2)                                                 #base point of order 17

def key_gen(d):
    D=apply_double_and_add_method(G,d,101)
    return D
#keys 
da=1                                                   #secret key of Alice
D_a=key_gen(da)
db=5                                                   #secret key of Bob
D_b=key_gen(db)
dc=2                                                   #secret key of Carol
D_c=key_gen(dc)                



D=apply_double_and_add_method(G,(da+db+dc)%17,101)
print('Aggregated public key',D)





m = [] 
for _ in range(2):
   random_integer = random.randint(1, 101)  
   m.append(random_integer)
print('Messages m1,m2 ',m)                                           #k=2 case, we have two messages


sampleRa=[]
sampleRb=[]
ra=[]
rb=[]
for i in range(2):                                                   #k=2, each party send two nonces for each message
    ra.append(random.randint(1, 101))                                #secret nonce share of Alice
    sampleRa.append(apply_double_and_add_method(G,ra[i],101) )
    rb.append(random.randint(1, 101) )                               #secret nonce share of Bob
    sampleRb.append(apply_double_and_add_method(G,rb[i],101) )

print('Ra,ra',sampleRa,ra)
print('Rb,rb',sampleRb,rb)

#Attack

Rc=(1,2)                                                             #random evil nonce value of Carol
rc=1
print('Rc',Rc)

m_prime=1                                                         # evil message

add=add_points(sampleRa[0],sampleRb[0],101)
R_agg=add_points(add_points(add_points(add ,sampleRa[1],101),sampleRb[1],101),Rc,101)                 
print('R_prime',R_agg)


e_prime=find_hash(int(bin(R_agg[0])[2:]+bin(D[0])[2:]+bin(m_prime)[2:],2))  %17                                #evil challenge
print('e_prime',e_prime)


t=20  #\lambda

#Wagner's algorithm to find x1,x2 for k=2 case
X=[]
for i in range(t):  
    X.append(apply_double_and_add_method(G,random.randint(0,16),101) )
Y=[]
for i in range(t):  
   Y.append(apply_double_and_add_method(G,random.randint(0,16),101) )
#Lists
hashX=[]
for i in range(t):

    hashX.append(find_hash(int(bin(X[i][0])[2:]+bin(D[0])[2:]+bin(m[0])[2:],2)))

hashY=[]
for i in range(t):
    hashY.append(find_hash(int(bin(Y[i][0])[2:]+bin(D[0])[2:]+bin(m[1])[2:],2)))


 #Wagner k-sum algorithm
for i in range(t):
    for j in range(t):
        if (hashX[i]+hashY[j]-e_prime)% 17 == 0:                     
            e1=hashX[i]
            e2=hashY[j]    

#print('L_1',X)
print('e1, point',e1%17, apply_double_and_add_method(G,e1%101,101))
#print('L_2',Y) 
print('e2, point',e2%17,apply_double_and_add_method(G,e2%101,101))




sa1=ra[0]+(e1)*da                               #Alice computes her signature share for each signing session
sa2=ra[1]+(e2)*da

sb1=rb[0]+(e1)*db                               #Bob computes her signature share for each signing session
sb2=rb[1]+(e2)*db

s_prime=(sa1+sa2+sb1+sb2+rc+(e_prime*dc)) %17   #Carol computes s_prime

print('s_prime', s_prime)

#check that s_prime is correctly evaluated
if ( (e1+e2-e_prime)%17 == 0 ) and (e_prime%17 !=0) and (e1%17 !=0)  and (e2%17 !=0): 
  ss_prime=((ra[0]+ra[1]+rb[0]+rb[1]+rc) + (((e1+e2)%17)*(da+db)+(e_prime%17)*dc)) %17                       #s'=r'+e'd
assert(ss_prime==s_prime)


#Aggregated forged signature and its verification    
r_prime=(ra[0]+ra[1]+rb[0]+rb[1]+rc)%17                                                      #r' is aggregatedly calculated 
E=apply_double_and_add_method(D,e_prime%101,101)
Sum=add_points(R_agg,E,101)
S_prime=apply_double_and_add_method(G,(s_prime%101),101) 
print('Signature (R_prime, S_prime)=',R_agg, S_prime)                                         
      
if Sum == S_prime:                                                 #S_prime=?R_agg+e_prime*D  , evil signature is verified                                 
    print('Forged signature is valid')



References:

 

[Wag02] David A. Wagner. “A Generalized Birthday Problem”. In: Advances
in Cryptology — CRYPTO 2002, 22nd Annual International Cryptology Conference, Santa Barbara, California, USA, August 18–22, 2002, Proceedings. Ed. by Moti Yung. Vol. 2442. Lecture Notes in Computer Science. Springer, 2002, pp. 288–303. doi: 10.1007/3-540–45708–9 \ _19. url: https : / / doi . org / 10 . 1007 / 3–540 — 45708–9%5C_19.

[Ber+12] Daniel J. Bernstein et al. “High-speed high-security signatures”. In: J. Cryptogr. Eng. 2.2 (2012), pp. 77–89. doi: 10.1007/S13389–012-0027–1. url: https://doi.org/10.1007/s13389-012-0027-1.

[Dri+19] Manu Drijvers et al. “On the Security of Two-Round Multi-Signatures”. In: 2019 IEEE Symposium on Security and Privacy, SP 2019, San Francisco, CA, USA, May 19–23, 2019. IEEE, 2019, pp. 1084–1101. doi: 10.1109/SP.2019.00050. url: https://doi.org/10.1109/SP.2019.00050.

[Int] Polynomial Interpolation. In: (). url: https://en.wikipedia.org/
wiki/Polynomial_interpolation.

For your questions and suggestionsContact Us

The Newests