This turns out to be quite simple. We define a reversible permutation f(G,a)-->(G',a')
that is a permutation on the set of all (G,a)
pairs. The function can be the forward permutation with the a'
discarded. The inverse function is the reverse permutation with the a'
value set to either a fixed, random, or chosen value.
We define two functions, hash_P2s(P)=s
and hash_s2P(s)=P
, which take either a point P
or a scalar s
and pseudorandomly map it to the other type of thing.
The first can be as simple as:
def hash_P2s(P):
h=sha512(curve.encode(P))
return bytes_to_int(h)%curve.order
The second (hash to curve) is covered by this IETF document. The simplest option here is to generate random encoded points until one is valid.
Any good hash to curve function generates points whose discrete log is unknown.
def hash_s2P(s):
i=0
while 1:
h=sha512(s2bytes(s)+i.to_bytes(4,"little"))
#truncate to correct length
candidate=h[:EC_POINT_ENCODED_LEN]
#if the result is a valid point, we're done
if curve.is_valid_point(candidate):
return curve.decode(candidate)
#increment and try again
i+=1
Finally, we put these together into a Feistel-like permutation where a point and a scalar are used to generate another (point,scalar) pair.
def f(G,a):
G=curve.add(G,hash_s2P(a))
a=(a+hash_P2s(G))%curve.order
G=curve.add(G,hash_s2P(a))
return G,a
The output a
value can be discarded but if fed into the inverse function the original (point,scalar) pair will be produced.
def f_inverse(G,a):
G=curve.sub(G,hash_s2P(a))
a=(a-hash_P2s(G))%curve.order
G=curve.sub(G,hash_s2P(a))
return G,a
If you feed an output G
and a random scalar into the inverse function you'll get a random (G,a)
input pair of course.
preventing non-generator outputs
The input and output domains of the permutation include all valid points including the zero point which is not a generator. An attacker can find $(G,a)=f^{-1}(0)$ which when run through the function give a non-generator.
One easy way to solve this is to map the zero point to something else at the end of f()
. The inverse function won't care since it's not necessarily deterministic.
Security
This is similar to a Feistel network in that we take some state split into two parts (G,a)
and use pseudorandom functions that map between the part domains to alternately modify each side.
The GaG
modification sequence ensures the function cannot be solved to find a f(G,a)=xG
DLOG relation. aG
and aGa
both allow an attacker to find such a DLOG relationship trivially. GAG
is the smallest sequence that does not without a birthday type attack in the point or scalar domain which is as hard as just solving the DLOG problem directly.