Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java
Original file line number Diff line number Diff line change
Expand Up @@ -1373,4 +1373,62 @@ public static boolean verifyPrehash(byte[] sig, int sigOff, byte[] pk, int pkOff

return implVerify(sig, sigOff, pk, pkOff, ctx, phflag, m, 0, m.length);
}

private static int[] obtainYFromPublicKey(byte[] ed25519PublicKey)
{
PointAffine pA = new PointAffine();

boolean result = decodePointVar(ed25519PublicKey, 0, true, pA);
if (!result)
return null;

return pA.y;
}

public static byte[] toX25519PublicKey(byte[] ed25519PublicKey)
{
int[] one = new int[X25519Field.SIZE];
X25519Field.one(one);

int[] y = obtainYFromPublicKey(ed25519PublicKey);
if (y == null)
return null;

int[] oneMinusY = new int[X25519Field.SIZE];
X25519Field.sub(one, y, oneMinusY);

int[] onePlusY = new int[X25519Field.SIZE];
X25519Field.add(one, y, onePlusY);

int[] oneMinusYInverted = new int[X25519Field.SIZE];
X25519Field.inv(oneMinusY, oneMinusYInverted);

int[] u = new int[X25519Field.SIZE];
X25519Field.mul(onePlusY, oneMinusYInverted, u);

X25519Field.normalize(u);

byte[] x25519PublicKey = new byte[X25519.SCALAR_SIZE];
X25519Field.encode(u, x25519PublicKey, 0);

return x25519PublicKey;
}

public static byte[] toX25519PrivateKey(byte[] ed25519PrivateKey)
{
Digest d = Ed25519.createPrehash();
byte[] h = new byte[d.getDigestSize()];

d.update(ed25519PrivateKey, 0, ed25519PrivateKey.length);
d.doFinal(h, 0);

byte[] s = new byte[X25519.SCALAR_SIZE];

System.arraycopy(h, 0, s, 0, X25519.SCALAR_SIZE);
s[0] &= 0xF8;
s[X25519.SCALAR_SIZE - 1] &= 0x7F;
s[X25519.SCALAR_SIZE - 1] |= 0x40;

return s;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import junit.framework.TestCase;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.X25519PrivateKeyParameters;
import org.bouncycastle.math.ec.rfc8032.Ed25519;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
Expand Down Expand Up @@ -494,4 +496,43 @@ private static void checkEd25519phVector(String sSK, String sPK, String sM, Stri
assertFalse(text, shouldNotVerify);
}
}

// @Test
public void testEd25519ToX25519()
{
checkEd25519ToX25519Vector("be5bf46a933c8703fa48d0c4075c8fe35fb5f2358778c62008d7265ea6eb0858", "188dedb57fb265624e370e214eba35799cd17897f1d44663530606a2ed5cb57f", "2038f67c3fcfc38429819229d4c874d1f22540ab1349949a766cca0846363f28", "Ed25519 to X25519 vector #1");
checkEd25519ToX25519Vector("7013fabacfa4bd6eafb75e9d2d426a1f956ccd9acb19b615d3041d3e0b3000e6", "c0418dcd2fc1da92d6fb07c2ae4e0e4ddd71819533326047deab1c8c882e806f", "ec3e66a867e1f383dbcda7084569ffced6af071e85cb20791523347c59ec3459", "Ed25519 to X25519 vector #2");
// This vector checks proper normalization of 'u' (X25519 public key) when converting from Ed25519 public key
checkEd25519ToX25519Vector("f81fe2c27e3884dfa6c3a288f37d0ff5699ddade04b6c7dbc379c68a7e8129a0", "e0ee579cf0e094f9aa2c2f87caf8a2e48843fca000325b45400189991c684564", "4d8f5ab537e51507965ed841c35cb896ef6c474f789188cd3dd86dfb769ac661", "Ed25519 to X25519 vector #3");
}

private void checkEd25519ToX25519Vector(String ed25519SK, String x25519SK, String x25519PK, String text)
{
byte[] esk = Hex.decode(ed25519SK);
byte[] xsk = Hex.decode(x25519SK);
byte[] xpk = Hex.decode(x25519PK);

// Check Ed25519 secret key converts to expected X25519 secret key
{
byte[] converted = Ed25519.toX25519PrivateKey(esk);
assertTrue(text, Arrays.areEqual(xsk, converted));
}

// Derive X25519 public key from X25519 secret key and check
{
X25519PrivateKeyParameters x25519PrivateKeyParams = new X25519PrivateKeyParameters(xsk, 0);
byte[] derived = x25519PrivateKeyParams.generatePublicKey().getEncoded();
assertTrue(text, Arrays.areEqual(xpk, derived));
}

// Derive Ed25519 public key from Ed25519 secret key,
// then convert Ed25519 public key to X25519 public key and check
{
Ed25519PrivateKeyParameters ed25519PrivateKeyParams = new Ed25519PrivateKeyParameters(esk, 0);
byte[] derived = ed25519PrivateKeyParams.generatePublicKey().getEncoded();

byte[] converted = Ed25519.toX25519PublicKey(derived);
assertTrue(text, Arrays.areEqual(xpk, derived));
}
}
}