Another ErgoMix Vulnerability
Since reporting the first ErgoMix vulnerability, after some further digging, I found another vulnerability!
ErgoMix Recap
The following is the fixed half-mix script:
{
val g = groupGenerator
val gX = SELF.R4[GroupElement].get
val c1 = OUTPUTS(0).R4[GroupElement].get
val c2 = OUTPUTS(0).R5[GroupElement].get
val u1 = OUTPUTS(0).R6[GroupElement].get
val u2 = OUTPUTS(1).R6[GroupElement].get
(sigmaProp(
OUTPUTS(0).value == SELF.value &&
OUTPUTS(1).value == SELF.value &&
u1 == gX && u2 == gX &&
blake2b256(OUTPUTS(0).propositionBytes) == fullMixScriptHash &&
blake2b256(OUTPUTS(1).propositionBytes) == fullMixScriptHash &&
OUTPUTS(1).R4[GroupElement].get == c2 &&
OUTPUTS(1).R5[GroupElement].get == c1 &&
SELF.id == INPUTS(0).id &&
c1 != c2
) && {
proveDHTuple(g, gX, c1, c2) ||
proveDHTuple(g, gX, c2, c1)
}) || (proveDlog(gX))
}
The full-mix script is:
{
val g = groupGenerator
val c1 = SELF.R4[GroupElement].get
val c2 = SELF.R5[GroupElement].get
val gX = SELF.R6[GroupElement].get
proveDlog(c2) ||
proveDHTuple(g, c1, gX, c2)
}
Given any half-mix box, a user of ErgoMix can choose a secret y
and compute
c1=g^xy
, c2=g^y
. This allows them to create two indistinguishable full-mix
boxes (simply swap c1
and c2
):
- The original half-mix box creator can spend in the case
proveDHTuple(g, c1=g^y, gX, c2=g^xy)
. - The user can spend in the other case, with
proveDlog(c2=g^y)
.
Poisoned Half-Mix Attack
Notice that the value c1=g^xy
utilises g^x
, which is chosen by the half-mix
box creator. Is it possible for an attacker to pick a “poisonous” value of
g^x
?
Look again at the full-mix box script. The proveDHTuple(g, c1, gX, c2)
condition can be satisfied if we know x
such that g^x=gX
and c1^x=c2
.
We already know we can spend in the case proveDHTuple(g, c1=g^y, gX, c2=g^xy)
. The other case is proveDHTuple(g, c1=g^xy, gX, c2=g^y)
, which
would require:
(g^xy)^x = g^y
x^2y = y
x^2 = 1
x = +1 or x = -1
Note that in the case x=1
, the attack wouldn’t work because of the c1 != c2
condition in the half-mix script. However, the x=-1
case would allow an
attacker to create a poisoned half-mix box with g^x = g^-1
. Anyone spending
such a half-mix box would risk losing all of their funds as the attacker could
immediately spend both of the resulting full-mix boxes.
Similar to the previous vulnerability, the attacker does risk their funds by
creating poisoned half-mix boxes, since anyone else with knowledge of the
vulnerability could spend their funds using x=-1
, as well as the resulting
full-mix boxes.
The recommended fix is that clients ignore any poisoned half-mix boxes. This
is simpler than modifying the on-chain scripts, which would add complexity and
increase verification costs. For completeness and defence-in-depth, the case
g^x=1
should also be checked and ignored.
Disclosure
The vulnerability was reported to core developer Alex Chepurnoy on 23rd September 2020. Thanks for the quick response and bug bounty.