- no gaurantees of constant time
- ElGamal homomorphic properties enable the recovery of encrypted messages by an attacker through chosen ciphertext attack: if the attacker has:
e
, the encryption of messagem
- the ability to retrieve the decryption of an arbitrary forged message
they may find
m
, the original message. Thus, it should be made sure the attacker cannot retrieve the decryption of an arbitrary forged message
TODO: double-check that https://link.springer.com/content/pdf/10.1007/3-540-44448-3_3.pdf, especially section 4, doesn't apply given sufficient random padding
all n=d+1
Nodes are nodes that will perform threshold decryption, where d is the degree of the polynomials
that they will all work with. The nodes first agree on a common polynomial, A, that nobody knows. This is DKG,
or distributed key generation.
The DKG process is somewhat simple:
- All n nodes create a keygen polynomial:
A_1
,A_2
, ...A_n
A
is the sum of all keygen polynomials
How is the shared public key created, since nobody knows A
but rather only their own A_i
? Recall a public key in ElGamal is a private key scalar
multiplied by the generator point. In this library, the generator point used is the BabyJubJub base point.
Let the private key scalar be A(0)
. The public key is A(0)*B
, where B
is the base point.
Sounds fine and dandy so far, but how do we calculate A(0)*B
?
- Each node sends their keygen polynomial evaluated at 0 times the base point,
A_i(0)*B
. Let's callA_i(0)*B
a pubkey share for node i. A(0)*B
is just the sum of all pubkey shares
Yet A_i
is actually not the i'th node's keyshare for decryption. It was only used for generating the keyshares,
but it's not the actual key. As we will soon notice, the keyshare used to decrypt messages is a point on A.
A
is the shared polynomial, not the individual polynomial. Node 1 will know A(1)
. Node 2 will know A(2)
. Node n will
know A(n)
. Hence, the actual secret share is not A_i but rather A(i).
How will we construct A(i)
such that only node i
knows it? A familiar-ish process:
- Each node sends their keygen polynomial evaluated at i to node
i
- Node i then reconstructs
A(i)
= the sum of all other polynomialsA_j(i)
Essentially, the nodes just send their evaluations at pointi
to nodei
, then nodei
sums them to getA(i)
Now each node has A(i), they can use A(i), their decryption share, to decrypt messages together.
Recall ElGamal decryption involves
- Calculating a Diffie-Hellman shared secret
- Subtracting that shared secret from the encrypted message
That's pretty much it. The ciphertext is two points,
C1
andC2
.C1
isr*B
, wherer
is a random nonce used as to make the shared secret.C2
ism+s
, where m is the message to be encrypted ands
is the shared secret. The shared secret is calculated the standard Diffie-Hellman way:p*r*B
, wherep
is the private key of whoever will decrypt it.
Obviously, this doesn't quite work in the threshold method without some modifications -- it relies on a private key, p
, that no party knows.
Here, A(0) is used for p
. The secret key is the evaluation of the shared, unknown, polynomial at 0.
To decrypt it, node i
sends decryption share A(i)*C1
. Note A(i)
is a scalar despite A
being uppercase
Once enough nodes have sent decryption shares, the polynomial can be reconstructed by modified Lagrange interpolation.
The Lagrange bases for n
nodes are polynomials L_i
for i=1..n
such that
- L_i(x) = 1 when x is i
- L_i(x) = 0 when x is not i
A polynomial can be represented as a sum of unique Lagrange bases. Therefore, A can be represented as a weighted sum ∑w*L_i
for i=1..n where weight w=A(i)
What's A(0)
in terms of Lagrange bases? It is ∑L_i(0)*A(i)
for i=1..n
Anyone can compute L_i(0)
easily for any node's i. This is done in a polynomial.rs function
lagrange_basis_at_0
.
These components can be tied together to decrypt the message by giving decryption shares d_i
:
Node i
computes d_i
via:
multiplying C1
by their decryption key to get decryption share A(i)*C1
Once enough d_i
values are learned, one can recover the shared secret. Recall the Diffie-Hellman
shared secret is p*r*B
, where p
is A(0)
, the private key of the decryptor. And recall
that C1
is r*B
.
p*r*B = A(0)*r*B
Recall A(0)
is ∑L_i(0)*A(i) for i=1..n
p*r*b = ∑L_i(0)*A(i)*C1
for i=1..n
p*r*b = ∑L_i(0)*d_i
for i=1..n
There you have it. You've reconstructed the shared secret via only L_i(0)
and d_i given nodes i=1..n
once you have the shared secret, you can subtract it from C2 to get the original message.
The message is represented as a point. A variation of the Koblitz encoding method is used in this modifed babyjubjub-rs-with-elgamal library.