Skip to content

Commit 717762c

Browse files
authored
Merge pull request #8 from pdb0102/ML-DSA-DerivePub
-Implemented method to derive the public ML-DSA key from the private key
2 parents 0cc89da + 647abf5 commit 717762c

File tree

7 files changed

+103
-3
lines changed

7 files changed

+103
-3
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@
1414
[Dd]ebug/
1515
[Rr]elease/
1616
[Oo]bj/
17-
[Bb]in/
17+
[Bb]in/
18+
BenchMarkDotNet.Artifacts/

PQnet.Test/PQApiRoundtripTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public void TestSignatureApi(string algorithm) {
4747
ISignature signature;
4848
byte[] private_key;
4949
byte[] public_key;
50+
byte[] derived_public_key;
5051
byte[] signature_bytes;
5152
byte[] message;
5253
string error;
@@ -66,6 +67,10 @@ public void TestSignatureApi(string algorithm) {
6667
Assert.AreEqual(signature.PrivateKeyBytes, private_key.Length, $"Private key length is incorrect for '{algorithm}' algorithm");
6768
Assert.AreEqual(signature.PublicKeyBytes, public_key.Length, $"Public key length is incorrect for '{algorithm}' algorithm");
6869

70+
success = signature.DerivePublicFromPrivateKey(private_key, out derived_public_key, out error);
71+
Assert.IsTrue(success, $"Failed to derive public key from private key for '{algorithm}' algorithm: {error}");
72+
CollectionAssert.AreEqual(public_key, derived_public_key, $"Derived public key does not match original public key for '{algorithm}' algorithm");
73+
6974
success = signature.Sign(message, private_key, null, out signature_bytes, out error);
7075
Assert.IsTrue(success, $"Failed to sign message for '{algorithm}' algorithm: {error}");
7176
Assert.IsNull(error, $"Error was not null despite success [Error: {error}]");

PQnet/Common/ISignature.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ public interface ISignature {
7575
/// <returns><c>true</c> if the key pair was successfully generated, <c>false</c> otherwise</returns>
7676
bool GenerateKeyPair(out byte[] public_key, out byte[] private_key, byte[] seed, out string error);
7777

78+
/// <summary>
79+
/// Derive the public key from a private key
80+
/// </summary>
81+
/// <param name="private_key">The private key</param>
82+
/// <param name="public_key">Receives the public key</param>
83+
/// <param name="error">Receives an error that occurred, or <c>null</c></param>
84+
/// <returns><c>true</c> if the key was successfully returned, <c>false</c> otherwise</returns>
85+
bool DerivePublicFromPrivateKey(byte[] private_key, out byte[] public_key, out string error);
86+
7887
/// <summary>
7988
/// Generate a pure signature
8089
/// </summary>

PQnet/ML-DSA/MlDsaBase.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,26 @@ public bool GenerateKeyPair(out byte[] public_key, out byte[] private_key, byte[
197197
return false;
198198
}
199199

200+
/// <summary>
201+
/// Derive the ML-DSA public key from a private key
202+
/// </summary>
203+
/// <param name="private_key">The private key</param>
204+
/// <param name="public_key">Receives the public key</param>
205+
/// <param name="error">Receives an error that occurred, or <c>null</c></param>
206+
/// <returns><c>true</c> if the key was successfully returned, <c>false</c> otherwise</returns>
207+
public bool DerivePublicFromPrivateKey(byte[] private_key, out byte[] public_key, out string error) {
208+
if (private_key.Length != PrivateKeyBytes) {
209+
public_key = null;
210+
error = $"Private key must be {PrivateKeyBytes} bytes long";
211+
return false;
212+
}
213+
214+
ml_derive_public(private_key, out public_key);
215+
216+
error = null;
217+
return true;
218+
}
219+
200220
/// <summary>
201221
/// Generate a pure ML-DSA signature
202222
/// </summary>

PQnet/ML-DSA/Sign.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,53 @@ internal bool ml_keygen(out byte[] pk, out byte[] sk, byte[] seed = null) {
119119
return true;
120120
}
121121

122+
internal bool ml_derive_public(byte[] sk, out byte[] pk) {
123+
byte[] tr;
124+
byte[] rho;
125+
byte[] key;
126+
PolyVecL[] mat;
127+
PolyVecL s1, s1hat;
128+
PolyVecK s2, t1, t0;
129+
130+
mat = new PolyVecL[K];
131+
for (int i = 0; i < K; i++) {
132+
mat[i] = new PolyVecL(L, N);
133+
}
134+
135+
rho = new byte[(2 * SeedBytes) + CrhBytes];
136+
tr = new byte[TrBytes];
137+
key = new byte[SeedBytes];
138+
t0 = new PolyVecK(K, N);
139+
s1 = new PolyVecL(L, N);
140+
s2 = new PolyVecK(K, N);
141+
142+
/* unpack the private key */
143+
unpack_sk(rho, tr, key, t0, s1, s2, sk);
144+
145+
/* Expand matrix */
146+
polyvec_matrix_expand(mat, rho);
147+
148+
/* Matrix-vector multiplication */
149+
t0 = new PolyVecK(K, N);
150+
t1 = new PolyVecK(K, N);
151+
s1hat = s1.Clone();
152+
polyvecl_ntt(s1hat);
153+
polyvec_matrix_pointwise_montgomery(t1, mat, s1hat);
154+
polyveck_reduce(t1);
155+
polyveck_invntt_tomont(t1);
156+
157+
/* Add error vector s2 */
158+
polyveck_add(t1, t1, s2);
159+
160+
/* Extract t1 and write public key */
161+
polyveck_caddq(t1);
162+
polyveck_power2round(t1, t0, t1);
163+
pk = new byte[PublicKeyBytes];
164+
pack_pk(pk, rho, t1);
165+
166+
return true;
167+
}
168+
122169
/*************************************************
123170
* Name: crypto_sign_signature_internal
124171
*

PQnet/SLH-DSA/SlhDsaBase.Api.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public bool GenerateKeyPair(out byte[] public_key, out byte[] private_key, byte[
8888
}
8989

9090
/// <summary>
91-
/// Derive an SLH-DSA public key from a private key
91+
/// Derive the SLH-DSA public key from a private key
9292
/// </summary>
9393
/// <param name="private_key">The private key</param>
9494
/// <param name="public_key">Receives the public key</param>

Sandcastle/PQnet.xml

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)