Skip to content

Commit

Permalink
Remove eddsa library dependency as I have re-implemented the function…
Browse files Browse the repository at this point in the history
…ality we require
  • Loading branch information
ml-james committed Oct 4, 2024
1 parent e9698b1 commit d2076b6
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 6 deletions.
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ dependencies {
checkstyle 'com.puppycrawl.tools:checkstyle:10.4'
// dependencies
implementation 'org.bitcoinj:bitcoinj-core:0.16.3'
implementation 'net.i2p.crypto:eddsa:0.3.0'

// test support dependencies
testSupportImplementation 'org.testcontainers:testcontainers:1.19.8'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.lmax.solana4j.api.ProgramDerivedAddress;
import com.lmax.solana4j.api.PublicKey;
import net.i2p.crypto.eddsa.math.GroupElement;
import com.lmax.solana4j.util.Ed25519;
import org.bitcoinj.core.Sha256Hash;

import java.nio.ByteBuffer;
Expand All @@ -12,7 +12,6 @@

import static com.lmax.solana4j.api.PublicKey.PUBLIC_KEY_LENGTH;
import static java.util.Objects.requireNonNull;
import static net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.ED_25519_CURVE_SPEC;

final class SolanaProgramDerivedAddress implements ProgramDerivedAddress
{
Expand Down Expand Up @@ -53,8 +52,7 @@ private static boolean isOffCurve(final byte[] programAddress)
{
try
{
final GroupElement point = ED_25519_CURVE_SPEC.getCurve().createPoint(programAddress, false);
return !point.isOnCurve();
return !Ed25519.isOnCurve(programAddress);
}
catch (final IllegalArgumentException e)
{
Expand Down
87 changes: 87 additions & 0 deletions src/main/java/com/lmax/solana4j/util/Ed25519.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.lmax.solana4j.util;

import java.math.BigInteger;
import java.nio.ByteBuffer;

public class Ed25519
{
private static final BigInteger P = new BigInteger("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", 16);
private static final BigInteger D = new BigInteger("52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3", 16);
private static final BigInteger PM5D8 = P.subtract(BigInteger.valueOf(5)).divide(BigInteger.valueOf(8));

/**
* Checks if the public key represented by a 32-byte array is on the Ed25519 curve.
*
* @param publicKeyBytes The 32-byte array representing the public key.
* @return true if the point is on the curve, false otherwise.
*/
public static boolean isOnCurve(final byte[] publicKeyBytes)
{
if (publicKeyBytes.length != 32)
{
throw new IllegalArgumentException("Public key must be 32 bytes long");
}

// elliptic curve equation dx^2y^2 + x^2 = y^2 - 1 (mod P)
// let x^2 = u/v where u = y^2 - 1 & v = dy^2 + 1 (mod P)
byte[] yBytes = publicKeyBytes.clone();
yBytes[31] &= 0x7F;
// publicKey is in little endian encoding, so we must reverse the byte array before we "math"
final BigInteger y = new BigInteger(1, reverseByteArray(yBytes));
final BigInteger y2 = y.multiply(y).mod(P);

final BigInteger u = y2.subtract(BigInteger.ONE).mod(P);
final BigInteger v = D.multiply(y2).add(BigInteger.ONE).mod(P);

final BigInteger x1 = calculateCandidateRoot(u, v);

return isCandidateRootValid(x1, v, u);
}

private static BigInteger calculateCandidateRoot(final BigInteger u, final BigInteger v)
{
final BigInteger v3 = v.modPow(BigInteger.TEN, P).multiply(v).mod(P);
final BigInteger v7 = v3.multiply(v3).multiply(v).mod(P);

final BigInteger uv3 = u.multiply(v3).mod(P);

final BigInteger uv7 = u.multiply(v7).mod(P);
final BigInteger uv7Pow22523 = uv7.modPow(PM5D8, P);

// after some transformations, given the properties of the e25519 curve we can say
// x^2 = (u/v)
// x1 = (u/v)^(P+3/8) where x1 is the candidate root
// x1 = uv^3(uv^7)^(P-5/8)
return uv3.multiply(uv7Pow22523).mod(P);
}

private static boolean isCandidateRootValid(final BigInteger x1, final BigInteger v, final BigInteger u)
{
// given x1 = uv^3(uv^7)^(P-5/8) we can check to see if the candidate root satisfies
// vx^2 = u (mod P) for x = x1 is a candidate root
// vx^2 = -u (mod P) for x = x1 is a candidate root
// if no roots then the point does not lie on the curve
final BigInteger vx12 = v.multiply(x1.modPow(BigInteger.TWO, P)).mod(P);

if (vx12.equals(u))
{
return true;
}

return vx12.equals(u.negate().mod(P));
}

private static byte[] reverseByteArray(final byte[] array)
{
final ByteBuffer buffer = ByteBuffer.allocate(array.length);

for (int i = array.length - 1; i >= 0; i--)
{
buffer.put(array[i]);
}

buffer.flip();

return buffer.array();
}
}
1 change: 0 additions & 1 deletion src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*/
module com.lmax.solana4j {
requires org.bitcoinj.core;
requires net.i2p.crypto.eddsa;

exports com.lmax.solana4j;
exports com.lmax.solana4j.api;
Expand Down

0 comments on commit d2076b6

Please sign in to comment.