Skip to content

Commit 32a9232

Browse files
author
gefeili
committed
Pass the test vectors for signing of Snova.
1 parent 54b3283 commit 32a9232

File tree

4 files changed

+313
-220
lines changed

4 files changed

+313
-220
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
class PublicKey
44
{
5-
public final byte[] publicKeySeed;
5+
public byte[] publicKeySeed;
66
public final byte[] P22;
77

88
public PublicKey(SnovaParameters params)

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

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package org.bouncycastle.pqc.crypto.snova;
22

3+
import org.bouncycastle.crypto.BlockCipher;
4+
import org.bouncycastle.crypto.digests.SHAKEDigest;
5+
import org.bouncycastle.crypto.engines.AESEngine;
6+
import org.bouncycastle.crypto.modes.CTRModeCipher;
7+
import org.bouncycastle.crypto.modes.SICBlockCipher;
8+
import org.bouncycastle.crypto.params.KeyParameter;
9+
import org.bouncycastle.crypto.params.ParametersWithIV;
310
import org.bouncycastle.util.Arrays;
411

512
public class SnovaEngine
@@ -470,4 +477,170 @@ public void genP22(byte[] outP22, byte[][][] T12, byte[][][][] P21, byte[][][][]
470477
Arrays.fill(temp2, (byte)0);
471478
}
472479
}
480+
481+
void genSeedsAndT12(byte[][][] T12, byte[] skSeed)
482+
{
483+
int bytesPrngPrivate = (params.getV() * params.getO() * params.getL() + 1) >>> 1;
484+
int gf16sPrngPrivate = params.getV() * params.getO() * params.getL();
485+
byte[] prngOutput = new byte[bytesPrngPrivate];
486+
487+
// Generate PRNG output using SHAKE-256
488+
SHAKEDigest shake = new SHAKEDigest(256);
489+
shake.update(skSeed, 0, skSeed.length);
490+
shake.doFinal(prngOutput, 0, prngOutput.length);
491+
492+
// Convert bytes to GF16 array
493+
byte[] gf16PrngOutput = new byte[gf16sPrngPrivate];
494+
GF16Utils.decode(prngOutput, gf16PrngOutput, gf16sPrngPrivate);
495+
496+
// Generate T12 matrices
497+
int ptArray = 0;
498+
int l = params.getL();
499+
for (int j = 0; j < params.getV(); j++)
500+
{
501+
for (int k = 0; k < params.getO(); k++)
502+
{
503+
//gen_a_FqS_ct
504+
genAFqSCT(gf16PrngOutput, ptArray, T12[j][k]);
505+
ptArray += l;
506+
}
507+
}
508+
}
509+
510+
void genABQP(MapGroup1 map1, byte[] pkSeed, byte[] fixedAbq)
511+
{
512+
int l = params.getL();
513+
int lsq = l * l;
514+
int m = params.getM();
515+
int alpha = params.getAlpha();
516+
int v = params.getV();
517+
int o = params.getO();
518+
int n = v + o;
519+
520+
int gf16sPrngPublic = lsq * (2 * m * alpha + m * (n * n - m * m)) + l * 2 * m * alpha;
521+
byte[] qTemp = new byte[(m * alpha * lsq + m * alpha * lsq) / l];
522+
byte[] prngOutput = new byte[(gf16sPrngPublic + 1) >> 1];
523+
524+
if (params.isPkExpandShake())
525+
{
526+
snovaShake(pkSeed, prngOutput.length, prngOutput);
527+
}
528+
else
529+
{
530+
// Create a 16-byte IV (all zeros)
531+
byte[] iv = new byte[16]; // automatically zero-initialized
532+
// AES-CTR-based expansion
533+
// Set up AES engine in CTR (SIC) mode.
534+
BlockCipher aesEngine = AESEngine.newInstance();
535+
// SICBlockCipher implements CTR mode for AES.
536+
CTRModeCipher ctrCipher = SICBlockCipher.newInstance(aesEngine);
537+
ParametersWithIV params = new ParametersWithIV(new KeyParameter(pkSeed), iv);
538+
ctrCipher.init(true, params);
539+
int blockSize = ctrCipher.getBlockSize(); // typically 16 bytes
540+
byte[] zeroBlock = new byte[blockSize]; // block of zeros
541+
byte[] blockOut = new byte[blockSize];
542+
543+
int offset = 0;
544+
// Process full blocks
545+
while (offset + blockSize <= prngOutput.length)
546+
{
547+
ctrCipher.processBlock(zeroBlock, 0, blockOut, 0);
548+
System.arraycopy(blockOut, 0, prngOutput, offset, blockSize);
549+
offset += blockSize;
550+
}
551+
// Process any remaining partial block.
552+
if (offset < prngOutput.length)
553+
{
554+
ctrCipher.processBlock(zeroBlock, 0, blockOut, 0);
555+
int remaining = prngOutput.length - offset;
556+
System.arraycopy(blockOut, 0, prngOutput, offset, remaining);
557+
}
558+
}
559+
byte[] temp = new byte[gf16sPrngPublic - qTemp.length];
560+
GF16Utils.decode(prngOutput, temp, temp.length);
561+
map1.fill(temp);
562+
if (l >= 4)
563+
{
564+
GF16Utils.decode(prngOutput, temp.length >> 1, qTemp, 0, qTemp.length);
565+
566+
// Post-processing for invertible matrices
567+
for (int pi = 0; pi < m; ++pi)
568+
{
569+
for (int a = 0; a < alpha; ++a)
570+
{
571+
makeInvertibleByAddingAS(map1.aAlpha[pi][a], 0);
572+
}
573+
}
574+
for (int pi = 0; pi < m; ++pi)
575+
{
576+
for (int a = 0; a < alpha; ++a)
577+
{
578+
makeInvertibleByAddingAS(map1.bAlpha[pi][a], 0);
579+
}
580+
}
581+
582+
int ptArray = 0;
583+
for (int pi = 0; pi < m; ++pi)
584+
{
585+
for (int a = 0; a < alpha; ++a)
586+
{
587+
genAFqS(qTemp, ptArray, map1.qAlpha1[pi][a], 0);
588+
ptArray += l;
589+
}
590+
}
591+
for (int pi = 0; pi < m; ++pi)
592+
{
593+
for (int a = 0; a < alpha; ++a)
594+
{
595+
genAFqS(qTemp, ptArray, map1.qAlpha2[pi][a], 0);
596+
ptArray += l;
597+
}
598+
}
599+
}
600+
else
601+
{
602+
MapGroup1.fillAlpha(fixedAbq, 0, map1.aAlpha, m * o * alpha * lsq);
603+
MapGroup1.fillAlpha(fixedAbq, o * alpha * lsq, map1.bAlpha, (m - 1) * o * alpha * lsq);
604+
MapGroup1.fillAlpha(fixedAbq, o * alpha * lsq * 2, map1.qAlpha1, (m - 2) * o * alpha * lsq);
605+
MapGroup1.fillAlpha(fixedAbq, o * alpha * lsq * 3, map1.qAlpha2, (m - 3) * o * alpha * lsq);
606+
}
607+
}
608+
609+
public static void snovaShake(byte[] ptSeed, int outputBytes, byte[] out)
610+
{
611+
final int SHAKE128_RATE = 168; // 1344-bit rate = 168 bytes
612+
long blockCounter = 0;
613+
int offset = 0;
614+
int remaining = outputBytes;
615+
616+
while (remaining > 0)
617+
{
618+
SHAKEDigest shake = new SHAKEDigest(128);
619+
620+
// Process seed + counter
621+
shake.update(ptSeed, 0, ptSeed.length);
622+
updateWithCounter(shake, blockCounter);
623+
624+
// Calculate bytes to generate in this iteration
625+
int bytesToGenerate = Math.min(remaining, SHAKE128_RATE);
626+
627+
// Generate output (XOF mode)
628+
shake.doFinal(out, offset, bytesToGenerate);
629+
630+
offset += bytesToGenerate;
631+
remaining -= bytesToGenerate;
632+
blockCounter++;
633+
}
634+
}
635+
636+
private static void updateWithCounter(SHAKEDigest shake, long counter)
637+
{
638+
byte[] counterBytes = new byte[8];
639+
// Little-endian conversion
640+
for (int i = 0; i < 8; i++)
641+
{
642+
counterBytes[i] = (byte)(counter >> (i * 8));
643+
}
644+
shake.update(counterBytes, 0, 8);
645+
}
473646
}

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

Lines changed: 2 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,7 @@
44

55
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
66
import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
7-
import org.bouncycastle.crypto.BlockCipher;
87
import org.bouncycastle.crypto.KeyGenerationParameters;
9-
import org.bouncycastle.crypto.digests.SHAKEDigest;
10-
import org.bouncycastle.crypto.engines.AESEngine;
11-
import org.bouncycastle.crypto.modes.CTRModeCipher;
12-
import org.bouncycastle.crypto.modes.SICBlockCipher;
13-
import org.bouncycastle.crypto.params.KeyParameter;
14-
import org.bouncycastle.crypto.params.ParametersWithIV;
158
import org.bouncycastle.util.Arrays;
169

1710
public class SnovaKeyPairGenerator
@@ -79,181 +72,15 @@ public AsymmetricCipherKeyPair generateKeyPair()
7972
private void generateKeysCore(SnovaKeyElements keyElements, byte[] pkSeed, byte[] skSeed)
8073
{
8174
// Generate T12 matrix
82-
genSeedsAndT12(keyElements.T12, skSeed);
75+
engine.genSeedsAndT12(keyElements.T12, skSeed);
8376

8477
// Generate map components
85-
genABQP(keyElements.map1, pkSeed, keyElements.fixedAbq);
78+
engine.genABQP(keyElements.map1, pkSeed, keyElements.fixedAbq);
8679

8780
// Generate F matrices
8881
engine.genF(keyElements.map2, keyElements.map1, keyElements.T12);
8982

9083
// Generate P22 matrix
9184
engine.genP22(keyElements.publicKey.P22, keyElements.T12, keyElements.map1.p21, keyElements.map2.f12);
9285
}
93-
94-
private void genSeedsAndT12(byte[][][] T12, byte[] skSeed)
95-
{
96-
int bytesPrngPrivate = (params.getV() * params.getO() * params.getL() + 1) >>> 1;
97-
int gf16sPrngPrivate = params.getV() * params.getO() * params.getL();
98-
byte[] prngOutput = new byte[bytesPrngPrivate];
99-
100-
// Generate PRNG output using SHAKE-256
101-
SHAKEDigest shake = new SHAKEDigest(256);
102-
shake.update(skSeed, 0, skSeed.length);
103-
shake.doFinal(prngOutput, 0, prngOutput.length);
104-
105-
// Convert bytes to GF16 array
106-
byte[] gf16PrngOutput = new byte[gf16sPrngPrivate];
107-
GF16Utils.decode(prngOutput, gf16PrngOutput, gf16sPrngPrivate);
108-
109-
// Generate T12 matrices
110-
int ptArray = 0;
111-
int l = params.getL();
112-
for (int j = 0; j < params.getV(); j++)
113-
{
114-
for (int k = 0; k < params.getO(); k++)
115-
{
116-
//gen_a_FqS_ct
117-
engine.genAFqSCT(gf16PrngOutput, ptArray, T12[j][k]);
118-
ptArray += l;
119-
}
120-
}
121-
}
122-
123-
private void genABQP(MapGroup1 map1, byte[] pkSeed, byte[] fixedAbq)
124-
{
125-
int l = params.getL();
126-
int lsq = l * l;
127-
int m = params.getM();
128-
int alpha = params.getAlpha();
129-
int v = params.getV();
130-
int o = params.getO();
131-
int n = v + o;
132-
133-
int gf16sPrngPublic = lsq * (2 * m * alpha + m * (n * n - m * m)) + l * 2 * m * alpha;
134-
byte[] qTemp = new byte[(m * alpha * lsq + m * alpha * lsq) / l];
135-
byte[] prngOutput = new byte[(gf16sPrngPublic + 1) >> 1];
136-
137-
if (params.isPkExpandShake())
138-
{
139-
snovaShake(pkSeed, prngOutput.length, prngOutput);
140-
}
141-
else
142-
{
143-
// Create a 16-byte IV (all zeros)
144-
byte[] iv = new byte[16]; // automatically zero-initialized
145-
// AES-CTR-based expansion
146-
// Set up AES engine in CTR (SIC) mode.
147-
BlockCipher aesEngine = AESEngine.newInstance();
148-
// SICBlockCipher implements CTR mode for AES.
149-
CTRModeCipher ctrCipher = SICBlockCipher.newInstance(aesEngine);
150-
ParametersWithIV params = new ParametersWithIV(new KeyParameter(pkSeed), iv);
151-
ctrCipher.init(true, params);
152-
int blockSize = ctrCipher.getBlockSize(); // typically 16 bytes
153-
byte[] zeroBlock = new byte[blockSize]; // block of zeros
154-
byte[] blockOut = new byte[blockSize];
155-
156-
int offset = 0;
157-
// Process full blocks
158-
while (offset + blockSize <= prngOutput.length)
159-
{
160-
ctrCipher.processBlock(zeroBlock, 0, blockOut, 0);
161-
System.arraycopy(blockOut, 0, prngOutput, offset, blockSize);
162-
offset += blockSize;
163-
}
164-
// Process any remaining partial block.
165-
if (offset < prngOutput.length)
166-
{
167-
ctrCipher.processBlock(zeroBlock, 0, blockOut, 0);
168-
int remaining = prngOutput.length - offset;
169-
System.arraycopy(blockOut, 0, prngOutput, offset, remaining);
170-
}
171-
}
172-
byte[] temp = new byte[gf16sPrngPublic - qTemp.length];
173-
GF16Utils.decode(prngOutput, temp, temp.length);
174-
map1.fill(temp);
175-
if (l >= 4)
176-
{
177-
GF16Utils.decode(prngOutput, temp.length >> 1, qTemp, 0, qTemp.length);
178-
179-
// Post-processing for invertible matrices
180-
for (int pi = 0; pi < m; ++pi)
181-
{
182-
for (int a = 0; a < alpha; ++a)
183-
{
184-
engine.makeInvertibleByAddingAS(map1.aAlpha[pi][a], 0);
185-
}
186-
}
187-
for (int pi = 0; pi < m; ++pi)
188-
{
189-
for (int a = 0; a < alpha; ++a)
190-
{
191-
engine.makeInvertibleByAddingAS(map1.bAlpha[pi][a], 0);
192-
}
193-
}
194-
195-
int ptArray = 0;
196-
for (int pi = 0; pi < m; ++pi)
197-
{
198-
for (int a = 0; a < alpha; ++a)
199-
{
200-
engine.genAFqS(qTemp, ptArray, map1.qAlpha1[pi][a], 0);
201-
ptArray += l;
202-
}
203-
}
204-
for (int pi = 0; pi < m; ++pi)
205-
{
206-
for (int a = 0; a < alpha; ++a)
207-
{
208-
engine.genAFqS(qTemp, ptArray, map1.qAlpha2[pi][a], 0);
209-
ptArray += l;
210-
}
211-
}
212-
}
213-
else
214-
{
215-
MapGroup1.fillAlpha(fixedAbq, 0, map1.aAlpha, m * o * alpha * lsq);
216-
MapGroup1.fillAlpha(fixedAbq, o * alpha * lsq, map1.bAlpha, (m - 1) * o * alpha * lsq);
217-
MapGroup1.fillAlpha(fixedAbq, o * alpha * lsq * 2, map1.qAlpha1, (m - 2) * o * alpha * lsq);
218-
MapGroup1.fillAlpha(fixedAbq, o * alpha * lsq * 3, map1.qAlpha2, (m - 3) * o * alpha * lsq);
219-
}
220-
}
221-
222-
public static void snovaShake(byte[] ptSeed, int outputBytes, byte[] out)
223-
{
224-
final int SHAKE128_RATE = 168; // 1344-bit rate = 168 bytes
225-
long blockCounter = 0;
226-
int offset = 0;
227-
int remaining = outputBytes;
228-
229-
while (remaining > 0)
230-
{
231-
SHAKEDigest shake = new SHAKEDigest(128);
232-
233-
// Process seed + counter
234-
shake.update(ptSeed, 0, ptSeed.length);
235-
updateWithCounter(shake, blockCounter);
236-
237-
// Calculate bytes to generate in this iteration
238-
int bytesToGenerate = Math.min(remaining, SHAKE128_RATE);
239-
240-
// Generate output (XOF mode)
241-
shake.doFinal(out, offset, bytesToGenerate);
242-
243-
offset += bytesToGenerate;
244-
remaining -= bytesToGenerate;
245-
blockCounter++;
246-
}
247-
}
248-
249-
private static void updateWithCounter(SHAKEDigest shake, long counter)
250-
{
251-
byte[] counterBytes = new byte[8];
252-
// Little-endian conversion
253-
for (int i = 0; i < 8; i++)
254-
{
255-
counterBytes[i] = (byte)(counter >> (i * 8));
256-
}
257-
shake.update(counterBytes, 0, 8);
258-
}
25986
}

0 commit comments

Comments
 (0)