Forráskód Böngészése

Add Ed25519 -> X25519 key conversion

Add TCurve25519KeyUtilities with Ed25519 to X25519 key conversion and
tests, including a libsodium known-answer test.

Conversion (CryptoLib/Parameters):
- ToX25519PrivateKey: 32-byte Ed25519 seed → SHA-512 → first 32 bytes
  → clamp (TX25519.ClampPrivateKey) → X25519 private key. Matches
  Ed25519 scalar derivation (PruneScalar) so the same scalar is used
  for both schemes.
- ToX25519PublicKey: Decode Ed25519 public as full 32-byte V (same as
  TEd25519.DecodePointVar), compute Montgomery u = (1+V)/(1-V),
  normalize with TX25519Field.Normalize before encode so the result
  matches X25519.ScalarMultBase. No changes to ClpEd25519.pas.

Tests (Curve25519KeyUtilitiesTests):
- TestKeyPairConsistency: random Ed25519 keypair → convert both to
  X25519; assert converted public equals public from converted private.
- TestKeyPairConsistencyFromRfc8032Vector1: same check with RFC 8032
  vector 1 seed.
- TestKnownAnswerFromLibsodiumSeed: fixed seed and expected X25519 pk/sk
  from libsodium test/default/ed25519_convert.c and .exp; assert
  conversion matches those values.
- TestToX25519PublicKeyRaisesWhenNil / TestToX25519PrivateKeyRaisesWhenNil:
  nil parameter raises EArgumentNilCryptoLibException.
- TestConvertedKeysAgreement: two Ed25519 keypairs converted to X25519;
  run X25519 agreement both ways and assert shared secrets match.

Docs: XML summary/remarks and param/returns added on
ToX25519PublicKey and ToX25519PrivateKey describing the conversion
logic and normalization.
Ugochukwu Mmaduekwe 9 órája
szülő
commit
b31b30738a

+ 2 - 0
CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.dpr

@@ -498,6 +498,7 @@ uses
   ClpCmsParsers in '..\..\CryptoLib\src\Asn1\Cms\ClpCmsParsers.pas',
   ClpICmsAsn1Objects in '..\..\CryptoLib\src\Interfaces\Asn1\Cms\ClpICmsAsn1Objects.pas',
   ClpICmsParsers in '..\..\CryptoLib\src\Interfaces\Asn1\Cms\ClpICmsParsers.pas',
+  ClpCurve25519KeyUtilities in '..\..\CryptoLib\src\Crypto\Parameters\ClpCurve25519KeyUtilities.pas',
   ClpFixedSecureRandom in '..\src\Utils\ClpFixedSecureRandom.pas',
   ClpShortenedDigest in '..\src\Utils\ClpShortenedDigest.pas',
   BlowfishTestVectors in '..\src\Crypto\BlowfishTestVectors.pas',
@@ -596,6 +597,7 @@ uses
   DeltaCertificateTests in '..\src\Asn1\X509\DeltaCertificateTests.pas',
   X509CertGenTests in '..\src\X509\X509CertGenTests.pas',
   CertTests in '..\src\Others\CertTests.pas',
+  Curve25519KeyUtilitiesTests in '..\src\Others\Curve25519KeyUtilitiesTests.pas',
   CryptoLibTestBase in '..\src\CryptoLibTestBase.pas';
 
 begin

+ 5 - 1
CryptoLib.Tests/FreePascal.Tests/CryptoLib.Tests.lpi

@@ -79,7 +79,7 @@
         <PackageName Value="FCL"/>
       </Item4>
     </RequiredPackages>
-    <Units Count="100">
+    <Units Count="101">
       <Unit0>
         <Filename Value="CryptoLib.lpr"/>
         <IsPartOfProject Value="True"/>
@@ -481,6 +481,10 @@
         <Filename Value="..\src\Utils\Net\IPAddressUtilitiesTests.pas"/>
         <IsPartOfProject Value="True"/>
       </Unit99>
+      <Unit100>
+        <Filename Value="..\src\Others\Curve25519KeyUtilitiesTests.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit100>
     </Units>
   </ProjectOptions>
   <CompilerOptions>

+ 19 - 19
CryptoLib.Tests/FreePascal.Tests/CryptoLib.lpr

@@ -3,25 +3,25 @@ program CryptoLib.Tests;
 {$mode objfpc}{$H+}
 
 uses
-  Interfaces, Forms, GuiTestRunner,
-  Asn1SequenceParserTests, EqualsAndHashCodeTests, OIDTests, EnumeratedTests,
-  ParsingTests, ParseTests, StringTests, TagTests, BigIntegerTests,
-  ECAlgorithmsTests, ECPointTests, SecP256R1FieldTests, SecP384R1FieldTests,
-  ECDsa5Tests, ECTests, NamedCurveTests, SignerUtilitiesTests,
-  SecureRandomTests, DigestRandomNumberTests, FixedPointTests, AESTests,
-  BlockCipherVectorTests, BlockCipherMonteCarloTests, AESTestVectors,
-  BlowfishTestVectors, SpeckTestVectors, RijndaelTestVectors, AESSICTests,
-  SPECKTests, IESCipherTests, MD5HMacTests, SHA1HMacTests, SHA224HMacTests,
-  SHA256HMacTests, SHA384HMacTests, SHA512HMacTests, RIPEMD128HMacTests,
-  RIPEMD160HMacTests, HMacTests, Pkcs5Tests, HkdfGeneratorTests, ECIESTests,
-  PascalCoinECIESTests, ECNRTests, PrimesTests, PaddingTests, DSATests,
-  DeterministicDsaTests, Salsa20Tests, XSalsa20Tests, ChaChaTests,
-  StreamCipherResetTests, CTSTests, X25519Tests, Ed25519Tests,
-  X25519HigherLevelTests, Ed25519HigherLevelTests, ShortenedDigestTests,
-  Kdf1GeneratorTests, Kdf2GeneratorTests, Argon2Tests, ScryptTests, DigestTests,
-  CertTests, DigestUtilitiesTests, DHTests, Asn1IntegerTests, BitStringTests,
-  GeneralizedTimeTests, OctetStringTests, RelativeOidTests,
-  UtcTimeTests, InputStreamTests, SetTests, X9Tests, PrivateKeyInfoTests,
+  Interfaces, Forms, GuiTestRunner, Asn1SequenceParserTests,
+  EqualsAndHashCodeTests, OIDTests, EnumeratedTests, ParsingTests, ParseTests,
+  StringTests, TagTests, BigIntegerTests, ECAlgorithmsTests, ECPointTests,
+  SecP256R1FieldTests, SecP384R1FieldTests, ECDsa5Tests, ECTests,
+  NamedCurveTests, SignerUtilitiesTests, SecureRandomTests,
+  DigestRandomNumberTests, FixedPointTests, AESTests, BlockCipherVectorTests,
+  BlockCipherMonteCarloTests, AESTestVectors, BlowfishTestVectors,
+  SpeckTestVectors, RijndaelTestVectors, AESSICTests, SPECKTests,
+  IESCipherTests, MD5HMacTests, SHA1HMacTests, SHA224HMacTests, SHA256HMacTests,
+  SHA384HMacTests, SHA512HMacTests, RIPEMD128HMacTests, RIPEMD160HMacTests,
+  HMacTests, Pkcs5Tests, HkdfGeneratorTests, ECIESTests, PascalCoinECIESTests,
+  ECNRTests, PrimesTests, PaddingTests, DSATests, DeterministicDsaTests,
+  Salsa20Tests, XSalsa20Tests, ChaChaTests, StreamCipherResetTests, CTSTests,
+  X25519Tests, Ed25519Tests, X25519HigherLevelTests, Ed25519HigherLevelTests,
+  ShortenedDigestTests, Kdf1GeneratorTests, Kdf2GeneratorTests, Argon2Tests,
+  ScryptTests, DigestTests, CertTests, Curve25519KeyUtilitiesTests,
+  DigestUtilitiesTests, DHTests, Asn1IntegerTests, BitStringTests,
+  GeneralizedTimeTests, OctetStringTests, RelativeOidTests, UtcTimeTests,
+  InputStreamTests, SetTests, X9Tests, PrivateKeyInfoTests,
   Pkcs10CertRequestTests, DeltaCertificateTests, CertificateTests, X509AltTests,
   X509ExtensionsTests, X509NameTests, SubjectKeyIdentifierTests, KeyUsageTests,
   GeneralNameTests, KMacTests, RSATests, PssTests, ISO9796Tests,

+ 5 - 1
CryptoLib.Tests/FreePascal.Tests/CryptoLibConsole.lpi

@@ -39,7 +39,7 @@
         <PackageName Value="FCL"/>
       </Item2>
     </RequiredPackages>
-    <Units Count="100">
+    <Units Count="101">
       <Unit0>
         <Filename Value="CryptoLibConsole.lpr"/>
         <IsPartOfProject Value="True"/>
@@ -440,6 +440,10 @@
         <Filename Value="..\src\Utils\Net\IPAddressUtilitiesTests.pas"/>
         <IsPartOfProject Value="True"/>
       </Unit99>
+      <Unit100>
+        <Filename Value="..\src\Others\Curve25519KeyUtilitiesTests.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit100>
     </Units>
   </ProjectOptions>
   <CompilerOptions>

+ 2 - 1
CryptoLib.Tests/FreePascal.Tests/CryptoLibConsole.lpr

@@ -16,7 +16,8 @@ uses
   HkdfGeneratorTests, ECIESTests, PascalCoinECIESTests, ECNRTests, PrimesTests,
   PaddingTests, DSATests, DeterministicDsaTests, Salsa20Tests, XSalsa20Tests,
   ChaChaTests, StreamCipherResetTests, CTSTests, X25519Tests, Ed25519Tests,
-  X25519HigherLevelTests, Ed25519HigherLevelTests, ShortenedDigestTests,
+  X25519HigherLevelTests, Ed25519HigherLevelTests, Curve25519KeyUtilitiesTests,
+  ShortenedDigestTests,
   Kdf1GeneratorTests, Kdf2GeneratorTests, Argon2Tests, ScryptTests, DigestTests,
   CertTests, DigestUtilitiesTests, DHTests, Asn1IntegerTests,
   GeneralizedTimeTests, BitStringTests, InputStreamTests,

+ 233 - 0
CryptoLib.Tests/src/Others/Curve25519KeyUtilitiesTests.pas

@@ -0,0 +1,233 @@
+{ *********************************************************************************** }
+{ *                              CryptoLib Library                                  * }
+{ *                Copyright (c) 2018 - 20XX Ugochukwu Mmaduekwe                    * }
+{ *                 Github Repository <https://github.com/Xor-el>                   * }
+
+{ *  Distributed under the MIT software license, see the accompanying file LICENSE  * }
+{ *          or visit http://www.opensource.org/licenses/mit-license.php.           * }
+
+{ *                              Acknowledgements:                                  * }
+{ *                                                                                 * }
+{ *      Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring     * }
+{ *                           development of this library                           * }
+
+{ * ******************************************************************************* * }
+
+(* &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& *)
+
+unit Curve25519KeyUtilitiesTests;
+
+interface
+
+{$IFDEF FPC}
+{$MODE DELPHI}
+{$ENDIF FPC}
+
+uses
+  SysUtils,
+{$IFDEF FPC}
+  fpcunit,
+  testregistry,
+{$ELSE}
+  TestFramework,
+{$ENDIF FPC}
+  ClpCurve25519KeyUtilities,
+  ClpEd25519Parameters,
+  ClpIEd25519Parameters,
+  ClpEd25519Generators,
+  ClpIEd25519Generators,
+  ClpX25519Parameters,
+  ClpIX25519Parameters,
+  ClpX25519Agreement,
+  ClpIX25519Agreement,
+  ClpIAsymmetricCipherKeyPair,
+  ClpSecureRandom,
+  ClpISecureRandom,
+  ClpCryptoLibTypes,
+  CryptoLibTestBase;
+
+type
+
+  TTestCurve25519KeyUtilities = class(TCryptoLibAlgorithmTestCase)
+  private
+  var
+    FRandom: ISecureRandom;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+  published
+    procedure TestKeyPairConsistency;
+    procedure TestKeyPairConsistencyFromRfc8032Vector1;
+    procedure TestKnownAnswerFromLibsodiumSeed;
+    procedure TestToX25519PublicKeyRaisesWhenNil;
+    procedure TestToX25519PrivateKeyRaisesWhenNil;
+    procedure TestConvertedKeysAgreement;
+  end;
+
+implementation
+
+resourcestring
+  SKeyPairConsistencyFailed = 'Key pair consistency failed: converted X25519 public does not match public derived from converted private';
+  SExpectedArgumentNilException = 'Expected EArgumentNilCryptoLibException';
+  SConvertedKeysAgreementFailed = 'X25519 agreement with converted keys failed';
+
+{ TTestCurve25519KeyUtilities }
+
+procedure TTestCurve25519KeyUtilities.SetUp;
+begin
+  inherited SetUp();
+  FRandom := TSecureRandom.Create();
+end;
+
+procedure TTestCurve25519KeyUtilities.TearDown;
+begin
+  FRandom := nil;
+  inherited TearDown();
+end;
+
+procedure TTestCurve25519KeyUtilities.TestKeyPairConsistency;
+var
+  LKpg: IEd25519KeyPairGenerator;
+  LKp: IAsymmetricCipherKeyPair;
+  LEdPriv: IEd25519PrivateKeyParameters;
+  LEdPub: IEd25519PublicKeyParameters;
+  LX25519Sk: IX25519PrivateKeyParameters;
+  LX25519PkFromConversion: IX25519PublicKeyParameters;
+  LX25519PkFromSk: IX25519PublicKeyParameters;
+begin
+  LKpg := TEd25519KeyPairGenerator.Create() as IEd25519KeyPairGenerator;
+  LKpg.Init(TEd25519KeyGenerationParameters.Create(FRandom)
+    as IEd25519KeyGenerationParameters);
+  LKp := LKpg.GenerateKeyPair();
+  if not Supports(LKp.Private, IEd25519PrivateKeyParameters, LEdPriv) then
+    Fail(SKeyPairConsistencyFailed);
+  if not Supports(LKp.Public, IEd25519PublicKeyParameters, LEdPub) then
+    Fail(SKeyPairConsistencyFailed);
+  LX25519Sk := TCurve25519KeyUtilities.ToX25519PrivateKey(LEdPriv);
+  LX25519PkFromConversion := TCurve25519KeyUtilities.ToX25519PublicKey(LEdPub);
+  LX25519PkFromSk := LX25519Sk.GeneratePublicKey();
+  if not AreEqual(LX25519PkFromConversion.GetEncoded(), LX25519PkFromSk.GetEncoded()) then
+    Fail(SKeyPairConsistencyFailed);
+end;
+
+procedure TTestCurve25519KeyUtilities.TestKeyPairConsistencyFromRfc8032Vector1;
+var
+  LSeed: TBytes;
+  LEdPriv: IEd25519PrivateKeyParameters;
+  LEdPub: IEd25519PublicKeyParameters;
+  LX25519Sk: IX25519PrivateKeyParameters;
+  LX25519PkFromConversion: IX25519PublicKeyParameters;
+  LX25519PkFromSk: IX25519PublicKeyParameters;
+begin
+  LSeed := DecodeHex('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60');
+  LEdPriv := TEd25519PrivateKeyParameters.Create(LSeed);
+  LEdPub := LEdPriv.GeneratePublicKey();
+  LX25519Sk := TCurve25519KeyUtilities.ToX25519PrivateKey(LEdPriv);
+  LX25519PkFromConversion := TCurve25519KeyUtilities.ToX25519PublicKey(LEdPub);
+  LX25519PkFromSk := LX25519Sk.GeneratePublicKey();
+  if not AreEqual(LX25519PkFromConversion.GetEncoded(), LX25519PkFromSk.GetEncoded()) then
+    Fail(SKeyPairConsistencyFailed);
+end;
+
+procedure TTestCurve25519KeyUtilities.TestKnownAnswerFromLibsodiumSeed;
+var
+  LSeed: TBytes;
+  LEdPriv: IEd25519PrivateKeyParameters;
+  LEdPub: IEd25519PublicKeyParameters;
+  LX25519Sk: IX25519PrivateKeyParameters;
+  LX25519Pk: IX25519PublicKeyParameters;
+  LExpectedPkHex, LExpectedSkHex: String;
+begin
+  (*
+    Seed and expected X25519 pk/sk from libsodium:
+    keypair_seed -> https://github.com/jedisct1/libsodium/blob/master/test/default/ed25519_convert.c
+    X25519 pk/sk -> https://github.com/jedisct1/libsodium/blob/master/test/default/ed25519_convert.exp
+  *)
+  LSeed := DecodeHex('421151a459faeade3d247115f94aedae42318124095afabe4d1451a559faedee');
+  LEdPriv := TEd25519PrivateKeyParameters.Create(LSeed);
+  LEdPub := LEdPriv.GeneratePublicKey();
+  LX25519Sk := TCurve25519KeyUtilities.ToX25519PrivateKey(LEdPriv);
+  LX25519Pk := TCurve25519KeyUtilities.ToX25519PublicKey(LEdPub);
+  LExpectedPkHex := 'f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50';
+  LExpectedSkHex := '8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166';
+  if not AreEqual(LX25519Pk.GetEncoded(), DecodeHex(LExpectedPkHex)) then
+    Fail(SKeyPairConsistencyFailed);
+  if not AreEqual(LX25519Sk.GetEncoded(), DecodeHex(LExpectedSkHex)) then
+    Fail(SKeyPairConsistencyFailed);
+end;
+
+procedure TTestCurve25519KeyUtilities.TestToX25519PublicKeyRaisesWhenNil;
+begin
+  try
+    TCurve25519KeyUtilities.ToX25519PublicKey(nil);
+    Fail(SExpectedArgumentNilException);
+  except
+    on E: EArgumentNilCryptoLibException do
+      ; // expected
+  else
+    raise;
+  end;
+end;
+
+procedure TTestCurve25519KeyUtilities.TestToX25519PrivateKeyRaisesWhenNil;
+begin
+  try
+    TCurve25519KeyUtilities.ToX25519PrivateKey(nil);
+    Fail(SExpectedArgumentNilException);
+  except
+    on E: EArgumentNilCryptoLibException do
+      ; // expected
+  else
+    raise;
+  end;
+end;
+
+procedure TTestCurve25519KeyUtilities.TestConvertedKeysAgreement;
+var
+  LKpg: IEd25519KeyPairGenerator;
+  LKpA, LKpB: IAsymmetricCipherKeyPair;
+  LEdPrivA, LEdPrivB: IEd25519PrivateKeyParameters;
+  LEdPubA, LEdPubB: IEd25519PublicKeyParameters;
+  LX25519SkA, LX25519SkB: IX25519PrivateKeyParameters;
+  LX25519PubA, LX25519PubB: IX25519PublicKeyParameters;
+  LAgreeA, LAgreeB: IX25519Agreement;
+  LSecretA, LSecretB: TBytes;
+begin
+  LKpg := TEd25519KeyPairGenerator.Create() as IEd25519KeyPairGenerator;
+  LKpg.Init(TEd25519KeyGenerationParameters.Create(FRandom)
+    as IEd25519KeyGenerationParameters);
+  LKpA := LKpg.GenerateKeyPair();
+  LKpB := LKpg.GenerateKeyPair();
+  if not Supports(LKpA.Private, IEd25519PrivateKeyParameters, LEdPrivA) then
+    Fail(SConvertedKeysAgreementFailed);
+  if not Supports(LKpA.Public, IEd25519PublicKeyParameters, LEdPubA) then
+    Fail(SConvertedKeysAgreementFailed);
+  if not Supports(LKpB.Private, IEd25519PrivateKeyParameters, LEdPrivB) then
+    Fail(SConvertedKeysAgreementFailed);
+  if not Supports(LKpB.Public, IEd25519PublicKeyParameters, LEdPubB) then
+    Fail(SConvertedKeysAgreementFailed);
+  LX25519SkA := TCurve25519KeyUtilities.ToX25519PrivateKey(LEdPrivA);
+  LX25519PubA := TCurve25519KeyUtilities.ToX25519PublicKey(LEdPubA);
+  LX25519SkB := TCurve25519KeyUtilities.ToX25519PrivateKey(LEdPrivB);
+  LX25519PubB := TCurve25519KeyUtilities.ToX25519PublicKey(LEdPubB);
+  LAgreeA := TX25519Agreement.Create() as IX25519Agreement;
+  LAgreeA.Init(LX25519SkA);
+  System.SetLength(LSecretA, LAgreeA.AgreementSize);
+  LAgreeA.CalculateAgreement(LX25519PubB, LSecretA, 0);
+  LAgreeB := TX25519Agreement.Create() as IX25519Agreement;
+  LAgreeB.Init(LX25519SkB);
+  System.SetLength(LSecretB, LAgreeB.AgreementSize);
+  LAgreeB.CalculateAgreement(LX25519PubA, LSecretB, 0);
+  if not AreEqual(LSecretA, LSecretB) then
+    Fail(SConvertedKeysAgreementFailed);
+end;
+
+initialization
+
+{$IFDEF FPC}
+  RegisterTest(TTestCurve25519KeyUtilities);
+{$ELSE}
+  RegisterTest(TTestCurve25519KeyUtilities.Suite);
+{$ENDIF FPC}
+
+end.

+ 122 - 0
CryptoLib/src/Crypto/Parameters/ClpCurve25519KeyUtilities.pas

@@ -0,0 +1,122 @@
+{ *********************************************************************************** }
+{ *                              CryptoLib Library                                  * }
+{ *                Copyright (c) 2018 - 20XX Ugochukwu Mmaduekwe                    * }
+{ *                 Github Repository <https://github.com/Xor-el>                   * }
+
+{ *  Distributed under the MIT software license, see the accompanying file LICENSE  * }
+{ *          or visit http://www.opensource.org/licenses/mit-license.php.           * }
+
+{ *                              Acknowledgements:                                  * }
+{ *                                                                                 * }
+{ *      Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring     * }
+{ *                           development of this library                           * }
+
+{ * ******************************************************************************* * }
+
+(* &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& *)
+
+unit ClpCurve25519KeyUtilities;
+
+{$I ..\..\Include\CryptoLib.inc}
+
+interface
+
+uses
+  SysUtils,
+  ClpDigestUtilities,
+  ClpX25519,
+  ClpX25519Field,
+  ClpX25519Parameters,
+  ClpIEd25519Parameters,
+  ClpIX25519Parameters,
+  ClpCryptoLibTypes;
+
+resourcestring
+  SCurve25519Ed25519PublicKeyNil = 'Ed25519 public key cannot be nil';
+  SCurve25519Ed25519PrivateKeyNil = 'Ed25519 private key cannot be nil';
+
+type
+  /// <summary>
+  /// Curve25519 key utilities (Ed25519 / X25519 conversion and related helpers).
+  /// </summary>
+  TCurve25519KeyUtilities = class sealed(TObject)
+  private
+    const
+      /// <summary>Size in bytes of Ed25519/X25519 public and private key material.</summary>
+      KeySizeBytes = 32;
+  public
+    /// <summary>
+    /// Convert an Ed25519 public key to its X25519 equivalent.
+    /// </summary>
+    /// <remarks>
+    /// Curve25519 exists in two forms: Montgomery (X25519) and twisted Edwards (Ed25519), with a birational map between them.
+    /// The Montgomery u-coordinate is u = (1+y)/(1-y) where y is the Edwards y. This implementation decodes the Ed25519
+    /// public key bytes (full 32-byte decode, same as TEd25519.DecodePointVar) to get the internal y-value V, computes
+    /// u = (1+V)/(1-V) in the field, then normalizes u before encoding so the result matches X25519.ScalarMultBase output.
+    /// Normalization is required for a canonical 32-byte encoding; without it the same point can encode differently and break
+    /// consistency with the public key derived from the converted private key.
+    /// </remarks>
+    /// <param name="AEd25519PublicKey">The Ed25519 public key to convert.</param>
+    /// <returns>The X25519 public key (32-byte u-coordinate) for the same curve point.</returns>
+    class function ToX25519PublicKey(const AEd25519PublicKey: IEd25519PublicKeyParameters): IX25519PublicKeyParameters; static;
+    /// <summary>
+    /// Convert an Ed25519 private key to its X25519 equivalent.
+    /// </summary>
+    /// <remarks>
+    /// Ed25519 and X25519 use the same scalar on Curve25519: Ed25519 derives it as the first 32 bytes of SHA-512(seed)
+    /// then applies the same clamping as X25519 (PruneScalar in Ed25519 matches ClampPrivateKey in X25519). This method
+    /// therefore hashes the Ed25519 seed with SHA-512, takes the first 32 bytes, clamps them with TX25519.ClampPrivateKey,
+    /// and returns an X25519 private key. The resulting scalar is the same as used for the Ed25519 public key, so the
+    /// converted X25519 public (from this private via ScalarMultBase) matches the converted Ed25519 public (via ToX25519PublicKey).
+    /// Clamping is done in this utility; TX25519PrivateKeyParameters does not clamp internally.
+    /// </remarks>
+    /// <param name="AEd25519PrivateKey">The Ed25519 private key (32-byte seed).</param>
+    /// <returns>The X25519 private key (32-byte clamped scalar) for the same scalar.</returns>
+    class function ToX25519PrivateKey(const AEd25519PrivateKey: IEd25519PrivateKeyParameters): IX25519PrivateKeyParameters; static;
+  end;
+
+implementation
+
+{ TCurve25519KeyUtilities }
+
+class function TCurve25519KeyUtilities.ToX25519PrivateKey(const AEd25519PrivateKey: IEd25519PrivateKeyParameters): IX25519PrivateKeyParameters;
+var
+  LSeed, LHash, LScalar: TCryptoLibByteArray;
+begin
+  if AEd25519PrivateKey = nil then
+    raise EArgumentNilCryptoLibException.Create(SCurve25519Ed25519PrivateKeyNil);
+  LSeed := AEd25519PrivateKey.GetEncoded();
+  LHash := TDigestUtilities.CalculateDigest('SHA-512', LSeed);
+  System.SetLength(LScalar, KeySizeBytes);
+  System.Move(LHash[0], LScalar[0], KeySizeBytes);
+  TX25519.ClampPrivateKey(LScalar);
+  Result := TX25519PrivateKeyParameters.Create(LScalar);
+end;
+
+class function TCurve25519KeyUtilities.ToX25519PublicKey(const AEd25519PublicKey: IEd25519PublicKeyParameters): IX25519PublicKeyParameters;
+var
+  LPk, LEncoded: TCryptoLibByteArray;
+  LY, LOne, LOneMinusY, LOnePlusY, LInv, LU: TCryptoLibInt32Array;
+begin
+  if AEd25519PublicKey = nil then
+    raise EArgumentNilCryptoLibException.Create(SCurve25519Ed25519PublicKeyNil);
+  LPk := AEd25519PublicKey.GetEncoded();
+  LY := TX25519Field.Create();
+  TX25519Field.Decode(LPk, LY);
+  LOne := TX25519Field.Create();
+  TX25519Field.One(LOne);
+  LOneMinusY := TX25519Field.Create();
+  TX25519Field.Sub(LOne, LY, LOneMinusY);
+  LOnePlusY := TX25519Field.Create();
+  TX25519Field.Add(LOne, LY, LOnePlusY);
+  LInv := TX25519Field.Create();
+  TX25519Field.Inv(LOneMinusY, LInv);
+  LU := TX25519Field.Create();
+  TX25519Field.Mul(LOnePlusY, LInv, LU);
+  TX25519Field.Normalize(LU);
+  System.SetLength(LEncoded, KeySizeBytes);
+  TX25519Field.Encode(LU, LEncoded);
+  Result := TX25519PublicKeyParameters.Create(LEncoded);
+end;
+
+end.

+ 16 - 12
CryptoLib/src/Packages/FPC/CryptoLib4PascalPackage.lpk

@@ -25,7 +25,7 @@
  Acknowledgements: 
 Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring the development of this library "/>
     <Version Major="3" Minor="3"/>
-    <Files Count="474">
+    <Files Count="475">
       <Item1>
         <Filename Value="..\..\Asn1\ClpOidTokenizer.pas"/>
         <UnitName Value="ClpOidTokenizer"/>
@@ -712,17 +712,21 @@ Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring the devel
         <UnitName Value="ClpX25519Parameters"/>
       </Item171>
       <Item172>
-        <Filename Value="..\..\Crypto\Agreements\ClpDHBasicAgreement.pas"/>
-        <UnitName Value="ClpDHBasicAgreement"/>
+        <Filename Value="..\..\Crypto\Parameters\ClpCurve25519KeyUtilities.pas"/>
+        <UnitName Value="ClpCurve25519KeyUtilities"/>
       </Item172>
       <Item173>
-        <Filename Value="..\..\Crypto\Parameters\ClpRsaParameters.pas"/>
-        <UnitName Value="ClpRsaParameters"/>
+        <Filename Value="..\..\Crypto\Agreements\ClpDHBasicAgreement.pas"/>
+        <UnitName Value="ClpDHBasicAgreement"/>
       </Item173>
       <Item174>
+        <Filename Value="..\..\Crypto\Parameters\ClpRsaParameters.pas"/>
+        <UnitName Value="ClpRsaParameters"/>
+      </Item174>
+      <Item176>
         <Filename Value="..\..\Crypto\Signers\ClpX931Signer.pas"/>
         <UnitName Value="ClpX931Signer"/>
-      </Item174>
+      </Item176>
       <Item175>
         <Filename Value="..\..\Crypto\Signers\SignerEncodings\ClpPlainDsaEncoding.pas"/>
         <UnitName Value="ClpPlainDsaEncoding"/>
@@ -1911,18 +1915,18 @@ Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring the devel
         <Filename Value="..\..\Asn1\Cms\ClpCmsObjectIdentifiers.pas"/>
         <UnitName Value="ClpCmsObjectIdentifiers"/>
       </Item471>
-      <Item472>
+      <Item473>
         <Filename Value="..\..\Asn1\Cms\ClpCmsParsers.pas"/>
         <UnitName Value="ClpCmsParsers"/>
-      </Item472>
-      <Item473>
-        <Filename Value="..\..\Interfaces\Asn1\Cms\ClpICmsAsn1Objects.pas"/>
-        <UnitName Value="ClpICmsAsn1Objects"/>
       </Item473>
       <Item474>
+        <Filename Value="..\..\Interfaces\Asn1\Cms\ClpICmsAsn1Objects.pas"/>
+        <UnitName Value="ClpICmsAsn1Objects"/>
+      </Item474>
+      <Item475>
         <Filename Value="..\..\Interfaces\Asn1\Cms\ClpICmsParsers.pas"/>
         <UnitName Value="ClpICmsParsers"/>
-      </Item474>
+      </Item475>
     </Files>
     <CompatibilityMode Value="True"/>
     <RequiredPkgs Count="3">

+ 5 - 5
CryptoLib/src/Packages/FPC/CryptoLib4PascalPackage.pas

@@ -60,11 +60,11 @@ uses
   ClpAsymmetricKeyParameter, ClpBufferedIesCipher, ClpDHAgreement, 
   ClpEd25519Generators, ClpEd25519Parameters, ClpKeyGenerationParameters, 
   ClpX25519Agreement, ClpX25519Generators, ClpX25519Parameters, 
-  ClpDHBasicAgreement, ClpRsaParameters, ClpX931Signer, ClpPlainDsaEncoding, 
-  ClpCtsBlockCipher, ClpBufferedStreamCipher, ClpBufferedBlockCipher, 
-  ClpISO7816d4Padding, ClpISO10126d2Padding, ClpTBCPadding, 
-  ClpZeroBytePadding, ClpX923Padding, ClpPkcs7Padding, ClpSicBlockCipher, 
-  ClpOfbBlockCipher, ClpCbcBlockCipher, ClpCfbBlockCipher, 
+  ClpCurve25519KeyUtilities, ClpDHBasicAgreement, ClpRsaParameters, 
+  ClpPlainDsaEncoding, ClpX931Signer, ClpBufferedStreamCipher, 
+  ClpBufferedBlockCipher, ClpISO7816d4Padding, ClpISO10126d2Padding, 
+  ClpTBCPadding, ClpZeroBytePadding, ClpX923Padding, ClpPkcs7Padding, 
+  ClpSicBlockCipher, ClpOfbBlockCipher, ClpCbcBlockCipher, ClpCfbBlockCipher, 
   ClpStandardDsaEncoding, ClpAgreementUtilities, ClpMacUtilities, 
   ClpBufferedCipherBase, ClpBufferedAsymmetricBlockCipher, 
   ClpHMacDsaKCalculator, ClpParameterUtilities,