From 42da1c6596f239eb89fa40aa310152535c185b19 Mon Sep 17 00:00:00 2001 From: catbref Date: Thu, 12 Sep 2019 11:35:32 +0100 Subject: [PATCH] Added Ed25519 to X25519 key conversion & tests --- .../bouncycastle/math/ec/rfc8032/Ed25519.java | 58 +++++++++++++++++++ .../math/ec/rfc8032/test/Ed25519Test.java | 41 +++++++++++++ 2 files changed, 99 insertions(+) diff --git a/core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java b/core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java index 1fe562177c..99ae25aa48 100644 --- a/core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java +++ b/core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java @@ -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; + } } diff --git a/core/src/test/java/org/bouncycastle/math/ec/rfc8032/test/Ed25519Test.java b/core/src/test/java/org/bouncycastle/math/ec/rfc8032/test/Ed25519Test.java index e7e45b7d98..1fec57d9c6 100644 --- a/core/src/test/java/org/bouncycastle/math/ec/rfc8032/test/Ed25519Test.java +++ b/core/src/test/java/org/bouncycastle/math/ec/rfc8032/test/Ed25519Test.java @@ -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; @@ -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)); + } + } }