Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
java
By: DoctorEvil, 14th March 2014
TL;DR
NXT's Crypto.java and Curve25519.java look kosher aside from a signing bug that is
currently being worked around.
General Methodology
1. Reviewed the primary/secondary literature on Curve25519 and EC-KCDSA to
evaluate them vis-a-vis the NXT use-case.
2. Re-implemented these primitives from scratch in Python (differently than the
Java version).
3. Scoured every line of the Crypto.java/Curve25519.java.
4. Ran tests to uncover output discrepancies between my implementation and
the Java version.
Choice of Algorithm
Curve25519+EC-KCDSA are theoretically defensible choices for NXT's use-case.
However, since cryptocurrency applications are dominated by signature verification,
Ed25519 would have arguably been a slightly better pick (although no high quality
Java implementations of it exist so NXT's choice is understandable).
Signing Bug
As I (and others) have noted before, the Curve25519.sign function has a legitimate
flaw that causes it to occasionally produce invalid signatures. This flaw may have
arisen because the original C code the Java was ported from used unsigned primitive
integral types underlying its representation of big numbers whereas the Java port is
restricted to signed types.
BloodyRookie's proposed patch is essentially sound, although it can be simplified
and made constant-time. Here is a fixed sign function inspired by his changes:
The implementation also deviates from the standard with regard to the computation
of w, instead computing it as H(H(m) + x1) (using the terminology of Guide to Elliptic
Curve Cryptography ). That said, I couldn't find anything obviously wrong with this
deviation; it appears sound.
The implementation's EC-KCDSA key generation is also non-standard due to
complications of Curve25519 using x-coordinate only representation. While this
ambiguous representation is not problematic in the context of Diffie-Hellman key
agreement (for which Curve25519 was specifically designed), it is inconvienent for
nave EC-KCDSA. The implementation uses a workaround during key generation to
address this issue. While I couldn't find any reference to anyone else using the
specific workaround, it appears to me to be sound.
Curve25519.verify uses a variant of a Montgomery ladder differential addition chain
to calculate the curve point (vP + hG). I can't find any reference to this variant in the
literature and this is easily the most optimization-obfuscated part of the
implementation. That being said, nothing about it stands out as wrong and I can
understand the performance motivation of using the variant.
Signature Malleability and Signature Canonicalization
It's worth noting that Daniel Bernstein, the primary designer of Curve25519, explicitly
states in his paper on the related Ed25519 signature scheme that he does not
consider signature malleability a part of the threat model when designing signature
schemes. This should give pause to those who think they will be able to define
canonicalization conventions to eliminate all sources of malleability. It may be
possible to achieve using such conventions but it is not safe to assume without
formal, peer-reviewed proof.
As far as I can tell, NXT's fix in response to the attack I reported earlier, makes it
immune to signature malleability.
One malleability-related observation: public keys are EC x-coordinates over a field of
order 2^255-19. This means every public key has two 32-byte representations. While
an interesting sidenote, I don't see how this opens up any avenues for attack in the
context of NXT.
I'd also like to make one correction to my earlier vulnerability report. The
GROUP_ORDER constant in Curve25519 I reported as 2^252+2^124 (from
misreading
of
code
comment)
but
is
actually
defined
as
7237005577332262213973186563042994240857116359379907606001950938285
454250989. My exploit code worked anyway simply because it used the constant as
defined.
Side Channel Attacks
The implementation leaks a small amount (up to 2 bits?) of private key information via
timing during signing in Crypto.sign because this function recomputes the signing key
from the passphrase each invocation (Curve25519.keygen is not timing attack
resistant when used to generate EC-KCDSA keys). I don't see any way to exploit this
in the context of NXT. However, precomputing the signing key once at user login and
passing it into the sign function would be appropriate if NXT were to ever have a code
path where an external observer could cause a target to sign anything on-demand
and observe it's timing (e.g. like some interactive authentication protocol).
Alternative Implementation
As part of my analysis I developed an alternative Python implementation of the crypto
primitives. As noted, my code is pedagogical (slow and not timing-attack resistant).
However, since it uses fundamentally different EC arithmetic (affine XY coordinates
vs projective XZ coordinates), it serves as a useful sanity check of the Java version's
math (as well as makes for a more understandable exposition of the underlying
algorithms).
In a test with 1024 signers, it produced the same signature outputs and had the same
verification results as the (patched) Java implementation.
Other Checks
The Curve25519 committed into NXT's repository (as of the publication of this report)
exactly matchesthe port distributed by Dmitry Skiba. Not hugely notable, but thought
this was worth checking for the sake of the tin-foil-hat crowd.