Skip to content

Commit 465731d

Browse files
author
gefeili
committed
Pass all test vectors of Snova
1 parent 4380c3a commit 465731d

File tree

2 files changed

+191
-2
lines changed

2 files changed

+191
-2
lines changed

core/src/main/java/org/bouncycastle/pqc/crypto/snova/SnovaSigner.java

Lines changed: 190 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,19 @@ public byte[] generateSignature(byte[] message)
8585
@Override
8686
public boolean verifySignature(byte[] message, byte[] signature)
8787
{
88-
return false;
88+
byte[] hash = new byte[digest.getDigestSize()];
89+
digest.update(message, 0, message.length);
90+
digest.doFinal(hash, 0);
91+
SnovaKeyElements keyElements = new SnovaKeyElements(params, engine);
92+
byte[] pk = pubKey.getEncoded();
93+
System.arraycopy(pk, 0, keyElements.publicKey.publicKeySeed, 0, SnovaKeyPairGenerator.publicSeedLength);
94+
System.arraycopy(pk, SnovaKeyPairGenerator.publicSeedLength, keyElements.publicKey.P22, 0, keyElements.publicKey.P22.length);
95+
engine.genABQP(keyElements.map1, keyElements.publicKey.publicKeySeed, keyElements.fixedAbq);
96+
byte[] p22_gf16s = new byte[keyElements.publicKey.P22.length << 1];
97+
GF16Utils.decode(keyElements.publicKey.P22, p22_gf16s, p22_gf16s.length);
98+
byte[][][][] p22 = new byte[params.getM()][params.getO()][params.getO()][params.getLsq()];
99+
MapGroup1.fillP(p22_gf16s, 0, p22, p22_gf16s.length);
100+
return verifySignatureCore(hash, signature, keyElements.publicKey, keyElements.map1, p22);
89101
}
90102

91103
public static void createSignedHash(
@@ -365,6 +377,172 @@ public void signDigestCore(byte[] ptSignature, byte[] digest, byte[] arraySalt,
365377
Arrays.fill(gf16mSecretTemp0, (byte)0);
366378
}
367379

380+
public boolean verifySignatureCore(byte[] digest, byte[] signature, PublicKey pkx, MapGroup1 map1, byte[][][][] p22)
381+
{
382+
final int bytesHash = (params.getO() * params.getLsq() + 1) >>> 1;
383+
final int bytesSalt = params.getSaltLength();
384+
final int l = params.getL();
385+
final int lsq = params.getLsq();
386+
final int m = params.getM();
387+
final int n = params.getN();
388+
final int v = params.getV();
389+
final int o = params.getO();
390+
int bytesSignature = ((n * lsq) + 1) >>> 1;
391+
392+
// Extract salt from signature
393+
byte[] ptSalt = Arrays.copyOfRange(signature, bytesSignature, bytesSignature + bytesSalt);
394+
//byte[] signatureBody = Arrays.copyOf(signature, signature.length - bytesSalt);
395+
396+
// Step 1: Regenerate signed hash using public key seed, digest and salt
397+
byte[] signedHash = new byte[bytesHash];
398+
SHAKEDigest shake = new SHAKEDigest(256);
399+
shake.update(pkx.publicKeySeed, 0, pkx.publicKeySeed.length);
400+
shake.update(digest, 0, digest.length);
401+
shake.update(ptSalt, 0, ptSalt.length);
402+
shake.doFinal(signedHash, 0, bytesHash);
403+
404+
// Handle odd-length adjustment (if needed)
405+
if ((o * lsq) % 2 != 0)
406+
{
407+
signedHash[bytesHash - 1] &= 0x0F;
408+
}
409+
410+
// Step 2: Convert signature to GF16 matrices
411+
byte[][][] signatureGF16Matrix = new byte[n][l][l];
412+
byte[] decodedSig = new byte[n * lsq];
413+
GF16Utils.decode(signature, 0, decodedSig, 0, decodedSig.length);
414+
415+
for (int i = 0; i < n; i++)
416+
{
417+
for (int row = 0; row < l; row++)
418+
{
419+
System.arraycopy(decodedSig, i * lsq + row * l,
420+
signatureGF16Matrix[i][row], 0, l);
421+
}
422+
}
423+
424+
// Step 3: Evaluate signature using public key
425+
byte[][][] computedHashMatrix = new byte[m][l][l];
426+
evaluation(computedHashMatrix, map1, p22, signatureGF16Matrix);
427+
428+
// Convert computed hash matrix to bytes
429+
byte[] computedHashBytes = new byte[m * lsq];
430+
for (int i = 0; i < m; i++)
431+
{
432+
for (int row = 0; row < l; row++)
433+
{
434+
System.arraycopy(computedHashMatrix[i][row], 0,
435+
computedHashBytes, i * lsq + row * l, l);
436+
}
437+
}
438+
byte[] encodedHash = new byte[bytesHash];
439+
GF16Utils.encode(computedHashBytes, encodedHash, 0, computedHashBytes.length);
440+
441+
// Step 4: Compare hashes
442+
return Arrays.areEqual(signedHash, encodedHash);
443+
}
444+
445+
private void evaluation(byte[][][] hashMatrix, MapGroup1 map1, byte[][][][] p22, byte[][][] signature)
446+
{
447+
final int m = params.getM();
448+
final int alpha = params.getAlpha();
449+
final int n = params.getN();
450+
final int v = params.getV();
451+
final int l = params.getL();
452+
453+
byte[][][][][] Left = new byte[m][alpha][n][l][l];
454+
byte[][][][][] Right = new byte[m][alpha][n][l][l];
455+
byte[][] temp = new byte[l][l];
456+
byte[][] transposedSig = new byte[l][l];
457+
458+
// Evaluate Left and Right matrices
459+
for (int mi = 0; mi < m; mi++)
460+
{
461+
for (int si = 0; si < n; si++)
462+
{
463+
transposeGF16Matrix(signature[si], transposedSig);
464+
for (int a = 0; a < alpha; a++)
465+
{
466+
// Left[mi][a][si] = Aalpha * (sig^T * Qalpha1)
467+
multiplyGF16Matrices(transposedSig, map1.qAlpha1[mi][a], temp);
468+
multiplyGF16Matrices(map1.aAlpha[mi][a], temp, Left[mi][a][si]);
469+
470+
// Right[mi][a][si] = (Qalpha2 * sig) * Balpha
471+
multiplyGF16Matrices(map1.qAlpha2[mi][a], signature[si], temp);
472+
multiplyGF16Matrices(temp, map1.bAlpha[mi][a], Right[mi][a][si]);
473+
}
474+
}
475+
}
476+
477+
// Initialize hash matrix to zero
478+
for (int mi = 0; mi < m; mi++)
479+
{
480+
for (int i = 0; i < l; i++)
481+
{
482+
Arrays.fill(hashMatrix[mi][i], (byte)0);
483+
}
484+
}
485+
486+
// Process P matrices and accumulate results
487+
byte[][] sumTemp = new byte[l][l];
488+
byte[][] pTemp = new byte[l][l];
489+
for (int mi = 0; mi < m; mi++)
490+
{
491+
for (int a = 0; a < alpha; a++)
492+
{
493+
int miPrime = iPrime(mi, a);
494+
495+
for (int ni = 0; ni < n; ni++)
496+
{
497+
// sum_t0 = sum(P[miPrime][ni][nj] * Right[mi][a][nj])
498+
for (int i = 0; i < l; i++)
499+
{
500+
Arrays.fill(sumTemp[i], (byte)0);
501+
}
502+
503+
for (int nj = 0; nj < n; nj++)
504+
{
505+
byte[] p = getPMatrix(map1, p22, miPrime, ni, nj);
506+
multiplyGF16Matrices(p, Right[mi][a][nj], pTemp);
507+
addGF16Matrices(sumTemp, pTemp, sumTemp);
508+
}
509+
510+
// hashMatrix += Left[mi][a][ni] * sumTemp
511+
multiplyGF16Matrices(Left[mi][a][ni], sumTemp, temp);
512+
addGF16Matrices(hashMatrix[mi], temp, hashMatrix[mi]);
513+
}
514+
}
515+
}
516+
}
517+
518+
// Helper method to get appropriate P matrix based on indices
519+
private byte[] getPMatrix(MapGroup1 map1, byte[][][][] p22, int mi, int ni, int nj)
520+
{
521+
final int v = params.getV();
522+
if (ni < v)
523+
{
524+
if (nj < v)
525+
{
526+
return map1.p11[mi][ni][nj];
527+
}
528+
else
529+
{
530+
return map1.p12[mi][ni][nj - v];
531+
}
532+
}
533+
else
534+
{
535+
if (nj < v)
536+
{
537+
return map1.p21[mi][ni - v][nj];
538+
}
539+
else
540+
{
541+
return p22[mi][ni - v][nj - v];
542+
}
543+
}
544+
}
545+
368546
private void transposeGF16Matrix(byte[][] src, byte[][] dest)
369547
{
370548
for (int i = 0; i < params.getL(); i++)
@@ -529,6 +707,17 @@ private void addGF16Matrices(byte[] a, byte[][] b, byte[] result)
529707
}
530708
}
531709

710+
private void addGF16Matrices(byte[][] a, byte[][] b, byte[][] result)
711+
{
712+
for (int i = 0; i < b.length; i++)
713+
{
714+
for (int j = 0; j < b[i].length; ++j)
715+
{
716+
result[i][j] = GF16Utils.add(a[i][j], b[i][j]);
717+
}
718+
}
719+
}
720+
532721
private int iPrime(int mi, int alpha)
533722
{
534723
// Implement index calculation based on SNOVA specification

core/src/test/java/org/bouncycastle/pqc/crypto/test/AllTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static Test suite()
5252
suite.addTestSuite(AllTests.SimpleTestTest.class);
5353
suite.addTestSuite(SLHDSATest.class);
5454
suite.addTestSuite(MayoTest.class);
55-
55+
suite.addTestSuite(SnovaTest.class);
5656
return new BCTestSetup(suite);
5757
}
5858

0 commit comments

Comments
 (0)