diff options
author | Roberto Tyley <roberto.tyley@gmail.com> | 2014-07-15 01:38:01 +0400 |
---|---|---|
committer | Roberto Tyley <roberto.tyley@gmail.com> | 2014-07-26 11:23:17 +0400 |
commit | 7cb752aaf746dc0b473afeb9e892b7fbc12666c5 (patch) | |
tree | cc4f91ddc18332b5adbe82e3fcb040d976c90105 /core/src/main/java/org/spongycastle/pqc | |
parent | 551830f8ea5177042af2c7dd1fc90888bc67387d (diff) |
Execute become-spongy.sh
https://github.com/rtyley/spongycastle/blob/3040af/become-spongy.sh
Diffstat (limited to 'core/src/main/java/org/spongycastle/pqc')
125 files changed, 35770 insertions, 0 deletions
diff --git a/core/src/main/java/org/spongycastle/pqc/asn1/GMSSPrivateKey.java b/core/src/main/java/org/spongycastle/pqc/asn1/GMSSPrivateKey.java new file mode 100644 index 00000000..b56974ad --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/asn1/GMSSPrivateKey.java @@ -0,0 +1,1312 @@ +package org.spongycastle.pqc.asn1; + +import java.math.BigInteger; +import java.util.Vector; + +import org.spongycastle.asn1.ASN1Encodable; +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.x509.AlgorithmIdentifier; +import org.spongycastle.pqc.crypto.gmss.GMSSLeaf; +import org.spongycastle.pqc.crypto.gmss.GMSSParameters; +import org.spongycastle.pqc.crypto.gmss.GMSSRootCalc; +import org.spongycastle.pqc.crypto.gmss.GMSSRootSig; +import org.spongycastle.pqc.crypto.gmss.Treehash; + +public class GMSSPrivateKey + extends ASN1Object +{ + private ASN1Primitive primitive; + + private GMSSPrivateKey(ASN1Sequence mtsPrivateKey) + { + // --- Decode <index>. + ASN1Sequence indexPart = (ASN1Sequence)mtsPrivateKey.getObjectAt(0); + int[] index = new int[indexPart.size()]; + for (int i = 0; i < indexPart.size(); i++) + { + index[i] = checkBigIntegerInIntRange(indexPart.getObjectAt(i)); + } + + // --- Decode <curSeeds>. + ASN1Sequence curSeedsPart = (ASN1Sequence)mtsPrivateKey.getObjectAt(1); + byte[][] curSeeds = new byte[curSeedsPart.size()][]; + for (int i = 0; i < curSeeds.length; i++) + { + curSeeds[i] = ((DEROctetString)curSeedsPart.getObjectAt(i)).getOctets(); + } + + // --- Decode <nextNextSeeds>. + ASN1Sequence nextNextSeedsPart = (ASN1Sequence)mtsPrivateKey.getObjectAt(2); + byte[][] nextNextSeeds = new byte[nextNextSeedsPart.size()][]; + for (int i = 0; i < nextNextSeeds.length; i++) + { + nextNextSeeds[i] = ((DEROctetString)nextNextSeedsPart.getObjectAt(i)).getOctets(); + } + + // --- Decode <curAuth>. + ASN1Sequence curAuthPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(3); + ASN1Sequence curAuthPart1; + + byte[][][] curAuth = new byte[curAuthPart0.size()][][]; + for (int i = 0; i < curAuth.length; i++) + { + curAuthPart1 = (ASN1Sequence)curAuthPart0.getObjectAt(i); + curAuth[i] = new byte[curAuthPart1.size()][]; + for (int j = 0; j < curAuth[i].length; j++) + { + curAuth[i][j] = ((DEROctetString)curAuthPart1.getObjectAt(j)).getOctets(); + } + } + + // --- Decode <nextAuth>. + ASN1Sequence nextAuthPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(4); + ASN1Sequence nextAuthPart1; + + byte[][][] nextAuth = new byte[nextAuthPart0.size()][][]; + for (int i = 0; i < nextAuth.length; i++) + { + nextAuthPart1 = (ASN1Sequence)nextAuthPart0.getObjectAt(i); + nextAuth[i] = new byte[nextAuthPart1.size()][]; + for (int j = 0; j < nextAuth[i].length; j++) + { + nextAuth[i][j] = ((DEROctetString)nextAuthPart1.getObjectAt(j)).getOctets(); + } + } + + // --- Decode <curTreehash>. + ASN1Sequence seqOfcurTreehash0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(5); + ASN1Sequence seqOfcurTreehash1; + ASN1Sequence seqOfcurTreehashStat; + ASN1Sequence seqOfcurTreehashBytes; + ASN1Sequence seqOfcurTreehashInts; + ASN1Sequence seqOfcurTreehashString; + + Treehash[][] curTreehash = new Treehash[seqOfcurTreehash0.size()][]; + /* + for (int i = 0; i < curTreehash.length; i++) + { + seqOfcurTreehash1 = (ASN1Sequence)seqOfcurTreehash0.getObjectAt(i); + curTreehash[i] = new Treehash[seqOfcurTreehash1.size()]; + for (int j = 0; j < curTreehash[i].length; j++) + { + seqOfcurTreehashStat = (ASN1Sequence)seqOfcurTreehash1.getObjectAt(j); + seqOfcurTreehashString = (ASN1Sequence)seqOfcurTreehashStat + .getObjectAt(0); + seqOfcurTreehashBytes = (ASN1Sequence)seqOfcurTreehashStat + .getObjectAt(1); + seqOfcurTreehashInts = (ASN1Sequence)seqOfcurTreehashStat + .getObjectAt(2); + + String[] name = new String[2]; + name[0] = ((DERIA5String)seqOfcurTreehashString.getObjectAt(0)).getString(); + name[1] = ((DERIA5String)seqOfcurTreehashString.getObjectAt(1)).getString(); + + int tailLength = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(1)); + byte[][] statByte = new byte[3 + tailLength][]; + statByte[0] = ((DEROctetString)seqOfcurTreehashBytes.getObjectAt(0)).getOctets(); + + if (statByte[0].length == 0) + { // if null was encoded + statByte[0] = null; + } + + statByte[1] = ((DEROctetString)seqOfcurTreehashBytes.getObjectAt(1)).getOctets(); + statByte[2] = ((DEROctetString)seqOfcurTreehashBytes.getObjectAt(2)).getOctets(); + for (int k = 0; k < tailLength; k++) + { + statByte[3 + k] = ((DEROctetString)seqOfcurTreehashBytes + .getObjectAt(3 + k)).getOctets(); + } + int[] statInt = new int[6 + tailLength]; + statInt[0] = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(0)); + statInt[1] = tailLength; + statInt[2] = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(2)); + statInt[3] = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(3)); + statInt[4] = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(4)); + statInt[5] = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(5)); + for (int k = 0; k < tailLength; k++) + { + statInt[6 + k] = checkBigIntegerInIntRange(seqOfcurTreehashInts.getObjectAt(6 + k)); + } + + // TODO: Check if we can do better than throwing away name[1] !!! + curTreehash[i][j] = new Treehash(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt); + } + } + + + // --- Decode <nextTreehash>. + ASN1Sequence seqOfNextTreehash0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(6); + ASN1Sequence seqOfNextTreehash1; + ASN1Sequence seqOfNextTreehashStat; + ASN1Sequence seqOfNextTreehashBytes; + ASN1Sequence seqOfNextTreehashInts; + ASN1Sequence seqOfNextTreehashString; + + Treehash[][] nextTreehash = new Treehash[seqOfNextTreehash0.size()][]; + + for (int i = 0; i < nextTreehash.length; i++) + { + seqOfNextTreehash1 = (ASN1Sequence)seqOfNextTreehash0.getObjectAt(i); + nextTreehash[i] = new Treehash[seqOfNextTreehash1.size()]; + for (int j = 0; j < nextTreehash[i].length; j++) + { + seqOfNextTreehashStat = (ASN1Sequence)seqOfNextTreehash1 + .getObjectAt(j); + seqOfNextTreehashString = (ASN1Sequence)seqOfNextTreehashStat + .getObjectAt(0); + seqOfNextTreehashBytes = (ASN1Sequence)seqOfNextTreehashStat + .getObjectAt(1); + seqOfNextTreehashInts = (ASN1Sequence)seqOfNextTreehashStat + .getObjectAt(2); + + String[] name = new String[2]; + name[0] = ((DERIA5String)seqOfNextTreehashString.getObjectAt(0)) + .getString(); + name[1] = ((DERIA5String)seqOfNextTreehashString.getObjectAt(1)) + .getString(); + + int tailLength = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(1)); + + byte[][] statByte = new byte[3 + tailLength][]; + statByte[0] = ((DEROctetString)seqOfNextTreehashBytes.getObjectAt(0)).getOctets(); + if (statByte[0].length == 0) + { // if null was encoded + statByte[0] = null; + } + + statByte[1] = ((DEROctetString)seqOfNextTreehashBytes.getObjectAt(1)).getOctets(); + statByte[2] = ((DEROctetString)seqOfNextTreehashBytes.getObjectAt(2)).getOctets(); + for (int k = 0; k < tailLength; k++) + { + statByte[3 + k] = ((DEROctetString)seqOfNextTreehashBytes + .getObjectAt(3 + k)).getOctets(); + } + int[] statInt = new int[6 + tailLength]; + statInt[0] = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(0)); + + statInt[1] = tailLength; + statInt[2] = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(2)); + + statInt[3] = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(3)); + + statInt[4] = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(4)); + + statInt[5] = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(5)); + + for (int k = 0; k < tailLength; k++) + { + statInt[6 + k] = checkBigIntegerInIntRange(seqOfNextTreehashInts.getObjectAt(6 + k)); + + } + nextTreehash[i][j] = new Treehash(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt); + } + } + + + // --- Decode <keep>. + ASN1Sequence keepPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(7); + ASN1Sequence keepPart1; + + byte[][][] keep = new byte[keepPart0.size()][][]; + for (int i = 0; i < keep.length; i++) + { + keepPart1 = (ASN1Sequence)keepPart0.getObjectAt(i); + keep[i] = new byte[keepPart1.size()][]; + for (int j = 0; j < keep[i].length; j++) + { + keep[i][j] = ((DEROctetString)keepPart1.getObjectAt(j)).getOctets(); + } + } + + // --- Decode <curStack>. + ASN1Sequence curStackPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(8); + ASN1Sequence curStackPart1; + + Vector[] curStack = new Vector[curStackPart0.size()]; + for (int i = 0; i < curStack.length; i++) + { + curStackPart1 = (ASN1Sequence)curStackPart0.getObjectAt(i); + curStack[i] = new Vector(); + for (int j = 0; j < curStackPart1.size(); j++) + { + curStack[i].addElement(((DEROctetString)curStackPart1.getObjectAt(j)).getOctets()); + } + } + + // --- Decode <nextStack>. + ASN1Sequence nextStackPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(9); + ASN1Sequence nextStackPart1; + + Vector[] nextStack = new Vector[nextStackPart0.size()]; + for (int i = 0; i < nextStack.length; i++) + { + nextStackPart1 = (ASN1Sequence)nextStackPart0.getObjectAt(i); + nextStack[i] = new Vector(); + for (int j = 0; j < nextStackPart1.size(); j++) + { + nextStack[i].addElement(((DEROctetString)nextStackPart1 + .getObjectAt(j)).getOctets()); + } + } + + // --- Decode <curRetain>. + ASN1Sequence curRetainPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(10); + ASN1Sequence curRetainPart1; + ASN1Sequence curRetainPart2; + + Vector[][] curRetain = new Vector[curRetainPart0.size()][]; + for (int i = 0; i < curRetain.length; i++) + { + curRetainPart1 = (ASN1Sequence)curRetainPart0.getObjectAt(i); + curRetain[i] = new Vector[curRetainPart1.size()]; + for (int j = 0; j < curRetain[i].length; j++) + { + curRetainPart2 = (ASN1Sequence)curRetainPart1.getObjectAt(j); + curRetain[i][j] = new Vector(); + for (int k = 0; k < curRetainPart2.size(); k++) + { + curRetain[i][j] + .addElement(((DEROctetString)curRetainPart2 + .getObjectAt(k)).getOctets()); + } + } + } + + // --- Decode <nextRetain>. + ASN1Sequence nextRetainPart0 = (ASN1Sequence)mtsPrivateKey.getObjectAt(11); + ASN1Sequence nextRetainPart1; + ASN1Sequence nextRetainPart2; + + Vector[][] nextRetain = new Vector[nextRetainPart0.size()][]; + for (int i = 0; i < nextRetain.length; i++) + { + nextRetainPart1 = (ASN1Sequence)nextRetainPart0.getObjectAt(i); + nextRetain[i] = new Vector[nextRetainPart1.size()]; + for (int j = 0; j < nextRetain[i].length; j++) + { + nextRetainPart2 = (ASN1Sequence)nextRetainPart1.getObjectAt(j); + nextRetain[i][j] = new Vector(); + for (int k = 0; k < nextRetainPart2.size(); k++) + { + nextRetain[i][j] + .addElement(((DEROctetString)nextRetainPart2 + .getObjectAt(k)).getOctets()); + } + } + } + + // --- Decode <nextNextLeaf>. + ASN1Sequence seqOfLeafs = (ASN1Sequence)mtsPrivateKey.getObjectAt(12); + ASN1Sequence seqOfLeafStat; + ASN1Sequence seqOfLeafBytes; + ASN1Sequence seqOfLeafInts; + ASN1Sequence seqOfLeafString; + + GMSSLeaf[] nextNextLeaf = new GMSSLeaf[seqOfLeafs.size()]; + + for (int i = 0; i < nextNextLeaf.length; i++) + { + seqOfLeafStat = (ASN1Sequence)seqOfLeafs.getObjectAt(i); + // nextNextAuth[i]= new byte[nextNextAuthPart1.size()][]; + seqOfLeafString = (ASN1Sequence)seqOfLeafStat.getObjectAt(0); + seqOfLeafBytes = (ASN1Sequence)seqOfLeafStat.getObjectAt(1); + seqOfLeafInts = (ASN1Sequence)seqOfLeafStat.getObjectAt(2); + + String[] name = new String[2]; + name[0] = ((DERIA5String)seqOfLeafString.getObjectAt(0)).getString(); + name[1] = ((DERIA5String)seqOfLeafString.getObjectAt(1)).getString(); + byte[][] statByte = new byte[4][]; + statByte[0] = ((DEROctetString)seqOfLeafBytes.getObjectAt(0)) + .getOctets(); + statByte[1] = ((DEROctetString)seqOfLeafBytes.getObjectAt(1)) + .getOctets(); + statByte[2] = ((DEROctetString)seqOfLeafBytes.getObjectAt(2)) + .getOctets(); + statByte[3] = ((DEROctetString)seqOfLeafBytes.getObjectAt(3)) + .getOctets(); + int[] statInt = new int[4]; + statInt[0] = checkBigIntegerInIntRange(seqOfLeafInts.getObjectAt(0)); + statInt[1] = checkBigIntegerInIntRange(seqOfLeafInts.getObjectAt(1)); + statInt[2] = checkBigIntegerInIntRange(seqOfLeafInts.getObjectAt(2)); + statInt[3] = checkBigIntegerInIntRange(seqOfLeafInts.getObjectAt(3)); + nextNextLeaf[i] = new GMSSLeaf(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt); + } + + // --- Decode <upperLeaf>. + ASN1Sequence seqOfUpperLeafs = (ASN1Sequence)mtsPrivateKey.getObjectAt(13); + ASN1Sequence seqOfUpperLeafStat; + ASN1Sequence seqOfUpperLeafBytes; + ASN1Sequence seqOfUpperLeafInts; + ASN1Sequence seqOfUpperLeafString; + + GMSSLeaf[] upperLeaf = new GMSSLeaf[seqOfUpperLeafs.size()]; + + for (int i = 0; i < upperLeaf.length; i++) + { + seqOfUpperLeafStat = (ASN1Sequence)seqOfUpperLeafs.getObjectAt(i); + seqOfUpperLeafString = (ASN1Sequence)seqOfUpperLeafStat.getObjectAt(0); + seqOfUpperLeafBytes = (ASN1Sequence)seqOfUpperLeafStat.getObjectAt(1); + seqOfUpperLeafInts = (ASN1Sequence)seqOfUpperLeafStat.getObjectAt(2); + + String[] name = new String[2]; + name[0] = ((DERIA5String)seqOfUpperLeafString.getObjectAt(0)).getString(); + name[1] = ((DERIA5String)seqOfUpperLeafString.getObjectAt(1)).getString(); + byte[][] statByte = new byte[4][]; + statByte[0] = ((DEROctetString)seqOfUpperLeafBytes.getObjectAt(0)) + .getOctets(); + statByte[1] = ((DEROctetString)seqOfUpperLeafBytes.getObjectAt(1)) + .getOctets(); + statByte[2] = ((DEROctetString)seqOfUpperLeafBytes.getObjectAt(2)) + .getOctets(); + statByte[3] = ((DEROctetString)seqOfUpperLeafBytes.getObjectAt(3)) + .getOctets(); + int[] statInt = new int[4]; + statInt[0] = checkBigIntegerInIntRange(seqOfUpperLeafInts.getObjectAt(0)); + statInt[1] = checkBigIntegerInIntRange(seqOfUpperLeafInts.getObjectAt(1)); + statInt[2] = checkBigIntegerInIntRange(seqOfUpperLeafInts.getObjectAt(2)); + statInt[3] = checkBigIntegerInIntRange(seqOfUpperLeafInts.getObjectAt(3)); + upperLeaf[i] = new GMSSLeaf(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt); + } + + // --- Decode <upperTreehashLeaf>. + ASN1Sequence seqOfUpperTHLeafs = (ASN1Sequence)mtsPrivateKey.getObjectAt(14); + ASN1Sequence seqOfUpperTHLeafStat; + ASN1Sequence seqOfUpperTHLeafBytes; + ASN1Sequence seqOfUpperTHLeafInts; + ASN1Sequence seqOfUpperTHLeafString; + + GMSSLeaf[] upperTHLeaf = new GMSSLeaf[seqOfUpperTHLeafs.size()]; + + for (int i = 0; i < upperTHLeaf.length; i++) + { + seqOfUpperTHLeafStat = (ASN1Sequence)seqOfUpperTHLeafs.getObjectAt(i); + seqOfUpperTHLeafString = (ASN1Sequence)seqOfUpperTHLeafStat.getObjectAt(0); + seqOfUpperTHLeafBytes = (ASN1Sequence)seqOfUpperTHLeafStat.getObjectAt(1); + seqOfUpperTHLeafInts = (ASN1Sequence)seqOfUpperTHLeafStat.getObjectAt(2); + + String[] name = new String[2]; + name[0] = ((DERIA5String)seqOfUpperTHLeafString.getObjectAt(0)) + .getString(); + name[1] = ((DERIA5String)seqOfUpperTHLeafString.getObjectAt(1)) + .getString(); + byte[][] statByte = new byte[4][]; + statByte[0] = ((DEROctetString)seqOfUpperTHLeafBytes.getObjectAt(0)) + .getOctets(); + statByte[1] = ((DEROctetString)seqOfUpperTHLeafBytes.getObjectAt(1)) + .getOctets(); + statByte[2] = ((DEROctetString)seqOfUpperTHLeafBytes.getObjectAt(2)) + .getOctets(); + statByte[3] = ((DEROctetString)seqOfUpperTHLeafBytes.getObjectAt(3)) + .getOctets(); + int[] statInt = new int[4]; + statInt[0] = checkBigIntegerInIntRange(seqOfUpperTHLeafInts.getObjectAt(0)); + statInt[1] = checkBigIntegerInIntRange(seqOfUpperTHLeafInts.getObjectAt(1)); + statInt[2] = checkBigIntegerInIntRange(seqOfUpperTHLeafInts.getObjectAt(2)); + statInt[3] = checkBigIntegerInIntRange(seqOfUpperTHLeafInts.getObjectAt(3)); + upperTHLeaf[i] = new GMSSLeaf(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt); + } + + // --- Decode <minTreehash>. + ASN1Sequence minTreehashPart = (ASN1Sequence)mtsPrivateKey.getObjectAt(15); + int[] minTreehash = new int[minTreehashPart.size()]; + for (int i = 0; i < minTreehashPart.size(); i++) + { + minTreehash[i] = checkBigIntegerInIntRange(minTreehashPart.getObjectAt(i)); + } + + // --- Decode <nextRoot>. + ASN1Sequence seqOfnextRoots = (ASN1Sequence)mtsPrivateKey.getObjectAt(16); + byte[][] nextRoot = new byte[seqOfnextRoots.size()][]; + for (int i = 0; i < nextRoot.length; i++) + { + nextRoot[i] = ((DEROctetString)seqOfnextRoots.getObjectAt(i)) + .getOctets(); + } + + // --- Decode <nextNextRoot>. + ASN1Sequence seqOfnextNextRoot = (ASN1Sequence)mtsPrivateKey.getObjectAt(17); + ASN1Sequence seqOfnextNextRootStat; + ASN1Sequence seqOfnextNextRootBytes; + ASN1Sequence seqOfnextNextRootInts; + ASN1Sequence seqOfnextNextRootString; + ASN1Sequence seqOfnextNextRootTreeH; + ASN1Sequence seqOfnextNextRootRetain; + + GMSSRootCalc[] nextNextRoot = new GMSSRootCalc[seqOfnextNextRoot.size()]; + + for (int i = 0; i < nextNextRoot.length; i++) + { + seqOfnextNextRootStat = (ASN1Sequence)seqOfnextNextRoot.getObjectAt(i); + seqOfnextNextRootString = (ASN1Sequence)seqOfnextNextRootStat + .getObjectAt(0); + seqOfnextNextRootBytes = (ASN1Sequence)seqOfnextNextRootStat + .getObjectAt(1); + seqOfnextNextRootInts = (ASN1Sequence)seqOfnextNextRootStat.getObjectAt(2); + seqOfnextNextRootTreeH = (ASN1Sequence)seqOfnextNextRootStat + .getObjectAt(3); + seqOfnextNextRootRetain = (ASN1Sequence)seqOfnextNextRootStat + .getObjectAt(4); + + // decode treehash of nextNextRoot + // --------------------------------- + ASN1Sequence seqOfnextNextRootTreeHStat; + ASN1Sequence seqOfnextNextRootTreeHBytes; + ASN1Sequence seqOfnextNextRootTreeHInts; + ASN1Sequence seqOfnextNextRootTreeHString; + + Treehash[] nnRTreehash = new Treehash[seqOfnextNextRootTreeH.size()]; + + for (int k = 0; k < nnRTreehash.length; k++) + { + seqOfnextNextRootTreeHStat = (ASN1Sequence)seqOfnextNextRootTreeH + .getObjectAt(k); + seqOfnextNextRootTreeHString = (ASN1Sequence)seqOfnextNextRootTreeHStat + .getObjectAt(0); + seqOfnextNextRootTreeHBytes = (ASN1Sequence)seqOfnextNextRootTreeHStat + .getObjectAt(1); + seqOfnextNextRootTreeHInts = (ASN1Sequence)seqOfnextNextRootTreeHStat + .getObjectAt(2); + + String[] name = new String[2]; + name[0] = ((DERIA5String)seqOfnextNextRootTreeHString.getObjectAt(0)) + .getString(); + name[1] = ((DERIA5String)seqOfnextNextRootTreeHString.getObjectAt(1)) + .getString(); + + int tailLength = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts.getObjectAt(1)); + + byte[][] statByte = new byte[3 + tailLength][]; + statByte[0] = ((DEROctetString)seqOfnextNextRootTreeHBytes + .getObjectAt(0)).getOctets(); + if (statByte[0].length == 0) + { // if null was encoded + statByte[0] = null; + } + + statByte[1] = ((DEROctetString)seqOfnextNextRootTreeHBytes + .getObjectAt(1)).getOctets(); + statByte[2] = ((DEROctetString)seqOfnextNextRootTreeHBytes + .getObjectAt(2)).getOctets(); + for (int j = 0; j < tailLength; j++) + { + statByte[3 + j] = ((DEROctetString)seqOfnextNextRootTreeHBytes + .getObjectAt(3 + j)).getOctets(); + } + int[] statInt = new int[6 + tailLength]; + statInt[0] = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts.getObjectAt(0)); + + statInt[1] = tailLength; + statInt[2] = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts.getObjectAt(2)); + + statInt[3] = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts.getObjectAt(3)); + + statInt[4] = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts.getObjectAt(4)); + + statInt[5] = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts.getObjectAt(5)); + + for (int j = 0; j < tailLength; j++) + { + statInt[6 + j] = checkBigIntegerInIntRange(seqOfnextNextRootTreeHInts + .getObjectAt(6 + j)); + } + nnRTreehash[k] = new Treehash(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt); + } + // --------------------------------- + + // decode retain of nextNextRoot + // --------------------------------- + // ASN1Sequence seqOfnextNextRootRetainPart0 = + // (ASN1Sequence)seqOfnextNextRootRetain.get(0); + ASN1Sequence seqOfnextNextRootRetainPart1; + + Vector[] nnRRetain = new Vector[seqOfnextNextRootRetain.size()]; + for (int j = 0; j < nnRRetain.length; j++) + { + seqOfnextNextRootRetainPart1 = (ASN1Sequence)seqOfnextNextRootRetain + .getObjectAt(j); + nnRRetain[j] = new Vector(); + for (int k = 0; k < seqOfnextNextRootRetainPart1.size(); k++) + { + nnRRetain[j] + .addElement(((DEROctetString)seqOfnextNextRootRetainPart1 + .getObjectAt(k)).getOctets()); + } + } + // --------------------------------- + + String[] name = new String[2]; + name[0] = ((DERIA5String)seqOfnextNextRootString.getObjectAt(0)) + .getString(); + name[1] = ((DERIA5String)seqOfnextNextRootString.getObjectAt(1)) + .getString(); + + int heightOfTree = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(0)); + int tailLength = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(7)); + byte[][] statByte = new byte[1 + heightOfTree + tailLength][]; + statByte[0] = ((DEROctetString)seqOfnextNextRootBytes.getObjectAt(0)) + .getOctets(); + for (int j = 0; j < heightOfTree; j++) + { + statByte[1 + j] = ((DEROctetString)seqOfnextNextRootBytes + .getObjectAt(1 + j)).getOctets(); + } + for (int j = 0; j < tailLength; j++) + { + statByte[1 + heightOfTree + j] = ((DEROctetString)seqOfnextNextRootBytes + .getObjectAt(1 + heightOfTree + j)).getOctets(); + } + int[] statInt = new int[8 + heightOfTree + tailLength]; + statInt[0] = heightOfTree; + statInt[1] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(1)); + statInt[2] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(2)); + statInt[3] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(3)); + statInt[4] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(4)); + statInt[5] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(5)); + statInt[6] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(6)); + statInt[7] = tailLength; + for (int j = 0; j < heightOfTree; j++) + { + statInt[8 + j] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(8 + j)); + } + for (int j = 0; j < tailLength; j++) + { + statInt[8 + heightOfTree + j] = checkBigIntegerInIntRange(seqOfnextNextRootInts.getObjectAt(8 + + heightOfTree + j)); + } + nextNextRoot[i] = new GMSSRootCalc(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt, + nnRTreehash, nnRRetain); + } + + // --- Decode <curRootSig>. + ASN1Sequence seqOfcurRootSig = (ASN1Sequence)mtsPrivateKey.getObjectAt(18); + byte[][] curRootSig = new byte[seqOfcurRootSig.size()][]; + for (int i = 0; i < curRootSig.length; i++) + { + curRootSig[i] = ((DEROctetString)seqOfcurRootSig.getObjectAt(i)) + .getOctets(); + } + + // --- Decode <nextRootSig>. + ASN1Sequence seqOfnextRootSigs = (ASN1Sequence)mtsPrivateKey.getObjectAt(19); + ASN1Sequence seqOfnRSStats; + ASN1Sequence seqOfnRSStrings; + ASN1Sequence seqOfnRSInts; + ASN1Sequence seqOfnRSBytes; + + GMSSRootSig[] nextRootSig = new GMSSRootSig[seqOfnextRootSigs.size()]; + + for (int i = 0; i < nextRootSig.length; i++) + { + seqOfnRSStats = (ASN1Sequence)seqOfnextRootSigs.getObjectAt(i); + // nextNextAuth[i]= new byte[nextNextAuthPart1.size()][]; + seqOfnRSStrings = (ASN1Sequence)seqOfnRSStats.getObjectAt(0); + seqOfnRSBytes = (ASN1Sequence)seqOfnRSStats.getObjectAt(1); + seqOfnRSInts = (ASN1Sequence)seqOfnRSStats.getObjectAt(2); + + String[] name = new String[2]; + name[0] = ((DERIA5String)seqOfnRSStrings.getObjectAt(0)).getString(); + name[1] = ((DERIA5String)seqOfnRSStrings.getObjectAt(1)).getString(); + byte[][] statByte = new byte[5][]; + statByte[0] = ((DEROctetString)seqOfnRSBytes.getObjectAt(0)) + .getOctets(); + statByte[1] = ((DEROctetString)seqOfnRSBytes.getObjectAt(1)) + .getOctets(); + statByte[2] = ((DEROctetString)seqOfnRSBytes.getObjectAt(2)) + .getOctets(); + statByte[3] = ((DEROctetString)seqOfnRSBytes.getObjectAt(3)) + .getOctets(); + statByte[4] = ((DEROctetString)seqOfnRSBytes.getObjectAt(4)) + .getOctets(); + int[] statInt = new int[9]; + statInt[0] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(0)); + statInt[1] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(1)); + statInt[2] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(2)); + statInt[3] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(3)); + statInt[4] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(4)); + statInt[5] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(5)); + statInt[6] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(6)); + statInt[7] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(7)); + statInt[8] = checkBigIntegerInIntRange(seqOfnRSInts.getObjectAt(8)); + nextRootSig[i] = new GMSSRootSig(DigestFactory.getDigest(name[0]).getClass(), statByte, statInt); + } + + // --- Decode <name>. + + // TODO: Really check, why there are multiple algorithms, we only + // use the first one!!! + ASN1Sequence namePart = (ASN1Sequence)mtsPrivateKey.getObjectAt(20); + String[] name = new String[namePart.size()]; + for (int i = 0; i < name.length; i++) + { + name[i] = ((DERIA5String)namePart.getObjectAt(i)).getString(); + } + */ + } + + public GMSSPrivateKey(int[] index, byte[][] currentSeed, + byte[][] nextNextSeed, byte[][][] currentAuthPath, + byte[][][] nextAuthPath, Treehash[][] currentTreehash, + Treehash[][] nextTreehash, Vector[] currentStack, + Vector[] nextStack, Vector[][] currentRetain, + Vector[][] nextRetain, byte[][][] keep, GMSSLeaf[] nextNextLeaf, + GMSSLeaf[] upperLeaf, GMSSLeaf[] upperTreehashLeaf, + int[] minTreehash, byte[][] nextRoot, GMSSRootCalc[] nextNextRoot, + byte[][] currentRootSig, GMSSRootSig[] nextRootSig, + GMSSParameters gmssParameterset, AlgorithmIdentifier digestAlg) + { + AlgorithmIdentifier[] names = new AlgorithmIdentifier[] { digestAlg }; + this.primitive = encode(index, currentSeed, nextNextSeed, currentAuthPath, nextAuthPath, keep, currentTreehash, nextTreehash, currentStack, nextStack, currentRetain, nextRetain, nextNextLeaf, upperLeaf, upperTreehashLeaf, minTreehash, nextRoot, nextNextRoot, currentRootSig, nextRootSig, gmssParameterset, names); + } + + + // TODO: change method signature to something more integrated into BouncyCastle + + /** + * @param index tree indices + * @param currentSeeds seed for the generation of private OTS keys for the + * current subtrees (TREE) + * @param nextNextSeeds seed for the generation of private OTS keys for the + * subtrees after next (TREE++) + * @param currentAuthPaths array of current authentication paths (AUTHPATH) + * @param nextAuthPaths array of next authentication paths (AUTHPATH+) + * @param keep keep array for the authPath algorithm + * @param currentTreehash treehash for authPath algorithm of current tree + * @param nextTreehash treehash for authPath algorithm of next tree (TREE+) + * @param currentStack shared stack for authPath algorithm of current tree + * @param nextStack shared stack for authPath algorithm of next tree (TREE+) + * @param currentRetain retain stack for authPath algorithm of current tree + * @param nextRetain retain stack for authPath algorithm of next tree (TREE+) + * @param nextNextLeaf array of upcoming leafs of the tree after next (LEAF++) of + * each layer + * @param upperLeaf needed for precomputation of upper nodes + * @param upperTreehashLeaf needed for precomputation of upper treehash nodes + * @param minTreehash index of next treehash instance to receive an update + * @param nextRoot the roots of the next trees (ROOT+) + * @param nextNextRoot the roots of the tree after next (ROOT++) + * @param currentRootSig array of signatures of the roots of the current subtrees + * (SIG) + * @param nextRootSig array of signatures of the roots of the next subtree + * (SIG+) + * @param gmssParameterset the GMSS Parameterset + * @param algorithms An array of algorithm identifiers, containing the hash function details + */ + private ASN1Primitive encode(int[] index, byte[][] currentSeeds, + byte[][] nextNextSeeds, byte[][][] currentAuthPaths, + byte[][][] nextAuthPaths, byte[][][] keep, + Treehash[][] currentTreehash, Treehash[][] nextTreehash, + Vector[] currentStack, Vector[] nextStack, + Vector[][] currentRetain, Vector[][] nextRetain, + GMSSLeaf[] nextNextLeaf, GMSSLeaf[] upperLeaf, + GMSSLeaf[] upperTreehashLeaf, int[] minTreehash, byte[][] nextRoot, + GMSSRootCalc[] nextNextRoot, byte[][] currentRootSig, + GMSSRootSig[] nextRootSig, GMSSParameters gmssParameterset, + AlgorithmIdentifier[] algorithms) + { + + ASN1EncodableVector result = new ASN1EncodableVector(); + + // --- Encode <index>. + ASN1EncodableVector indexPart = new ASN1EncodableVector(); + for (int i = 0; i < index.length; i++) + { + indexPart.add(new ASN1Integer(index[i])); + } + result.add(new DERSequence(indexPart)); + + // --- Encode <curSeeds>. + ASN1EncodableVector curSeedsPart = new ASN1EncodableVector(); + for (int i = 0; i < currentSeeds.length; i++) + { + curSeedsPart.add(new DEROctetString(currentSeeds[i])); + } + result.add(new DERSequence(curSeedsPart)); + + // --- Encode <nextNextSeeds>. + ASN1EncodableVector nextNextSeedsPart = new ASN1EncodableVector(); + for (int i = 0; i < nextNextSeeds.length; i++) + { + nextNextSeedsPart.add(new DEROctetString(nextNextSeeds[i])); + } + result.add(new DERSequence(nextNextSeedsPart)); + + // --- Encode <curAuth>. + ASN1EncodableVector curAuthPart0 = new ASN1EncodableVector(); + ASN1EncodableVector curAuthPart1 = new ASN1EncodableVector(); + for (int i = 0; i < currentAuthPaths.length; i++) + { + for (int j = 0; j < currentAuthPaths[i].length; j++) + { + curAuthPart0.add(new DEROctetString(currentAuthPaths[i][j])); + } + curAuthPart1.add(new DERSequence(curAuthPart0)); + curAuthPart0 = new ASN1EncodableVector(); + } + result.add(new DERSequence(curAuthPart1)); + + // --- Encode <nextAuth>. + ASN1EncodableVector nextAuthPart0 = new ASN1EncodableVector(); + ASN1EncodableVector nextAuthPart1 = new ASN1EncodableVector(); + for (int i = 0; i < nextAuthPaths.length; i++) + { + for (int j = 0; j < nextAuthPaths[i].length; j++) + { + nextAuthPart0.add(new DEROctetString(nextAuthPaths[i][j])); + } + nextAuthPart1.add(new DERSequence(nextAuthPart0)); + nextAuthPart0 = new ASN1EncodableVector(); + } + result.add(new DERSequence(nextAuthPart1)); + + // --- Encode <curTreehash>. + ASN1EncodableVector seqOfTreehash0 = new ASN1EncodableVector(); + ASN1EncodableVector seqOfTreehash1 = new ASN1EncodableVector(); + ASN1EncodableVector seqOfStat = new ASN1EncodableVector(); + ASN1EncodableVector seqOfByte = new ASN1EncodableVector(); + ASN1EncodableVector seqOfInt = new ASN1EncodableVector(); + + for (int i = 0; i < currentTreehash.length; i++) + { + for (int j = 0; j < currentTreehash[i].length; j++) + { + seqOfStat.add(new DERSequence(algorithms[0])); + + int tailLength = currentTreehash[i][j].getStatInt()[1]; + + seqOfByte.add(new DEROctetString(currentTreehash[i][j] + .getStatByte()[0])); + seqOfByte.add(new DEROctetString(currentTreehash[i][j] + .getStatByte()[1])); + seqOfByte.add(new DEROctetString(currentTreehash[i][j] + .getStatByte()[2])); + for (int k = 0; k < tailLength; k++) + { + seqOfByte.add(new DEROctetString(currentTreehash[i][j] + .getStatByte()[3 + k])); + } + seqOfStat.add(new DERSequence(seqOfByte)); + seqOfByte = new ASN1EncodableVector(); + + seqOfInt.add(new ASN1Integer( + currentTreehash[i][j].getStatInt()[0])); + seqOfInt.add(new ASN1Integer(tailLength)); + seqOfInt.add(new ASN1Integer( + currentTreehash[i][j].getStatInt()[2])); + seqOfInt.add(new ASN1Integer( + currentTreehash[i][j].getStatInt()[3])); + seqOfInt.add(new ASN1Integer( + currentTreehash[i][j].getStatInt()[4])); + seqOfInt.add(new ASN1Integer( + currentTreehash[i][j].getStatInt()[5])); + for (int k = 0; k < tailLength; k++) + { + seqOfInt.add(new ASN1Integer(currentTreehash[i][j] + .getStatInt()[6 + k])); + } + seqOfStat.add(new DERSequence(seqOfInt)); + seqOfInt = new ASN1EncodableVector(); + + seqOfTreehash1.add(new DERSequence(seqOfStat)); + seqOfStat = new ASN1EncodableVector(); + } + seqOfTreehash0.add(new DERSequence(seqOfTreehash1)); + seqOfTreehash1 = new ASN1EncodableVector(); + } + result.add(new DERSequence(seqOfTreehash0)); + + // --- Encode <nextTreehash>. + seqOfTreehash0 = new ASN1EncodableVector(); + seqOfTreehash1 = new ASN1EncodableVector(); + seqOfStat = new ASN1EncodableVector(); + seqOfByte = new ASN1EncodableVector(); + seqOfInt = new ASN1EncodableVector(); + + for (int i = 0; i < nextTreehash.length; i++) + { + for (int j = 0; j < nextTreehash[i].length; j++) + { + seqOfStat.add(new DERSequence(algorithms[0])); + + int tailLength = nextTreehash[i][j].getStatInt()[1]; + + seqOfByte.add(new DEROctetString(nextTreehash[i][j] + .getStatByte()[0])); + seqOfByte.add(new DEROctetString(nextTreehash[i][j] + .getStatByte()[1])); + seqOfByte.add(new DEROctetString(nextTreehash[i][j] + .getStatByte()[2])); + for (int k = 0; k < tailLength; k++) + { + seqOfByte.add(new DEROctetString(nextTreehash[i][j] + .getStatByte()[3 + k])); + } + seqOfStat.add(new DERSequence(seqOfByte)); + seqOfByte = new ASN1EncodableVector(); + + seqOfInt + .add(new ASN1Integer(nextTreehash[i][j].getStatInt()[0])); + seqOfInt.add(new ASN1Integer(tailLength)); + seqOfInt + .add(new ASN1Integer(nextTreehash[i][j].getStatInt()[2])); + seqOfInt + .add(new ASN1Integer(nextTreehash[i][j].getStatInt()[3])); + seqOfInt + .add(new ASN1Integer(nextTreehash[i][j].getStatInt()[4])); + seqOfInt + .add(new ASN1Integer(nextTreehash[i][j].getStatInt()[5])); + for (int k = 0; k < tailLength; k++) + { + seqOfInt.add(new ASN1Integer(nextTreehash[i][j] + .getStatInt()[6 + k])); + } + seqOfStat.add(new DERSequence(seqOfInt)); + seqOfInt = new ASN1EncodableVector(); + + seqOfTreehash1.add(new DERSequence(seqOfStat)); + seqOfStat = new ASN1EncodableVector(); + } + seqOfTreehash0.add(new DERSequence(new DERSequence(seqOfTreehash1))); + seqOfTreehash1 = new ASN1EncodableVector(); + } + result.add(new DERSequence(seqOfTreehash0)); + + // --- Encode <keep>. + ASN1EncodableVector keepPart0 = new ASN1EncodableVector(); + ASN1EncodableVector keepPart1 = new ASN1EncodableVector(); + for (int i = 0; i < keep.length; i++) + { + for (int j = 0; j < keep[i].length; j++) + { + keepPart0.add(new DEROctetString(keep[i][j])); + } + keepPart1.add(new DERSequence(keepPart0)); + keepPart0 = new ASN1EncodableVector(); + } + result.add(new DERSequence(keepPart1)); + + // --- Encode <curStack>. + ASN1EncodableVector curStackPart0 = new ASN1EncodableVector(); + ASN1EncodableVector curStackPart1 = new ASN1EncodableVector(); + for (int i = 0; i < currentStack.length; i++) + { + for (int j = 0; j < currentStack[i].size(); j++) + { + curStackPart0.add(new DEROctetString((byte[])currentStack[i] + .elementAt(j))); + } + curStackPart1.add(new DERSequence(curStackPart0)); + curStackPart0 = new ASN1EncodableVector(); + } + result.add(new DERSequence(curStackPart1)); + + // --- Encode <nextStack>. + ASN1EncodableVector nextStackPart0 = new ASN1EncodableVector(); + ASN1EncodableVector nextStackPart1 = new ASN1EncodableVector(); + for (int i = 0; i < nextStack.length; i++) + { + for (int j = 0; j < nextStack[i].size(); j++) + { + nextStackPart0.add(new DEROctetString((byte[])nextStack[i] + .elementAt(j))); + } + nextStackPart1.add(new DERSequence(nextStackPart0)); + nextStackPart0 = new ASN1EncodableVector(); + } + result.add(new DERSequence(nextStackPart1)); + + // --- Encode <curRetain>. + ASN1EncodableVector currentRetainPart0 = new ASN1EncodableVector(); + ASN1EncodableVector currentRetainPart1 = new ASN1EncodableVector(); + ASN1EncodableVector currentRetainPart2 = new ASN1EncodableVector(); + for (int i = 0; i < currentRetain.length; i++) + { + for (int j = 0; j < currentRetain[i].length; j++) + { + for (int k = 0; k < currentRetain[i][j].size(); k++) + { + currentRetainPart0.add(new DEROctetString( + (byte[])currentRetain[i][j].elementAt(k))); + } + currentRetainPart1.add(new DERSequence(currentRetainPart0)); + currentRetainPart0 = new ASN1EncodableVector(); + } + currentRetainPart2.add(new DERSequence(currentRetainPart1)); + currentRetainPart1 = new ASN1EncodableVector(); + } + result.add(new DERSequence(currentRetainPart2)); + + // --- Encode <nextRetain>. + ASN1EncodableVector nextRetainPart0 = new ASN1EncodableVector(); + ASN1EncodableVector nextRetainPart1 = new ASN1EncodableVector(); + ASN1EncodableVector nextRetainPart2 = new ASN1EncodableVector(); + for (int i = 0; i < nextRetain.length; i++) + { + for (int j = 0; j < nextRetain[i].length; j++) + { + for (int k = 0; k < nextRetain[i][j].size(); k++) + { + nextRetainPart0.add(new DEROctetString( + (byte[])nextRetain[i][j].elementAt(k))); + } + nextRetainPart1.add(new DERSequence(nextRetainPart0)); + nextRetainPart0 = new ASN1EncodableVector(); + } + nextRetainPart2.add(new DERSequence(nextRetainPart1)); + nextRetainPart1 = new ASN1EncodableVector(); + } + result.add(new DERSequence(nextRetainPart2)); + + // --- Encode <nextNextLeaf>. + ASN1EncodableVector seqOfLeaf = new ASN1EncodableVector(); + seqOfStat = new ASN1EncodableVector(); + seqOfByte = new ASN1EncodableVector(); + seqOfInt = new ASN1EncodableVector(); + + for (int i = 0; i < nextNextLeaf.length; i++) + { + seqOfStat.add(new DERSequence(algorithms[0])); + + byte[][] tempByte = nextNextLeaf[i].getStatByte(); + seqOfByte.add(new DEROctetString(tempByte[0])); + seqOfByte.add(new DEROctetString(tempByte[1])); + seqOfByte.add(new DEROctetString(tempByte[2])); + seqOfByte.add(new DEROctetString(tempByte[3])); + seqOfStat.add(new DERSequence(seqOfByte)); + seqOfByte = new ASN1EncodableVector(); + + int[] tempInt = nextNextLeaf[i].getStatInt(); + seqOfInt.add(new ASN1Integer(tempInt[0])); + seqOfInt.add(new ASN1Integer(tempInt[1])); + seqOfInt.add(new ASN1Integer(tempInt[2])); + seqOfInt.add(new ASN1Integer(tempInt[3])); + seqOfStat.add(new DERSequence(seqOfInt)); + seqOfInt = new ASN1EncodableVector(); + + seqOfLeaf.add(new DERSequence(seqOfStat)); + seqOfStat = new ASN1EncodableVector(); + } + result.add(new DERSequence(seqOfLeaf)); + + // --- Encode <upperLEAF>. + ASN1EncodableVector seqOfUpperLeaf = new ASN1EncodableVector(); + seqOfStat = new ASN1EncodableVector(); + seqOfByte = new ASN1EncodableVector(); + seqOfInt = new ASN1EncodableVector(); + + for (int i = 0; i < upperLeaf.length; i++) + { + seqOfStat.add(new DERSequence(algorithms[0])); + + byte[][] tempByte = upperLeaf[i].getStatByte(); + seqOfByte.add(new DEROctetString(tempByte[0])); + seqOfByte.add(new DEROctetString(tempByte[1])); + seqOfByte.add(new DEROctetString(tempByte[2])); + seqOfByte.add(new DEROctetString(tempByte[3])); + seqOfStat.add(new DERSequence(seqOfByte)); + seqOfByte = new ASN1EncodableVector(); + + int[] tempInt = upperLeaf[i].getStatInt(); + seqOfInt.add(new ASN1Integer(tempInt[0])); + seqOfInt.add(new ASN1Integer(tempInt[1])); + seqOfInt.add(new ASN1Integer(tempInt[2])); + seqOfInt.add(new ASN1Integer(tempInt[3])); + seqOfStat.add(new DERSequence(seqOfInt)); + seqOfInt = new ASN1EncodableVector(); + + seqOfUpperLeaf.add(new DERSequence(seqOfStat)); + seqOfStat = new ASN1EncodableVector(); + } + result.add(new DERSequence(seqOfUpperLeaf)); + + // encode <upperTreehashLeaf> + ASN1EncodableVector seqOfUpperTreehashLeaf = new ASN1EncodableVector(); + seqOfStat = new ASN1EncodableVector(); + seqOfByte = new ASN1EncodableVector(); + seqOfInt = new ASN1EncodableVector(); + + for (int i = 0; i < upperTreehashLeaf.length; i++) + { + seqOfStat.add(new DERSequence(algorithms[0])); + + byte[][] tempByte = upperTreehashLeaf[i].getStatByte(); + seqOfByte.add(new DEROctetString(tempByte[0])); + seqOfByte.add(new DEROctetString(tempByte[1])); + seqOfByte.add(new DEROctetString(tempByte[2])); + seqOfByte.add(new DEROctetString(tempByte[3])); + seqOfStat.add(new DERSequence(seqOfByte)); + seqOfByte = new ASN1EncodableVector(); + + int[] tempInt = upperTreehashLeaf[i].getStatInt(); + seqOfInt.add(new ASN1Integer(tempInt[0])); + seqOfInt.add(new ASN1Integer(tempInt[1])); + seqOfInt.add(new ASN1Integer(tempInt[2])); + seqOfInt.add(new ASN1Integer(tempInt[3])); + seqOfStat.add(new DERSequence(seqOfInt)); + seqOfInt = new ASN1EncodableVector(); + + seqOfUpperTreehashLeaf.add(new DERSequence(seqOfStat)); + seqOfStat = new ASN1EncodableVector(); + } + result.add(new DERSequence(seqOfUpperTreehashLeaf)); + + // --- Encode <minTreehash>. + ASN1EncodableVector minTreehashPart = new ASN1EncodableVector(); + for (int i = 0; i < minTreehash.length; i++) + { + minTreehashPart.add(new ASN1Integer(minTreehash[i])); + } + result.add(new DERSequence(minTreehashPart)); + + // --- Encode <nextRoot>. + ASN1EncodableVector nextRootPart = new ASN1EncodableVector(); + for (int i = 0; i < nextRoot.length; i++) + { + nextRootPart.add(new DEROctetString(nextRoot[i])); + } + result.add(new DERSequence(nextRootPart)); + + // --- Encode <nextNextRoot>. + ASN1EncodableVector seqOfnextNextRoot = new ASN1EncodableVector(); + ASN1EncodableVector seqOfnnRStats = new ASN1EncodableVector(); + ASN1EncodableVector seqOfnnRStrings = new ASN1EncodableVector(); + ASN1EncodableVector seqOfnnRBytes = new ASN1EncodableVector(); + ASN1EncodableVector seqOfnnRInts = new ASN1EncodableVector(); + ASN1EncodableVector seqOfnnRTreehash = new ASN1EncodableVector(); + ASN1EncodableVector seqOfnnRRetain = new ASN1EncodableVector(); + + for (int i = 0; i < nextNextRoot.length; i++) + { + seqOfnnRStats.add(new DERSequence(algorithms[0])); + seqOfnnRStrings = new ASN1EncodableVector(); + + int heightOfTree = nextNextRoot[i].getStatInt()[0]; + int tailLength = nextNextRoot[i].getStatInt()[7]; + + seqOfnnRBytes.add(new DEROctetString( + nextNextRoot[i].getStatByte()[0])); + for (int j = 0; j < heightOfTree; j++) + { + seqOfnnRBytes.add(new DEROctetString(nextNextRoot[i] + .getStatByte()[1 + j])); + } + for (int j = 0; j < tailLength; j++) + { + seqOfnnRBytes.add(new DEROctetString(nextNextRoot[i] + .getStatByte()[1 + heightOfTree + j])); + } + + seqOfnnRStats.add(new DERSequence(seqOfnnRBytes)); + seqOfnnRBytes = new ASN1EncodableVector(); + + seqOfnnRInts.add(new ASN1Integer(heightOfTree)); + seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[1])); + seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[2])); + seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[3])); + seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[4])); + seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[5])); + seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[6])); + seqOfnnRInts.add(new ASN1Integer(tailLength)); + for (int j = 0; j < heightOfTree; j++) + { + seqOfnnRInts.add(new ASN1Integer( + nextNextRoot[i].getStatInt()[8 + j])); + } + for (int j = 0; j < tailLength; j++) + { + seqOfnnRInts.add(new ASN1Integer(nextNextRoot[i].getStatInt()[8 + + heightOfTree + j])); + } + + seqOfnnRStats.add(new DERSequence(seqOfnnRInts)); + seqOfnnRInts = new ASN1EncodableVector(); + + // add treehash of nextNextRoot object + // ---------------------------- + seqOfStat = new ASN1EncodableVector(); + seqOfByte = new ASN1EncodableVector(); + seqOfInt = new ASN1EncodableVector(); + + if (nextNextRoot[i].getTreehash() != null) + { + for (int j = 0; j < nextNextRoot[i].getTreehash().length; j++) + { + seqOfStat.add(new DERSequence(algorithms[0])); + + tailLength = nextNextRoot[i].getTreehash()[j].getStatInt()[1]; + + seqOfByte.add(new DEROctetString(nextNextRoot[i] + .getTreehash()[j].getStatByte()[0])); + seqOfByte.add(new DEROctetString(nextNextRoot[i] + .getTreehash()[j].getStatByte()[1])); + seqOfByte.add(new DEROctetString(nextNextRoot[i] + .getTreehash()[j].getStatByte()[2])); + for (int k = 0; k < tailLength; k++) + { + seqOfByte.add(new DEROctetString(nextNextRoot[i] + .getTreehash()[j].getStatByte()[3 + k])); + } + seqOfStat.add(new DERSequence(seqOfByte)); + seqOfByte = new ASN1EncodableVector(); + + seqOfInt.add(new ASN1Integer( + nextNextRoot[i].getTreehash()[j].getStatInt()[0])); + seqOfInt.add(new ASN1Integer(tailLength)); + seqOfInt.add(new ASN1Integer( + nextNextRoot[i].getTreehash()[j].getStatInt()[2])); + seqOfInt.add(new ASN1Integer( + nextNextRoot[i].getTreehash()[j].getStatInt()[3])); + seqOfInt.add(new ASN1Integer( + nextNextRoot[i].getTreehash()[j].getStatInt()[4])); + seqOfInt.add(new ASN1Integer( + nextNextRoot[i].getTreehash()[j].getStatInt()[5])); + for (int k = 0; k < tailLength; k++) + { + seqOfInt.add(new ASN1Integer(nextNextRoot[i] + .getTreehash()[j].getStatInt()[6 + k])); + } + seqOfStat.add(new DERSequence(seqOfInt)); + seqOfInt = new ASN1EncodableVector(); + + seqOfnnRTreehash.add(new DERSequence(seqOfStat)); + seqOfStat = new ASN1EncodableVector(); + } + } + // ---------------------------- + seqOfnnRStats.add(new DERSequence(seqOfnnRTreehash)); + seqOfnnRTreehash = new ASN1EncodableVector(); + + // encode retain of nextNextRoot + // ---------------------------- + // --- Encode <curRetain>. + currentRetainPart0 = new ASN1EncodableVector(); + if (nextNextRoot[i].getRetain() != null) + { + for (int j = 0; j < nextNextRoot[i].getRetain().length; j++) + { + for (int k = 0; k < nextNextRoot[i].getRetain()[j].size(); k++) + { + currentRetainPart0.add(new DEROctetString( + (byte[])nextNextRoot[i].getRetain()[j] + .elementAt(k))); + } + seqOfnnRRetain.add(new DERSequence(currentRetainPart0)); + currentRetainPart0 = new ASN1EncodableVector(); + } + } + // ---------------------------- + seqOfnnRStats.add(new DERSequence(seqOfnnRRetain)); + seqOfnnRRetain = new ASN1EncodableVector(); + + seqOfnextNextRoot.add(new DERSequence(seqOfnnRStats)); + seqOfnnRStats = new ASN1EncodableVector(); + } + result.add(new DERSequence(seqOfnextNextRoot)); + + // --- Encode <curRootSig>. + ASN1EncodableVector curRootSigPart = new ASN1EncodableVector(); + for (int i = 0; i < currentRootSig.length; i++) + { + curRootSigPart.add(new DEROctetString(currentRootSig[i])); + } + result.add(new DERSequence(curRootSigPart)); + + // --- Encode <nextRootSig>. + ASN1EncodableVector seqOfnextRootSigs = new ASN1EncodableVector(); + ASN1EncodableVector seqOfnRSStats = new ASN1EncodableVector(); + ASN1EncodableVector seqOfnRSStrings = new ASN1EncodableVector(); + ASN1EncodableVector seqOfnRSBytes = new ASN1EncodableVector(); + ASN1EncodableVector seqOfnRSInts = new ASN1EncodableVector(); + + for (int i = 0; i < nextRootSig.length; i++) + { + seqOfnRSStats.add(new DERSequence(algorithms[0])); + seqOfnRSStrings = new ASN1EncodableVector(); + + seqOfnRSBytes.add(new DEROctetString( + nextRootSig[i].getStatByte()[0])); + seqOfnRSBytes.add(new DEROctetString( + nextRootSig[i].getStatByte()[1])); + seqOfnRSBytes.add(new DEROctetString( + nextRootSig[i].getStatByte()[2])); + seqOfnRSBytes.add(new DEROctetString( + nextRootSig[i].getStatByte()[3])); + seqOfnRSBytes.add(new DEROctetString( + nextRootSig[i].getStatByte()[4])); + + seqOfnRSStats.add(new DERSequence(seqOfnRSBytes)); + seqOfnRSBytes = new ASN1EncodableVector(); + + seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[0])); + seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[1])); + seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[2])); + seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[3])); + seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[4])); + seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[5])); + seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[6])); + seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[7])); + seqOfnRSInts.add(new ASN1Integer(nextRootSig[i].getStatInt()[8])); + + seqOfnRSStats.add(new DERSequence(seqOfnRSInts)); + seqOfnRSInts = new ASN1EncodableVector(); + + seqOfnextRootSigs.add(new DERSequence(seqOfnRSStats)); + seqOfnRSStats = new ASN1EncodableVector(); + } + result.add(new DERSequence(seqOfnextRootSigs)); + + // --- Encode <parameterset>. + ASN1EncodableVector parSetPart0 = new ASN1EncodableVector(); + ASN1EncodableVector parSetPart1 = new ASN1EncodableVector(); + ASN1EncodableVector parSetPart2 = new ASN1EncodableVector(); + ASN1EncodableVector parSetPart3 = new ASN1EncodableVector(); + + for (int i = 0; i < gmssParameterset.getHeightOfTrees().length; i++) + { + parSetPart1.add(new ASN1Integer( + gmssParameterset.getHeightOfTrees()[i])); + parSetPart2.add(new ASN1Integer(gmssParameterset + .getWinternitzParameter()[i])); + parSetPart3.add(new ASN1Integer(gmssParameterset.getK()[i])); + } + parSetPart0.add(new ASN1Integer(gmssParameterset.getNumOfLayers())); + parSetPart0.add(new DERSequence(parSetPart1)); + parSetPart0.add(new DERSequence(parSetPart2)); + parSetPart0.add(new DERSequence(parSetPart3)); + result.add(new DERSequence(parSetPart0)); + + // --- Encode <names>. + ASN1EncodableVector namesPart = new ASN1EncodableVector(); + + for (int i = 0; i < algorithms.length; i++) + { + namesPart.add(algorithms[i]); + } + + result.add(new DERSequence(namesPart)); + return new DERSequence(result); + + } + + private static int checkBigIntegerInIntRange(ASN1Encodable a) + { + BigInteger b = ((ASN1Integer)a).getValue(); + if ((b.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) || + (b.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0)) + { + throw new IllegalArgumentException("BigInteger not in Range: " + b.toString()); + } + return b.intValue(); + } + + + public ASN1Primitive toASN1Primitive() + { + return this.primitive; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/asn1/GMSSPublicKey.java b/core/src/main/java/org/spongycastle/pqc/asn1/GMSSPublicKey.java new file mode 100644 index 00000000..77992283 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/asn1/GMSSPublicKey.java @@ -0,0 +1,74 @@ +package org.spongycastle.pqc.asn1; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.util.Arrays; + +/** + * This class implements an ASN.1 encoded GMSS public key. The ASN.1 definition + * of this structure is: + * <pre> + * GMSSPublicKey ::= SEQUENCE{ + * version INTEGER + * publicKey OCTET STRING + * } + * </pre> + */ +public class GMSSPublicKey + extends ASN1Object +{ + private ASN1Integer version; + private byte[] publicKey; + + private GMSSPublicKey(ASN1Sequence seq) + { + if (seq.size() != 2) + { + throw new IllegalArgumentException("size of seq = " + seq.size()); + } + + this.version = ASN1Integer.getInstance(seq.getObjectAt(0)); + this.publicKey = ASN1OctetString.getInstance(seq.getObjectAt(1)).getOctets(); + } + + public GMSSPublicKey(byte[] publicKeyBytes) + { + this.version = new ASN1Integer(0); + this.publicKey = publicKeyBytes; + } + + public static GMSSPublicKey getInstance(Object o) + { + if (o instanceof GMSSPublicKey) + { + return (GMSSPublicKey)o; + } + else if (o != null) + { + return new GMSSPublicKey(ASN1Sequence.getInstance(o)); + } + + return null; + } + + public byte[] getPublicKey() + { + return Arrays.clone(publicKey); + } + + public ASN1Primitive toASN1Primitive() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(new DEROctetString(publicKey)); + + return new DERSequence(v); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/asn1/McElieceCCA2PrivateKey.java b/core/src/main/java/org/spongycastle/pqc/asn1/McElieceCCA2PrivateKey.java new file mode 100644 index 00000000..5f6a8b9d --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/asn1/McElieceCCA2PrivateKey.java @@ -0,0 +1,173 @@ +package org.spongycastle.pqc.asn1; + +import java.math.BigInteger; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; + +import org.spongycastle.pqc.math.linearalgebra.GF2Matrix; +import org.spongycastle.pqc.math.linearalgebra.GF2mField; +import org.spongycastle.pqc.math.linearalgebra.Permutation; +import org.spongycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM; + +public class McElieceCCA2PrivateKey + extends ASN1Object +{ + private ASN1ObjectIdentifier oid; + private int n; + private int k; + private byte[] encField; + private byte[] encGp; + private byte[] encP; + private byte[] encH; + private byte[][] encqInv; + + + public McElieceCCA2PrivateKey(ASN1ObjectIdentifier oid, int n, int k, GF2mField field, PolynomialGF2mSmallM goppaPoly, Permutation p, GF2Matrix h, PolynomialGF2mSmallM[] qInv) + { + this.oid = oid; + this.n = n; + this.k = k; + this.encField = field.getEncoded(); + this.encGp = goppaPoly.getEncoded(); + this.encP = p.getEncoded(); + this.encH = h.getEncoded(); + this.encqInv = new byte[qInv.length][]; + + for (int i = 0; i != qInv.length; i++) + { + encqInv[i] = qInv[i].getEncoded(); + } + } + + private McElieceCCA2PrivateKey(ASN1Sequence seq) + { + oid = ((ASN1ObjectIdentifier)seq.getObjectAt(0)); + + BigInteger bigN = ((ASN1Integer)seq.getObjectAt(1)).getValue(); + n = bigN.intValue(); + + BigInteger bigK = ((ASN1Integer)seq.getObjectAt(2)).getValue(); + k = bigK.intValue(); + + encField = ((ASN1OctetString)seq.getObjectAt(3)).getOctets(); + + encGp = ((ASN1OctetString)seq.getObjectAt(4)).getOctets(); + + encP = ((ASN1OctetString)seq.getObjectAt(5)).getOctets(); + + encH = ((ASN1OctetString)seq.getObjectAt(6)).getOctets(); + + ASN1Sequence asnQInv = (ASN1Sequence)seq.getObjectAt(7); + encqInv = new byte[asnQInv.size()][]; + for (int i = 0; i < asnQInv.size(); i++) + { + encqInv[i] = ((ASN1OctetString)asnQInv.getObjectAt(i)).getOctets(); + } + } + + public ASN1ObjectIdentifier getOID() + { + return oid; + } + + public int getN() + { + return n; + } + + public int getK() + { + return k; + } + + public GF2mField getField() + { + return new GF2mField(encField); + } + + public PolynomialGF2mSmallM getGoppaPoly() + { + return new PolynomialGF2mSmallM(this.getField(), encGp); + } + + public Permutation getP() + { + return new Permutation(encP); + } + + public GF2Matrix getH() + { + return new GF2Matrix(encH); + } + + public PolynomialGF2mSmallM[] getQInv() + { + PolynomialGF2mSmallM[] qInv = new PolynomialGF2mSmallM[encqInv.length]; + GF2mField field = this.getField(); + + for (int i = 0; i < encqInv.length; i++) + { + qInv[i] = new PolynomialGF2mSmallM(field, encqInv[i]); + } + + return qInv; + } + + public ASN1Primitive toASN1Primitive() + { + + ASN1EncodableVector v = new ASN1EncodableVector(); + // encode <oidString> + v.add(oid); + // encode <n> + v.add(new ASN1Integer(n)); + + // encode <k> + v.add(new ASN1Integer(k)); + + // encode <field> + v.add(new DEROctetString(encField)); + + // encode <gp> + v.add(new DEROctetString(encGp)); + + // encode <p> + v.add(new DEROctetString(encP)); + + // encode <h> + v.add(new DEROctetString(encH)); + + // encode <q> + ASN1EncodableVector asnQInv = new ASN1EncodableVector(); + for (int i = 0; i < encqInv.length; i++) + { + asnQInv.add(new DEROctetString(encqInv[i])); + } + + v.add(new DERSequence(asnQInv)); + + return new DERSequence(v); + } + + public static McElieceCCA2PrivateKey getInstance(Object o) + { + if (o instanceof McElieceCCA2PrivateKey) + { + return (McElieceCCA2PrivateKey)o; + } + else if (o != null) + { + return new McElieceCCA2PrivateKey(ASN1Sequence.getInstance(o)); + } + + return null; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/asn1/McElieceCCA2PublicKey.java b/core/src/main/java/org/spongycastle/pqc/asn1/McElieceCCA2PublicKey.java new file mode 100644 index 00000000..186d8ca4 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/asn1/McElieceCCA2PublicKey.java @@ -0,0 +1,96 @@ +package org.spongycastle.pqc.asn1; + +import java.math.BigInteger; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.pqc.math.linearalgebra.GF2Matrix; + +public class McElieceCCA2PublicKey + extends ASN1Object +{ + private ASN1ObjectIdentifier oid; + private int n; + private int t; + + private byte[] matrixG; + + public McElieceCCA2PublicKey(ASN1ObjectIdentifier oid, int n, int t, GF2Matrix g) + { + this.oid = oid; + this.n = n; + this.t = t; + this.matrixG = g.getEncoded(); + } + + private McElieceCCA2PublicKey(ASN1Sequence seq) + { + oid = ((ASN1ObjectIdentifier)seq.getObjectAt(0)); + BigInteger bigN = ((ASN1Integer)seq.getObjectAt(1)).getValue(); + n = bigN.intValue(); + + BigInteger bigT = ((ASN1Integer)seq.getObjectAt(2)).getValue(); + t = bigT.intValue(); + + matrixG = ((ASN1OctetString)seq.getObjectAt(3)).getOctets(); + } + + public ASN1ObjectIdentifier getOID() + { + return oid; + } + + public int getN() + { + return n; + } + + public int getT() + { + return t; + } + + public GF2Matrix getG() + { + return new GF2Matrix(matrixG); + } + + public ASN1Primitive toASN1Primitive() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + // encode <oidString> + v.add(oid); + + // encode <n> + v.add(new ASN1Integer(n)); + + // encode <t> + v.add(new ASN1Integer(t)); + + // encode <matrixG> + v.add(new DEROctetString(matrixG)); + + return new DERSequence(v); + } + + public static McElieceCCA2PublicKey getInstance(Object o) + { + if (o instanceof McElieceCCA2PublicKey) + { + return (McElieceCCA2PublicKey)o; + } + else if (o != null) + { + return new McElieceCCA2PublicKey(ASN1Sequence.getInstance(o)); + } + + return null; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/asn1/McEliecePrivateKey.java b/core/src/main/java/org/spongycastle/pqc/asn1/McEliecePrivateKey.java new file mode 100644 index 00000000..e0ba1ed7 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/asn1/McEliecePrivateKey.java @@ -0,0 +1,197 @@ +package org.spongycastle.pqc.asn1; + +import java.math.BigInteger; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.pqc.math.linearalgebra.GF2Matrix; +import org.spongycastle.pqc.math.linearalgebra.GF2mField; +import org.spongycastle.pqc.math.linearalgebra.Permutation; +import org.spongycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM; + +public class McEliecePrivateKey + extends ASN1Object +{ + private ASN1ObjectIdentifier oid; + private int n; + private int k; + private byte[] encField; + private byte[] encGp; + private byte[] encSInv; + private byte[] encP1; + private byte[] encP2; + private byte[] encH; + private byte[][] encqInv; + + + public McEliecePrivateKey(ASN1ObjectIdentifier oid, int n, int k, GF2mField field, PolynomialGF2mSmallM goppaPoly, GF2Matrix sInv, Permutation p1, Permutation p2, GF2Matrix h, PolynomialGF2mSmallM[] qInv) + { + this.oid = oid; + this.n = n; + this.k = k; + this.encField = field.getEncoded(); + this.encGp = goppaPoly.getEncoded(); + this.encSInv = sInv.getEncoded(); + this.encP1 = p1.getEncoded(); + this.encP2 = p2.getEncoded(); + this.encH = h.getEncoded(); + this.encqInv = new byte[qInv.length][]; + + for (int i = 0; i != qInv.length; i++) + { + encqInv[i] = qInv[i].getEncoded(); + } + } + + public static McEliecePrivateKey getInstance(Object o) + { + if (o instanceof McEliecePrivateKey) + { + return (McEliecePrivateKey)o; + } + else if (o != null) + { + return new McEliecePrivateKey(ASN1Sequence.getInstance(o)); + } + + return null; + } + + private McEliecePrivateKey(ASN1Sequence seq) + { + // <oidString> + oid = ((ASN1ObjectIdentifier)seq.getObjectAt(0)); + + BigInteger bigN = ((ASN1Integer)seq.getObjectAt(1)).getValue(); + n = bigN.intValue(); + + BigInteger bigK = ((ASN1Integer)seq.getObjectAt(2)).getValue(); + k = bigK.intValue(); + + encField = ((ASN1OctetString)seq.getObjectAt(3)).getOctets(); + + encGp = ((ASN1OctetString)seq.getObjectAt(4)).getOctets(); + + encSInv = ((ASN1OctetString)seq.getObjectAt(5)).getOctets(); + + encP1 = ((ASN1OctetString)seq.getObjectAt(6)).getOctets(); + + encP2 = ((ASN1OctetString)seq.getObjectAt(7)).getOctets(); + + encH = ((ASN1OctetString)seq.getObjectAt(8)).getOctets(); + + ASN1Sequence asnQInv = (ASN1Sequence)seq.getObjectAt(9); + encqInv = new byte[asnQInv.size()][]; + for (int i = 0; i < asnQInv.size(); i++) + { + encqInv[i] = ((ASN1OctetString)asnQInv.getObjectAt(i)).getOctets(); + } + } + + public ASN1ObjectIdentifier getOID() + { + return oid; + } + + public int getN() + { + return n; + } + + public int getK() + { + return k; + } + + public GF2mField getField() + { + return new GF2mField(encField); + } + + public PolynomialGF2mSmallM getGoppaPoly() + { + return new PolynomialGF2mSmallM(this.getField(), encGp); + } + + public GF2Matrix getSInv() + { + return new GF2Matrix(encSInv); + } + + public Permutation getP1() + { + return new Permutation(encP1); + } + + public Permutation getP2() + { + return new Permutation(encP2); + } + + public GF2Matrix getH() + { + return new GF2Matrix(encH); + } + + public PolynomialGF2mSmallM[] getQInv() + { + PolynomialGF2mSmallM[] qInv = new PolynomialGF2mSmallM[encqInv.length]; + GF2mField field = this.getField(); + + for (int i = 0; i < encqInv.length; i++) + { + qInv[i] = new PolynomialGF2mSmallM(field, encqInv[i]); + } + + return qInv; + } + + public ASN1Primitive toASN1Primitive() + { + + ASN1EncodableVector v = new ASN1EncodableVector(); + // encode <oidString> + v.add(oid); + // encode <n> + v.add(new ASN1Integer(n)); + + // encode <k> + v.add(new ASN1Integer(k)); + + // encode <fieldPoly> + v.add(new DEROctetString(encField)); + + // encode <goppaPoly> + v.add(new DEROctetString(encGp)); + + // encode <sInv> + v.add(new DEROctetString(encSInv)); + + // encode <p1> + v.add(new DEROctetString(encP1)); + + // encode <p2> + v.add(new DEROctetString(encP2)); + + // encode <h> + v.add(new DEROctetString(encH)); + + // encode <q> + ASN1EncodableVector asnQInv = new ASN1EncodableVector(); + for (int i = 0; i < encqInv.length; i++) + { + asnQInv.add(new DEROctetString(encqInv[i])); + } + + v.add(new DERSequence(asnQInv)); + + return new DERSequence(v); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/asn1/McEliecePublicKey.java b/core/src/main/java/org/spongycastle/pqc/asn1/McEliecePublicKey.java new file mode 100644 index 00000000..1415587d --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/asn1/McEliecePublicKey.java @@ -0,0 +1,97 @@ +package org.spongycastle.pqc.asn1; + +import java.math.BigInteger; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.pqc.math.linearalgebra.GF2Matrix; + +public class McEliecePublicKey + extends ASN1Object +{ + + private ASN1ObjectIdentifier oid; + private int n; + private int t; + + private byte[] matrixG; + + public McEliecePublicKey(ASN1ObjectIdentifier oid, int n, int t, GF2Matrix g) + { + this.oid = oid; + this.n = n; + this.t = t; + this.matrixG = g.getEncoded(); + } + + private McEliecePublicKey(ASN1Sequence seq) + { + oid = ((ASN1ObjectIdentifier)seq.getObjectAt(0)); + BigInteger bigN = ((ASN1Integer)seq.getObjectAt(1)).getValue(); + n = bigN.intValue(); + + BigInteger bigT = ((ASN1Integer)seq.getObjectAt(2)).getValue(); + t = bigT.intValue(); + + matrixG = ((ASN1OctetString)seq.getObjectAt(3)).getOctets(); + } + + public ASN1ObjectIdentifier getOID() + { + return oid; + } + + public int getN() + { + return n; + } + + public int getT() + { + return t; + } + + public GF2Matrix getG() + { + return new GF2Matrix(matrixG); + } + + public ASN1Primitive toASN1Primitive() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + // encode <oidString> + v.add(oid); + + // encode <n> + v.add(new ASN1Integer(n)); + + // encode <t> + v.add(new ASN1Integer(t)); + + // encode <matrixG> + v.add(new DEROctetString(matrixG)); + + return new DERSequence(v); + } + + public static McEliecePublicKey getInstance(Object o) + { + if (o instanceof McEliecePublicKey) + { + return (McEliecePublicKey)o; + } + else if (o != null) + { + return new McEliecePublicKey(ASN1Sequence.getInstance(o)); + } + + return null; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/asn1/PQCObjectIdentifiers.java b/core/src/main/java/org/spongycastle/pqc/asn1/PQCObjectIdentifiers.java new file mode 100644 index 00000000..1a0e5ce7 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/asn1/PQCObjectIdentifiers.java @@ -0,0 +1,46 @@ +package org.spongycastle.pqc.asn1; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; + +/** + * PQC: + * <p> + * { iso(1) identifier-organization(3) dod(6) internet(1) private(4) 1 8301 3 1 3 5 3 ... } + */ +public interface PQCObjectIdentifiers +{ + /** 1.3.6.1.4.1.8301.3.1.3.5.3.2 */ + public static final ASN1ObjectIdentifier rainbow = new ASN1ObjectIdentifier("1.3.6.1.4.1.8301.3.1.3.5.3.2"); + + /** 1.3.6.1.4.1.8301.3.1.3.5.3.2.1 */ + public static final ASN1ObjectIdentifier rainbowWithSha1 = rainbow.branch("1"); + /** 1.3.6.1.4.1.8301.3.1.3.5.3.2.2 */ + public static final ASN1ObjectIdentifier rainbowWithSha224 = rainbow.branch("2"); + /** 1.3.6.1.4.1.8301.3.1.3.5.3.2.3 */ + public static final ASN1ObjectIdentifier rainbowWithSha256 = rainbow.branch("3"); + /** 1.3.6.1.4.1.8301.3.1.3.5.3.2.4 */ + public static final ASN1ObjectIdentifier rainbowWithSha384 = rainbow.branch("4"); + /** 1.3.6.1.4.1.8301.3.1.3.5.3.2.5 */ + public static final ASN1ObjectIdentifier rainbowWithSha512 = rainbow.branch("5"); + + /** 1.3.6.1.4.1.8301.3.1.3.3 */ + public static final ASN1ObjectIdentifier gmss = new ASN1ObjectIdentifier("1.3.6.1.4.1.8301.3.1.3.3"); + + /** 1.3.6.1.4.1.8301.3.1.3.3.1 */ + public static final ASN1ObjectIdentifier gmssWithSha1 = gmss.branch("1"); + /** 1.3.6.1.4.1.8301.3.1.3.3.2 */ + public static final ASN1ObjectIdentifier gmssWithSha224 = gmss.branch("2"); + /** 1.3.6.1.4.1.8301.3.1.3.3.3 */ + public static final ASN1ObjectIdentifier gmssWithSha256 = gmss.branch("3"); + /** 1.3.6.1.4.1.8301.3.1.3.3.4 */ + public static final ASN1ObjectIdentifier gmssWithSha384 = gmss.branch("4"); + /** 1.3.6.1.4.1.8301.3.1.3.3.5 */ + public static final ASN1ObjectIdentifier gmssWithSha512 = gmss.branch("5"); + + /** 1.3.6.1.4.1.8301.3.1.3.4.1 */ + public static final ASN1ObjectIdentifier mcEliece = new ASN1ObjectIdentifier("1.3.6.1.4.1.8301.3.1.3.4.1"); + + /** 1.3.6.1.4.1.8301.3.1.3.4.2 */ + public static final ASN1ObjectIdentifier mcElieceCca2 = new ASN1ObjectIdentifier("1.3.6.1.4.1.8301.3.1.3.4.2"); + +} diff --git a/core/src/main/java/org/spongycastle/pqc/asn1/ParSet.java b/core/src/main/java/org/spongycastle/pqc/asn1/ParSet.java new file mode 100644 index 00000000..d5110a2c --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/asn1/ParSet.java @@ -0,0 +1,140 @@ +package org.spongycastle.pqc.asn1; + +import java.math.BigInteger; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.util.Arrays; + +/** + * <pre> + * ParSet ::= SEQUENCE { + * T INTEGER + * h SEQUENCE OF INTEGER + * w SEQUENCE OF INTEGER + * K SEQUENCE OF INTEGER + * } + * </pre> + */ +public class ParSet + extends ASN1Object +{ + private static final BigInteger ZERO = BigInteger.valueOf(0); + + private int t; + private int[] h; + private int[] w; + private int[] k; + + private static int checkBigIntegerInIntRangeAndPositive(BigInteger b) + { + if ((b.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) || + (b.compareTo(ZERO) <= 0)) + { + throw new IllegalArgumentException("BigInteger not in Range: " + b.toString()); + } + return b.intValue(); + } + + private ParSet(ASN1Sequence seq) + { + if (seq.size() != 4) + { + throw new IllegalArgumentException("sie of seqOfParams = " + seq.size()); + } + BigInteger asn1int = ((ASN1Integer)seq.getObjectAt(0)).getValue(); + + t = checkBigIntegerInIntRangeAndPositive(asn1int); + + ASN1Sequence seqOfPSh = (ASN1Sequence)seq.getObjectAt(1); + ASN1Sequence seqOfPSw = (ASN1Sequence)seq.getObjectAt(2); + ASN1Sequence seqOfPSK = (ASN1Sequence)seq.getObjectAt(3); + + if ((seqOfPSh.size() != t) || + (seqOfPSw.size() != t) || + (seqOfPSK.size() != t)) + { + throw new IllegalArgumentException("invalid size of sequences"); + } + + h = new int[seqOfPSh.size()]; + w = new int[seqOfPSw.size()]; + k = new int[seqOfPSK.size()]; + + for (int i = 0; i < t; i++) + { + h[i] = checkBigIntegerInIntRangeAndPositive((((ASN1Integer)seqOfPSh.getObjectAt(i))).getValue()); + w[i] = checkBigIntegerInIntRangeAndPositive((((ASN1Integer)seqOfPSw.getObjectAt(i))).getValue()); + k[i] = checkBigIntegerInIntRangeAndPositive((((ASN1Integer)seqOfPSK.getObjectAt(i))).getValue()); + } + } + + public ParSet(int t, int[] h, int[] w, int[] k) + { + this.t = t; + this.h = h; + this.w = w; + this.k = k; + } + + public static ParSet getInstance(Object o) + { + if (o instanceof ParSet) + { + return (ParSet)o; + } + else if (o != null) + { + return new ParSet(ASN1Sequence.getInstance(o)); + } + + return null; + } + + public int getT() + { + return t; + } + + public int[] getH() + { + return Arrays.clone(h); + } + + public int[] getW() + { + return Arrays.clone(w); + } + + public int[] getK() + { + return Arrays.clone(k); + } + + public ASN1Primitive toASN1Primitive() + { + ASN1EncodableVector seqOfPSh = new ASN1EncodableVector(); + ASN1EncodableVector seqOfPSw = new ASN1EncodableVector(); + ASN1EncodableVector seqOfPSK = new ASN1EncodableVector(); + + for (int i = 0; i < h.length; i++) + { + seqOfPSh.add(new ASN1Integer(h[i])); + seqOfPSw.add(new ASN1Integer(w[i])); + seqOfPSK.add(new ASN1Integer(k[i])); + } + + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new ASN1Integer(t)); + v.add(new DERSequence(seqOfPSh)); + v.add(new DERSequence(seqOfPSw)); + v.add(new DERSequence(seqOfPSK)); + + return new DERSequence(v); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/asn1/RainbowPrivateKey.java b/core/src/main/java/org/spongycastle/pqc/asn1/RainbowPrivateKey.java new file mode 100644 index 00000000..96648900 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/asn1/RainbowPrivateKey.java @@ -0,0 +1,349 @@ +package org.spongycastle.pqc.asn1; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.pqc.crypto.rainbow.Layer; +import org.spongycastle.pqc.crypto.rainbow.util.RainbowUtil; + +/** + * Return the key data to encode in the PrivateKeyInfo structure. + * <p> + * The ASN.1 definition of the key structure is + * <pre> + * RainbowPrivateKey ::= SEQUENCE { + * CHOICE + * { + * oid OBJECT IDENTIFIER -- OID identifying the algorithm + * version INTEGER -- 0 + * } + * A1inv SEQUENCE OF OCTET STRING -- inversed matrix of L1 + * b1 OCTET STRING -- translation vector of L1 + * A2inv SEQUENCE OF OCTET STRING -- inversed matrix of L2 + * b2 OCTET STRING -- translation vector of L2 + * vi OCTET STRING -- num of elmts in each Set S + * layers SEQUENCE OF Layer -- layers of F + * } + * + * Layer ::= SEQUENCE OF Poly + * + * Poly ::= SEQUENCE { + * alpha SEQUENCE OF OCTET STRING + * beta SEQUENCE OF OCTET STRING + * gamma OCTET STRING + * eta INTEGER + * } + * </pre> + */ +public class RainbowPrivateKey + extends ASN1Object +{ + private ASN1Integer version; + private ASN1ObjectIdentifier oid; + + private byte[][] invA1; + private byte[] b1; + private byte[][] invA2; + private byte[] b2; + private byte[] vi; + private Layer[] layers; + + private RainbowPrivateKey(ASN1Sequence seq) + { + // <oidString> or version + if (seq.getObjectAt(0) instanceof ASN1Integer) + { + version = ASN1Integer.getInstance(seq.getObjectAt(0)); + } + else + { + oid = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0)); + } + + // <A1inv> + ASN1Sequence asnA1 = (ASN1Sequence)seq.getObjectAt(1); + invA1 = new byte[asnA1.size()][]; + for (int i = 0; i < asnA1.size(); i++) + { + invA1[i] = ((ASN1OctetString)asnA1.getObjectAt(i)).getOctets(); + } + + // <b1> + ASN1Sequence asnb1 = (ASN1Sequence)seq.getObjectAt(2); + b1 = ((ASN1OctetString)asnb1.getObjectAt(0)).getOctets(); + + // <A2inv> + ASN1Sequence asnA2 = (ASN1Sequence)seq.getObjectAt(3); + invA2 = new byte[asnA2.size()][]; + for (int j = 0; j < asnA2.size(); j++) + { + invA2[j] = ((ASN1OctetString)asnA2.getObjectAt(j)).getOctets(); + } + + // <b2> + ASN1Sequence asnb2 = (ASN1Sequence)seq.getObjectAt(4); + b2 = ((ASN1OctetString)asnb2.getObjectAt(0)).getOctets(); + + // <vi> + ASN1Sequence asnvi = (ASN1Sequence)seq.getObjectAt(5); + vi = ((ASN1OctetString)asnvi.getObjectAt(0)).getOctets(); + + // <layers> + ASN1Sequence asnLayers = (ASN1Sequence)seq.getObjectAt(6); + + byte[][][][] alphas = new byte[asnLayers.size()][][][]; + byte[][][][] betas = new byte[asnLayers.size()][][][]; + byte[][][] gammas = new byte[asnLayers.size()][][]; + byte[][] etas = new byte[asnLayers.size()][]; + // a layer: + for (int l = 0; l < asnLayers.size(); l++) + { + ASN1Sequence asnLayer = (ASN1Sequence)asnLayers.getObjectAt(l); + + // alphas (num of alpha-2d-array = oi) + ASN1Sequence alphas3d = (ASN1Sequence)asnLayer.getObjectAt(0); + alphas[l] = new byte[alphas3d.size()][][]; + for (int m = 0; m < alphas3d.size(); m++) + { + ASN1Sequence alphas2d = (ASN1Sequence)alphas3d.getObjectAt(m); + alphas[l][m] = new byte[alphas2d.size()][]; + for (int n = 0; n < alphas2d.size(); n++) + { + alphas[l][m][n] = ((ASN1OctetString)alphas2d.getObjectAt(n)).getOctets(); + } + } + + // betas .... + ASN1Sequence betas3d = (ASN1Sequence)asnLayer.getObjectAt(1); + betas[l] = new byte[betas3d.size()][][]; + for (int mb = 0; mb < betas3d.size(); mb++) + { + ASN1Sequence betas2d = (ASN1Sequence)betas3d.getObjectAt(mb); + betas[l][mb] = new byte[betas2d.size()][]; + for (int nb = 0; nb < betas2d.size(); nb++) + { + betas[l][mb][nb] = ((ASN1OctetString)betas2d.getObjectAt(nb)).getOctets(); + } + } + + // gammas ... + ASN1Sequence gammas2d = (ASN1Sequence)asnLayer.getObjectAt(2); + gammas[l] = new byte[gammas2d.size()][]; + for (int mg = 0; mg < gammas2d.size(); mg++) + { + gammas[l][mg] = ((ASN1OctetString)gammas2d.getObjectAt(mg)).getOctets(); + } + + // eta ... + etas[l] = ((ASN1OctetString)asnLayer.getObjectAt(3)).getOctets(); + } + + int numOfLayers = vi.length - 1; + this.layers = new Layer[numOfLayers]; + for (int i = 0; i < numOfLayers; i++) + { + Layer l = new Layer(vi[i], vi[i + 1], RainbowUtil.convertArray(alphas[i]), + RainbowUtil.convertArray(betas[i]), RainbowUtil.convertArray(gammas[i]), RainbowUtil.convertArray(etas[i])); + this.layers[i] = l; + + } + } + + public RainbowPrivateKey(short[][] invA1, short[] b1, short[][] invA2, + short[] b2, int[] vi, Layer[] layers) + { + this.version = new ASN1Integer(1); + this.invA1 = RainbowUtil.convertArray(invA1); + this.b1 = RainbowUtil.convertArray(b1); + this.invA2 = RainbowUtil.convertArray(invA2); + this.b2 = RainbowUtil.convertArray(b2); + this.vi = RainbowUtil.convertIntArray(vi); + this.layers = layers; + } + + public static RainbowPrivateKey getInstance(Object o) + { + if (o instanceof RainbowPrivateKey) + { + return (RainbowPrivateKey)o; + } + else if (o != null) + { + return new RainbowPrivateKey(ASN1Sequence.getInstance(o)); + } + + return null; + } + + public ASN1Integer getVersion() + { + return version; + } + + /** + * Getter for the inverse matrix of A1. + * + * @return the A1inv inverse + */ + public short[][] getInvA1() + { + return RainbowUtil.convertArray(invA1); + } + + /** + * Getter for the translation part of the private quadratic map L1. + * + * @return b1 the translation part of L1 + */ + public short[] getB1() + { + return RainbowUtil.convertArray(b1); + } + + /** + * Getter for the translation part of the private quadratic map L2. + * + * @return b2 the translation part of L2 + */ + public short[] getB2() + { + return RainbowUtil.convertArray(b2); + } + + /** + * Getter for the inverse matrix of A2 + * + * @return the A2inv + */ + public short[][] getInvA2() + { + return RainbowUtil.convertArray(invA2); + } + + /** + * Returns the layers contained in the private key + * + * @return layers + */ + public Layer[] getLayers() + { + return this.layers; + } + + /** + * Returns the array of vi-s + * + * @return the vi + */ + public int[] getVi() + { + return RainbowUtil.convertArraytoInt(vi); + } + + public ASN1Primitive toASN1Primitive() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + // encode <oidString> or version + if (version != null) + { + v.add(version); + } + else + { + v.add(oid); + } + + // encode <A1inv> + ASN1EncodableVector asnA1 = new ASN1EncodableVector(); + for (int i = 0; i < invA1.length; i++) + { + asnA1.add(new DEROctetString(invA1[i])); + } + v.add(new DERSequence(asnA1)); + + // encode <b1> + ASN1EncodableVector asnb1 = new ASN1EncodableVector(); + asnb1.add(new DEROctetString(b1)); + v.add(new DERSequence(asnb1)); + + // encode <A2inv> + ASN1EncodableVector asnA2 = new ASN1EncodableVector(); + for (int i = 0; i < invA2.length; i++) + { + asnA2.add(new DEROctetString(invA2[i])); + } + v.add(new DERSequence(asnA2)); + + // encode <b2> + ASN1EncodableVector asnb2 = new ASN1EncodableVector(); + asnb2.add(new DEROctetString(b2)); + v.add(new DERSequence(asnb2)); + + // encode <vi> + ASN1EncodableVector asnvi = new ASN1EncodableVector(); + asnvi.add(new DEROctetString(vi)); + v.add(new DERSequence(asnvi)); + + // encode <layers> + ASN1EncodableVector asnLayers = new ASN1EncodableVector(); + // a layer: + for (int l = 0; l < layers.length; l++) + { + ASN1EncodableVector aLayer = new ASN1EncodableVector(); + + // alphas (num of alpha-2d-array = oi) + byte[][][] alphas = RainbowUtil.convertArray(layers[l].getCoeffAlpha()); + ASN1EncodableVector alphas3d = new ASN1EncodableVector(); + for (int i = 0; i < alphas.length; i++) + { + ASN1EncodableVector alphas2d = new ASN1EncodableVector(); + for (int j = 0; j < alphas[i].length; j++) + { + alphas2d.add(new DEROctetString(alphas[i][j])); + } + alphas3d.add(new DERSequence(alphas2d)); + } + aLayer.add(new DERSequence(alphas3d)); + + // betas .... + byte[][][] betas = RainbowUtil.convertArray(layers[l].getCoeffBeta()); + ASN1EncodableVector betas3d = new ASN1EncodableVector(); + for (int i = 0; i < betas.length; i++) + { + ASN1EncodableVector betas2d = new ASN1EncodableVector(); + for (int j = 0; j < betas[i].length; j++) + { + betas2d.add(new DEROctetString(betas[i][j])); + } + betas3d.add(new DERSequence(betas2d)); + } + aLayer.add(new DERSequence(betas3d)); + + // gammas ... + byte[][] gammas = RainbowUtil.convertArray(layers[l].getCoeffGamma()); + ASN1EncodableVector asnG = new ASN1EncodableVector(); + for (int i = 0; i < gammas.length; i++) + { + asnG.add(new DEROctetString(gammas[i])); + } + aLayer.add(new DERSequence(asnG)); + + // eta + aLayer.add(new DEROctetString(RainbowUtil.convertArray(layers[l].getCoeffEta()))); + + // now, layer built up. add it! + asnLayers.add(new DERSequence(aLayer)); + } + + v.add(new DERSequence(asnLayers)); + + return new DERSequence(v); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/asn1/RainbowPublicKey.java b/core/src/main/java/org/spongycastle/pqc/asn1/RainbowPublicKey.java new file mode 100644 index 00000000..c31aabb4 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/asn1/RainbowPublicKey.java @@ -0,0 +1,174 @@ +package org.spongycastle.pqc.asn1; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.ASN1Object; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.pqc.crypto.rainbow.util.RainbowUtil; + +/** + * This class implements an ASN.1 encoded Rainbow public key. The ASN.1 definition + * of this structure is: + * <pre> + * RainbowPublicKey ::= SEQUENCE { + * CHOICE + * { + * oid OBJECT IDENTIFIER -- OID identifying the algorithm + * version INTEGER -- 0 + * } + * docLength Integer -- length of the code + * coeffquadratic SEQUENCE OF OCTET STRING -- quadratic (mixed) coefficients + * coeffsingular SEQUENCE OF OCTET STRING -- singular coefficients + * coeffscalar SEQUENCE OF OCTET STRING -- scalar coefficients + * } + * </pre> + */ +public class RainbowPublicKey + extends ASN1Object +{ + private ASN1Integer version; + private ASN1ObjectIdentifier oid; + private ASN1Integer docLength; + private byte[][] coeffQuadratic; + private byte[][] coeffSingular; + private byte[] coeffScalar; + + private RainbowPublicKey(ASN1Sequence seq) + { + // <oidString> or version + if (seq.getObjectAt(0) instanceof ASN1Integer) + { + version = ASN1Integer.getInstance(seq.getObjectAt(0)); + } + else + { + oid = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0)); + } + + docLength = ASN1Integer.getInstance(seq.getObjectAt(1)); + + ASN1Sequence asnCoeffQuad = ASN1Sequence.getInstance(seq.getObjectAt(2)); + coeffQuadratic = new byte[asnCoeffQuad.size()][]; + for (int quadSize = 0; quadSize < asnCoeffQuad.size(); quadSize++) + { + coeffQuadratic[quadSize] = ASN1OctetString.getInstance(asnCoeffQuad.getObjectAt(quadSize)).getOctets(); + } + + ASN1Sequence asnCoeffSing = (ASN1Sequence)seq.getObjectAt(3); + coeffSingular = new byte[asnCoeffSing.size()][]; + for (int singSize = 0; singSize < asnCoeffSing.size(); singSize++) + { + coeffSingular[singSize] = ASN1OctetString.getInstance(asnCoeffSing.getObjectAt(singSize)).getOctets(); + } + + ASN1Sequence asnCoeffScalar = (ASN1Sequence)seq.getObjectAt(4); + coeffScalar = ASN1OctetString.getInstance(asnCoeffScalar.getObjectAt(0)).getOctets(); + } + + public RainbowPublicKey(int docLength, short[][] coeffQuadratic, short[][] coeffSingular, short[] coeffScalar) + { + this.version = new ASN1Integer(0); + this.docLength = new ASN1Integer(docLength); + this.coeffQuadratic = RainbowUtil.convertArray(coeffQuadratic); + this.coeffSingular = RainbowUtil.convertArray(coeffSingular); + this.coeffScalar = RainbowUtil.convertArray(coeffScalar); + } + + public static RainbowPublicKey getInstance(Object o) + { + if (o instanceof RainbowPublicKey) + { + return (RainbowPublicKey)o; + } + else if (o != null) + { + return new RainbowPublicKey(ASN1Sequence.getInstance(o)); + } + + return null; + } + + public ASN1Integer getVersion() + { + return version; + } + + /** + * @return the docLength + */ + public int getDocLength() + { + return this.docLength.getValue().intValue(); + } + + /** + * @return the coeffquadratic + */ + public short[][] getCoeffQuadratic() + { + return RainbowUtil.convertArray(coeffQuadratic); + } + + /** + * @return the coeffsingular + */ + public short[][] getCoeffSingular() + { + return RainbowUtil.convertArray(coeffSingular); + } + + /** + * @return the coeffscalar + */ + public short[] getCoeffScalar() + { + return RainbowUtil.convertArray(coeffScalar); + } + + public ASN1Primitive toASN1Primitive() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + // encode <oidString> or version + if (version != null) + { + v.add(version); + } + else + { + v.add(oid); + } + + // encode <docLength> + v.add(docLength); + + // encode <coeffQuadratic> + ASN1EncodableVector asnCoeffQuad = new ASN1EncodableVector(); + for (int i = 0; i < coeffQuadratic.length; i++) + { + asnCoeffQuad.add(new DEROctetString(coeffQuadratic[i])); + } + v.add(new DERSequence(asnCoeffQuad)); + + // encode <coeffSingular> + ASN1EncodableVector asnCoeffSing = new ASN1EncodableVector(); + for (int i = 0; i < coeffSingular.length; i++) + { + asnCoeffSing.add(new DEROctetString(coeffSingular[i])); + } + v.add(new DERSequence(asnCoeffSing)); + + // encode <coeffScalar> + ASN1EncodableVector asnCoeffScalar = new ASN1EncodableVector(); + asnCoeffScalar.add(new DEROctetString(coeffScalar)); + v.add(new DERSequence(asnCoeffScalar)); + + + return new DERSequence(v); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/DigestingMessageSigner.java b/core/src/main/java/org/spongycastle/pqc/crypto/DigestingMessageSigner.java new file mode 100644 index 00000000..b58a5278 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/DigestingMessageSigner.java @@ -0,0 +1,117 @@ +package org.spongycastle.pqc.crypto; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.Signer; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ParametersWithRandom; + + +/** + * Implements the sign and verify functions for a Signature Scheme which can use a hash function. + */ +public class DigestingMessageSigner + implements Signer +{ + private final Digest messDigest; + private final MessageSigner messSigner; + private boolean forSigning; + + public DigestingMessageSigner(MessageSigner messSigner, Digest messDigest) + { + this.messSigner = messSigner; + this.messDigest = messDigest; + } + + public void init(boolean forSigning, + CipherParameters param) + { + + this.forSigning = forSigning; + AsymmetricKeyParameter k; + + if (param instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)param).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)param; + } + + if (forSigning && !k.isPrivate()) + { + throw new IllegalArgumentException("Signing Requires Private Key."); + } + + if (!forSigning && k.isPrivate()) + { + throw new IllegalArgumentException("Verification Requires Public Key."); + } + + reset(); + + messSigner.init(forSigning, param); + } + + + /** + * This function signs the message that has been updated, making use of the + * private key. + * + * @return the signature of the message. + */ + public byte[] generateSignature() + { + if (!forSigning) + { + throw new IllegalStateException("RainbowDigestSigner not initialised for signature generation."); + } + + byte[] hash = new byte[messDigest.getDigestSize()]; + messDigest.doFinal(hash, 0); + + return messSigner.generateSignature(hash); + } + + /** + * This function verifies the signature of the message that has been + * updated, with the aid of the public key. + * + * @param signature the signature of the message is given as a byte array. + * @return true if the signature has been verified, false otherwise. + */ + public boolean verify(byte[] signature) + { + if (forSigning) + { + throw new IllegalStateException("RainbowDigestSigner not initialised for verification"); + } + + byte[] hash = new byte[messDigest.getDigestSize()]; + messDigest.doFinal(hash, 0); + + return messSigner.verifySignature(hash, signature); + + } + + public void update(byte b) + { + messDigest.update(b); + } + + public void update(byte[] in, int off, int len) + { + messDigest.update(in, off, len); + } + + public void reset() + { + messDigest.reset(); + } + + public boolean verifySignature(byte[] signature) + { + return this.verify(signature); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/MessageEncryptor.java b/core/src/main/java/org/spongycastle/pqc/crypto/MessageEncryptor.java new file mode 100644 index 00000000..4d5ea6b2 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/MessageEncryptor.java @@ -0,0 +1,30 @@ +package org.spongycastle.pqc.crypto; + + +import org.spongycastle.crypto.CipherParameters; + +public interface MessageEncryptor +{ + + /** + * + * @param forEncrypting true if we are encrypting a signature, false + * otherwise. + * @param param key parameters for encryption or decryption. + */ + public void init(boolean forEncrypting, CipherParameters param); + + /** + * + * @param message the message to be signed. + * @throws Exception + */ + public byte[] messageEncrypt(byte[] message) throws Exception; + + /** + * + * @param cipher the cipher text of the message + * @throws Exception + */ + public byte[] messageDecrypt(byte[] cipher) throws Exception; +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/MessageSigner.java b/core/src/main/java/org/spongycastle/pqc/crypto/MessageSigner.java new file mode 100644 index 00000000..b09c6195 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/MessageSigner.java @@ -0,0 +1,32 @@ +package org.spongycastle.pqc.crypto; + +import org.spongycastle.crypto.CipherParameters; + +public interface MessageSigner +{ + /** + * initialise the signer for signature generation or signature + * verification. + * + * @param forSigning true if we are generating a signature, false + * otherwise. + * @param param key parameters for signature generation. + */ + public void init(boolean forSigning, CipherParameters param); + + /** + * sign the passed in message (usually the output of a hash function). + * + * @param message the message to be signed. + * @return the signature of the message + */ + public byte[] generateSignature(byte[] message); + + /** + * verify the message message against the signature values r and s. + * + * @param message the message that was supposed to have been signed. + * @param signature the signature of the message + */ + public boolean verifySignature(byte[] message, byte[] signature); +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSDigestProvider.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSDigestProvider.java new file mode 100644 index 00000000..373ca503 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSDigestProvider.java @@ -0,0 +1,8 @@ +package org.spongycastle.pqc.crypto.gmss; + +import org.spongycastle.crypto.Digest; + +public interface GMSSDigestProvider +{ + Digest get(); +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSKeyGenerationParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSKeyGenerationParameters.java new file mode 100644 index 00000000..27d7b697 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSKeyGenerationParameters.java @@ -0,0 +1,26 @@ +package org.spongycastle.pqc.crypto.gmss; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.KeyGenerationParameters; + +public class GMSSKeyGenerationParameters + extends KeyGenerationParameters +{ + + private GMSSParameters params; + + public GMSSKeyGenerationParameters( + SecureRandom random, + GMSSParameters params) + { + // XXX key size? + super(random, 1); + this.params = params; + } + + public GMSSParameters getParameters() + { + return params; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSKeyPairGenerator.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSKeyPairGenerator.java new file mode 100644 index 00000000..bb1ef36d --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSKeyPairGenerator.java @@ -0,0 +1,476 @@ +package org.spongycastle.pqc.crypto.gmss; + +import java.security.SecureRandom; +import java.util.Vector; + +import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.KeyGenerationParameters; +import org.spongycastle.pqc.crypto.gmss.util.GMSSRandom; +import org.spongycastle.pqc.crypto.gmss.util.WinternitzOTSVerify; +import org.spongycastle.pqc.crypto.gmss.util.WinternitzOTSignature; + + +/** + * This class implements key pair generation of the generalized Merkle signature + * scheme (GMSS). + * + * @see GMSSSigner + */ +public class GMSSKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + /** + * The source of randomness for OTS private key generation + */ + private GMSSRandom gmssRandom; + + /** + * The hash function used for the construction of the authentication trees + */ + private Digest messDigestTree; + + /** + * An array of the seeds for the PRGN (for main tree, and all current + * subtrees) + */ + private byte[][] currentSeeds; + + /** + * An array of seeds for the PRGN (for all subtrees after next) + */ + private byte[][] nextNextSeeds; + + /** + * An array of the RootSignatures + */ + private byte[][] currentRootSigs; + + /** + * Class of hash function to use + */ + private GMSSDigestProvider digestProvider; + + /** + * The length of the seed for the PRNG + */ + private int mdLength; + + /** + * the number of Layers + */ + private int numLayer; + + + /** + * Flag indicating if the class already has been initialized + */ + private boolean initialized = false; + + /** + * Instance of GMSSParameterset + */ + private GMSSParameters gmssPS; + + /** + * An array of the heights of the authentication trees of each layer + */ + private int[] heightOfTrees; + + /** + * An array of the Winternitz parameter 'w' of each layer + */ + private int[] otsIndex; + + /** + * The parameter K needed for the authentication path computation + */ + private int[] K; + + private GMSSKeyGenerationParameters gmssParams; + + /** + * The GMSS OID. + */ + public static final String OID = "1.3.6.1.4.1.8301.3.1.3.3"; + + /** + * The standard constructor tries to generate the GMSS algorithm identifier + * with the corresponding OID. + * + * @param digestProvider provider for digest implementations. + */ + public GMSSKeyPairGenerator(GMSSDigestProvider digestProvider) + { + this.digestProvider = digestProvider; + messDigestTree = digestProvider.get(); + + // set mdLength + this.mdLength = messDigestTree.getDigestSize(); + // construct randomizer + this.gmssRandom = new GMSSRandom(messDigestTree); + + } + + /** + * Generates the GMSS key pair. The public key is an instance of + * JDKGMSSPublicKey, the private key is an instance of JDKGMSSPrivateKey. + * + * @return Key pair containing a JDKGMSSPublicKey and a JDKGMSSPrivateKey + */ + private AsymmetricCipherKeyPair genKeyPair() + { + if (!initialized) + { + initializeDefault(); + } + + // initialize authenticationPaths and treehash instances + byte[][][] currentAuthPaths = new byte[numLayer][][]; + byte[][][] nextAuthPaths = new byte[numLayer - 1][][]; + Treehash[][] currentTreehash = new Treehash[numLayer][]; + Treehash[][] nextTreehash = new Treehash[numLayer - 1][]; + + Vector[] currentStack = new Vector[numLayer]; + Vector[] nextStack = new Vector[numLayer - 1]; + + Vector[][] currentRetain = new Vector[numLayer][]; + Vector[][] nextRetain = new Vector[numLayer - 1][]; + + for (int i = 0; i < numLayer; i++) + { + currentAuthPaths[i] = new byte[heightOfTrees[i]][mdLength]; + currentTreehash[i] = new Treehash[heightOfTrees[i] - K[i]]; + + if (i > 0) + { + nextAuthPaths[i - 1] = new byte[heightOfTrees[i]][mdLength]; + nextTreehash[i - 1] = new Treehash[heightOfTrees[i] - K[i]]; + } + + currentStack[i] = new Vector(); + if (i > 0) + { + nextStack[i - 1] = new Vector(); + } + } + + // initialize roots + byte[][] currentRoots = new byte[numLayer][mdLength]; + byte[][] nextRoots = new byte[numLayer - 1][mdLength]; + // initialize seeds + byte[][] seeds = new byte[numLayer][mdLength]; + // initialize seeds[] by copying starting-seeds of first trees of each + // layer + for (int i = 0; i < numLayer; i++) + { + System.arraycopy(currentSeeds[i], 0, seeds[i], 0, mdLength); + } + + // initialize rootSigs + currentRootSigs = new byte[numLayer - 1][mdLength]; + + // ------------------------- + // ------------------------- + // --- calculation of current authpaths and current rootsigs (AUTHPATHS, + // SIG)------ + // from bottom up to the root + for (int h = numLayer - 1; h >= 0; h--) + { + GMSSRootCalc tree = new GMSSRootCalc(this.heightOfTrees[h], this.K[h], digestProvider); + try + { + // on lowest layer no lower root is available, so just call + // the method with null as first parameter + if (h == numLayer - 1) + { + tree = this.generateCurrentAuthpathAndRoot(null, currentStack[h], seeds[h], h); + } + else + // otherwise call the method with the former computed root + // value + { + tree = this.generateCurrentAuthpathAndRoot(currentRoots[h + 1], currentStack[h], seeds[h], h); + } + + } + catch (Exception e1) + { + e1.printStackTrace(); + } + + // set initial values needed for the private key construction + for (int i = 0; i < heightOfTrees[h]; i++) + { + System.arraycopy(tree.getAuthPath()[i], 0, currentAuthPaths[h][i], 0, mdLength); + } + currentRetain[h] = tree.getRetain(); + currentTreehash[h] = tree.getTreehash(); + System.arraycopy(tree.getRoot(), 0, currentRoots[h], 0, mdLength); + } + + // --- calculation of next authpaths and next roots (AUTHPATHS+, ROOTS+) + // ------ + for (int h = numLayer - 2; h >= 0; h--) + { + GMSSRootCalc tree = this.generateNextAuthpathAndRoot(nextStack[h], seeds[h + 1], h + 1); + + // set initial values needed for the private key construction + for (int i = 0; i < heightOfTrees[h + 1]; i++) + { + System.arraycopy(tree.getAuthPath()[i], 0, nextAuthPaths[h][i], 0, mdLength); + } + nextRetain[h] = tree.getRetain(); + nextTreehash[h] = tree.getTreehash(); + System.arraycopy(tree.getRoot(), 0, nextRoots[h], 0, mdLength); + + // create seed for the Merkle tree after next (nextNextSeeds) + // SEEDs++ + System.arraycopy(seeds[h + 1], 0, this.nextNextSeeds[h], 0, mdLength); + } + // ------------ + + // generate JDKGMSSPublicKey + GMSSPublicKeyParameters publicKey = new GMSSPublicKeyParameters(currentRoots[0], gmssPS); + + // generate the JDKGMSSPrivateKey + GMSSPrivateKeyParameters privateKey = new GMSSPrivateKeyParameters(currentSeeds, nextNextSeeds, currentAuthPaths, + nextAuthPaths, currentTreehash, nextTreehash, currentStack, nextStack, currentRetain, nextRetain, nextRoots, currentRootSigs, gmssPS, digestProvider); + + // return the KeyPair + return (new AsymmetricCipherKeyPair(publicKey, privateKey)); + } + + /** + * calculates the authpath for tree in layer h which starts with seed[h] + * additionally computes the rootSignature of underlaying root + * + * @param currentStack stack used for the treehash instance created by this method + * @param lowerRoot stores the root of the lower tree + * @param seed starting seeds + * @param h actual layer + */ + private GMSSRootCalc generateCurrentAuthpathAndRoot(byte[] lowerRoot, Vector currentStack, byte[] seed, int h) + { + byte[] help = new byte[mdLength]; + + byte[] OTSseed = new byte[mdLength]; + OTSseed = gmssRandom.nextSeed(seed); + + WinternitzOTSignature ots; + + // data structure that constructs the whole tree and stores + // the initial values for treehash, Auth and retain + GMSSRootCalc treeToConstruct = new GMSSRootCalc(this.heightOfTrees[h], this.K[h], digestProvider); + + treeToConstruct.initialize(currentStack); + + // generate the first leaf + if (h == numLayer - 1) + { + ots = new WinternitzOTSignature(OTSseed, digestProvider.get(), otsIndex[h]); + help = ots.getPublicKey(); + } + else + { + // for all layers except the lowest, generate the signature of the + // underlying root + // and reuse this signature to compute the first leaf of acual layer + // more efficiently (by verifiing the signature) + ots = new WinternitzOTSignature(OTSseed, digestProvider.get(), otsIndex[h]); + currentRootSigs[h] = ots.getSignature(lowerRoot); + WinternitzOTSVerify otsver = new WinternitzOTSVerify(digestProvider.get(), otsIndex[h]); + help = otsver.Verify(lowerRoot, currentRootSigs[h]); + } + // update the tree with the first leaf + treeToConstruct.update(help); + + int seedForTreehashIndex = 3; + int count = 0; + + // update the tree 2^(H) - 1 times, from the second to the last leaf + for (int i = 1; i < (1 << this.heightOfTrees[h]); i++) + { + // initialize the seeds for the leaf generation with index 3 * 2^h + if (i == seedForTreehashIndex && count < this.heightOfTrees[h] - this.K[h]) + { + treeToConstruct.initializeTreehashSeed(seed, count); + seedForTreehashIndex *= 2; + count++; + } + + OTSseed = gmssRandom.nextSeed(seed); + ots = new WinternitzOTSignature(OTSseed, digestProvider.get(), otsIndex[h]); + treeToConstruct.update(ots.getPublicKey()); + } + + if (treeToConstruct.wasFinished()) + { + return treeToConstruct; + } + System.err.println("Baum noch nicht fertig konstruiert!!!"); + return null; + } + + /** + * calculates the authpath and root for tree in layer h which starts with + * seed[h] + * + * @param nextStack stack used for the treehash instance created by this method + * @param seed starting seeds + * @param h actual layer + */ + private GMSSRootCalc generateNextAuthpathAndRoot(Vector nextStack, byte[] seed, int h) + { + byte[] OTSseed = new byte[numLayer]; + WinternitzOTSignature ots; + + // data structure that constructs the whole tree and stores + // the initial values for treehash, Auth and retain + GMSSRootCalc treeToConstruct = new GMSSRootCalc(this.heightOfTrees[h], this.K[h], this.digestProvider); + treeToConstruct.initialize(nextStack); + + int seedForTreehashIndex = 3; + int count = 0; + + // update the tree 2^(H) times, from the first to the last leaf + for (int i = 0; i < (1 << this.heightOfTrees[h]); i++) + { + // initialize the seeds for the leaf generation with index 3 * 2^h + if (i == seedForTreehashIndex && count < this.heightOfTrees[h] - this.K[h]) + { + treeToConstruct.initializeTreehashSeed(seed, count); + seedForTreehashIndex *= 2; + count++; + } + + OTSseed = gmssRandom.nextSeed(seed); + ots = new WinternitzOTSignature(OTSseed, digestProvider.get(), otsIndex[h]); + treeToConstruct.update(ots.getPublicKey()); + } + + if (treeToConstruct.wasFinished()) + { + return treeToConstruct; + } + System.err.println("N�chster Baum noch nicht fertig konstruiert!!!"); + return null; + } + + /** + * This method initializes the GMSS KeyPairGenerator using an integer value + * <code>keySize</code> as input. It provides a simple use of the GMSS for + * testing demands. + * <p> + * A given <code>keysize</code> of less than 10 creates an amount 2^10 + * signatures. A keySize between 10 and 20 creates 2^20 signatures. Given an + * integer greater than 20 the key pair generator creates 2^40 signatures. + * + * @param keySize Assigns the parameters used for the GMSS signatures. There are + * 3 choices:<br> + * 1. keysize <= 10: creates 2^10 signatures using the + * parameterset<br> + * P = (2, (5, 5), (3, 3), (3, 3))<br> + * 2. keysize > 10 and <= 20: creates 2^20 signatures using the + * parameterset<br> + * P = (2, (10, 10), (5, 4), (2, 2))<br> + * 3. keysize > 20: creates 2^40 signatures using the + * parameterset<br> + * P = (2, (10, 10, 10, 10), (9, 9, 9, 3), (2, 2, 2, 2)) + * @param secureRandom not used by GMSS, the SHA1PRNG of the SUN Provider is always + * used + */ + public void initialize(int keySize, SecureRandom secureRandom) + { + + KeyGenerationParameters kgp; + if (keySize <= 10) + { // create 2^10 keys + int[] defh = {10}; + int[] defw = {3}; + int[] defk = {2}; + // XXX sec random neede? + kgp = new GMSSKeyGenerationParameters(secureRandom, new GMSSParameters(defh.length, defh, defw, defk)); + } + else if (keySize <= 20) + { // create 2^20 keys + int[] defh = {10, 10}; + int[] defw = {5, 4}; + int[] defk = {2, 2}; + kgp = new GMSSKeyGenerationParameters(secureRandom, new GMSSParameters(defh.length, defh, defw, defk)); + } + else + { // create 2^40 keys, keygen lasts around 80 seconds + int[] defh = {10, 10, 10, 10}; + int[] defw = {9, 9, 9, 3}; + int[] defk = {2, 2, 2, 2}; + kgp = new GMSSKeyGenerationParameters(secureRandom, new GMSSParameters(defh.length, defh, defw, defk)); + } + + // call the initializer with the chosen parameters + this.initialize(kgp); + + } + + + /** + * Initalizes the key pair generator using a parameter set as input + */ + public void initialize(KeyGenerationParameters param) + { + + this.gmssParams = (GMSSKeyGenerationParameters)param; + + // generate GMSSParameterset + this.gmssPS = new GMSSParameters(gmssParams.getParameters().getNumOfLayers(), gmssParams.getParameters().getHeightOfTrees(), + gmssParams.getParameters().getWinternitzParameter(), gmssParams.getParameters().getK()); + + this.numLayer = gmssPS.getNumOfLayers(); + this.heightOfTrees = gmssPS.getHeightOfTrees(); + this.otsIndex = gmssPS.getWinternitzParameter(); + this.K = gmssPS.getK(); + + // seeds + this.currentSeeds = new byte[numLayer][mdLength]; + this.nextNextSeeds = new byte[numLayer - 1][mdLength]; + + // construct SecureRandom for initial seed generation + SecureRandom secRan = new SecureRandom(); + + // generation of initial seeds + for (int i = 0; i < numLayer; i++) + { + secRan.nextBytes(currentSeeds[i]); + gmssRandom.nextSeed(currentSeeds[i]); + } + + this.initialized = true; + } + + /** + * This method is called by generateKeyPair() in case that no other + * initialization method has been called by the user + */ + private void initializeDefault() + { + int[] defh = {10, 10, 10, 10}; + int[] defw = {3, 3, 3, 3}; + int[] defk = {2, 2, 2, 2}; + + KeyGenerationParameters kgp = new GMSSKeyGenerationParameters(new SecureRandom(), new GMSSParameters(defh.length, defh, defw, defk)); + this.initialize(kgp); + + } + + public void init(KeyGenerationParameters param) + { + this.initialize(param); + + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + return genKeyPair(); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSKeyParameters.java new file mode 100644 index 00000000..d2dcbba7 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSKeyParameters.java @@ -0,0 +1,22 @@ +package org.spongycastle.pqc.crypto.gmss; + +import org.spongycastle.crypto.params.AsymmetricKeyParameter; + +public class GMSSKeyParameters + extends AsymmetricKeyParameter +{ + private GMSSParameters params; + + public GMSSKeyParameters( + boolean isPrivate, + GMSSParameters params) + { + super(isPrivate); + this.params = params; + } + + public GMSSParameters getParameters() + { + return params; + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSLeaf.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSLeaf.java new file mode 100644 index 00000000..ade340ad --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSLeaf.java @@ -0,0 +1,376 @@ +package org.spongycastle.pqc.crypto.gmss; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.pqc.crypto.gmss.util.GMSSRandom; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.encoders.Hex; + + +/** + * This class implements the distributed computation of the public key of the + * Winternitz one-time signature scheme (OTSS). The class is used by the GMSS + * classes for calculation of upcoming leafs. + */ +public class GMSSLeaf +{ + + /** + * The hash function used by the OTS and the PRNG + */ + private Digest messDigestOTS; + + /** + * The length of the message digest and private key + */ + private int mdsize, keysize; + + /** + * The source of randomness for OTS private key generation + */ + private GMSSRandom gmssRandom; + + /** + * Byte array for distributed computation of the upcoming leaf + */ + private byte[] leaf; + + /** + * Byte array for storing the concatenated hashes of private key parts + */ + private byte[] concHashs; + + /** + * indices for distributed computation + */ + private int i, j; + + /** + * storing 2^w + */ + private int two_power_w; + + /** + * Winternitz parameter w + */ + private int w; + + /** + * the amount of distributed computation steps when updateLeaf is called + */ + private int steps; + + /** + * the internal seed + */ + private byte[] seed; + + /** + * the OTS privateKey parts + */ + byte[] privateKeyOTS; + + /** + * This constructor regenerates a prior GMSSLeaf object + * + * @param digest an array of strings, containing the name of the used hash + * function and PRNG and the name of the corresponding + * provider + * @param otsIndex status bytes + * @param numLeafs status ints + */ + public GMSSLeaf(Digest digest, byte[][] otsIndex, int[] numLeafs) + { + this.i = numLeafs[0]; + this.j = numLeafs[1]; + this.steps = numLeafs[2]; + this.w = numLeafs[3]; + + messDigestOTS = digest; + + gmssRandom = new GMSSRandom(messDigestOTS); + + // calulate keysize for private key and the help array + mdsize = messDigestOTS.getDigestSize(); + int mdsizeBit = mdsize << 3; + int messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w); + int checksumsize = getLog((messagesize << w) + 1); + this.keysize = messagesize + + (int)Math.ceil((double)checksumsize / (double)w); + this.two_power_w = 1 << w; + + // calculate steps + // ((2^w)-1)*keysize + keysize + 1 / (2^h -1) + + // initialize arrays + this.privateKeyOTS = otsIndex[0]; + this.seed = otsIndex[1]; + this.concHashs = otsIndex[2]; + this.leaf = otsIndex[3]; + } + + /** + * The constructor precomputes some needed variables for distributed leaf + * calculation + * + * @param digest an array of strings, containing the digest of the used hash + * function and PRNG and the digest of the corresponding + * provider + * @param w the winterniz parameter of that tree the leaf is computed + * for + * @param numLeafs the number of leafs of the tree from where the distributed + * computation is called + */ + GMSSLeaf(Digest digest, int w, int numLeafs) + { + this.w = w; + + messDigestOTS = digest; + + gmssRandom = new GMSSRandom(messDigestOTS); + + // calulate keysize for private key and the help array + mdsize = messDigestOTS.getDigestSize(); + int mdsizeBit = mdsize << 3; + int messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w); + int checksumsize = getLog((messagesize << w) + 1); + this.keysize = messagesize + + (int)Math.ceil((double)checksumsize / (double)w); + this.two_power_w = 1 << w; + + // calculate steps + // ((2^w)-1)*keysize + keysize + 1 / (2^h -1) + this.steps = (int)Math + .ceil((double)(((1 << w) - 1) * keysize + 1 + keysize) + / (double)(numLeafs)); + + // initialize arrays + this.seed = new byte[mdsize]; + this.leaf = new byte[mdsize]; + this.privateKeyOTS = new byte[mdsize]; + this.concHashs = new byte[mdsize * keysize]; + } + + public GMSSLeaf(Digest digest, int w, int numLeafs, byte[] seed0) + { + this.w = w; + + messDigestOTS = digest; + + gmssRandom = new GMSSRandom(messDigestOTS); + + // calulate keysize for private key and the help array + mdsize = messDigestOTS.getDigestSize(); + int mdsizeBit = mdsize << 3; + int messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w); + int checksumsize = getLog((messagesize << w) + 1); + this.keysize = messagesize + + (int)Math.ceil((double)checksumsize / (double)w); + this.two_power_w = 1 << w; + + // calculate steps + // ((2^w)-1)*keysize + keysize + 1 / (2^h -1) + this.steps = (int)Math + .ceil((double)(((1 << w) - 1) * keysize + 1 + keysize) + / (double)(numLeafs)); + + // initialize arrays + this.seed = new byte[mdsize]; + this.leaf = new byte[mdsize]; + this.privateKeyOTS = new byte[mdsize]; + this.concHashs = new byte[mdsize * keysize]; + + initLeafCalc(seed0); + } + + private GMSSLeaf(GMSSLeaf original) + { + this.messDigestOTS = original.messDigestOTS; + this.mdsize = original.mdsize; + this.keysize = original.keysize; + this.gmssRandom = original.gmssRandom; + this.leaf = Arrays.clone(original.leaf); + this.concHashs = Arrays.clone(original.concHashs); + this.i = original.i; + this.j = original.j; + this.two_power_w = original.two_power_w; + this.w = original.w; + this.steps = original.steps; + this.seed = Arrays.clone(original.seed); + this.privateKeyOTS = Arrays.clone(original.privateKeyOTS); + } + + /** + * initialize the distributed leaf calculation reset i,j and compute OTSseed + * with seed0 + * + * @param seed0 the starting seed + */ + // TODO: this really looks like it should be either always called from a constructor or nextLeaf. + void initLeafCalc(byte[] seed0) + { + this.i = 0; + this.j = 0; + byte[] dummy = new byte[mdsize]; + System.arraycopy(seed0, 0, dummy, 0, seed.length); + this.seed = gmssRandom.nextSeed(dummy); + } + + GMSSLeaf nextLeaf() + { + GMSSLeaf nextLeaf = new GMSSLeaf(this); + + nextLeaf.updateLeafCalc(); + + return nextLeaf; + } + + /** + * Processes <code>steps</code> steps of distributed leaf calculation + * + * @return true if leaf is completed, else false + */ + private void updateLeafCalc() + { + byte[] buf = new byte[messDigestOTS.getDigestSize()]; + + // steps times do + // TODO: this really needs to be looked at, the 10000 has been added as + // prior to this the leaf value always ended up as zeros. + for (int s = 0; s < steps + 10000; s++) + { + if (i == keysize && j == two_power_w - 1) + { // [3] at last hash the + // concatenation + messDigestOTS.update(concHashs, 0, concHashs.length); + leaf = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(leaf, 0); + return; + } + else if (i == 0 || j == two_power_w - 1) + { // [1] at the + // beginning and + // when [2] is + // finished: get the + // next private key + // part + i++; + j = 0; + // get next privKey part + this.privateKeyOTS = gmssRandom.nextSeed(seed); + } + else + { // [2] hash the privKey part + messDigestOTS.update(privateKeyOTS, 0, privateKeyOTS.length); + privateKeyOTS = buf; + messDigestOTS.doFinal(privateKeyOTS, 0); + j++; + if (j == two_power_w - 1) + { // after w hashes add to the + // concatenated array + System.arraycopy(privateKeyOTS, 0, concHashs, mdsize + * (i - 1), mdsize); + } + } + } + + throw new IllegalStateException("unable to updateLeaf in steps: " + steps + " " + i + " " + j); + } + + /** + * Returns the leaf value. + * + * @return the leaf value + */ + public byte[] getLeaf() + { + return Arrays.clone(leaf); + } + + /** + * This method returns the least integer that is greater or equal to the + * logarithm to the base 2 of an integer <code>intValue</code>. + * + * @param intValue an integer + * @return The least integer greater or equal to the logarithm to the base 2 + * of <code>intValue</code> + */ + private int getLog(int intValue) + { + int log = 1; + int i = 2; + while (i < intValue) + { + i <<= 1; + log++; + } + return log; + } + + /** + * Returns the status byte array used by the GMSSPrivateKeyASN.1 class + * + * @return The status bytes + */ + public byte[][] getStatByte() + { + + byte[][] statByte = new byte[4][]; + statByte[0] = new byte[mdsize]; + statByte[1] = new byte[mdsize]; + statByte[2] = new byte[mdsize * keysize]; + statByte[3] = new byte[mdsize]; + statByte[0] = privateKeyOTS; + statByte[1] = seed; + statByte[2] = concHashs; + statByte[3] = leaf; + + return statByte; + } + + /** + * Returns the status int array used by the GMSSPrivateKeyASN.1 class + * + * @return The status ints + */ + public int[] getStatInt() + { + + int[] statInt = new int[4]; + statInt[0] = i; + statInt[1] = j; + statInt[2] = steps; + statInt[3] = w; + return statInt; + } + + /** + * Returns a String representation of the main part of this element + * + * @return a String representation of the main part of this element + */ + public String toString() + { + String out = ""; + + for (int i = 0; i < 4; i++) + { + out = out + this.getStatInt()[i] + " "; + } + out = out + " " + this.mdsize + " " + this.keysize + " " + + this.two_power_w + " "; + + byte[][] temp = this.getStatByte(); + for (int i = 0; i < 4; i++) + { + if (temp[i] != null) + { + out = out + new String(Hex.encode(temp[i])) + " "; + } + else + { + out = out + "null "; + } + } + return out; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSParameters.java new file mode 100644 index 00000000..c78d50e3 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSParameters.java @@ -0,0 +1,155 @@ +package org.spongycastle.pqc.crypto.gmss; + +import org.spongycastle.util.Arrays; + +/** + * This class provides a specification for the GMSS parameters that are used by + * the GMSSKeyPairGenerator and GMSSSignature classes. + * + * @see org.spongycastle.pqc.crypto.gmss.GMSSKeyPairGenerator + */ +public class GMSSParameters +{ + /** + * The number of authentication tree layers. + */ + private int numOfLayers; + + /** + * The height of the authentication trees of each layer. + */ + private int[] heightOfTrees; + + /** + * The Winternitz Parameter 'w' of each layer. + */ + private int[] winternitzParameter; + + /** + * The parameter K needed for the authentication path computation + */ + private int[] K; + + /** + * The constructor for the parameters of the GMSSKeyPairGenerator. + * + * @param layers the number of authentication tree layers + * @param heightOfTrees the height of the authentication trees + * @param winternitzParameter the Winternitz Parameter 'w' of each layer + * @param K parameter for authpath computation + */ + public GMSSParameters(int layers, int[] heightOfTrees, int[] winternitzParameter, int[] K) + throws IllegalArgumentException + { + init(layers, heightOfTrees, winternitzParameter, K); + } + + private void init(int layers, int[] heightOfTrees, + int[] winternitzParameter, int[] K) + throws IllegalArgumentException + { + boolean valid = true; + String errMsg = ""; + this.numOfLayers = layers; + if ((numOfLayers != winternitzParameter.length) + || (numOfLayers != heightOfTrees.length) + || (numOfLayers != K.length)) + { + valid = false; + errMsg = "Unexpected parameterset format"; + } + for (int i = 0; i < numOfLayers; i++) + { + if ((K[i] < 2) || ((heightOfTrees[i] - K[i]) % 2 != 0)) + { + valid = false; + errMsg = "Wrong parameter K (K >= 2 and H-K even required)!"; + } + + if ((heightOfTrees[i] < 4) || (winternitzParameter[i] < 2)) + { + valid = false; + errMsg = "Wrong parameter H or w (H > 3 and w > 1 required)!"; + } + } + + if (valid) + { + this.heightOfTrees = Arrays.clone(heightOfTrees); + this.winternitzParameter = Arrays.clone(winternitzParameter); + this.K = Arrays.clone(K); + } + else + { + throw new IllegalArgumentException(errMsg); + } + } + + public GMSSParameters(int keySize) + throws IllegalArgumentException + { + if (keySize <= 10) + { // create 2^10 keys + int[] defh = {10}; + int[] defw = {3}; + int[] defk = {2}; + this.init(defh.length, defh, defw, defk); + } + else if (keySize <= 20) + { // create 2^20 keys + int[] defh = {10, 10}; + int[] defw = {5, 4}; + int[] defk = {2, 2}; + this.init(defh.length, defh, defw, defk); + } + else + { // create 2^40 keys, keygen lasts around 80 seconds + int[] defh = {10, 10, 10, 10}; + int[] defw = {9, 9, 9, 3}; + int[] defk = {2, 2, 2, 2}; + this.init(defh.length, defh, defw, defk); + } + } + + /** + * Returns the number of levels of the authentication trees. + * + * @return The number of levels of the authentication trees. + */ + public int getNumOfLayers() + { + return numOfLayers; + } + + /** + * Returns the array of height (for each layer) of the authentication trees + * + * @return The array of height (for each layer) of the authentication trees + */ + public int[] getHeightOfTrees() + { + return Arrays.clone(heightOfTrees); + } + + /** + * Returns the array of WinternitzParameter (for each layer) of the + * authentication trees + * + * @return The array of WinternitzParameter (for each layer) of the + * authentication trees + */ + public int[] getWinternitzParameter() + { + return Arrays.clone(winternitzParameter); + } + + /** + * Returns the parameter K needed for authentication path computation + * + * @return The parameter K needed for authentication path computation + */ + public int[] getK() + { + return Arrays.clone(K); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSPrivateKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSPrivateKeyParameters.java new file mode 100644 index 00000000..29e84b1f --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSPrivateKeyParameters.java @@ -0,0 +1,1041 @@ +package org.spongycastle.pqc.crypto.gmss; + +import java.util.Vector; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.pqc.crypto.gmss.util.GMSSRandom; +import org.spongycastle.pqc.crypto.gmss.util.WinternitzOTSignature; +import org.spongycastle.util.Arrays; + + +/** + * This class provides a specification for a GMSS private key. + */ +public class GMSSPrivateKeyParameters + extends GMSSKeyParameters +{ + private int[] index; + + private byte[][] currentSeeds; + private byte[][] nextNextSeeds; + + private byte[][][] currentAuthPaths; + private byte[][][] nextAuthPaths; + + private Treehash[][] currentTreehash; + private Treehash[][] nextTreehash; + + private Vector[] currentStack; + private Vector[] nextStack; + + private Vector[][] currentRetain; + private Vector[][] nextRetain; + + private byte[][][] keep; + + private GMSSLeaf[] nextNextLeaf; + private GMSSLeaf[] upperLeaf; + private GMSSLeaf[] upperTreehashLeaf; + + private int[] minTreehash; + + private GMSSParameters gmssPS; + + private byte[][] nextRoot; + private GMSSRootCalc[] nextNextRoot; + + private byte[][] currentRootSig; + private GMSSRootSig[] nextRootSig; + + private GMSSDigestProvider digestProvider; + + private boolean used = false; + + /** + * An array of the heights of the authentication trees of each layer + */ + private int[] heightOfTrees; + + /** + * An array of the Winternitz parameter 'w' of each layer + */ + private int[] otsIndex; + + /** + * The parameter K needed for the authentication path computation + */ + private int[] K; + + /** + * the number of Layers + */ + private int numLayer; + + /** + * The hash function used to construct the authentication trees + */ + private Digest messDigestTrees; + + /** + * The message digest length + */ + private int mdLength; + + /** + * The PRNG used for private key generation + */ + private GMSSRandom gmssRandom; + + + /** + * The number of leafs of one tree of each layer + */ + private int[] numLeafs; + + + /** + * Generates a new GMSS private key + * + * @param currentSeed seed for the generation of private OTS keys for the + * current subtrees + * @param nextNextSeed seed for the generation of private OTS keys for the next + * subtrees + * @param currentAuthPath array of current authentication paths + * @param nextAuthPath array of next authentication paths + * @param currentTreehash array of current treehash instances + * @param nextTreehash array of next treehash instances + * @param currentStack array of current shared stacks + * @param nextStack array of next shared stacks + * @param currentRetain array of current retain stacks + * @param nextRetain array of next retain stacks + * @param nextRoot the roots of the next subtree + * @param currentRootSig array of signatures of the roots of the current subtrees + * @param gmssParameterset the GMSS Parameterset + * @see org.spongycastle.pqc.crypto.gmss.GMSSKeyPairGenerator + */ + + public GMSSPrivateKeyParameters(byte[][] currentSeed, byte[][] nextNextSeed, + byte[][][] currentAuthPath, byte[][][] nextAuthPath, + Treehash[][] currentTreehash, Treehash[][] nextTreehash, + Vector[] currentStack, Vector[] nextStack, + Vector[][] currentRetain, Vector[][] nextRetain, byte[][] nextRoot, + byte[][] currentRootSig, GMSSParameters gmssParameterset, + GMSSDigestProvider digestProvider) + { + this(null, currentSeed, nextNextSeed, currentAuthPath, nextAuthPath, + null, currentTreehash, nextTreehash, currentStack, nextStack, + currentRetain, nextRetain, null, null, null, null, nextRoot, + null, currentRootSig, null, gmssParameterset, digestProvider); + } + + /** + * /** + * + * @param index tree indices + * @param keep keep array for the authPath algorithm + * @param currentTreehash treehash for authPath algorithm of current tree + * @param nextTreehash treehash for authPath algorithm of next tree (TREE+) + * @param currentStack shared stack for authPath algorithm of current tree + * @param nextStack shared stack for authPath algorithm of next tree (TREE+) + * @param currentRetain retain stack for authPath algorithm of current tree + * @param nextRetain retain stack for authPath algorithm of next tree (TREE+) + * @param nextNextLeaf array of upcoming leafs of the tree after next (LEAF++) of + * each layer + * @param upperLeaf needed for precomputation of upper nodes + * @param upperTreehashLeaf needed for precomputation of upper treehash nodes + * @param minTreehash index of next treehash instance to receive an update + * @param nextRoot the roots of the next trees (ROOT+) + * @param nextNextRoot the roots of the tree after next (ROOT++) + * @param currentRootSig array of signatures of the roots of the current subtrees + * (SIG) + * @param nextRootSig array of signatures of the roots of the next subtree + * (SIG+) + * @param gmssParameterset the GMSS Parameterset + */ + public GMSSPrivateKeyParameters(int[] index, byte[][] currentSeeds, + byte[][] nextNextSeeds, byte[][][] currentAuthPaths, + byte[][][] nextAuthPaths, byte[][][] keep, + Treehash[][] currentTreehash, Treehash[][] nextTreehash, + Vector[] currentStack, Vector[] nextStack, + Vector[][] currentRetain, Vector[][] nextRetain, + GMSSLeaf[] nextNextLeaf, GMSSLeaf[] upperLeaf, + GMSSLeaf[] upperTreehashLeaf, int[] minTreehash, byte[][] nextRoot, + GMSSRootCalc[] nextNextRoot, byte[][] currentRootSig, + GMSSRootSig[] nextRootSig, GMSSParameters gmssParameterset, + GMSSDigestProvider digestProvider) + { + + super(true, gmssParameterset); + + // construct message digest + + this.messDigestTrees = digestProvider.get(); + this.mdLength = messDigestTrees.getDigestSize(); + + + // Parameter + this.gmssPS = gmssParameterset; + this.otsIndex = gmssParameterset.getWinternitzParameter(); + this.K = gmssParameterset.getK(); + this.heightOfTrees = gmssParameterset.getHeightOfTrees(); + // initialize numLayer + this.numLayer = gmssPS.getNumOfLayers(); + + // initialize index if null + if (index == null) + { + this.index = new int[numLayer]; + for (int i = 0; i < numLayer; i++) + { + this.index[i] = 0; + } + } + else + { + this.index = index; + } + + this.currentSeeds = currentSeeds; + this.nextNextSeeds = nextNextSeeds; + + this.currentAuthPaths = currentAuthPaths; + this.nextAuthPaths = nextAuthPaths; + + // initialize keep if null + if (keep == null) + { + this.keep = new byte[numLayer][][]; + for (int i = 0; i < numLayer; i++) + { + this.keep[i] = new byte[(int)Math.floor(heightOfTrees[i] / 2)][mdLength]; + } + } + else + { + this.keep = keep; + } + + // initialize stack if null + if (currentStack == null) + { + this.currentStack = new Vector[numLayer]; + for (int i = 0; i < numLayer; i++) + { + this.currentStack[i] = new Vector(); + } + } + else + { + this.currentStack = currentStack; + } + + // initialize nextStack if null + if (nextStack == null) + { + this.nextStack = new Vector[numLayer - 1]; + for (int i = 0; i < numLayer - 1; i++) + { + this.nextStack[i] = new Vector(); + } + } + else + { + this.nextStack = nextStack; + } + + this.currentTreehash = currentTreehash; + this.nextTreehash = nextTreehash; + + this.currentRetain = currentRetain; + this.nextRetain = nextRetain; + + this.nextRoot = nextRoot; + + this.digestProvider = digestProvider; + + if (nextNextRoot == null) + { + this.nextNextRoot = new GMSSRootCalc[numLayer - 1]; + for (int i = 0; i < numLayer - 1; i++) + { + this.nextNextRoot[i] = new GMSSRootCalc( + this.heightOfTrees[i + 1], this.K[i + 1], this.digestProvider); + } + } + else + { + this.nextNextRoot = nextNextRoot; + } + this.currentRootSig = currentRootSig; + + // calculate numLeafs + numLeafs = new int[numLayer]; + for (int i = 0; i < numLayer; i++) + { + numLeafs[i] = 1 << heightOfTrees[i]; + } + // construct PRNG + this.gmssRandom = new GMSSRandom(messDigestTrees); + + if (numLayer > 1) + { + // construct the nextNextLeaf (LEAFs++) array for upcoming leafs in + // tree after next (TREE++) + if (nextNextLeaf == null) + { + this.nextNextLeaf = new GMSSLeaf[numLayer - 2]; + for (int i = 0; i < numLayer - 2; i++) + { + this.nextNextLeaf[i] = new GMSSLeaf(digestProvider.get(), otsIndex[i + 1], numLeafs[i + 2], this.nextNextSeeds[i]); + } + } + else + { + this.nextNextLeaf = nextNextLeaf; + } + } + else + { + this.nextNextLeaf = new GMSSLeaf[0]; + } + + // construct the upperLeaf array for upcoming leafs in tree over the + // actual + if (upperLeaf == null) + { + this.upperLeaf = new GMSSLeaf[numLayer - 1]; + for (int i = 0; i < numLayer - 1; i++) + { + this.upperLeaf[i] = new GMSSLeaf(digestProvider.get(), otsIndex[i], + numLeafs[i + 1], this.currentSeeds[i]); + } + } + else + { + this.upperLeaf = upperLeaf; + } + + // construct the leafs for upcoming leafs in treehashs in tree over the + // actual + if (upperTreehashLeaf == null) + { + this.upperTreehashLeaf = new GMSSLeaf[numLayer - 1]; + for (int i = 0; i < numLayer - 1; i++) + { + this.upperTreehashLeaf[i] = new GMSSLeaf(digestProvider.get(), otsIndex[i], numLeafs[i + 1]); + } + } + else + { + this.upperTreehashLeaf = upperTreehashLeaf; + } + + if (minTreehash == null) + { + this.minTreehash = new int[numLayer - 1]; + for (int i = 0; i < numLayer - 1; i++) + { + this.minTreehash[i] = -1; + } + } + else + { + this.minTreehash = minTreehash; + } + + // construct the nextRootSig (RootSig++) + byte[] dummy = new byte[mdLength]; + byte[] OTSseed = new byte[mdLength]; + if (nextRootSig == null) + { + this.nextRootSig = new GMSSRootSig[numLayer - 1]; + for (int i = 0; i < numLayer - 1; i++) + { + System.arraycopy(currentSeeds[i], 0, dummy, 0, mdLength); + gmssRandom.nextSeed(dummy); + OTSseed = gmssRandom.nextSeed(dummy); + this.nextRootSig[i] = new GMSSRootSig(digestProvider.get(), otsIndex[i], + heightOfTrees[i + 1]); + this.nextRootSig[i].initSign(OTSseed, nextRoot[i]); + } + } + else + { + this.nextRootSig = nextRootSig; + } + } + + // we assume this only gets called from nextKey so used is never copied. + private GMSSPrivateKeyParameters(GMSSPrivateKeyParameters original) + { + super(true, original.getParameters()); + + this.index = Arrays.clone(original.index); + this.currentSeeds = Arrays.clone(original.currentSeeds); + this.nextNextSeeds = Arrays.clone(original.nextNextSeeds); + this.currentAuthPaths = Arrays.clone(original.currentAuthPaths); + this.nextAuthPaths = Arrays.clone(original.nextAuthPaths); + this.currentTreehash = original.currentTreehash; + this.nextTreehash = original.nextTreehash; + this.currentStack = original.currentStack; + this.nextStack = original.nextStack; + this.currentRetain = original.currentRetain; + this.nextRetain = original.nextRetain; + this.keep = Arrays.clone(original.keep); + this.nextNextLeaf = original.nextNextLeaf; + this.upperLeaf = original.upperLeaf; + this.upperTreehashLeaf = original.upperTreehashLeaf; + this.minTreehash = original.minTreehash; + this.gmssPS = original.gmssPS; + this.nextRoot = Arrays.clone(original.nextRoot); + this.nextNextRoot = original.nextNextRoot; + this.currentRootSig = original.currentRootSig; + this.nextRootSig = original.nextRootSig; + this.digestProvider = original.digestProvider; + this.heightOfTrees = original.heightOfTrees; + this.otsIndex = original.otsIndex; + this.K = original.K; + this.numLayer = original.numLayer; + this.messDigestTrees = original.messDigestTrees; + this.mdLength = original.mdLength; + this.gmssRandom = original.gmssRandom; + this.numLeafs = original.numLeafs; + } + + public boolean isUsed() + { + return this.used; + } + + public void markUsed() + { + this.used = true; + } + + public GMSSPrivateKeyParameters nextKey() + { + GMSSPrivateKeyParameters nKey = new GMSSPrivateKeyParameters(this); + + nKey.nextKey(gmssPS.getNumOfLayers() - 1); + + return nKey; + } + + /** + * This method updates the GMSS private key for the next signature + * + * @param layer the layer where the next key is processed + */ + private void nextKey(int layer) + { + // only for lowest layer ( other layers indices are raised in nextTree() + // method ) + if (layer == numLayer - 1) + { + index[layer]++; + } // else System.out.println(" --- nextKey on layer " + layer + " + // index is now : " + index[layer]); + + // if tree of this layer is depleted + if (index[layer] == numLeafs[layer]) + { + if (numLayer != 1) + { + nextTree(layer); + index[layer] = 0; + } + } + else + { + updateKey(layer); + } + } + + /** + * Switch to next subtree if the current one is depleted + * + * @param layer the layer where the next tree is processed + */ + private void nextTree(int layer) + { + // System.out.println("NextTree method called on layer " + layer); + // dont create next tree for the top layer + if (layer > 0) + { + // raise index for upper layer + index[layer - 1]++; + + // test if it is already the last tree + boolean lastTree = true; + int z = layer; + do + { + z--; + if (index[z] < numLeafs[z]) + { + lastTree = false; + } + } + while (lastTree && (z > 0)); + + // only construct next subtree if last one is not already in use + if (!lastTree) + { + gmssRandom.nextSeed(currentSeeds[layer]); + + // last step of distributed signature calculation + nextRootSig[layer - 1].updateSign(); + + // last step of distributed leaf calculation for nextNextLeaf + if (layer > 1) + { + nextNextLeaf[layer - 1 - 1] = nextNextLeaf[layer - 1 - 1].nextLeaf(); + } + + // last step of distributed leaf calculation for upper leaf + upperLeaf[layer - 1] = upperLeaf[layer - 1].nextLeaf(); + + // last step of distributed leaf calculation for all treehashs + + if (minTreehash[layer - 1] >= 0) + { + upperTreehashLeaf[layer - 1] = upperTreehashLeaf[layer - 1].nextLeaf(); + byte[] leaf = this.upperTreehashLeaf[layer - 1].getLeaf(); + // if update is required use the precomputed leaf to update + // treehash + try + { + currentTreehash[layer - 1][minTreehash[layer - 1]] + .update(this.gmssRandom, leaf); + // System.out.println("UUUpdated TH " + + // minTreehash[layer - 1]); + if (currentTreehash[layer - 1][minTreehash[layer - 1]] + .wasFinished()) + { + // System.out.println("FFFinished TH " + + // minTreehash[layer - 1]); + } + } + catch (Exception e) + { + System.out.println(e); + } + } + + // last step of nextNextAuthRoot calculation + this.updateNextNextAuthRoot(layer); + + // ******************************************************** / + + // NOW: advance to next tree on layer 'layer' + + // NextRootSig --> currentRootSigs + this.currentRootSig[layer - 1] = nextRootSig[layer - 1] + .getSig(); + + // ----------------------- + + // nextTreehash --> currentTreehash + // nextNextTreehash --> nextTreehash + for (int i = 0; i < heightOfTrees[layer] - K[layer]; i++) + { + this.currentTreehash[layer][i] = this.nextTreehash[layer - 1][i]; + this.nextTreehash[layer - 1][i] = this.nextNextRoot[layer - 1] + .getTreehash()[i]; + } + + // NextAuthPath --> currentAuthPath + // nextNextAuthPath --> nextAuthPath + for (int i = 0; i < heightOfTrees[layer]; i++) + { + System.arraycopy(nextAuthPaths[layer - 1][i], 0, + currentAuthPaths[layer][i], 0, mdLength); + System.arraycopy(nextNextRoot[layer - 1].getAuthPath()[i], + 0, nextAuthPaths[layer - 1][i], 0, mdLength); + } + + // nextRetain --> currentRetain + // nextNextRetain --> nextRetain + for (int i = 0; i < K[layer] - 1; i++) + { + this.currentRetain[layer][i] = this.nextRetain[layer - 1][i]; + this.nextRetain[layer - 1][i] = this.nextNextRoot[layer - 1] + .getRetain()[i]; + } + + // nextStack --> currentStack + this.currentStack[layer] = this.nextStack[layer - 1]; + // nextNextStack --> nextStack + this.nextStack[layer - 1] = this.nextNextRoot[layer - 1] + .getStack(); + + // nextNextRoot --> nextRoot + this.nextRoot[layer - 1] = this.nextNextRoot[layer - 1] + .getRoot(); + // ----------------------- + + // ----------------- + byte[] OTSseed = new byte[mdLength]; + byte[] dummy = new byte[mdLength]; + // gmssRandom.setSeed(currentSeeds[layer]); + System + .arraycopy(currentSeeds[layer - 1], 0, dummy, 0, + mdLength); + OTSseed = gmssRandom.nextSeed(dummy); // only need OTSSeed + OTSseed = gmssRandom.nextSeed(dummy); + OTSseed = gmssRandom.nextSeed(dummy); + // nextWinSig[layer-1]=new + // GMSSWinSig(OTSseed,algNames,otsIndex[layer-1],heightOfTrees[layer],nextRoot[layer-1]); + nextRootSig[layer - 1].initSign(OTSseed, nextRoot[layer - 1]); + + // nextKey for upper layer + nextKey(layer - 1); + } + } + } + + /** + * This method computes the authpath (AUTH) for the current tree, + * Additionally the root signature for the next tree (SIG+), the authpath + * (AUTH++) and root (ROOT++) for the tree after next in layer + * <code>layer</code>, and the LEAF++^1 for the next next tree in the + * layer above are updated This method is used by nextKey() + * + * @param layer + */ + private void updateKey(int layer) + { + // ----------current tree processing of actual layer--------- + // compute upcoming authpath for current Tree (AUTH) + computeAuthPaths(layer); + + // -----------distributed calculations part------------ + // not for highest tree layer + if (layer > 0) + { + + // compute (partial) next leaf on TREE++ (not on layer 1 and 0) + if (layer > 1) + { + nextNextLeaf[layer - 1 - 1] = nextNextLeaf[layer - 1 - 1].nextLeaf(); + } + + // compute (partial) next leaf on tree above (not on layer 0) + upperLeaf[layer - 1] = upperLeaf[layer - 1].nextLeaf(); + + // compute (partial) next leaf for all treehashs on tree above (not + // on layer 0) + + int t = (int)Math + .floor((double)(this.getNumLeafs(layer) * 2) + / (double)(this.heightOfTrees[layer - 1] - this.K[layer - 1])); + + if (index[layer] % t == 1) + { + // System.out.println(" layer: " + layer + " index: " + + // index[layer] + " t : " + t); + + // take precomputed node for treehash update + // ------------------------------------------------ + if (index[layer] > 1 && minTreehash[layer - 1] >= 0) + { + byte[] leaf = this.upperTreehashLeaf[layer - 1].getLeaf(); + // if update is required use the precomputed leaf to update + // treehash + try + { + currentTreehash[layer - 1][minTreehash[layer - 1]] + .update(this.gmssRandom, leaf); + // System.out.println("Updated TH " + minTreehash[layer + // - 1]); + if (currentTreehash[layer - 1][minTreehash[layer - 1]] + .wasFinished()) + { + // System.out.println("Finished TH " + + // minTreehash[layer - 1]); + } + } + catch (Exception e) + { + System.out.println(e); + } + // ------------------------------------------------ + } + + // initialize next leaf precomputation + // ------------------------------------------------ + + // get lowest index of treehashs + this.minTreehash[layer - 1] = getMinTreehashIndex(layer - 1); + + if (this.minTreehash[layer - 1] >= 0) + { + // initialize leaf + byte[] seed = this.currentTreehash[layer - 1][this.minTreehash[layer - 1]] + .getSeedActive(); + this.upperTreehashLeaf[layer - 1] = new GMSSLeaf( + this.digestProvider.get(), this.otsIndex[layer - 1], t, seed); + this.upperTreehashLeaf[layer - 1] = this.upperTreehashLeaf[layer - 1].nextLeaf(); + // System.out.println("restarted treehashleaf (" + (layer - + // 1) + "," + this.minTreehash[layer - 1] + ")"); + } + // ------------------------------------------------ + + } + else + { + // update the upper leaf for the treehash one step + if (this.minTreehash[layer - 1] >= 0) + { + this.upperTreehashLeaf[layer - 1] = this.upperTreehashLeaf[layer - 1].nextLeaf(); + // if (minTreehash[layer - 1] > 3) + // System.out.print("#"); + } + } + + // compute (partial) the signature of ROOT+ (RootSig+) (not on top + // layer) + nextRootSig[layer - 1].updateSign(); + + // compute (partial) AUTHPATH++ & ROOT++ (not on top layer) + if (index[layer] == 1) + { + // init root and authpath calculation for tree after next + // (AUTH++, ROOT++) + this.nextNextRoot[layer - 1].initialize(new Vector()); + } + + // update root and authpath calculation for tree after next (AUTH++, + // ROOT++) + this.updateNextNextAuthRoot(layer); + } + // ----------- end distributed calculations part----------------- + } + + /** + * This method returns the index of the next Treehash instance that should + * receive an update + * + * @param layer the layer of the GMSS tree + * @return index of the treehash instance that should get the update + */ + private int getMinTreehashIndex(int layer) + { + int minTreehash = -1; + for (int h = 0; h < heightOfTrees[layer] - K[layer]; h++) + { + if (currentTreehash[layer][h].wasInitialized() + && !currentTreehash[layer][h].wasFinished()) + { + if (minTreehash == -1) + { + minTreehash = h; + } + else if (currentTreehash[layer][h].getLowestNodeHeight() < currentTreehash[layer][minTreehash] + .getLowestNodeHeight()) + { + minTreehash = h; + } + } + } + return minTreehash; + } + + /** + * Computes the upcoming currentAuthpath of layer <code>layer</code> using + * the revisited authentication path computation of Dahmen/Schneider 2008 + * + * @param layer the actual layer + */ + private void computeAuthPaths(int layer) + { + + int Phi = index[layer]; + int H = heightOfTrees[layer]; + int K = this.K[layer]; + + // update all nextSeeds for seed scheduling + for (int i = 0; i < H - K; i++) + { + currentTreehash[layer][i].updateNextSeed(gmssRandom); + } + + // STEP 1 of Algorithm + int Tau = heightOfPhi(Phi); + + byte[] OTSseed = new byte[mdLength]; + OTSseed = gmssRandom.nextSeed(currentSeeds[layer]); + + // STEP 2 of Algorithm + // if phi's parent on height tau + 1 if left node, store auth_tau + // in keep_tau. + // TODO check it, formerly was + // int L = Phi / (int) Math.floor(Math.pow(2, Tau + 1)); + // L %= 2; + int L = (Phi >>> (Tau + 1)) & 1; + + byte[] tempKeep = new byte[mdLength]; + // store the keep node not in keep[layer][tau/2] because it might be in + // use + // wait until the space is freed in step 4a + if (Tau < H - 1 && L == 0) + { + System.arraycopy(currentAuthPaths[layer][Tau], 0, tempKeep, 0, + mdLength); + } + + byte[] help = new byte[mdLength]; + // STEP 3 of Algorithm + // if phi is left child, compute and store leaf for next currentAuthPath + // path, + // (obtained by veriying current signature) + if (Tau == 0) + { + // LEAFCALC !!! + if (layer == numLayer - 1) + { // lowest layer computes the + // necessary leaf completely at this + // time + WinternitzOTSignature ots = new WinternitzOTSignature(OTSseed, + digestProvider.get(), otsIndex[layer]); + help = ots.getPublicKey(); + } + else + { // other layers use the precomputed leafs in + // nextNextLeaf + byte[] dummy = new byte[mdLength]; + System.arraycopy(currentSeeds[layer], 0, dummy, 0, mdLength); + gmssRandom.nextSeed(dummy); + help = upperLeaf[layer].getLeaf(); + this.upperLeaf[layer].initLeafCalc(dummy); + + // WinternitzOTSVerify otsver = new + // WinternitzOTSVerify(algNames, otsIndex[layer]); + // byte[] help2 = otsver.Verify(currentRoot[layer], + // currentRootSig[layer]); + // System.out.println(" --- " + layer + " " + + // ByteUtils.toHexString(help) + " " + + // ByteUtils.toHexString(help2)); + } + System.arraycopy(help, 0, currentAuthPaths[layer][0], 0, mdLength); + } + else + { + // STEP 4a of Algorithm + // get new left currentAuthPath node on height tau + byte[] toBeHashed = new byte[mdLength << 1]; + System.arraycopy(currentAuthPaths[layer][Tau - 1], 0, toBeHashed, + 0, mdLength); + // free the shared keep[layer][tau/2] + System.arraycopy(keep[layer][(int)Math.floor((Tau - 1) / 2)], 0, + toBeHashed, mdLength, mdLength); + messDigestTrees.update(toBeHashed, 0, toBeHashed.length); + currentAuthPaths[layer][Tau] = new byte[messDigestTrees.getDigestSize()]; + messDigestTrees.doFinal(currentAuthPaths[layer][Tau], 0); + + // STEP 4b and 4c of Algorithm + // copy right nodes to currentAuthPath on height 0..Tau-1 + for (int i = 0; i < Tau; i++) + { + + // STEP 4b of Algorithm + // 1st: copy from treehashs + if (i < H - K) + { + if (currentTreehash[layer][i].wasFinished()) + { + System.arraycopy(currentTreehash[layer][i] + .getFirstNode(), 0, currentAuthPaths[layer][i], + 0, mdLength); + currentTreehash[layer][i].destroy(); + } + else + { + System.err + .println("Treehash (" + + layer + + "," + + i + + ") not finished when needed in AuthPathComputation"); + } + } + + // 2nd: copy precomputed values from Retain + if (i < H - 1 && i >= H - K) + { + if (currentRetain[layer][i - (H - K)].size() > 0) + { + // pop element from retain + System.arraycopy(currentRetain[layer][i - (H - K)] + .lastElement(), 0, currentAuthPaths[layer][i], + 0, mdLength); + currentRetain[layer][i - (H - K)] + .removeElementAt(currentRetain[layer][i + - (H - K)].size() - 1); + } + } + + // STEP 4c of Algorithm + // initialize new stack at heights 0..Tau-1 + if (i < H - K) + { + // create stacks anew + int startPoint = Phi + 3 * (1 << i); + if (startPoint < numLeafs[layer]) + { + // if (layer < 2) { + // System.out.println("initialized TH " + i + " on layer + // " + layer); + // } + currentTreehash[layer][i].initialize(); + } + } + } + } + + // now keep space is free to use + if (Tau < H - 1 && L == 0) + { + System.arraycopy(tempKeep, 0, + keep[layer][(int)Math.floor(Tau / 2)], 0, mdLength); + } + + // only update empty stack at height h if all other stacks have + // tailnodes with height >h + // finds active stack with lowest node height, choses lower index in + // case of tie + + // on the lowest layer leafs must be computed at once, no precomputation + // is possible. So all treehash updates are done at once here + if (layer == numLayer - 1) + { + for (int tmp = 1; tmp <= (H - K) / 2; tmp++) + { + // index of the treehash instance that receives the next update + int minTreehash = getMinTreehashIndex(layer); + + // if active treehash is found update with a leaf + if (minTreehash >= 0) + { + try + { + byte[] seed = new byte[mdLength]; + System.arraycopy( + this.currentTreehash[layer][minTreehash] + .getSeedActive(), 0, seed, 0, mdLength); + byte[] seed2 = gmssRandom.nextSeed(seed); + WinternitzOTSignature ots = new WinternitzOTSignature( + seed2, this.digestProvider.get(), this.otsIndex[layer]); + byte[] leaf = ots.getPublicKey(); + currentTreehash[layer][minTreehash].update( + this.gmssRandom, leaf); + } + catch (Exception e) + { + System.out.println(e); + } + } + } + } + else + { // on higher layers the updates are done later + this.minTreehash[layer] = getMinTreehashIndex(layer); + } + } + + /** + * Returns the largest h such that 2^h | Phi + * + * @param Phi the leaf index + * @return The largest <code>h</code> with <code>2^h | Phi</code> if + * <code>Phi!=0</code> else return <code>-1</code> + */ + private int heightOfPhi(int Phi) + { + if (Phi == 0) + { + return -1; + } + int Tau = 0; + int modul = 1; + while (Phi % modul == 0) + { + modul *= 2; + Tau += 1; + } + return Tau - 1; + } + + /** + * Updates the authentication path and root calculation for the tree after + * next (AUTH++, ROOT++) in layer <code>layer</code> + * + * @param layer + */ + private void updateNextNextAuthRoot(int layer) + { + + byte[] OTSseed = new byte[mdLength]; + OTSseed = gmssRandom.nextSeed(nextNextSeeds[layer - 1]); + + // get the necessary leaf + if (layer == numLayer - 1) + { // lowest layer computes the necessary + // leaf completely at this time + WinternitzOTSignature ots = new WinternitzOTSignature(OTSseed, + digestProvider.get(), otsIndex[layer]); + this.nextNextRoot[layer - 1].update(nextNextSeeds[layer - 1], ots + .getPublicKey()); + } + else + { // other layers use the precomputed leafs in nextNextLeaf + this.nextNextRoot[layer - 1].update(nextNextSeeds[layer - 1], nextNextLeaf[layer - 1].getLeaf()); + this.nextNextLeaf[layer - 1].initLeafCalc(nextNextSeeds[layer - 1]); + } + } + + public int[] getIndex() + { + return index; + } + + /** + * @return The current index of layer i + */ + public int getIndex(int i) + { + return index[i]; + } + + public byte[][] getCurrentSeeds() + { + return Arrays.clone(currentSeeds); + } + + public byte[][][] getCurrentAuthPaths() + { + return Arrays.clone(currentAuthPaths); + } + + /** + * @return The one-time signature of the root of the current subtree + */ + public byte[] getSubtreeRootSig(int i) + { + return currentRootSig[i]; + } + + + public GMSSDigestProvider getName() + { + return digestProvider; + } + + /** + * @return The number of leafs of each tree of layer i + */ + public int getNumLeafs(int i) + { + return numLeafs[i]; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSPublicKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSPublicKeyParameters.java new file mode 100644 index 00000000..381ed00b --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSPublicKeyParameters.java @@ -0,0 +1,33 @@ +package org.spongycastle.pqc.crypto.gmss; + + +public class GMSSPublicKeyParameters + extends GMSSKeyParameters +{ + /** + * The GMSS public key + */ + private byte[] gmssPublicKey; + + /** + * The constructor. + * + * @param key a raw GMSS public key + * @param gmssParameterSet an instance of GMSSParameterset + */ + public GMSSPublicKeyParameters(byte[] key, GMSSParameters gmssParameterSet) + { + super(false, gmssParameterSet); + this.gmssPublicKey = key; + } + + /** + * Returns the GMSS public key + * + * @return The GMSS public key + */ + public byte[] getPublicKey() + { + return gmssPublicKey; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSRootCalc.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSRootCalc.java new file mode 100644 index 00000000..88e87e9c --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSRootCalc.java @@ -0,0 +1,596 @@ +package org.spongycastle.pqc.crypto.gmss; + +import java.util.Enumeration; +import java.util.Vector; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.Integers; +import org.spongycastle.util.encoders.Hex; + + +/** + * This class computes a whole Merkle tree and saves the needed values for + * AuthPath computation. It is used for precomputation of the root of a + * following tree. After initialization, 2^H updates are required to complete + * the root. Every update requires one leaf value as parameter. While computing + * the root all initial values for the authentication path algorithm (treehash, + * auth, retain) are stored for later use. + */ +public class GMSSRootCalc +{ + + /** + * max height of the tree + */ + private int heightOfTree; + + /** + * length of the messageDigest + */ + private int mdLength; + + /** + * the treehash instances of the tree + */ + private Treehash[] treehash; + + /** + * stores the retain nodes for authPath computation + */ + private Vector[] retain; + + /** + * finally stores the root of the tree when finished + */ + private byte[] root; + + /** + * stores the authentication path y_1(i), i = 0..H-1 + */ + private byte[][] AuthPath; + + /** + * the value K for the authentication path computation + */ + private int K; + + /** + * Vector element that stores the nodes on the stack + */ + private Vector tailStack; + + /** + * stores the height of all nodes laying on the tailStack + */ + private Vector heightOfNodes; + /** + * The hash function used for the construction of the authentication trees + */ + private Digest messDigestTree; + + /** + * An array of strings containing the name of the hash function used to + * construct the authentication trees and used by the OTS. + */ + private GMSSDigestProvider digestProvider; + + /** + * stores the index of the current node on each height of the tree + */ + private int[] index; + + /** + * true if instance was already initialized, false otherwise + */ + private boolean isInitialized; + + /** + * true it instance was finished + */ + private boolean isFinished; + + /** + * Integer that stores the index of the next seed that has to be omitted to + * the treehashs + */ + private int indexForNextSeed; + + /** + * temporary integer that stores the height of the next treehash instance + * that gets initialized with a seed + */ + private int heightOfNextSeed; + + /** + * This constructor regenerates a prior treehash object + * + * @param digest an array of strings, containing the digest of the used hash + * function and PRNG and the digest of the corresponding + * provider + * @param statByte status bytes + * @param statInt status ints + */ + public GMSSRootCalc(Digest digest, byte[][] statByte, int[] statInt, + Treehash[] treeH, Vector[] ret) + { + this.messDigestTree = digestProvider.get(); + this.digestProvider = digestProvider; + // decode statInt + this.heightOfTree = statInt[0]; + this.mdLength = statInt[1]; + this.K = statInt[2]; + this.indexForNextSeed = statInt[3]; + this.heightOfNextSeed = statInt[4]; + if (statInt[5] == 1) + { + this.isFinished = true; + } + else + { + this.isFinished = false; + } + if (statInt[6] == 1) + { + this.isInitialized = true; + } + else + { + this.isInitialized = false; + } + + int tailLength = statInt[7]; + + this.index = new int[heightOfTree]; + for (int i = 0; i < heightOfTree; i++) + { + this.index[i] = statInt[8 + i]; + } + + this.heightOfNodes = new Vector(); + for (int i = 0; i < tailLength; i++) + { + this.heightOfNodes.addElement(Integers.valueOf(statInt[8 + heightOfTree + + i])); + } + + // decode statByte + this.root = statByte[0]; + + this.AuthPath = new byte[heightOfTree][mdLength]; + for (int i = 0; i < heightOfTree; i++) + { + this.AuthPath[i] = statByte[1 + i]; + } + + this.tailStack = new Vector(); + for (int i = 0; i < tailLength; i++) + { + this.tailStack.addElement(statByte[1 + heightOfTree + i]); + } + + // decode treeH + this.treehash = GMSSUtils.clone(treeH); + + // decode ret + this.retain = GMSSUtils.clone(ret); + } + + /** + * Constructor + * + * @param heightOfTree maximal height of the tree + * @param digestProvider an array of strings, containing the name of the used hash + * function and PRNG and the name of the corresponding + * provider + */ + public GMSSRootCalc(int heightOfTree, int K, GMSSDigestProvider digestProvider) + { + this.heightOfTree = heightOfTree; + this.digestProvider = digestProvider; + this.messDigestTree = digestProvider.get(); + this.mdLength = messDigestTree.getDigestSize(); + this.K = K; + this.index = new int[heightOfTree]; + this.AuthPath = new byte[heightOfTree][mdLength]; + this.root = new byte[mdLength]; + // this.treehash = new Treehash[this.heightOfTree - this.K]; + this.retain = new Vector[this.K - 1]; + for (int i = 0; i < K - 1; i++) + { + this.retain[i] = new Vector(); + } + + } + + /** + * Initializes the calculation of a new root + * + * @param sharedStack the stack shared by all treehash instances of this tree + */ + public void initialize(Vector sharedStack) + { + this.treehash = new Treehash[this.heightOfTree - this.K]; + for (int i = 0; i < this.heightOfTree - this.K; i++) + { + this.treehash[i] = new Treehash(sharedStack, i, this.digestProvider.get()); + } + + this.index = new int[heightOfTree]; + this.AuthPath = new byte[heightOfTree][mdLength]; + this.root = new byte[mdLength]; + + this.tailStack = new Vector(); + this.heightOfNodes = new Vector(); + this.isInitialized = true; + this.isFinished = false; + + for (int i = 0; i < heightOfTree; i++) + { + this.index[i] = -1; + } + + this.retain = new Vector[this.K - 1]; + for (int i = 0; i < K - 1; i++) + { + this.retain[i] = new Vector(); + } + + this.indexForNextSeed = 3; + this.heightOfNextSeed = 0; + } + + /** + * updates the root with one leaf and stores needed values in retain, + * treehash or authpath. Additionally counts the seeds used. This method is + * used when performing the updates for TREE++. + * + * @param seed the initial seed for treehash: seedNext + * @param leaf the height of the treehash + */ + public void update(byte[] seed, byte[] leaf) + { + if (this.heightOfNextSeed < (this.heightOfTree - this.K) + && this.indexForNextSeed - 2 == index[0]) + { + this.initializeTreehashSeed(seed, this.heightOfNextSeed); + this.heightOfNextSeed++; + this.indexForNextSeed *= 2; + } + // now call the simple update + this.update(leaf); + } + + /** + * Updates the root with one leaf and stores the needed values in retain, + * treehash or authpath + */ + public void update(byte[] leaf) + { + + if (isFinished) + { + System.out.print("Too much updates for Tree!!"); + return; + } + if (!isInitialized) + { + System.err.println("GMSSRootCalc not initialized!"); + return; + } + + // a new leaf was omitted, so raise index on lowest layer + index[0]++; + + // store the nodes on the lowest layer in treehash or authpath + if (index[0] == 1) + { + System.arraycopy(leaf, 0, AuthPath[0], 0, mdLength); + } + else if (index[0] == 3) + { + // store in treehash only if K < H + if (heightOfTree > K) + { + treehash[0].setFirstNode(leaf); + } + } + + if ((index[0] - 3) % 2 == 0 && index[0] >= 3) + { + // store in retain if K = H + if (heightOfTree == K) + // TODO: check it + { + retain[0].insertElementAt(leaf, 0); + } + } + + // if first update to this tree is made + if (index[0] == 0) + { + tailStack.addElement(leaf); + heightOfNodes.addElement(Integers.valueOf(0)); + } + else + { + + byte[] help = new byte[mdLength]; + byte[] toBeHashed = new byte[mdLength << 1]; + + // store the new leaf in help + System.arraycopy(leaf, 0, help, 0, mdLength); + int helpHeight = 0; + // while top to nodes have same height + while (tailStack.size() > 0 + && helpHeight == ((Integer)heightOfNodes.lastElement()) + .intValue()) + { + + // help <-- hash(stack top element || help) + System.arraycopy(tailStack.lastElement(), 0, toBeHashed, 0, + mdLength); + tailStack.removeElementAt(tailStack.size() - 1); + heightOfNodes.removeElementAt(heightOfNodes.size() - 1); + System.arraycopy(help, 0, toBeHashed, mdLength, mdLength); + + messDigestTree.update(toBeHashed, 0, toBeHashed.length); + help = new byte[messDigestTree.getDigestSize()]; + messDigestTree.doFinal(help, 0); + + // the new help node is one step higher + helpHeight++; + if (helpHeight < heightOfTree) + { + index[helpHeight]++; + + // add index 1 element to initial authpath + if (index[helpHeight] == 1) + { + System.arraycopy(help, 0, AuthPath[helpHeight], 0, + mdLength); + } + + if (helpHeight >= heightOfTree - K) + { + if (helpHeight == 0) + { + System.out.println("M���P"); + } + // add help element to retain stack if it is a right + // node + // and not stored in treehash + if ((index[helpHeight] - 3) % 2 == 0 + && index[helpHeight] >= 3) + // TODO: check it + { + retain[helpHeight - (heightOfTree - K)] + .insertElementAt(help, 0); + } + } + else + { + // if element is third in his line add it to treehash + if (index[helpHeight] == 3) + { + treehash[helpHeight].setFirstNode(help); + } + } + } + } + // push help element to the stack + tailStack.addElement(help); + heightOfNodes.addElement(Integers.valueOf(helpHeight)); + + // is the root calculation finished? + if (helpHeight == heightOfTree) + { + isFinished = true; + isInitialized = false; + root = (byte[])tailStack.lastElement(); + } + } + + } + + /** + * initializes the seeds for the treehashs of the tree precomputed by this + * class + * + * @param seed the initial seed for treehash: seedNext + * @param index the height of the treehash + */ + public void initializeTreehashSeed(byte[] seed, int index) + { + treehash[index].initializeSeed(seed); + } + + /** + * Method to check whether the instance has been initialized or not + * + * @return true if treehash was already initialized + */ + public boolean wasInitialized() + { + return isInitialized; + } + + /** + * Method to check whether the instance has been finished or not + * + * @return true if tree has reached its maximum height + */ + public boolean wasFinished() + { + return isFinished; + } + + /** + * returns the authentication path of the first leaf of the tree + * + * @return the authentication path of the first leaf of the tree + */ + public byte[][] getAuthPath() + { + return GMSSUtils.clone(AuthPath); + } + + /** + * returns the initial treehash instances, storing value y_3(i) + * + * @return the initial treehash instances, storing value y_3(i) + */ + public Treehash[] getTreehash() + { + return GMSSUtils.clone(treehash); + } + + /** + * returns the retain stacks storing all right nodes near to the root + * + * @return the retain stacks storing all right nodes near to the root + */ + public Vector[] getRetain() + { + return GMSSUtils.clone(retain); + } + + /** + * returns the finished root value + * + * @return the finished root value + */ + public byte[] getRoot() + { + return Arrays.clone(root); + } + + /** + * returns the shared stack + * + * @return the shared stack + */ + public Vector getStack() + { + Vector copy = new Vector(); + for (Enumeration en = tailStack.elements(); en.hasMoreElements();) + { + copy.addElement(en.nextElement()); + } + return copy; + } + + /** + * Returns the status byte array used by the GMSSPrivateKeyASN.1 class + * + * @return The status bytes + */ + public byte[][] getStatByte() + { + + int tailLength; + if (tailStack == null) + { + tailLength = 0; + } + else + { + tailLength = tailStack.size(); + } + byte[][] statByte = new byte[1 + heightOfTree + tailLength][64]; //FIXME: messDigestTree.getByteLength() + statByte[0] = root; + + for (int i = 0; i < heightOfTree; i++) + { + statByte[1 + i] = AuthPath[i]; + } + for (int i = 0; i < tailLength; i++) + { + statByte[1 + heightOfTree + i] = (byte[])tailStack.elementAt(i); + } + + return statByte; + } + + /** + * Returns the status int array used by the GMSSPrivateKeyASN.1 class + * + * @return The status ints + */ + public int[] getStatInt() + { + + int tailLength; + if (tailStack == null) + { + tailLength = 0; + } + else + { + tailLength = tailStack.size(); + } + int[] statInt = new int[8 + heightOfTree + tailLength]; + statInt[0] = heightOfTree; + statInt[1] = mdLength; + statInt[2] = K; + statInt[3] = indexForNextSeed; + statInt[4] = heightOfNextSeed; + if (isFinished) + { + statInt[5] = 1; + } + else + { + statInt[5] = 0; + } + if (isInitialized) + { + statInt[6] = 1; + } + else + { + statInt[6] = 0; + } + statInt[7] = tailLength; + + for (int i = 0; i < heightOfTree; i++) + { + statInt[8 + i] = index[i]; + } + for (int i = 0; i < tailLength; i++) + { + statInt[8 + heightOfTree + i] = ((Integer)heightOfNodes + .elementAt(i)).intValue(); + } + + return statInt; + } + + /** + * @return a human readable version of the structure + */ + public String toString() + { + String out = ""; + int tailLength; + if (tailStack == null) + { + tailLength = 0; + } + else + { + tailLength = tailStack.size(); + } + + for (int i = 0; i < 8 + heightOfTree + tailLength; i++) + { + out = out + getStatInt()[i] + " "; + } + for (int i = 0; i < 1 + heightOfTree + tailLength; i++) + { + out = out + new String(Hex.encode(getStatByte()[i])) + " "; + } + out = out + " " + digestProvider.get().getDigestSize(); + return out; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSRootSig.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSRootSig.java new file mode 100644 index 00000000..f08529cf --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSRootSig.java @@ -0,0 +1,666 @@ +package org.spongycastle.pqc.crypto.gmss; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.pqc.crypto.gmss.util.GMSSRandom; +import org.spongycastle.util.encoders.Hex; + + +/** + * This class implements the distributed signature generation of the Winternitz + * one-time signature scheme (OTSS), described in C.Dods, N.P. Smart, and M. + * Stam, "Hash Based Digital Signature Schemes", LNCS 3796, pages 96–115, + * 2005. The class is used by the GMSS classes. + */ +public class GMSSRootSig +{ + + /** + * The hash function used by the OTS + */ + private Digest messDigestOTS; + + /** + * The length of the message digest and private key + */ + private int mdsize, keysize; + + /** + * The private key + */ + private byte[] privateKeyOTS; + + /** + * The message bytes + */ + private byte[] hash; + + /** + * The signature bytes + */ + private byte[] sign; + + /** + * The Winternitz parameter + */ + private int w; + + /** + * The source of randomness for OTS private key generation + */ + private GMSSRandom gmssRandom; + + /** + * Sizes of the message + */ + private int messagesize; + + /** + * Some precalculated values + */ + private int k; + + /** + * Some variables for storing the actual status of distributed signing + */ + private int r, test, counter, ii; + + /** + * variables for storing big numbers for the actual status of distributed + * signing + */ + private long test8, big8; + + /** + * The necessary steps of each updateSign() call + */ + private int steps; + + /** + * The checksum part + */ + private int checksum; + + /** + * The height of the tree + */ + private int height; + + /** + * The current intern OTSseed + */ + private byte[] seed; + + /** + * This constructor regenerates a prior GMSSRootSig object used by the + * GMSSPrivateKeyASN.1 class + * + * @param digest an array of strings, containing the digest of the used hash + * function, the digest of the PRGN and the names of the + * corresponding providers + * @param statByte status byte array + * @param statInt status int array + */ + public GMSSRootSig(Digest digest, byte[][] statByte, int[] statInt) + { + messDigestOTS = digest; + gmssRandom = new GMSSRandom(messDigestOTS); + + this.counter = statInt[0]; + this.test = statInt[1]; + this.ii = statInt[2]; + this.r = statInt[3]; + this.steps = statInt[4]; + this.keysize = statInt[5]; + this.height = statInt[6]; + this.w = statInt[7]; + this.checksum = statInt[8]; + + this.mdsize = messDigestOTS.getDigestSize(); + + this.k = (1 << w) - 1; + + int mdsizeBit = mdsize << 3; + this.messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w); + + this.privateKeyOTS = statByte[0]; + this.seed = statByte[1]; + this.hash = statByte[2]; + + this.sign = statByte[3]; + + this.test8 = ((statByte[4][0] & 0xff)) + | ((long)(statByte[4][1] & 0xff) << 8) + | ((long)(statByte[4][2] & 0xff) << 16) + | ((long)(statByte[4][3] & 0xff)) << 24 + | ((long)(statByte[4][4] & 0xff)) << 32 + | ((long)(statByte[4][5] & 0xff)) << 40 + | ((long)(statByte[4][6] & 0xff)) << 48 + | ((long)(statByte[4][7] & 0xff)) << 56; + + this.big8 = ((statByte[4][8] & 0xff)) + | ((long)(statByte[4][9] & 0xff) << 8) + | ((long)(statByte[4][10] & 0xff) << 16) + | ((long)(statByte[4][11] & 0xff)) << 24 + | ((long)(statByte[4][12] & 0xff)) << 32 + | ((long)(statByte[4][13] & 0xff)) << 40 + | ((long)(statByte[4][14] & 0xff)) << 48 + | ((long)(statByte[4][15] & 0xff)) << 56; + } + + /** + * The constructor generates the PRNG and initializes some variables + * + * @param digest an array of strings, containing the digest of the used hash + * function, the digest of the PRGN and the names of the + * corresponding providers + * @param w the winternitz parameter + * @param height the heigth of the tree + */ + public GMSSRootSig(Digest digest, int w, int height) + { + messDigestOTS = digest; + gmssRandom = new GMSSRandom(messDigestOTS); + + this.mdsize = messDigestOTS.getDigestSize(); + this.w = w; + this.height = height; + + this.k = (1 << w) - 1; + + int mdsizeBit = mdsize << 3; + this.messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w); + } + + /** + * This method initializes the distributed sigature calculation. Variables + * are reseted and necessary steps are calculated + * + * @param seed0 the initial OTSseed + * @param message the massage which will be signed + */ + public void initSign(byte[] seed0, byte[] message) + { + + // create hash of message m + this.hash = new byte[mdsize]; + messDigestOTS.update(message, 0, message.length); + this.hash = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(this.hash, 0); + + // variables for calculation of steps + byte[] messPart = new byte[mdsize]; + System.arraycopy(hash, 0, messPart, 0, mdsize); + int checkPart = 0; + int sumH = 0; + int checksumsize = getLog((messagesize << w) + 1); + + // ------- calculation of necessary steps ------ + if (8 % w == 0) + { + int dt = 8 / w; + // message part + for (int a = 0; a < mdsize; a++) + { + // count necessary hashs in 'sumH' + for (int b = 0; b < dt; b++) + { + sumH += messPart[a] & k; + messPart[a] = (byte)(messPart[a] >>> w); + } + } + // checksum part + this.checksum = (messagesize << w) - sumH; + checkPart = checksum; + // count necessary hashs in 'sumH' + for (int b = 0; b < checksumsize; b += w) + { + sumH += checkPart & k; + checkPart >>>= w; + } + } // end if ( 8 % w == 0 ) + else if (w < 8) + { + long big8; + int ii = 0; + int dt = mdsize / w; + + // first d*w bytes of hash (main message part) + for (int i = 0; i < dt; i++) + { + big8 = 0; + for (int j = 0; j < w; j++) + { + big8 ^= (messPart[ii] & 0xff) << (j << 3); + ii++; + } + // count necessary hashs in 'sumH' + for (int j = 0; j < 8; j++) + { + sumH += (int)(big8 & k); + big8 >>>= w; + } + } + // rest of message part + dt = mdsize % w; + big8 = 0; + for (int j = 0; j < dt; j++) + { + big8 ^= (messPart[ii] & 0xff) << (j << 3); + ii++; + } + dt <<= 3; + // count necessary hashs in 'sumH' + for (int j = 0; j < dt; j += w) + { + sumH += (int)(big8 & k); + big8 >>>= w; + } + // checksum part + this.checksum = (messagesize << w) - sumH; + checkPart = checksum; + // count necessary hashs in 'sumH' + for (int i = 0; i < checksumsize; i += w) + { + sumH += checkPart & k; + checkPart >>>= w; + } + }// end if(w<8) + else if (w < 57) + { + long big8; + int r = 0; + int s, f, rest, ii; + + // first a*w bits of hash where a*w <= 8*mdsize < (a+1)*w (main + // message part) + while (r <= ((mdsize << 3) - w)) + { + s = r >>> 3; + rest = r % 8; + r += w; + f = (r + 7) >>> 3; + big8 = 0; + ii = 0; + for (int j = s; j < f; j++) + { + big8 ^= (messPart[j] & 0xff) << (ii << 3); + ii++; + } + big8 >>>= rest; + // count necessary hashs in 'sumH' + sumH += (big8 & k); + + } + // rest of message part + s = r >>> 3; + if (s < mdsize) + { + rest = r % 8; + big8 = 0; + ii = 0; + for (int j = s; j < mdsize; j++) + { + big8 ^= (messPart[j] & 0xff) << (ii << 3); + ii++; + } + + big8 >>>= rest; + // count necessary hashs in 'sumH' + sumH += (big8 & k); + } + // checksum part + this.checksum = (messagesize << w) - sumH; + checkPart = checksum; + // count necessary hashs in 'sumH' + for (int i = 0; i < checksumsize; i += w) + { + sumH += (checkPart & k); + checkPart >>>= w; + } + }// end if(w<57) + + // calculate keysize + this.keysize = messagesize + + (int)Math.ceil((double)checksumsize / (double)w); + + // calculate steps: 'keysize' times PRNG, 'sumH' times hashing, + // (1<<height)-1 updateSign() calls + this.steps = (int)Math.ceil((double)(keysize + sumH) + / (double)((1 << height))); + // ---------------------------- + + // reset variables + this.sign = new byte[keysize * mdsize]; + this.counter = 0; + this.test = 0; + this.ii = 0; + this.test8 = 0; + this.r = 0; + // define the private key messagesize + this.privateKeyOTS = new byte[mdsize]; + // copy the seed + this.seed = new byte[mdsize]; + System.arraycopy(seed0, 0, this.seed, 0, mdsize); + + } + + /** + * This Method performs <code>steps</code> steps of distributed signature + * calculaion + * + * @return true if signature is generated completly, else false + */ + public boolean updateSign() + { + // steps times do + + for (int s = 0; s < steps; s++) + { // do 'step' times + + if (counter < keysize) + { // generate the private key or perform + // the next hash + oneStep(); + } + if (counter == keysize) + {// finish + return true; + } + } + + return false; // leaf not finished yet + } + + /** + * @return The private OTS key + */ + public byte[] getSig() + { + + return sign; + } + + /** + * @return The one-time signature of the message, generated step by step + */ + private void oneStep() + { + // -------- if (8 % w == 0) ---------- + if (8 % w == 0) + { + if (test == 0) + { + // get current OTSprivateKey + this.privateKeyOTS = gmssRandom.nextSeed(seed); + // System.arraycopy(privateKeyOTS, 0, hlp, 0, mdsize); + + if (ii < mdsize) + { // for main message part + test = hash[ii] & k; + hash[ii] = (byte)(hash[ii] >>> w); + } + else + { // for checksum part + test = checksum & k; + checksum >>>= w; + } + } + else if (test > 0) + { // hash the private Key 'test' times (on + // time each step) + messDigestOTS.update(privateKeyOTS, 0, privateKeyOTS.length); + privateKeyOTS = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(privateKeyOTS, 0); + test--; + } + if (test == 0) + { // if all hashes done copy result to siganture + // array + System.arraycopy(privateKeyOTS, 0, sign, counter * mdsize, + mdsize); + counter++; + + if (counter % (8 / w) == 0) + { // raise array index for main + // massage part + ii++; + } + } + + }// ----- end if (8 % w == 0) ----- + // ---------- if ( w < 8 ) ---------------- + else if (w < 8) + { + + if (test == 0) + { + if (counter % 8 == 0 && ii < mdsize) + { // after every 8th "add + // to signature"-step + big8 = 0; + if (counter < ((mdsize / w) << 3)) + {// main massage + // (generate w*8 Bits + // every time) part + for (int j = 0; j < w; j++) + { + big8 ^= (hash[ii] & 0xff) << (j << 3); + ii++; + } + } + else + { // rest of massage part (once) + for (int j = 0; j < mdsize % w; j++) + { + big8 ^= (hash[ii] & 0xff) << (j << 3); + ii++; + } + } + } + if (counter == messagesize) + { // checksum part (once) + big8 = checksum; + } + + test = (int)(big8 & k); + // generate current OTSprivateKey + this.privateKeyOTS = gmssRandom.nextSeed(seed); + // System.arraycopy(privateKeyOTS, 0, hlp, 0, mdsize); + + } + else if (test > 0) + { // hash the private Key 'test' times (on + // time each step) + messDigestOTS.update(privateKeyOTS, 0, privateKeyOTS.length); + privateKeyOTS = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(privateKeyOTS, 0); + test--; + } + if (test == 0) + { // if all hashes done copy result to siganture + // array + System.arraycopy(privateKeyOTS, 0, sign, counter * mdsize, + mdsize); + big8 >>>= w; + counter++; + } + + }// ------- end if(w<8)-------------------------------- + // --------- if w < 57 ----------------------------- + else if (w < 57) + { + + if (test8 == 0) + { + int s, f, rest; + big8 = 0; + ii = 0; + rest = r % 8; + s = r >>> 3; + // --- message part--- + if (s < mdsize) + { + if (r <= ((mdsize << 3) - w)) + { // first message part + r += w; + f = (r + 7) >>> 3; + } + else + { // rest of message part (once) + f = mdsize; + r += w; + } + // generate long 'big8' with minimum w next bits of the + // message array + for (int i = s; i < f; i++) + { + big8 ^= (hash[i] & 0xff) << (ii << 3); + ii++; + } + // delete bits on the right side, which were used already by + // the last loop + big8 >>>= rest; + test8 = (big8 & k); + } + // --- checksum part + else + { + test8 = (checksum & k); + checksum >>>= w; + } + // generate current OTSprivateKey + this.privateKeyOTS = gmssRandom.nextSeed(seed); + // System.arraycopy(privateKeyOTS, 0, hlp, 0, mdsize); + + } + else if (test8 > 0) + { // hash the private Key 'test' times (on + // time each step) + messDigestOTS.update(privateKeyOTS, 0, privateKeyOTS.length); + privateKeyOTS = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(privateKeyOTS, 0); + test8--; + } + if (test8 == 0) + { // if all hashes done copy result to siganture + // array + System.arraycopy(privateKeyOTS, 0, sign, counter * mdsize, + mdsize); + counter++; + } + + } + } + + /** + * This method returns the least integer that is greater or equal to the + * logarithm to the base 2 of an integer <code>intValue</code>. + * + * @param intValue an integer + * @return The least integer greater or equal to the logarithm to the base 2 + * of <code>intValue</code> + */ + public int getLog(int intValue) + { + int log = 1; + int i = 2; + while (i < intValue) + { + i <<= 1; + log++; + } + return log; + } + + /** + * This method returns the status byte array + * + * @return statBytes + */ + public byte[][] getStatByte() + { + + byte[][] statByte = new byte[5][mdsize]; + statByte[0] = privateKeyOTS; + statByte[1] = seed; + statByte[2] = hash; + statByte[3] = sign; + statByte[4] = this.getStatLong(); + + return statByte; + } + + /** + * This method returns the status int array + * + * @return statInt + */ + public int[] getStatInt() + { + int[] statInt = new int[9]; + statInt[0] = counter; + statInt[1] = test; + statInt[2] = ii; + statInt[3] = r; + statInt[4] = steps; + statInt[5] = keysize; + statInt[6] = height; + statInt[7] = w; + statInt[8] = checksum; + return statInt; + } + + /** + * Converts the long parameters into byte arrays to store it in + * statByte-Array + */ + public byte[] getStatLong() + { + byte[] bytes = new byte[16]; + + bytes[0] = (byte)((test8) & 0xff); + bytes[1] = (byte)((test8 >> 8) & 0xff); + bytes[2] = (byte)((test8 >> 16) & 0xff); + bytes[3] = (byte)((test8 >> 24) & 0xff); + bytes[4] = (byte)((test8) >> 32 & 0xff); + bytes[5] = (byte)((test8 >> 40) & 0xff); + bytes[6] = (byte)((test8 >> 48) & 0xff); + bytes[7] = (byte)((test8 >> 56) & 0xff); + + bytes[8] = (byte)((big8) & 0xff); + bytes[9] = (byte)((big8 >> 8) & 0xff); + bytes[10] = (byte)((big8 >> 16) & 0xff); + bytes[11] = (byte)((big8 >> 24) & 0xff); + bytes[12] = (byte)((big8) >> 32 & 0xff); + bytes[13] = (byte)((big8 >> 40) & 0xff); + bytes[14] = (byte)((big8 >> 48) & 0xff); + bytes[15] = (byte)((big8 >> 56) & 0xff); + + return bytes; + } + + /** + * returns a string representation of the instance + * + * @return a string representation of the instance + */ + public String toString() + { + String out = "" + this.big8 + " "; + int[] statInt = new int[9]; + statInt = this.getStatInt(); + byte[][] statByte = new byte[5][mdsize]; + statByte = this.getStatByte(); + for (int i = 0; i < 9; i++) + { + out = out + statInt[i] + " "; + } + for (int i = 0; i < 5; i++) + { + out = out + new String(Hex.encode(statByte[i])) + " "; + } + + return out; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSSigner.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSSigner.java new file mode 100644 index 00000000..2a78d385 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSSigner.java @@ -0,0 +1,403 @@ +package org.spongycastle.pqc.crypto.gmss; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.pqc.crypto.MessageSigner; +import org.spongycastle.pqc.crypto.gmss.util.GMSSRandom; +import org.spongycastle.pqc.crypto.gmss.util.GMSSUtil; +import org.spongycastle.pqc.crypto.gmss.util.WinternitzOTSVerify; +import org.spongycastle.pqc.crypto.gmss.util.WinternitzOTSignature; +import org.spongycastle.util.Arrays; + +/** + * This class implements the GMSS signature scheme. + */ +public class GMSSSigner + implements MessageSigner +{ + + /** + * Instance of GMSSParameterSpec + */ + //private GMSSParameterSpec gmssParameterSpec; + + /** + * Instance of GMSSUtilities + */ + private GMSSUtil gmssUtil = new GMSSUtil(); + + + /** + * The raw GMSS public key + */ + private byte[] pubKeyBytes; + + /** + * Hash function for the construction of the authentication trees + */ + private Digest messDigestTrees; + + /** + * The length of the hash function output + */ + private int mdLength; + + /** + * The number of tree layers + */ + private int numLayer; + + /** + * The hash function used by the OTS + */ + private Digest messDigestOTS; + + /** + * An instance of the Winternitz one-time signature + */ + private WinternitzOTSignature ots; + + /** + * Array of strings containing the name of the hash function used by the OTS + * and the corresponding provider name + */ + private GMSSDigestProvider digestProvider; + + /** + * The current main tree and subtree indices + */ + private int[] index; + + /** + * Array of the authentication paths for the current trees of all layers + */ + private byte[][][] currentAuthPaths; + + /** + * The one-time signature of the roots of the current subtrees + */ + private byte[][] subtreeRootSig; + + + /** + * The GMSSParameterset + */ + private GMSSParameters gmssPS; + + /** + * The PRNG + */ + private GMSSRandom gmssRandom; + + GMSSKeyParameters key; + + // XXX needed? Source of randomness + private SecureRandom random; + + + /** + * The standard constructor tries to generate the MerkleTree Algorithm + * identifier with the corresponding OID. + * + * @param digest the digest to use + */ + // TODO + public GMSSSigner(GMSSDigestProvider digest) + { + digestProvider = digest; + messDigestTrees = digest.get(); + messDigestOTS = messDigestTrees; + mdLength = messDigestTrees.getDigestSize(); + gmssRandom = new GMSSRandom(messDigestTrees); + } + + public void init(boolean forSigning, + CipherParameters param) + { + + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + // XXX random needed? + this.random = rParam.getRandom(); + this.key = (GMSSPrivateKeyParameters)rParam.getParameters(); + initSign(); + + } + else + { + + this.random = new SecureRandom(); + this.key = (GMSSPrivateKeyParameters)param; + initSign(); + } + } + else + { + this.key = (GMSSPublicKeyParameters)param; + initVerify(); + + } + + } + + + /** + * Initializes the signature algorithm for signing a message. + */ + private void initSign() + { + messDigestTrees.reset(); + // set private key and take from it ots key, auth, tree and key + // counter, rootSign + GMSSPrivateKeyParameters gmssPrivateKey = (GMSSPrivateKeyParameters)key; + + if (gmssPrivateKey.isUsed()) + { + throw new IllegalStateException("Private key already used"); + } + + // check if last signature has been generated + if (gmssPrivateKey.getIndex(0) >= gmssPrivateKey.getNumLeafs(0)) + { + throw new IllegalStateException("No more signatures can be generated"); + } + + // get Parameterset + this.gmssPS = gmssPrivateKey.getParameters(); + // get numLayer + this.numLayer = gmssPS.getNumOfLayers(); + + // get OTS Instance of lowest layer + byte[] seed = gmssPrivateKey.getCurrentSeeds()[numLayer - 1]; + byte[] OTSSeed = new byte[mdLength]; + byte[] dummy = new byte[mdLength]; + System.arraycopy(seed, 0, dummy, 0, mdLength); + OTSSeed = gmssRandom.nextSeed(dummy); // secureRandom.nextBytes(currentSeeds[currentSeeds.length-1]);secureRandom.nextBytes(OTSseed); + this.ots = new WinternitzOTSignature(OTSSeed, digestProvider.get(), gmssPS.getWinternitzParameter()[numLayer - 1]); + + byte[][][] helpCurrentAuthPaths = gmssPrivateKey.getCurrentAuthPaths(); + currentAuthPaths = new byte[numLayer][][]; + + // copy the main tree authentication path + for (int j = 0; j < numLayer; j++) + { + currentAuthPaths[j] = new byte[helpCurrentAuthPaths[j].length][mdLength]; + for (int i = 0; i < helpCurrentAuthPaths[j].length; i++) + { + System.arraycopy(helpCurrentAuthPaths[j][i], 0, currentAuthPaths[j][i], 0, mdLength); + } + } + + // copy index + index = new int[numLayer]; + System.arraycopy(gmssPrivateKey.getIndex(), 0, index, 0, numLayer); + + // copy subtreeRootSig + byte[] helpSubtreeRootSig; + subtreeRootSig = new byte[numLayer - 1][]; + for (int i = 0; i < numLayer - 1; i++) + { + helpSubtreeRootSig = gmssPrivateKey.getSubtreeRootSig(i); + subtreeRootSig[i] = new byte[helpSubtreeRootSig.length]; + System.arraycopy(helpSubtreeRootSig, 0, subtreeRootSig[i], 0, helpSubtreeRootSig.length); + } + + gmssPrivateKey.markUsed(); + } + + /** + * Signs a message. + * + * @return the signature. + */ + public byte[] generateSignature(byte[] message) + { + + byte[] otsSig = new byte[mdLength]; + byte[] authPathBytes; + byte[] indexBytes; + + otsSig = ots.getSignature(message); + + // get concatenated lowest layer tree authentication path + authPathBytes = gmssUtil.concatenateArray(currentAuthPaths[numLayer - 1]); + + // put lowest layer index into a byte array + indexBytes = gmssUtil.intToBytesLittleEndian(index[numLayer - 1]); + + // create first part of GMSS signature + byte[] gmssSigFirstPart = new byte[indexBytes.length + otsSig.length + authPathBytes.length]; + System.arraycopy(indexBytes, 0, gmssSigFirstPart, 0, indexBytes.length); + System.arraycopy(otsSig, 0, gmssSigFirstPart, indexBytes.length, otsSig.length); + System.arraycopy(authPathBytes, 0, gmssSigFirstPart, (indexBytes.length + otsSig.length), authPathBytes.length); + // --- end first part + + // --- next parts of the signature + // create initial array with length 0 for iteration + byte[] gmssSigNextPart = new byte[0]; + + for (int i = numLayer - 1 - 1; i >= 0; i--) + { + + // get concatenated next tree authentication path + authPathBytes = gmssUtil.concatenateArray(currentAuthPaths[i]); + + // put next tree index into a byte array + indexBytes = gmssUtil.intToBytesLittleEndian(index[i]); + + // create next part of GMSS signature + + // create help array and copy actual gmssSig into it + byte[] helpGmssSig = new byte[gmssSigNextPart.length]; + System.arraycopy(gmssSigNextPart, 0, helpGmssSig, 0, gmssSigNextPart.length); + // adjust length of gmssSigNextPart for adding next part + gmssSigNextPart = new byte[helpGmssSig.length + indexBytes.length + subtreeRootSig[i].length + authPathBytes.length]; + + // copy old data (help array) and new data in gmssSigNextPart + System.arraycopy(helpGmssSig, 0, gmssSigNextPart, 0, helpGmssSig.length); + System.arraycopy(indexBytes, 0, gmssSigNextPart, helpGmssSig.length, indexBytes.length); + System.arraycopy(subtreeRootSig[i], 0, gmssSigNextPart, (helpGmssSig.length + indexBytes.length), subtreeRootSig[i].length); + System.arraycopy(authPathBytes, 0, gmssSigNextPart, (helpGmssSig.length + indexBytes.length + subtreeRootSig[i].length), authPathBytes.length); + + } + // --- end next parts + + // concatenate the two parts of the GMSS signature + byte[] gmssSig = new byte[gmssSigFirstPart.length + gmssSigNextPart.length]; + System.arraycopy(gmssSigFirstPart, 0, gmssSig, 0, gmssSigFirstPart.length); + System.arraycopy(gmssSigNextPart, 0, gmssSig, gmssSigFirstPart.length, gmssSigNextPart.length); + + // return the GMSS signature + return gmssSig; + } + + /** + * Initializes the signature algorithm for verifying a signature. + */ + private void initVerify() + { + messDigestTrees.reset(); + + GMSSPublicKeyParameters gmssPublicKey = (GMSSPublicKeyParameters)key; + pubKeyBytes = gmssPublicKey.getPublicKey(); + gmssPS = gmssPublicKey.getParameters(); + // get numLayer + this.numLayer = gmssPS.getNumOfLayers(); + + + } + + /** + * This function verifies the signature of the message that has been + * updated, with the aid of the public key. + * + * @param message the message + * @param signature the signature associated with the message + * @return true if the signature has been verified, false otherwise. + */ + public boolean verifySignature(byte[] message, byte[] signature) + { + + boolean success = false; + // int halfSigLength = signature.length >>> 1; + messDigestOTS.reset(); + WinternitzOTSVerify otsVerify; + int otsSigLength; + + byte[] help = message; + + byte[] otsSig; + byte[] otsPublicKey; + byte[][] authPath; + byte[] dest; + int nextEntry = 0; + int index; + // Verify signature + + // --- begin with message = 'message that was signed' + // and then in each step message = subtree root + for (int j = numLayer - 1; j >= 0; j--) + { + otsVerify = new WinternitzOTSVerify(digestProvider.get(), gmssPS.getWinternitzParameter()[j]); + otsSigLength = otsVerify.getSignatureLength(); + + message = help; + // get the subtree index + index = gmssUtil.bytesToIntLittleEndian(signature, nextEntry); + + // 4 is the number of bytes in integer + nextEntry += 4; + + // get one-time signature + otsSig = new byte[otsSigLength]; + System.arraycopy(signature, nextEntry, otsSig, 0, otsSigLength); + nextEntry += otsSigLength; + + // compute public OTS key from the one-time signature + otsPublicKey = otsVerify.Verify(message, otsSig); + + // test if OTSsignature is correct + if (otsPublicKey == null) + { + System.err.println("OTS Public Key is null in GMSSSignature.verify"); + return false; + } + + // get authentication path from the signature + authPath = new byte[gmssPS.getHeightOfTrees()[j]][mdLength]; + for (int i = 0; i < authPath.length; i++) + { + System.arraycopy(signature, nextEntry, authPath[i], 0, mdLength); + nextEntry = nextEntry + mdLength; + } + + // compute the root of the subtree from the authentication path + help = new byte[mdLength]; + + help = otsPublicKey; + + int count = 1 << authPath.length; + count = count + index; + + for (int i = 0; i < authPath.length; i++) + { + dest = new byte[mdLength << 1]; + + if ((count % 2) == 0) + { + System.arraycopy(help, 0, dest, 0, mdLength); + System.arraycopy(authPath[i], 0, dest, mdLength, mdLength); + count = count / 2; + } + else + { + System.arraycopy(authPath[i], 0, dest, 0, mdLength); + System.arraycopy(help, 0, dest, mdLength, help.length); + count = (count - 1) / 2; + } + messDigestTrees.update(dest, 0, dest.length); + help = new byte[messDigestTrees.getDigestSize()]; + messDigestTrees.doFinal(help, 0); + } + } + + // now help contains the root of the maintree + + // test if help is equal to the GMSS public key + if (Arrays.areEqual(pubKeyBytes, help)) + { + success = true; + } + + return success; + } + + +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSUtils.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSUtils.java new file mode 100644 index 00000000..1f79bdde --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/GMSSUtils.java @@ -0,0 +1,145 @@ +package org.spongycastle.pqc.crypto.gmss; + +import java.util.Enumeration; +import java.util.Vector; + +import org.spongycastle.util.Arrays; + +class GMSSUtils +{ + static GMSSLeaf[] clone(GMSSLeaf[] data) + { + if (data == null) + { + return null; + } + GMSSLeaf[] copy = new GMSSLeaf[data.length]; + + System.arraycopy(data, 0, copy, 0, data.length); + + return copy; + } + + static GMSSRootCalc[] clone(GMSSRootCalc[] data) + { + if (data == null) + { + return null; + } + GMSSRootCalc[] copy = new GMSSRootCalc[data.length]; + + System.arraycopy(data, 0, copy, 0, data.length); + + return copy; + } + + static GMSSRootSig[] clone(GMSSRootSig[] data) + { + if (data == null) + { + return null; + } + GMSSRootSig[] copy = new GMSSRootSig[data.length]; + + System.arraycopy(data, 0, copy, 0, data.length); + + return copy; + } + + static byte[][] clone(byte[][] data) + { + if (data == null) + { + return null; + } + byte[][] copy = new byte[data.length][]; + + for (int i = 0; i != data.length; i++) + { + copy[i] = Arrays.clone(data[i]); + } + + return copy; + } + + static byte[][][] clone(byte[][][] data) + { + if (data == null) + { + return null; + } + byte[][][] copy = new byte[data.length][][]; + + for (int i = 0; i != data.length; i++) + { + copy[i] = clone(data[i]); + } + + return copy; + } + + static Treehash[] clone(Treehash[] data) + { + if (data == null) + { + return null; + } + Treehash[] copy = new Treehash[data.length]; + + System.arraycopy(data, 0, copy, 0, data.length); + + return copy; + } + + static Treehash[][] clone(Treehash[][] data) + { + if (data == null) + { + return null; + } + Treehash[][] copy = new Treehash[data.length][]; + + for (int i = 0; i != data.length; i++) + { + copy[i] = clone(data[i]); + } + + return copy; + } + + static Vector[] clone(Vector[] data) + { + if (data == null) + { + return null; + } + Vector[] copy = new Vector[data.length]; + + for (int i = 0; i != data.length; i++) + { + copy[i] = new Vector(); + for (Enumeration en = data[i].elements(); en.hasMoreElements();) + { + copy[i].addElement(en.nextElement()); + } + } + + return copy; + } + + static Vector[][] clone(Vector[][] data) + { + if (data == null) + { + return null; + } + Vector[][] copy = new Vector[data.length][]; + + for (int i = 0; i != data.length; i++) + { + copy[i] = clone(data[i]); + } + + return copy; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/Treehash.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/Treehash.java new file mode 100644 index 00000000..82784e34 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/Treehash.java @@ -0,0 +1,525 @@ +package org.spongycastle.pqc.crypto.gmss; + +import java.util.Vector; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.pqc.crypto.gmss.util.GMSSRandom; +import org.spongycastle.util.Integers; +import org.spongycastle.util.encoders.Hex; + + +/** + * This class implements a treehash instance for the Merkle tree traversal + * algorithm. The first node of the stack is stored in this instance itself, + * additional tail nodes are stored on a tailstack. + */ +public class Treehash +{ + + /** + * max height of current treehash instance. + */ + private int maxHeight; + + /** + * Vector element that stores the nodes on the stack + */ + private Vector tailStack; + + /** + * Vector element that stores the height of the nodes on the stack + */ + private Vector heightOfNodes; + + /** + * the first node is stored in the treehash instance itself, not on stack + */ + private byte[] firstNode; + + /** + * seedActive needed for the actual node + */ + private byte[] seedActive; + + /** + * the seed needed for the next re-initialization of the treehash instance + */ + private byte[] seedNext; + + /** + * number of nodes stored on the stack and belonging to this treehash + * instance + */ + private int tailLength; + + /** + * the height in the tree of the first node stored in treehash + */ + private int firstNodeHeight; + + /** + * true if treehash instance was already initialized, false otherwise + */ + private boolean isInitialized; + + /** + * true if the first node's height equals the maxHeight of the treehash + */ + private boolean isFinished; + + /** + * true if the nextSeed has been initialized with index 3*2^h needed for the + * seed scheduling + */ + private boolean seedInitialized; + + /** + * denotes the Message Digest used by the tree to create nodes + */ + private Digest messDigestTree; + + /** + * This constructor regenerates a prior treehash object + * + * @param name an array of strings, containing the name of the used hash + * function and PRNG and the name of the corresponding provider + * @param statByte status bytes + * @param statInt status ints + */ + public Treehash(Digest name, byte[][] statByte, int[] statInt) + { + this.messDigestTree = name; + + // decode statInt + this.maxHeight = statInt[0]; + this.tailLength = statInt[1]; + this.firstNodeHeight = statInt[2]; + + if (statInt[3] == 1) + { + this.isFinished = true; + } + else + { + this.isFinished = false; + } + if (statInt[4] == 1) + { + this.isInitialized = true; + } + else + { + this.isInitialized = false; + } + if (statInt[5] == 1) + { + this.seedInitialized = true; + } + else + { + this.seedInitialized = false; + } + + this.heightOfNodes = new Vector(); + for (int i = 0; i < tailLength; i++) + { + this.heightOfNodes.addElement(Integers.valueOf(statInt[6 + i])); + } + + // decode statByte + this.firstNode = statByte[0]; + this.seedActive = statByte[1]; + this.seedNext = statByte[2]; + + this.tailStack = new Vector(); + for (int i = 0; i < tailLength; i++) + { + this.tailStack.addElement(statByte[3 + i]); + } + } + + /** + * Constructor + * + * @param tailStack a vector element where the stack nodes are stored + * @param maxHeight maximal height of the treehash instance + * @param digest an array of strings, containing the name of the used hash + * function and PRNG and the name of the corresponding provider + */ + public Treehash(Vector tailStack, int maxHeight, Digest digest) + { + this.tailStack = tailStack; + this.maxHeight = maxHeight; + this.firstNode = null; + this.isInitialized = false; + this.isFinished = false; + this.seedInitialized = false; + this.messDigestTree = digest; + + this.seedNext = new byte[messDigestTree.getDigestSize()]; + this.seedActive = new byte[messDigestTree.getDigestSize()]; + } + + /** + * Method to initialize the seeds needed for the precomputation of right + * nodes. Should be initialized with index 3*2^i for treehash_i + * + * @param seedIn + */ + public void initializeSeed(byte[] seedIn) + { + System.arraycopy(seedIn, 0, this.seedNext, 0, this.messDigestTree + .getDigestSize()); + this.seedInitialized = true; + } + + /** + * initializes the treehash instance. The seeds must already have been + * initialized to work correctly. + */ + public void initialize() + { + if (!this.seedInitialized) + { + System.err.println("Seed " + this.maxHeight + " not initialized"); + return; + } + + this.heightOfNodes = new Vector(); + this.tailLength = 0; + this.firstNode = null; + this.firstNodeHeight = -1; + this.isInitialized = true; + System.arraycopy(this.seedNext, 0, this.seedActive, 0, messDigestTree + .getDigestSize()); + } + + /** + * Calculates one update of the treehash instance, i.e. creates a new leaf + * and hashes if possible + * + * @param gmssRandom an instance of the PRNG + * @param leaf The byte value of the leaf needed for the update + */ + public void update(GMSSRandom gmssRandom, byte[] leaf) + { + + if (this.isFinished) + { + System.err + .println("No more update possible for treehash instance!"); + return; + } + if (!this.isInitialized) + { + System.err + .println("Treehash instance not initialized before update"); + return; + } + + byte[] help = new byte[this.messDigestTree.getDigestSize()]; + int helpHeight = -1; + + gmssRandom.nextSeed(this.seedActive); + + // if treehash gets first update + if (this.firstNode == null) + { + this.firstNode = leaf; + this.firstNodeHeight = 0; + } + else + { + // store the new node in help array, do not push it on the stack + help = leaf; + helpHeight = 0; + + // hash the nodes on the stack if possible + while (this.tailLength > 0 + && helpHeight == ((Integer)heightOfNodes.lastElement()) + .intValue()) + { + // put top element of the stack and help node in array + // 'tobehashed' + // and hash them together, put result again in help array + byte[] toBeHashed = new byte[this.messDigestTree + .getDigestSize() << 1]; + + // pop element from stack + System.arraycopy(this.tailStack.lastElement(), 0, toBeHashed, + 0, this.messDigestTree.getDigestSize()); + this.tailStack.removeElementAt(this.tailStack.size() - 1); + this.heightOfNodes + .removeElementAt(this.heightOfNodes.size() - 1); + + System.arraycopy(help, 0, toBeHashed, this.messDigestTree + .getDigestSize(), this.messDigestTree + .getDigestSize()); + messDigestTree.update(toBeHashed, 0, toBeHashed.length); + help = new byte[messDigestTree.getDigestSize()]; + messDigestTree.doFinal(help, 0); + + // increase help height, stack was reduced by one element + helpHeight++; + this.tailLength--; + } + + // push the new node on the stack + this.tailStack.addElement(help); + this.heightOfNodes.addElement(Integers.valueOf(helpHeight)); + this.tailLength++; + + // finally check whether the top node on stack and the first node + // in treehash have same height. If so hash them together + // and store them in treehash + if (((Integer)heightOfNodes.lastElement()).intValue() == this.firstNodeHeight) + { + byte[] toBeHashed = new byte[this.messDigestTree + .getDigestSize() << 1]; + System.arraycopy(this.firstNode, 0, toBeHashed, 0, + this.messDigestTree.getDigestSize()); + + // pop element from tailStack and copy it into help2 array + System.arraycopy(this.tailStack.lastElement(), 0, toBeHashed, + this.messDigestTree.getDigestSize(), + this.messDigestTree.getDigestSize()); + this.tailStack.removeElementAt(this.tailStack.size() - 1); + this.heightOfNodes + .removeElementAt(this.heightOfNodes.size() - 1); + + // store new element in firstNode, stack is then empty + messDigestTree.update(toBeHashed, 0, toBeHashed.length); + this.firstNode = new byte[messDigestTree.getDigestSize()]; + messDigestTree.doFinal(this.firstNode, 0); + this.firstNodeHeight++; + + // empty the stack + this.tailLength = 0; + } + } + + // check if treehash instance is completed + if (this.firstNodeHeight == this.maxHeight) + { + this.isFinished = true; + } + } + + /** + * Destroys a treehash instance after the top node was taken for + * authentication path. + */ + public void destroy() + { + this.isInitialized = false; + this.isFinished = false; + this.firstNode = null; + this.tailLength = 0; + this.firstNodeHeight = -1; + } + + /** + * Returns the height of the lowest node stored either in treehash or on the + * stack. It must not be set to infinity (as mentioned in the paper) because + * this cases are considered in the computeAuthPaths method of + * JDKGMSSPrivateKey + * + * @return Height of the lowest node + */ + public int getLowestNodeHeight() + { + if (this.firstNode == null) + { + return this.maxHeight; + } + else if (this.tailLength == 0) + { + return this.firstNodeHeight; + } + else + { + return Math.min(this.firstNodeHeight, ((Integer)heightOfNodes + .lastElement()).intValue()); + } + } + + /** + * Returns the top node height + * + * @return Height of the first node, the top node + */ + public int getFirstNodeHeight() + { + if (firstNode == null) + { + return maxHeight; + } + return firstNodeHeight; + } + + /** + * Method to check whether the instance has been initialized or not + * + * @return true if treehash was already initialized + */ + public boolean wasInitialized() + { + return this.isInitialized; + } + + /** + * Method to check whether the instance has been finished or not + * + * @return true if treehash has reached its maximum height + */ + public boolean wasFinished() + { + return this.isFinished; + } + + /** + * returns the first node stored in treehash instance itself + * + * @return the first node stored in treehash instance itself + */ + public byte[] getFirstNode() + { + return this.firstNode; + } + + /** + * returns the active seed + * + * @return the active seed + */ + public byte[] getSeedActive() + { + return this.seedActive; + } + + /** + * This method sets the first node stored in the treehash instance itself + * + * @param hash + */ + public void setFirstNode(byte[] hash) + { + if (!this.isInitialized) + { + this.initialize(); + } + this.firstNode = hash; + this.firstNodeHeight = this.maxHeight; + this.isFinished = true; + } + + /** + * updates the nextSeed of this treehash instance one step needed for the + * schedulng of the seeds + * + * @param gmssRandom the prng used for the seeds + */ + public void updateNextSeed(GMSSRandom gmssRandom) + { + gmssRandom.nextSeed(seedNext); + } + + /** + * Returns the tailstack + * + * @return the tailstack + */ + public Vector getTailStack() + { + return this.tailStack; + } + + /** + * Returns the status byte array used by the GMSSPrivateKeyASN.1 class + * + * @return The status bytes + */ + public byte[][] getStatByte() + { + + byte[][] statByte = new byte[3 + tailLength][this.messDigestTree + .getDigestSize()]; + statByte[0] = firstNode; + statByte[1] = seedActive; + statByte[2] = seedNext; + for (int i = 0; i < tailLength; i++) + { + statByte[3 + i] = (byte[])tailStack.elementAt(i); + } + return statByte; + } + + /** + * Returns the status int array used by the GMSSPrivateKeyASN.1 class + * + * @return The status ints + */ + public int[] getStatInt() + { + + int[] statInt = new int[6 + tailLength]; + statInt[0] = maxHeight; + statInt[1] = tailLength; + statInt[2] = firstNodeHeight; + if (this.isFinished) + { + statInt[3] = 1; + } + else + { + statInt[3] = 0; + } + if (this.isInitialized) + { + statInt[4] = 1; + } + else + { + statInt[4] = 0; + } + if (this.seedInitialized) + { + statInt[5] = 1; + } + else + { + statInt[5] = 0; + } + for (int i = 0; i < tailLength; i++) + { + statInt[6 + i] = ((Integer)heightOfNodes.elementAt(i)).intValue(); + } + return statInt; + } + + /** + * returns a String representation of the treehash instance + */ + public String toString() + { + String out = "Treehash : "; + for (int i = 0; i < 6 + tailLength; i++) + { + out = out + this.getStatInt()[i] + " "; + } + for (int i = 0; i < 3 + tailLength; i++) + { + if (this.getStatByte()[i] != null) + { + out = out + new String(Hex.encode((this.getStatByte()[i]))) + " "; + } + else + { + out = out + "null "; + } + } + out = out + " " + this.messDigestTree.getDigestSize(); + return out; + } + +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/util/GMSSRandom.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/util/GMSSRandom.java new file mode 100644 index 00000000..367aae0e --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/util/GMSSRandom.java @@ -0,0 +1,78 @@ +package org.spongycastle.pqc.crypto.gmss.util; + +import org.spongycastle.crypto.Digest; + +/** + * This class provides a PRNG for GMSS + */ +public class GMSSRandom +{ + /** + * Hash function for the construction of the authentication trees + */ + private Digest messDigestTree; + + /** + * Constructor + * + * @param messDigestTree2 + */ + public GMSSRandom(Digest messDigestTree2) + { + + this.messDigestTree = messDigestTree2; + } + + /** + * computes the next seed value, returns a random byte array and sets + * outseed to the next value + * + * @param outseed byte array in which ((1 + SEEDin +RAND) mod 2^n) will be + * stored + * @return byte array of H(SEEDin) + */ + public byte[] nextSeed(byte[] outseed) + { + // RAND <-- H(SEEDin) + byte[] rand = new byte[outseed.length]; + messDigestTree.update(outseed, 0, outseed.length); + rand = new byte[messDigestTree.getDigestSize()]; + messDigestTree.doFinal(rand, 0); + + // SEEDout <-- (1 + SEEDin +RAND) mod 2^n + addByteArrays(outseed, rand); + addOne(outseed); + + // System.arraycopy(outseed, 0, outseed, 0, outseed.length); + + return rand; + } + + private void addByteArrays(byte[] a, byte[] b) + { + + byte overflow = 0; + int temp; + + for (int i = 0; i < a.length; i++) + { + temp = (0xFF & a[i]) + (0xFF & b[i]) + overflow; + a[i] = (byte)temp; + overflow = (byte)(temp >> 8); + } + } + + private void addOne(byte[] a) + { + + byte overflow = 1; + int temp; + + for (int i = 0; i < a.length; i++) + { + temp = (0xFF & a[i]) + overflow; + a[i] = (byte)temp; + overflow = (byte)(temp >> 8); + } + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/util/GMSSUtil.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/util/GMSSUtil.java new file mode 100644 index 00000000..57678d85 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/util/GMSSUtil.java @@ -0,0 +1,151 @@ +package org.spongycastle.pqc.crypto.gmss.util; + +/** + * This class provides several methods that are required by the GMSS classes. + */ +public class GMSSUtil +{ + /** + * Converts a 32 bit integer into a byte array beginning at + * <code>offset</code> (little-endian representation) + * + * @param value the integer to convert + */ + public byte[] intToBytesLittleEndian(int value) + { + byte[] bytes = new byte[4]; + + bytes[0] = (byte)((value) & 0xff); + bytes[1] = (byte)((value >> 8) & 0xff); + bytes[2] = (byte)((value >> 16) & 0xff); + bytes[3] = (byte)((value >> 24) & 0xff); + return bytes; + } + + /** + * Converts a byte array beginning at <code>offset</code> into a 32 bit + * integer (little-endian representation) + * + * @param bytes the byte array + * @return The resulting integer + */ + public int bytesToIntLittleEndian(byte[] bytes) + { + + return ((bytes[0] & 0xff)) | ((bytes[1] & 0xff) << 8) + | ((bytes[2] & 0xff) << 16) | ((bytes[3] & 0xff)) << 24; + } + + /** + * Converts a byte array beginning at <code>offset</code> into a 32 bit + * integer (little-endian representation) + * + * @param bytes the byte array + * @param offset the integer offset into the byte array + * @return The resulting integer + */ + public int bytesToIntLittleEndian(byte[] bytes, int offset) + { + return ((bytes[offset++] & 0xff)) | ((bytes[offset++] & 0xff) << 8) + | ((bytes[offset++] & 0xff) << 16) + | ((bytes[offset] & 0xff)) << 24; + } + + /** + * This method concatenates a 2-dimensional byte array into a 1-dimensional + * byte array + * + * @param arraycp a 2-dimensional byte array. + * @return 1-dimensional byte array with concatenated input array + */ + public byte[] concatenateArray(byte[][] arraycp) + { + byte[] dest = new byte[arraycp.length * arraycp[0].length]; + int indx = 0; + for (int i = 0; i < arraycp.length; i++) + { + System.arraycopy(arraycp[i], 0, dest, indx, arraycp[i].length); + indx = indx + arraycp[i].length; + } + return dest; + } + + /** + * This method prints the values of a 2-dimensional byte array + * + * @param text a String + * @param array a 2-dimensional byte array + */ + public void printArray(String text, byte[][] array) + { + System.out.println(text); + int counter = 0; + for (int i = 0; i < array.length; i++) + { + for (int j = 0; j < array[0].length; j++) + { + System.out.println(counter + "; " + array[i][j]); + counter++; + } + } + } + + /** + * This method prints the values of a 1-dimensional byte array + * + * @param text a String + * @param array a 1-dimensional byte array. + */ + public void printArray(String text, byte[] array) + { + System.out.println(text); + int counter = 0; + for (int i = 0; i < array.length; i++) + { + System.out.println(counter + "; " + array[i]); + counter++; + } + } + + /** + * This method tests if an integer is a power of 2. + * + * @param testValue an integer + * @return <code>TRUE</code> if <code>testValue</code> is a power of 2, + * <code>FALSE</code> otherwise + */ + public boolean testPowerOfTwo(int testValue) + { + int a = 1; + while (a < testValue) + { + a <<= 1; + } + if (testValue == a) + { + return true; + } + + return false; + } + + /** + * This method returns the least integer that is greater or equal to the + * logarithm to the base 2 of an integer <code>intValue</code>. + * + * @param intValue an integer + * @return The least integer greater or equal to the logarithm to the base 2 + * of <code>intValue</code> + */ + public int getLog(int intValue) + { + int log = 1; + int i = 2; + while (i < intValue) + { + i <<= 1; + log++; + } + return log; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/util/WinternitzOTSVerify.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/util/WinternitzOTSVerify.java new file mode 100644 index 00000000..0a1e52eb --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/util/WinternitzOTSVerify.java @@ -0,0 +1,344 @@ +package org.spongycastle.pqc.crypto.gmss.util; + +import org.spongycastle.crypto.Digest; + +/** + * This class implements signature verification of the Winternitz one-time + * signature scheme (OTSS), described in C.Dods, N.P. Smart, and M. Stam, "Hash + * Based Digital Signature Schemes", LNCS 3796, pages 96–115, 2005. The + * class is used by the GMSS classes. + */ +public class WinternitzOTSVerify +{ + + private Digest messDigestOTS; + + /** + * The Winternitz parameter + */ + private int w; + + /** + * The constructor + * + * @param digest the name of the hash function used by the OTS and the provider + * name of the hash function + * @param w the Winternitz parameter + */ + public WinternitzOTSVerify(Digest digest, int w) + { + this.w = w; + + messDigestOTS = digest; + } + + /** + * @return The length of the one-time signature + */ + public int getSignatureLength() + { + int mdsize = messDigestOTS.getDigestSize(); + int size = ((mdsize << 3) + (w - 1)) / w; + int logs = getLog((size << w) + 1); + size += (logs + w - 1) / w; + + return mdsize * size; + } + + /** + * This method computes the public OTS key from the one-time signature of a + * message. This is *NOT* a complete OTS signature verification, but it + * suffices for usage with CMSS. + * + * @param message the message + * @param signature the one-time signature + * @return The public OTS key + */ + public byte[] Verify(byte[] message, byte[] signature) + { + + int mdsize = messDigestOTS.getDigestSize(); + byte[] hash = new byte[mdsize]; // hash of message m + + // create hash of message m + messDigestOTS.update(message, 0, message.length); + hash = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hash, 0); + + int size = ((mdsize << 3) + (w - 1)) / w; + int logs = getLog((size << w) + 1); + int keysize = size + (logs + w - 1) / w; + + int testKeySize = mdsize * keysize; + + if (testKeySize != signature.length) + { + return null; + } + + byte[] testKey = new byte[testKeySize]; + + int c = 0; + int counter = 0; + int test; + + if (8 % w == 0) + { + int d = 8 / w; + int k = (1 << w) - 1; + byte[] hlp = new byte[mdsize]; + + // verify signature + for (int i = 0; i < hash.length; i++) + { + for (int j = 0; j < d; j++) + { + test = hash[i] & k; + c += test; + + System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize); + + while (test < k) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test++; + } + + System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize); + hash[i] = (byte)(hash[i] >>> w); + counter++; + } + } + + c = (size << w) - c; + for (int i = 0; i < logs; i += w) + { + test = c & k; + + System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize); + + while (test < k) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test++; + } + System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize); + c >>>= w; + counter++; + } + } + else if (w < 8) + { + int d = mdsize / w; + int k = (1 << w) - 1; + byte[] hlp = new byte[mdsize]; + long big8; + int ii = 0; + // create signature + // first d*w bytes of hash + for (int i = 0; i < d; i++) + { + big8 = 0; + for (int j = 0; j < w; j++) + { + big8 ^= (hash[ii] & 0xff) << (j << 3); + ii++; + } + for (int j = 0; j < 8; j++) + { + test = (int)(big8 & k); + c += test; + + System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize); + + while (test < k) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test++; + } + + System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize); + big8 >>>= w; + counter++; + } + } + // rest of hash + d = mdsize % w; + big8 = 0; + for (int j = 0; j < d; j++) + { + big8 ^= (hash[ii] & 0xff) << (j << 3); + ii++; + } + d <<= 3; + for (int j = 0; j < d; j += w) + { + test = (int)(big8 & k); + c += test; + + System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize); + + while (test < k) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test++; + } + + System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize); + big8 >>>= w; + counter++; + } + + // check bytes + c = (size << w) - c; + for (int i = 0; i < logs; i += w) + { + test = c & k; + + System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize); + + while (test < k) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test++; + } + + System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize); + c >>>= w; + counter++; + } + }// end if(w<8) + else if (w < 57) + { + int d = (mdsize << 3) - w; + int k = (1 << w) - 1; + byte[] hlp = new byte[mdsize]; + long big8, test8; + int r = 0; + int s, f, rest, ii; + // create signature + // first a*w bits of hash where a*w <= 8*mdsize < (a+1)*w + while (r <= d) + { + s = r >>> 3; + rest = r % 8; + r += w; + f = (r + 7) >>> 3; + big8 = 0; + ii = 0; + for (int j = s; j < f; j++) + { + big8 ^= (hash[j] & 0xff) << (ii << 3); + ii++; + } + + big8 >>>= rest; + test8 = (big8 & k); + c += test8; + + System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize); + + while (test8 < k) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test8++; + } + + System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize); + counter++; + + } + // rest of hash + s = r >>> 3; + if (s < mdsize) + { + rest = r % 8; + big8 = 0; + ii = 0; + for (int j = s; j < mdsize; j++) + { + big8 ^= (hash[j] & 0xff) << (ii << 3); + ii++; + } + + big8 >>>= rest; + test8 = (big8 & k); + c += test8; + + System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize); + + while (test8 < k) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test8++; + } + + System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize); + counter++; + } + // check bytes + c = (size << w) - c; + for (int i = 0; i < logs; i += w) + { + test8 = (c & k); + + System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize); + + while (test8 < k) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test8++; + } + + System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize); + c >>>= w; + counter++; + } + }// end if(w<57) + + byte[] TKey = new byte[mdsize]; + messDigestOTS.update(testKey, 0, testKey.length); + TKey = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(TKey, 0); + + return TKey; + + } + + /** + * This method returns the least integer that is greater or equal to the + * logarithm to the base 2 of an integer <code>intValue</code>. + * + * @param intValue an integer + * @return The least integer greater or equal to the logarithm to the base + * 256 of <code>intValue</code> + */ + public int getLog(int intValue) + { + int log = 1; + int i = 2; + while (i < intValue) + { + i <<= 1; + log++; + } + return log; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/gmss/util/WinternitzOTSignature.java b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/util/WinternitzOTSignature.java new file mode 100644 index 00000000..2ec2c1ad --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/gmss/util/WinternitzOTSignature.java @@ -0,0 +1,404 @@ +package org.spongycastle.pqc.crypto.gmss.util; + +import org.spongycastle.crypto.Digest; + +/** + * This class implements key pair generation and signature generation of the + * Winternitz one-time signature scheme (OTSS), described in C.Dods, N.P. Smart, + * and M. Stam, "Hash Based Digital Signature Schemes", LNCS 3796, pages + * 96–115, 2005. The class is used by the GMSS classes. + */ + +public class WinternitzOTSignature +{ + + /** + * The hash function used by the OTS + */ + private Digest messDigestOTS; + + /** + * The length of the message digest and private key + */ + private int mdsize, keysize; + + /** + * An array of strings, containing the name of the used hash function, the + * name of the PRGN and the names of the corresponding providers + */ + // private String[] name = new String[2]; + /** + * The private key + */ + private byte[][] privateKeyOTS; + + /** + * The Winternitz parameter + */ + private int w; + + /** + * The source of randomness for OTS private key generation + */ + private GMSSRandom gmssRandom; + + /** + * Sizes of the message and the checksum, both + */ + private int messagesize, checksumsize; + + /** + * The constructor generates an OTS key pair, using <code>seed0</code> and + * the PRNG + * + * @param seed0 the seed for the PRGN + * @param digest an array of strings, containing the name of the used hash + * function, the name of the PRGN and the names of the + * corresponding providers + * @param w the Winternitz parameter + */ + public WinternitzOTSignature(byte[] seed0, Digest digest, int w) + { + // this.name = name; + this.w = w; + + messDigestOTS = digest; + + gmssRandom = new GMSSRandom(messDigestOTS); + + // calulate keysize for private and public key and also the help + // array + + mdsize = messDigestOTS.getDigestSize(); + int mdsizeBit = mdsize << 3; + messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w); + + checksumsize = getLog((messagesize << w) + 1); + + keysize = messagesize + + (int)Math.ceil((double)checksumsize / (double)w); + + /* + * mdsize = messDigestOTS.getDigestLength(); messagesize = + * ((mdsize<<3)+(w-1))/w; + * + * checksumsize = getlog((messagesize<<w)+1); + * + * keysize = messagesize + (checksumsize+w-1)/w; + */ + // define the private key messagesize + privateKeyOTS = new byte[keysize][mdsize]; + + // gmssRandom.setSeed(seed0); + byte[] dummy = new byte[mdsize]; + System.arraycopy(seed0, 0, dummy, 0, dummy.length); + + // generate random bytes and + // assign them to the private key + for (int i = 0; i < keysize; i++) + { + privateKeyOTS[i] = gmssRandom.nextSeed(dummy); + } + } + + /** + * @return The private OTS key + */ + public byte[][] getPrivateKey() + { + return privateKeyOTS; + } + + /** + * @return The public OTS key + */ + public byte[] getPublicKey() + { + byte[] helppubKey = new byte[keysize * mdsize]; + + byte[] help = new byte[mdsize]; + int two_power_t = 1 << w; + + for (int i = 0; i < keysize; i++) + { + // hash w-1 time the private key and assign it to the public key + messDigestOTS.update(privateKeyOTS[i], 0, privateKeyOTS[i].length); + help = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(help, 0); + for (int j = 2; j < two_power_t; j++) + { + messDigestOTS.update(help, 0, help.length); + help = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(help, 0); + } + System.arraycopy(help, 0, helppubKey, mdsize * i, mdsize); + } + + messDigestOTS.update(helppubKey, 0, helppubKey.length); + byte[] tmp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(tmp, 0); + return tmp; + } + + /** + * @return The one-time signature of the message, generated with the private + * key + */ + public byte[] getSignature(byte[] message) + { + byte[] sign = new byte[keysize * mdsize]; + // byte [] message; // message m as input + byte[] hash = new byte[mdsize]; // hash of message m + int counter = 0; + int c = 0; + int test = 0; + // create hash of message m + messDigestOTS.update(message, 0, message.length); + hash = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hash, 0); + + if (8 % w == 0) + { + int d = 8 / w; + int k = (1 << w) - 1; + byte[] hlp = new byte[mdsize]; + + // create signature + for (int i = 0; i < hash.length; i++) + { + for (int j = 0; j < d; j++) + { + test = hash[i] & k; + c += test; + + System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize); + + while (test > 0) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test--; + } + System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize); + hash[i] = (byte)(hash[i] >>> w); + counter++; + } + } + + c = (messagesize << w) - c; + for (int i = 0; i < checksumsize; i += w) + { + test = c & k; + + System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize); + + while (test > 0) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test--; + } + System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize); + c >>>= w; + counter++; + } + } + else if (w < 8) + { + int d = mdsize / w; + int k = (1 << w) - 1; + byte[] hlp = new byte[mdsize]; + long big8; + int ii = 0; + // create signature + // first d*w bytes of hash + for (int i = 0; i < d; i++) + { + big8 = 0; + for (int j = 0; j < w; j++) + { + big8 ^= (hash[ii] & 0xff) << (j << 3); + ii++; + } + for (int j = 0; j < 8; j++) + { + test = (int)(big8 & k); + c += test; + + System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize); + + while (test > 0) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test--; + } + System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize); + big8 >>>= w; + counter++; + } + } + // rest of hash + d = mdsize % w; + big8 = 0; + for (int j = 0; j < d; j++) + { + big8 ^= (hash[ii] & 0xff) << (j << 3); + ii++; + } + d <<= 3; + for (int j = 0; j < d; j += w) + { + test = (int)(big8 & k); + c += test; + + System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize); + + while (test > 0) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test--; + } + System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize); + big8 >>>= w; + counter++; + } + + // check bytes + c = (messagesize << w) - c; + for (int i = 0; i < checksumsize; i += w) + { + test = c & k; + + System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize); + + while (test > 0) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test--; + } + System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize); + c >>>= w; + counter++; + } + }// end if(w<8) + else if (w < 57) + { + int d = (mdsize << 3) - w; + int k = (1 << w) - 1; + byte[] hlp = new byte[mdsize]; + long big8, test8; + int r = 0; + int s, f, rest, ii; + // create signature + // first a*w bits of hash where a*w <= 8*mdsize < (a+1)*w + while (r <= d) + { + s = r >>> 3; + rest = r % 8; + r += w; + f = (r + 7) >>> 3; + big8 = 0; + ii = 0; + for (int j = s; j < f; j++) + { + big8 ^= (hash[j] & 0xff) << (ii << 3); + ii++; + } + + big8 >>>= rest; + test8 = (big8 & k); + c += test8; + + System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize); + while (test8 > 0) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test8--; + } + System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize); + counter++; + + } + // rest of hash + s = r >>> 3; + if (s < mdsize) + { + rest = r % 8; + big8 = 0; + ii = 0; + for (int j = s; j < mdsize; j++) + { + big8 ^= (hash[j] & 0xff) << (ii << 3); + ii++; + } + + big8 >>>= rest; + test8 = (big8 & k); + c += test8; + + System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize); + while (test8 > 0) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test8--; + } + System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize); + counter++; + } + // check bytes + c = (messagesize << w) - c; + for (int i = 0; i < checksumsize; i += w) + { + test8 = (c & k); + + System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize); + + while (test8 > 0) + { + messDigestOTS.update(hlp, 0, hlp.length); + hlp = new byte[messDigestOTS.getDigestSize()]; + messDigestOTS.doFinal(hlp, 0); + test8--; + } + System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize); + c >>>= w; + counter++; + } + }// end if(w<57) + + return sign; + } + + /** + * This method returns the least integer that is greater or equal to the + * logarithm to the base 2 of an integer <code>intValue</code>. + * + * @param intValue an integer + * @return The least integer greater or equal to the logarithm to the base 2 + * of <code>intValue</code> + */ + public int getLog(int intValue) + { + int log = 1; + int i = 2; + while (i < intValue) + { + i <<= 1; + log++; + } + return log; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/Conversions.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/Conversions.java new file mode 100644 index 00000000..9772d018 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/Conversions.java @@ -0,0 +1,236 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import java.math.BigInteger; + +import org.spongycastle.pqc.math.linearalgebra.BigIntUtils; +import org.spongycastle.pqc.math.linearalgebra.GF2Vector; +import org.spongycastle.pqc.math.linearalgebra.IntegerFunctions; + + +/** + * Provides methods for CCA2-Secure Conversions of McEliece PKCS + */ +final class Conversions +{ + private static final BigInteger ZERO = BigInteger.valueOf(0); + private static final BigInteger ONE = BigInteger.valueOf(1); + + /** + * Default constructor (private). + */ + private Conversions() + { + } + + /** + * Encode a number between 0 and (n|t) (binomial coefficient) into a binary + * vector of length n with weight t. The number is given as a byte array. + * Only the first s bits are used, where s = floor[log(n|t)]. + * + * @param n integer + * @param t integer + * @param m the message as a byte array + * @return the encoded message as {@link GF2Vector} + */ + public static GF2Vector encode(final int n, final int t, final byte[] m) + { + if (n < t) + { + throw new IllegalArgumentException("n < t"); + } + + // compute the binomial c = (n|t) + BigInteger c = IntegerFunctions.binomial(n, t); + // get the number encoded in m + BigInteger i = new BigInteger(1, m); + // compare + if (i.compareTo(c) >= 0) + { + throw new IllegalArgumentException("Encoded number too large."); + } + + GF2Vector result = new GF2Vector(n); + + int nn = n; + int tt = t; + for (int j = 0; j < n; j++) + { + c = c.multiply(BigInteger.valueOf(nn - tt)).divide( + BigInteger.valueOf(nn)); + nn--; + if (c.compareTo(i) <= 0) + { + result.setBit(j); + i = i.subtract(c); + tt--; + if (nn == tt) + { + c = ONE; + } + else + { + c = (c.multiply(BigInteger.valueOf(tt + 1))) + .divide(BigInteger.valueOf(nn - tt)); + } + } + } + + return result; + } + + /** + * Decode a binary vector of length n and weight t into a number between 0 + * and (n|t) (binomial coefficient). The result is given as a byte array of + * length floor[(s+7)/8], where s = floor[log(n|t)]. + * + * @param n integer + * @param t integer + * @param vec the binary vector + * @return the decoded vector as a byte array + */ + public static byte[] decode(int n, int t, GF2Vector vec) + { + if ((vec.getLength() != n) || (vec.getHammingWeight() != t)) + { + throw new IllegalArgumentException( + "vector has wrong length or hamming weight"); + } + int[] vecArray = vec.getVecArray(); + + BigInteger bc = IntegerFunctions.binomial(n, t); + BigInteger d = ZERO; + int nn = n; + int tt = t; + for (int i = 0; i < n; i++) + { + bc = bc.multiply(BigInteger.valueOf(nn - tt)).divide( + BigInteger.valueOf(nn)); + nn--; + + int q = i >> 5; + int e = vecArray[q] & (1 << (i & 0x1f)); + if (e != 0) + { + d = d.add(bc); + tt--; + if (nn == tt) + { + bc = ONE; + } + else + { + bc = bc.multiply(BigInteger.valueOf(tt + 1)).divide( + BigInteger.valueOf(nn - tt)); + } + + } + } + + return BigIntUtils.toMinimalByteArray(d); + } + + /** + * Compute a message representative of a message given as a vector of length + * <tt>n</tt> bit and of hamming weight <tt>t</tt>. The result is a + * byte array of length <tt>(s+7)/8</tt>, where + * <tt>s = floor[log(n|t)]</tt>. + * + * @param n integer + * @param t integer + * @param m the message vector as a byte array + * @return a message representative for <tt>m</tt> + */ + public static byte[] signConversion(int n, int t, byte[] m) + { + if (n < t) + { + throw new IllegalArgumentException("n < t"); + } + + BigInteger bc = IntegerFunctions.binomial(n, t); + // finds s = floor[log(binomial(n,t))] + int s = bc.bitLength() - 1; + // s = sq*8 + sr; + int sq = s >> 3; + int sr = s & 7; + if (sr == 0) + { + sq--; + sr = 8; + } + + // n = nq*8+nr; + int nq = n >> 3; + int nr = n & 7; + if (nr == 0) + { + nq--; + nr = 8; + } + // take s bit from m + byte[] data = new byte[nq + 1]; + if (m.length < data.length) + { + System.arraycopy(m, 0, data, 0, m.length); + for (int i = m.length; i < data.length; i++) + { + data[i] = 0; + } + } + else + { + System.arraycopy(m, 0, data, 0, nq); + int h = (1 << nr) - 1; + data[nq] = (byte)(h & m[nq]); + } + + BigInteger d = ZERO; + int nn = n; + int tt = t; + for (int i = 0; i < n; i++) + { + bc = (bc.multiply(new BigInteger(Integer.toString(nn - tt)))) + .divide(new BigInteger(Integer.toString(nn))); + nn--; + + int q = i >>> 3; + int r = i & 7; + r = 1 << r; + byte e = (byte)(r & data[q]); + if (e != 0) + { + d = d.add(bc); + tt--; + if (nn == tt) + { + bc = ONE; + } + else + { + bc = (bc + .multiply(new BigInteger(Integer.toString(tt + 1)))) + .divide(new BigInteger(Integer.toString(nn - tt))); + } + } + } + + byte[] result = new byte[sq + 1]; + byte[] help = d.toByteArray(); + if (help.length < result.length) + { + System.arraycopy(help, 0, result, 0, help.length); + for (int i = help.length; i < result.length; i++) + { + result[i] = 0; + } + } + else + { + System.arraycopy(help, 0, result, 0, sq); + result[sq] = (byte)(((1 << sr) - 1) & help[sq]); + } + + return result; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2KeyGenerationParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2KeyGenerationParameters.java new file mode 100644 index 00000000..8095d291 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2KeyGenerationParameters.java @@ -0,0 +1,25 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.KeyGenerationParameters; + +public class McElieceCCA2KeyGenerationParameters + extends KeyGenerationParameters +{ + private McElieceCCA2Parameters params; + + public McElieceCCA2KeyGenerationParameters( + SecureRandom random, + McElieceCCA2Parameters params) + { + // XXX key size? + super(random, 128); + this.params = params; + } + + public McElieceCCA2Parameters getParameters() + { + return params; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2KeyPairGenerator.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2KeyPairGenerator.java new file mode 100644 index 00000000..c950bda7 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2KeyPairGenerator.java @@ -0,0 +1,119 @@ +package org.spongycastle.pqc.crypto.mceliece; + + +import java.security.SecureRandom; + +import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.spongycastle.crypto.KeyGenerationParameters; +import org.spongycastle.pqc.math.linearalgebra.GF2Matrix; +import org.spongycastle.pqc.math.linearalgebra.GF2mField; +import org.spongycastle.pqc.math.linearalgebra.GoppaCode; +import org.spongycastle.pqc.math.linearalgebra.GoppaCode.MaMaPe; +import org.spongycastle.pqc.math.linearalgebra.Permutation; +import org.spongycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM; +import org.spongycastle.pqc.math.linearalgebra.PolynomialRingGF2m; + + +/** + * This class implements key pair generation of the McEliece Public Key + * Cryptosystem (McEliecePKC). + */ +public class McElieceCCA2KeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + + + /** + * The OID of the algorithm. + */ + public static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.2"; + + private McElieceCCA2KeyGenerationParameters mcElieceCCA2Params; + + // the extension degree of the finite field GF(2^m) + private int m; + + // the length of the code + private int n; + + // the error correction capability + private int t; + + // the field polynomial + private int fieldPoly; + + // the source of randomness + private SecureRandom random; + + // flag indicating whether the key pair generator has been initialized + private boolean initialized = false; + + /** + * Default initialization of the key pair generator. + */ + private void initializeDefault() + { + McElieceCCA2KeyGenerationParameters mcCCA2Params = new McElieceCCA2KeyGenerationParameters(new SecureRandom(), new McElieceCCA2Parameters()); + init(mcCCA2Params); + } + + // TODO + public void init( + KeyGenerationParameters param) + { + this.mcElieceCCA2Params = (McElieceCCA2KeyGenerationParameters)param; + + // set source of randomness + this.random = new SecureRandom(); + + this.m = this.mcElieceCCA2Params.getParameters().getM(); + this.n = this.mcElieceCCA2Params.getParameters().getN(); + this.t = this.mcElieceCCA2Params.getParameters().getT(); + this.fieldPoly = this.mcElieceCCA2Params.getParameters().getFieldPoly(); + this.initialized = true; + } + + + public AsymmetricCipherKeyPair generateKeyPair() + { + + if (!initialized) + { + initializeDefault(); + } + + // finite field GF(2^m) + GF2mField field = new GF2mField(m, fieldPoly); + + // irreducible Goppa polynomial + PolynomialGF2mSmallM gp = new PolynomialGF2mSmallM(field, t, + PolynomialGF2mSmallM.RANDOM_IRREDUCIBLE_POLYNOMIAL, random); + PolynomialRingGF2m ring = new PolynomialRingGF2m(field, gp); + + // matrix for computing square roots in (GF(2^m))^t + PolynomialGF2mSmallM[] qInv = ring.getSquareRootMatrix(); + + // generate canonical check matrix + GF2Matrix h = GoppaCode.createCanonicalCheckMatrix(field, gp); + + // compute short systematic form of check matrix + MaMaPe mmp = GoppaCode.computeSystematicForm(h, random); + GF2Matrix shortH = mmp.getSecondMatrix(); + Permutation p = mmp.getPermutation(); + + // compute short systematic form of generator matrix + GF2Matrix shortG = (GF2Matrix)shortH.computeTranspose(); + + // obtain number of rows of G (= dimension of the code) + int k = shortG.getNumRows(); + + // generate keys + McElieceCCA2PublicKeyParameters pubKey = new McElieceCCA2PublicKeyParameters(OID, n, t, shortG, mcElieceCCA2Params.getParameters()); + McElieceCCA2PrivateKeyParameters privKey = new McElieceCCA2PrivateKeyParameters(OID, n, k, + field, gp, p, h, qInv, mcElieceCCA2Params.getParameters()); + + // return key pair + return new AsymmetricCipherKeyPair(pubKey, privKey); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2KeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2KeyParameters.java new file mode 100644 index 00000000..4dc3b8d2 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2KeyParameters.java @@ -0,0 +1,25 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import org.spongycastle.crypto.params.AsymmetricKeyParameter; + + +public class McElieceCCA2KeyParameters + extends AsymmetricKeyParameter +{ + private McElieceCCA2Parameters params; + + public McElieceCCA2KeyParameters( + boolean isPrivate, + McElieceCCA2Parameters params) + { + super(isPrivate); + this.params = params; + } + + + public McElieceCCA2Parameters getParameters() + { + return params; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2Parameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2Parameters.java new file mode 100644 index 00000000..11eae2e0 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2Parameters.java @@ -0,0 +1,51 @@ +package org.spongycastle.pqc.crypto.mceliece; + + +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SHA256Digest; + +/** + * This class provides a specification for the parameters of the CCA2-secure + * variants of the McEliece PKCS that are used with + * {@link McElieceFujisakiCipher}, {@link McElieceKobaraImaiCipher}, and + * {@link McEliecePointchevalCipher}. + * + * @see McElieceFujisakiCipher + * @see McElieceKobaraImaiCipher + * @see McEliecePointchevalCipher + */ +public class McElieceCCA2Parameters + extends McElieceParameters +{ + + + public Digest digest; + + + /** + * Construct the default parameters. + * The default message digest is SHA256. + */ + public McElieceCCA2Parameters() + { + this.digest = new SHA256Digest(); + } + + public McElieceCCA2Parameters(int m, int t) + { + super(m, t); + this.digest = new SHA256Digest(); + } + + public McElieceCCA2Parameters(Digest digest) + { + this.digest = digest; + } + + public Digest getDigest() + { + return this.digest; + } + + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2Primitives.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2Primitives.java new file mode 100644 index 00000000..66c163a2 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2Primitives.java @@ -0,0 +1,86 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import org.spongycastle.pqc.math.linearalgebra.GF2Matrix; +import org.spongycastle.pqc.math.linearalgebra.GF2Vector; +import org.spongycastle.pqc.math.linearalgebra.GF2mField; +import org.spongycastle.pqc.math.linearalgebra.GoppaCode; +import org.spongycastle.pqc.math.linearalgebra.Permutation; +import org.spongycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM; +import org.spongycastle.pqc.math.linearalgebra.Vector; + +/** + * Core operations for the CCA-secure variants of McEliece. + */ +public final class McElieceCCA2Primitives +{ + + /** + * Default constructor (private). + */ + private McElieceCCA2Primitives() + { + } + + /** + * The McEliece encryption primitive. + * + * @param pubKey the public key + * @param m the message vector + * @param z the error vector + * @return <tt>m*G + z</tt> + */ + + + public static GF2Vector encryptionPrimitive(McElieceCCA2PublicKeyParameters pubKey, + GF2Vector m, GF2Vector z) + { + + GF2Matrix matrixG = pubKey.getMatrixG(); + Vector mG = matrixG.leftMultiplyLeftCompactForm(m); + return (GF2Vector)mG.add(z); + } + + /** + * The McEliece decryption primitive. + * + * @param privKey the private key + * @param c the ciphertext vector <tt>c = m*G + z</tt> + * @return the message vector <tt>m</tt> and the error vector <tt>z</tt> + */ + public static GF2Vector[] decryptionPrimitive( + McElieceCCA2PrivateKeyParameters privKey, GF2Vector c) + { + + // obtain values from private key + int k = privKey.getK(); + Permutation p = privKey.getP(); + GF2mField field = privKey.getField(); + PolynomialGF2mSmallM gp = privKey.getGoppaPoly(); + GF2Matrix h = privKey.getH(); + PolynomialGF2mSmallM[] q = privKey.getQInv(); + + // compute inverse permutation P^-1 + Permutation pInv = p.computeInverse(); + + // multiply c with permutation P^-1 + GF2Vector cPInv = (GF2Vector)c.multiply(pInv); + + // compute syndrome of cP^-1 + GF2Vector syndVec = (GF2Vector)h.rightMultiply(cPInv); + + // decode syndrome + GF2Vector errors = GoppaCode.syndromeDecode(syndVec, field, gp, q); + GF2Vector mG = (GF2Vector)cPInv.add(errors); + + // multiply codeword and error vector with P + mG = (GF2Vector)mG.multiply(p); + errors = (GF2Vector)errors.multiply(p); + + // extract plaintext vector (last k columns of mG) + GF2Vector m = mG.extractRightVector(k); + + // return vectors + return new GF2Vector[]{m, errors}; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2PrivateKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2PrivateKeyParameters.java new file mode 100644 index 00000000..a19a267b --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2PrivateKeyParameters.java @@ -0,0 +1,172 @@ +package org.spongycastle.pqc.crypto.mceliece; + + +import org.spongycastle.pqc.math.linearalgebra.GF2Matrix; +import org.spongycastle.pqc.math.linearalgebra.GF2mField; +import org.spongycastle.pqc.math.linearalgebra.Permutation; +import org.spongycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM; + +/** + * + * + * + */ +public class McElieceCCA2PrivateKeyParameters + extends McElieceCCA2KeyParameters +{ + + // the OID of the algorithm + private String oid; + + // the length of the code + private int n; + + // the dimension of the code + private int k; + + // the finte field GF(2^m) + private GF2mField field; + + // the irreducible Goppa polynomial + private PolynomialGF2mSmallM goppaPoly; + + // the permutation + private Permutation p; + + // the canonical check matrix + private GF2Matrix h; + + // the matrix used to compute square roots in (GF(2^m))^t + private PolynomialGF2mSmallM[] qInv; + + /** + * Constructor. + * + * @param n the length of the code + * @param k the dimension of the code + * @param field the finite field <tt>GF(2<sup>m</sup>)</tt> + * @param gp the irreducible Goppa polynomial + * @param p the permutation + * @param h the canonical check matrix + * @param qInv the matrix used to compute square roots in + * <tt>(GF(2^m))^t</tt> + * @param params McElieceCCA2Parameters + */ + public McElieceCCA2PrivateKeyParameters(String oid, int n, int k, GF2mField field, + PolynomialGF2mSmallM gp, Permutation p, GF2Matrix h, + PolynomialGF2mSmallM[] qInv, McElieceCCA2Parameters params) + { + super(true, params); + this.oid = oid; + this.n = n; + this.k = k; + this.field = field; + this.goppaPoly = gp; + this.p = p; + this.h = h; + this.qInv = qInv; + } + + /** + * Constructor used by the {@link McElieceKeyFactory}. + * + * @param n the length of the code + * @param k the dimension of the code + * @param encFieldPoly the encoded field polynomial defining the finite field + * <tt>GF(2<sup>m</sup>)</tt> + * @param encGoppaPoly the encoded irreducible Goppa polynomial + * @param encP the encoded permutation + * @param encH the encoded canonical check matrix + * @param encQInv the encoded matrix used to compute square roots in + * <tt>(GF(2^m))^t</tt> + * @param params McElieceCCA2Parameters + */ + public McElieceCCA2PrivateKeyParameters(String oid, int n, int k, byte[] encFieldPoly, + byte[] encGoppaPoly, byte[] encP, byte[] encH, byte[][] encQInv, McElieceCCA2Parameters params) + { + super(true, params); + this.oid = oid; + this.n = n; + this.k = k; + field = new GF2mField(encFieldPoly); + goppaPoly = new PolynomialGF2mSmallM(field, encGoppaPoly); + p = new Permutation(encP); + h = new GF2Matrix(encH); + qInv = new PolynomialGF2mSmallM[encQInv.length]; + for (int i = 0; i < encQInv.length; i++) + { + qInv[i] = new PolynomialGF2mSmallM(field, encQInv[i]); + } + } + + /** + * @return the length of the code + */ + public int getN() + { + return n; + } + + /** + * @return the dimension of the code + */ + public int getK() + { + return k; + } + + /** + * @return the degree of the Goppa polynomial (error correcting capability) + */ + public int getT() + { + return goppaPoly.getDegree(); + } + + /** + * @return the finite field + */ + public GF2mField getField() + { + return field; + } + + /** + * @return the irreducible Goppa polynomial + */ + public PolynomialGF2mSmallM getGoppaPoly() + { + return goppaPoly; + } + + /** + * @return the permutation P + */ + public Permutation getP() + { + return p; + } + + /** + * @return the canonical check matrix H + */ + public GF2Matrix getH() + { + return h; + } + + /** + * @return the matrix used to compute square roots in <tt>(GF(2^m))^t</tt> + */ + public PolynomialGF2mSmallM[] getQInv() + { + return qInv; + } + + public String getOIDString() + { + return oid; + + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2PublicKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2PublicKeyParameters.java new file mode 100644 index 00000000..9b5ba0a6 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceCCA2PublicKeyParameters.java @@ -0,0 +1,97 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import org.spongycastle.pqc.math.linearalgebra.GF2Matrix; + +/** + * + * + * + */ +public class McElieceCCA2PublicKeyParameters + extends McElieceCCA2KeyParameters +{ + + // the OID of the algorithm + private String oid; + + // the length of the code + private int n; + + // the error correction capability of the code + private int t; + + // the generator matrix + private GF2Matrix matrixG; + + /** + * Constructor. + * + * @param n length of the code + * @param t error correction capability + * @param matrix generator matrix + * @param params McElieceCCA2Parameters + */ + public McElieceCCA2PublicKeyParameters(String oid, int n, int t, GF2Matrix matrix, McElieceCCA2Parameters params) + { + super(false, params); + this.oid = oid; + this.n = n; + this.t = t; + this.matrixG = new GF2Matrix(matrix); + } + + /** + * Constructor (used by {@link McElieceKeyFactory}). + * + * @param n length of the code + * @param t error correction capability of the code + * @param encMatrix encoded generator matrix + * @param params McElieceCCA2Parameters + */ + public McElieceCCA2PublicKeyParameters(String oid, int n, int t, byte[] encMatrix, McElieceCCA2Parameters params) + { + super(false, params); + this.oid = oid; + this.n = n; + this.t = t; + this.matrixG = new GF2Matrix(encMatrix); + } + + /** + * @return the length of the code + */ + public int getN() + { + return n; + } + + /** + * @return the error correction capability of the code + */ + public int getT() + { + return t; + } + + /** + * @return the generator matrix + */ + public GF2Matrix getMatrixG() + { + return matrixG; + } + + /** + * @return the dimension of the code + */ + public int getK() + { + return matrixG.getNumRows(); + } + + public String getOIDString() + { + return oid; + + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceFujisakiCipher.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceFujisakiCipher.java new file mode 100644 index 00000000..810a69c8 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceFujisakiCipher.java @@ -0,0 +1,218 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.crypto.prng.DigestRandomGenerator; +import org.spongycastle.pqc.crypto.MessageEncryptor; +import org.spongycastle.pqc.math.linearalgebra.ByteUtils; +import org.spongycastle.pqc.math.linearalgebra.GF2Vector; + +/** + * This class implements the Fujisaki/Okamoto conversion of the McEliecePKCS. + * Fujisaki and Okamoto propose hybrid encryption that merges a symmetric + * encryption scheme which is secure in the find-guess model with an asymmetric + * one-way encryption scheme which is sufficiently probabilistic to obtain a + * public key cryptosystem which is CCA2-secure. For details, see D. Engelbert, + * R. Overbeck, A. Schmidt, "A summary of the development of the McEliece + * Cryptosystem", technical report. + */ +public class McElieceFujisakiCipher + implements MessageEncryptor +{ + + + /** + * The OID of the algorithm. + */ + public static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.2.1"; + + private static final String DEFAULT_PRNG_NAME = "SHA1PRNG"; + + private Digest messDigest; + + private SecureRandom sr; + + /** + * The McEliece main parameters + */ + private int n, k, t; + + McElieceCCA2KeyParameters key; + + + public void init(boolean forSigning, + CipherParameters param) + { + + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.sr = rParam.getRandom(); + this.key = (McElieceCCA2PublicKeyParameters)rParam.getParameters(); + this.initCipherEncrypt((McElieceCCA2PublicKeyParameters)key); + + } + else + { + this.sr = new SecureRandom(); + this.key = (McElieceCCA2PublicKeyParameters)param; + this.initCipherEncrypt((McElieceCCA2PublicKeyParameters)key); + } + } + else + { + this.key = (McElieceCCA2PrivateKeyParameters)param; + this.initCipherDecrypt((McElieceCCA2PrivateKeyParameters)key); + } + + } + + + public int getKeySize(McElieceCCA2KeyParameters key) + throws IllegalArgumentException + { + + if (key instanceof McElieceCCA2PublicKeyParameters) + { + return ((McElieceCCA2PublicKeyParameters)key).getN(); + + } + if (key instanceof McElieceCCA2PrivateKeyParameters) + { + return ((McElieceCCA2PrivateKeyParameters)key).getN(); + } + throw new IllegalArgumentException("unsupported type"); + + } + + + private void initCipherEncrypt(McElieceCCA2PublicKeyParameters pubKey) + { + this.sr = sr != null ? sr : new SecureRandom(); + this.messDigest = pubKey.getParameters().getDigest(); + n = pubKey.getN(); + k = pubKey.getK(); + t = pubKey.getT(); + } + + + public void initCipherDecrypt(McElieceCCA2PrivateKeyParameters privKey) + { + this.messDigest = privKey.getParameters().getDigest(); + n = privKey.getN(); + t = privKey.getT(); + } + + + public byte[] messageEncrypt(byte[] input) + throws Exception + { + + // generate random vector r of length k bits + GF2Vector r = new GF2Vector(k, sr); + + // convert r to byte array + byte[] rBytes = r.getEncoded(); + + // compute (r||input) + byte[] rm = ByteUtils.concatenate(rBytes, input); + + // compute H(r||input) + messDigest.update(rm, 0, rm.length); + byte[] hrm = new byte[messDigest.getDigestSize()]; + messDigest.doFinal(hrm, 0); + + // convert H(r||input) to error vector z + GF2Vector z = Conversions.encode(n, t, hrm); + + // compute c1 = E(r, z) + byte[] c1 = McElieceCCA2Primitives.encryptionPrimitive((McElieceCCA2PublicKeyParameters)key, r, z) + .getEncoded(); + + // get PRNG object + DigestRandomGenerator sr0 = new DigestRandomGenerator(new SHA1Digest()); + + // seed PRNG with r' + sr0.addSeedMaterial(rBytes); + + // generate random c2 + byte[] c2 = new byte[input.length]; + sr0.nextBytes(c2); + + // XOR with input + for (int i = 0; i < input.length; i++) + { + c2[i] ^= input[i]; + } + + // return (c1||c2) + return ByteUtils.concatenate(c1, c2); + } + + public byte[] messageDecrypt(byte[] input) + throws Exception + { + + int c1Len = (n + 7) >> 3; + int c2Len = input.length - c1Len; + + // split ciphertext (c1||c2) + byte[][] c1c2 = ByteUtils.split(input, c1Len); + byte[] c1 = c1c2[0]; + byte[] c2 = c1c2[1]; + + // decrypt c1 ... + GF2Vector hrmVec = GF2Vector.OS2VP(n, c1); + GF2Vector[] decC1 = McElieceCCA2Primitives.decryptionPrimitive((McElieceCCA2PrivateKeyParameters)key, + hrmVec); + byte[] rBytes = decC1[0].getEncoded(); + // ... and obtain error vector z + GF2Vector z = decC1[1]; + + // get PRNG object + DigestRandomGenerator sr0 = new DigestRandomGenerator(new SHA1Digest()); + + // seed PRNG with r' + sr0.addSeedMaterial(rBytes); + + // generate random sequence + byte[] mBytes = new byte[c2Len]; + sr0.nextBytes(mBytes); + + // XOR with c2 to obtain m + for (int i = 0; i < c2Len; i++) + { + mBytes[i] ^= c2[i]; + } + + // compute H(r||m) + byte[] rmBytes = ByteUtils.concatenate(rBytes, mBytes); + byte[] hrm = new byte[messDigest.getDigestSize()]; + messDigest.update(rmBytes, 0, rmBytes.length); + messDigest.doFinal(hrm, 0); + + + // compute Conv(H(r||m)) + hrmVec = Conversions.encode(n, t, hrm); + + // check that Conv(H(m||r)) = z + if (!hrmVec.equals(z)) + { + + throw new Exception("Bad Padding: invalid ciphertext"); + + } + + // return plaintext m + return mBytes; + } + + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceFujisakiDigestCipher.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceFujisakiDigestCipher.java new file mode 100644 index 00000000..9a5577ce --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceFujisakiDigestCipher.java @@ -0,0 +1,128 @@ +package org.spongycastle.pqc.crypto.mceliece; + + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.pqc.crypto.MessageEncryptor; + +// TODO should implement some interface? +public class McElieceFujisakiDigestCipher +{ + + private final Digest messDigest; + + private final MessageEncryptor mcElieceCCA2Cipher; + + private boolean forEncrypting; + + + public McElieceFujisakiDigestCipher(MessageEncryptor mcElieceCCA2Cipher, Digest messDigest) + { + this.mcElieceCCA2Cipher = mcElieceCCA2Cipher; + this.messDigest = messDigest; + } + + + public void init(boolean forEncrypting, + CipherParameters param) + { + + this.forEncrypting = forEncrypting; + AsymmetricKeyParameter k; + + if (param instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)param).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)param; + } + + if (forEncrypting && k.isPrivate()) + { + throw new IllegalArgumentException("Encrypting Requires Public Key."); + } + + if (!forEncrypting && !k.isPrivate()) + { + throw new IllegalArgumentException("Decrypting Requires Private Key."); + } + + reset(); + + mcElieceCCA2Cipher.init(forEncrypting, param); + } + + + public byte[] messageEncrypt() + { + if (!forEncrypting) + { + throw new IllegalStateException("McElieceFujisakiDigestCipher not initialised for encrypting."); + } + + byte[] hash = new byte[messDigest.getDigestSize()]; + messDigest.doFinal(hash, 0); + byte[] enc = null; + + try + { + enc = mcElieceCCA2Cipher.messageEncrypt(hash); + } + catch (Exception e) + { + e.printStackTrace(); + } + + + return enc; + } + + + public byte[] messageDecrypt(byte[] ciphertext) + { + byte[] output = null; + if (forEncrypting) + { + throw new IllegalStateException("McElieceFujisakiDigestCipher not initialised for decrypting."); + } + + + try + { + output = mcElieceCCA2Cipher.messageDecrypt(ciphertext); + } + catch (Exception e) + { + e.printStackTrace(); + } + + + return output; + } + + + public void update(byte b) + { + messDigest.update(b); + + } + + public void update(byte[] in, int off, int len) + { + messDigest.update(in, off, len); + + } + + + public void reset() + { + messDigest.reset(); + + } + + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKeyGenerationParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKeyGenerationParameters.java new file mode 100644 index 00000000..129a704c --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKeyGenerationParameters.java @@ -0,0 +1,25 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.KeyGenerationParameters; + +public class McElieceKeyGenerationParameters + extends KeyGenerationParameters +{ + private McElieceParameters params; + + public McElieceKeyGenerationParameters( + SecureRandom random, + McElieceParameters params) + { + // XXX key size? + super(random, 256); + this.params = params; + } + + public McElieceParameters getParameters() + { + return params; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKeyPairGenerator.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKeyPairGenerator.java new file mode 100644 index 00000000..07db4cd6 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKeyPairGenerator.java @@ -0,0 +1,151 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.spongycastle.crypto.KeyGenerationParameters; +import org.spongycastle.pqc.math.linearalgebra.GF2Matrix; +import org.spongycastle.pqc.math.linearalgebra.GF2mField; +import org.spongycastle.pqc.math.linearalgebra.GoppaCode; +import org.spongycastle.pqc.math.linearalgebra.GoppaCode.MaMaPe; +import org.spongycastle.pqc.math.linearalgebra.Permutation; +import org.spongycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM; +import org.spongycastle.pqc.math.linearalgebra.PolynomialRingGF2m; + + +/** + * This class implements key pair generation of the McEliece Public Key + * Cryptosystem (McEliecePKC). + */ +public class McElieceKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + + + public McElieceKeyPairGenerator() + { + + } + + + /** + * The OID of the algorithm. + */ + private static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.1"; + + private McElieceKeyGenerationParameters mcElieceParams; + + // the extension degree of the finite field GF(2^m) + private int m; + + // the length of the code + private int n; + + // the error correction capability + private int t; + + // the field polynomial + private int fieldPoly; + + // the source of randomness + private SecureRandom random; + + // flag indicating whether the key pair generator has been initialized + private boolean initialized = false; + + + /** + * Default initialization of the key pair generator. + */ + private void initializeDefault() + { + McElieceKeyGenerationParameters mcParams = new McElieceKeyGenerationParameters(new SecureRandom(), new McElieceParameters()); + initialize(mcParams); + } + + private void initialize( + KeyGenerationParameters param) + { + this.mcElieceParams = (McElieceKeyGenerationParameters)param; + + // set source of randomness + this.random = new SecureRandom(); + + this.m = this.mcElieceParams.getParameters().getM(); + this.n = this.mcElieceParams.getParameters().getN(); + this.t = this.mcElieceParams.getParameters().getT(); + this.fieldPoly = this.mcElieceParams.getParameters().getFieldPoly(); + this.initialized = true; + } + + + private AsymmetricCipherKeyPair genKeyPair() + { + + if (!initialized) + { + initializeDefault(); + } + + // finite field GF(2^m) + GF2mField field = new GF2mField(m, fieldPoly); + + // irreducible Goppa polynomial + PolynomialGF2mSmallM gp = new PolynomialGF2mSmallM(field, t, + PolynomialGF2mSmallM.RANDOM_IRREDUCIBLE_POLYNOMIAL, random); + PolynomialRingGF2m ring = new PolynomialRingGF2m(field, gp); + + // matrix used to compute square roots in (GF(2^m))^t + PolynomialGF2mSmallM[] sqRootMatrix = ring.getSquareRootMatrix(); + + // generate canonical check matrix + GF2Matrix h = GoppaCode.createCanonicalCheckMatrix(field, gp); + + // compute short systematic form of check matrix + MaMaPe mmp = GoppaCode.computeSystematicForm(h, random); + GF2Matrix shortH = mmp.getSecondMatrix(); + Permutation p1 = mmp.getPermutation(); + + // compute short systematic form of generator matrix + GF2Matrix shortG = (GF2Matrix)shortH.computeTranspose(); + + // extend to full systematic form + GF2Matrix gPrime = shortG.extendLeftCompactForm(); + + // obtain number of rows of G (= dimension of the code) + int k = shortG.getNumRows(); + + // generate random invertible (k x k)-matrix S and its inverse S^-1 + GF2Matrix[] matrixSandInverse = GF2Matrix + .createRandomRegularMatrixAndItsInverse(k, random); + + // generate random permutation P2 + Permutation p2 = new Permutation(n, random); + + // compute public matrix G=S*G'*P2 + GF2Matrix g = (GF2Matrix)matrixSandInverse[0].rightMultiply(gPrime); + g = (GF2Matrix)g.rightMultiply(p2); + + + // generate keys + McEliecePublicKeyParameters pubKey = new McEliecePublicKeyParameters(OID, n, t, g, mcElieceParams.getParameters()); + McEliecePrivateKeyParameters privKey = new McEliecePrivateKeyParameters(OID, n, k, + field, gp, matrixSandInverse[1], p1, p2, h, sqRootMatrix, mcElieceParams.getParameters()); + + // return key pair + return new AsymmetricCipherKeyPair(pubKey, privKey); + } + + public void init(KeyGenerationParameters param) + { + this.initialize(param); + + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + return genKeyPair(); + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKeyParameters.java new file mode 100644 index 00000000..a8b2ce84 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKeyParameters.java @@ -0,0 +1,25 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import org.spongycastle.crypto.params.AsymmetricKeyParameter; + + +public class McElieceKeyParameters + extends AsymmetricKeyParameter +{ + private McElieceParameters params; + + public McElieceKeyParameters( + boolean isPrivate, + McElieceParameters params) + { + super(isPrivate); + this.params = params; + } + + + public McElieceParameters getParameters() + { + return params; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKobaraImaiCipher.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKobaraImaiCipher.java new file mode 100644 index 00000000..0be981be --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKobaraImaiCipher.java @@ -0,0 +1,319 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.crypto.prng.DigestRandomGenerator; +import org.spongycastle.pqc.crypto.MessageEncryptor; +import org.spongycastle.pqc.math.linearalgebra.ByteUtils; +import org.spongycastle.pqc.math.linearalgebra.GF2Vector; +import org.spongycastle.pqc.math.linearalgebra.IntegerFunctions; + +/** + * This class implements the Kobara/Imai conversion of the McEliecePKCS. This is + * a conversion of the McEliecePKCS which is CCA2-secure. For details, see D. + * Engelbert, R. Overbeck, A. Schmidt, "A summary of the development of the + * McEliece Cryptosystem", technical report. + */ +public class McElieceKobaraImaiCipher + implements MessageEncryptor +{ + + /** + * The OID of the algorithm. + */ + public static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.2.3"; + + private static final String DEFAULT_PRNG_NAME = "SHA1PRNG"; + + /** + * A predetermined public constant. + */ + public static final byte[] PUBLIC_CONSTANT = "a predetermined public constant" + .getBytes(); + + + private Digest messDigest; + + private SecureRandom sr; + + McElieceCCA2KeyParameters key; + + /** + * The McEliece main parameters + */ + private int n, k, t; + + + public void init(boolean forSigning, + CipherParameters param) + { + + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.sr = rParam.getRandom(); + this.key = (McElieceCCA2PublicKeyParameters)rParam.getParameters(); + this.initCipherEncrypt((McElieceCCA2PublicKeyParameters)key); + + } + else + { + this.sr = new SecureRandom(); + this.key = (McElieceCCA2PublicKeyParameters)param; + this.initCipherEncrypt((McElieceCCA2PublicKeyParameters)key); + } + } + else + { + this.key = (McElieceCCA2PrivateKeyParameters)param; + this.initCipherDecrypt((McElieceCCA2PrivateKeyParameters)key); + } + + } + + /** + * Return the key size of the given key object. + * + * @param key the McElieceCCA2KeyParameters object + * @return the key size of the given key object + */ + public int getKeySize(McElieceCCA2KeyParameters key) + { + if (key instanceof McElieceCCA2PublicKeyParameters) + { + return ((McElieceCCA2PublicKeyParameters)key).getN(); + + } + if (key instanceof McElieceCCA2PrivateKeyParameters) + { + return ((McElieceCCA2PrivateKeyParameters)key).getN(); + } + throw new IllegalArgumentException("unsupported type"); + } + + private void initCipherEncrypt(McElieceCCA2PublicKeyParameters pubKey) + { + this.messDigest = pubKey.getParameters().getDigest(); + n = pubKey.getN(); + k = pubKey.getK(); + t = pubKey.getT(); + + } + + public void initCipherDecrypt(McElieceCCA2PrivateKeyParameters privKey) + { + this.messDigest = privKey.getParameters().getDigest(); + n = privKey.getN(); + k = privKey.getK(); + t = privKey.getT(); + } + + public byte[] messageEncrypt(byte[] input) + throws Exception + { + + int c2Len = messDigest.getDigestSize(); + int c4Len = k >> 3; + int c5Len = (IntegerFunctions.binomial(n, t).bitLength() - 1) >> 3; + + + int mLen = c4Len + c5Len - c2Len - PUBLIC_CONSTANT.length; + if (input.length > mLen) + { + mLen = input.length; + } + + int c1Len = mLen + PUBLIC_CONSTANT.length; + int c6Len = c1Len + c2Len - c4Len - c5Len; + + // compute (m||const) + byte[] mConst = new byte[c1Len]; + System.arraycopy(input, 0, mConst, 0, input.length); + System.arraycopy(PUBLIC_CONSTANT, 0, mConst, mLen, + PUBLIC_CONSTANT.length); + + // generate random r of length c2Len bytes + byte[] r = new byte[c2Len]; + sr.nextBytes(r); + + // get PRNG object + // get PRNG object + DigestRandomGenerator sr0 = new DigestRandomGenerator(new SHA1Digest()); + + // seed PRNG with r' + sr0.addSeedMaterial(r); + + // generate random sequence ... + byte[] c1 = new byte[c1Len]; + sr0.nextBytes(c1); + + // ... and XOR with (m||const) to obtain c1 + for (int i = c1Len - 1; i >= 0; i--) + { + c1[i] ^= mConst[i]; + } + + // compute H(c1) ... + byte[] c2 = new byte[messDigest.getDigestSize()]; + messDigest.update(c1, 0, c1.length); + messDigest.doFinal(c2, 0); + + // ... and XOR with r + for (int i = c2Len - 1; i >= 0; i--) + { + c2[i] ^= r[i]; + } + + // compute (c2||c1) + byte[] c2c1 = ByteUtils.concatenate(c2, c1); + + // split (c2||c1) into (c6||c5||c4), where c4Len is k/8 bytes, c5Len is + // floor[log(n|t)]/8 bytes, and c6Len is c1Len+c2Len-c4Len-c5Len (may be + // 0). + byte[] c6 = new byte[0]; + if (c6Len > 0) + { + c6 = new byte[c6Len]; + System.arraycopy(c2c1, 0, c6, 0, c6Len); + } + + byte[] c5 = new byte[c5Len]; + System.arraycopy(c2c1, c6Len, c5, 0, c5Len); + + byte[] c4 = new byte[c4Len]; + System.arraycopy(c2c1, c6Len + c5Len, c4, 0, c4Len); + + // convert c4 to vector over GF(2) + GF2Vector c4Vec = GF2Vector.OS2VP(k, c4); + + // convert c5 to error vector z + GF2Vector z = Conversions.encode(n, t, c5); + + // compute encC4 = E(c4, z) + byte[] encC4 = McElieceCCA2Primitives.encryptionPrimitive((McElieceCCA2PublicKeyParameters)key, + c4Vec, z).getEncoded(); + + // if c6Len > 0 + if (c6Len > 0) + { + // return (c6||encC4) + return ByteUtils.concatenate(c6, encC4); + } + // else, return encC4 + return encC4; + } + + + public byte[] messageDecrypt(byte[] input) + throws Exception + { + + int nDiv8 = n >> 3; + + if (input.length < nDiv8) + { + throw new Exception("Bad Padding: Ciphertext too short."); + } + + int c2Len = messDigest.getDigestSize(); + int c4Len = k >> 3; + int c6Len = input.length - nDiv8; + + // split cipher text (c6||encC4), where c6 may be empty + byte[] c6, encC4; + if (c6Len > 0) + { + byte[][] c6EncC4 = ByteUtils.split(input, c6Len); + c6 = c6EncC4[0]; + encC4 = c6EncC4[1]; + } + else + { + c6 = new byte[0]; + encC4 = input; + } + + // convert encC4 into vector over GF(2) + GF2Vector encC4Vec = GF2Vector.OS2VP(n, encC4); + + // decrypt encC4Vec to obtain c4 and error vector z + GF2Vector[] c4z = McElieceCCA2Primitives.decryptionPrimitive((McElieceCCA2PrivateKeyParameters)key, + encC4Vec); + byte[] c4 = c4z[0].getEncoded(); + GF2Vector z = c4z[1]; + + // if length of c4 is greater than c4Len (because of padding) ... + if (c4.length > c4Len) + { + // ... truncate the padding bytes + c4 = ByteUtils.subArray(c4, 0, c4Len); + } + + // compute c5 = Conv^-1(z) + byte[] c5 = Conversions.decode(n, t, z); + + // compute (c6||c5||c4) + byte[] c6c5c4 = ByteUtils.concatenate(c6, c5); + c6c5c4 = ByteUtils.concatenate(c6c5c4, c4); + + // split (c6||c5||c4) into (c2||c1), where c2Len = mdLen and c1Len = + // input.length-c2Len bytes. + int c1Len = c6c5c4.length - c2Len; + byte[][] c2c1 = ByteUtils.split(c6c5c4, c2Len); + byte[] c2 = c2c1[0]; + byte[] c1 = c2c1[1]; + + // compute H(c1) ... + byte[] rPrime = new byte[messDigest.getDigestSize()]; + messDigest.update(c1, 0, c1.length); + messDigest.doFinal(rPrime, 0); + + // ... and XOR with c2 to obtain r' + for (int i = c2Len - 1; i >= 0; i--) + { + rPrime[i] ^= c2[i]; + } + + // get PRNG object + DigestRandomGenerator sr0 = new DigestRandomGenerator(new SHA1Digest()); + + // seed PRNG with r' + sr0.addSeedMaterial(rPrime); + + // generate random sequence R(r') ... + byte[] mConstPrime = new byte[c1Len]; + sr0.nextBytes(mConstPrime); + + // ... and XOR with c1 to obtain (m||const') + for (int i = c1Len - 1; i >= 0; i--) + { + mConstPrime[i] ^= c1[i]; + } + + if (mConstPrime.length < c1Len) + { + throw new Exception("Bad Padding: invalid ciphertext"); + } + + byte[][] temp = ByteUtils.split(mConstPrime, c1Len + - PUBLIC_CONSTANT.length); + byte[] mr = temp[0]; + byte[] constPrime = temp[1]; + + if (!ByteUtils.equals(constPrime, PUBLIC_CONSTANT)) + { + throw new Exception("Bad Padding: invalid ciphertext"); + } + + return mr; + } + + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKobaraImaiDigestCipher.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKobaraImaiDigestCipher.java new file mode 100644 index 00000000..7df9cc0d --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceKobaraImaiDigestCipher.java @@ -0,0 +1,128 @@ +package org.spongycastle.pqc.crypto.mceliece; + + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.pqc.crypto.MessageEncryptor; + +// TODO should implement some interface? +public class McElieceKobaraImaiDigestCipher +{ + + private final Digest messDigest; + + private final MessageEncryptor mcElieceCCA2Cipher; + + private boolean forEncrypting; + + + public McElieceKobaraImaiDigestCipher(MessageEncryptor mcElieceCCA2Cipher, Digest messDigest) + { + this.mcElieceCCA2Cipher = mcElieceCCA2Cipher; + this.messDigest = messDigest; + } + + + public void init(boolean forEncrypting, + CipherParameters param) + { + + this.forEncrypting = forEncrypting; + AsymmetricKeyParameter k; + + if (param instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)param).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)param; + } + + if (forEncrypting && k.isPrivate()) + { + throw new IllegalArgumentException("Encrypting Requires Public Key."); + } + + if (!forEncrypting && !k.isPrivate()) + { + throw new IllegalArgumentException("Decrypting Requires Private Key."); + } + + reset(); + + mcElieceCCA2Cipher.init(forEncrypting, param); + } + + + public byte[] messageEncrypt() + { + if (!forEncrypting) + { + throw new IllegalStateException("McElieceKobaraImaiDigestCipher not initialised for encrypting."); + } + + byte[] hash = new byte[messDigest.getDigestSize()]; + messDigest.doFinal(hash, 0); + byte[] enc = null; + + try + { + enc = mcElieceCCA2Cipher.messageEncrypt(hash); + } + catch (Exception e) + { + e.printStackTrace(); + } + + + return enc; + } + + + public byte[] messageDecrypt(byte[] ciphertext) + { + byte[] output = null; + if (forEncrypting) + { + throw new IllegalStateException("McElieceKobaraImaiDigestCipher not initialised for decrypting."); + } + + + try + { + output = mcElieceCCA2Cipher.messageDecrypt(ciphertext); + } + catch (Exception e) + { + e.printStackTrace(); + } + + + return output; + } + + + public void update(byte b) + { + messDigest.update(b); + + } + + public void update(byte[] in, int off, int len) + { + messDigest.update(in, off, len); + + } + + + public void reset() + { + messDigest.reset(); + + } + + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePKCSCipher.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePKCSCipher.java new file mode 100644 index 00000000..2e843aa0 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePKCSCipher.java @@ -0,0 +1,224 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.pqc.crypto.MessageEncryptor; +import org.spongycastle.pqc.math.linearalgebra.GF2Matrix; +import org.spongycastle.pqc.math.linearalgebra.GF2Vector; +import org.spongycastle.pqc.math.linearalgebra.GF2mField; +import org.spongycastle.pqc.math.linearalgebra.GoppaCode; +import org.spongycastle.pqc.math.linearalgebra.Permutation; +import org.spongycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM; +import org.spongycastle.pqc.math.linearalgebra.Vector; + +/** + * This class implements the McEliece Public Key cryptosystem (McEliecePKCS). It + * was first described in R.J. McEliece, "A public key cryptosystem based on + * algebraic coding theory", DSN progress report, 42-44:114-116, 1978. The + * McEliecePKCS is the first cryptosystem which is based on error correcting + * codes. The trapdoor for the McEliece cryptosystem using Goppa codes is the + * knowledge of the Goppa polynomial used to generate the code. + */ +public class McEliecePKCSCipher + implements MessageEncryptor +{ + + /** + * The OID of the algorithm. + */ + public static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.1"; + + + // the source of randomness + private SecureRandom sr; + + // the McEliece main parameters + private int n, k, t; + + // The maximum number of bytes the cipher can decrypt + public int maxPlainTextSize; + + // The maximum number of bytes the cipher can encrypt + public int cipherTextSize; + + McElieceKeyParameters key; + + + public void init(boolean forSigning, + CipherParameters param) + { + + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.sr = rParam.getRandom(); + this.key = (McEliecePublicKeyParameters)rParam.getParameters(); + this.initCipherEncrypt((McEliecePublicKeyParameters)key); + + } + else + { + this.sr = new SecureRandom(); + this.key = (McEliecePublicKeyParameters)param; + this.initCipherEncrypt((McEliecePublicKeyParameters)key); + } + } + else + { + this.key = (McEliecePrivateKeyParameters)param; + this.initCipherDecrypt((McEliecePrivateKeyParameters)key); + } + + } + + + /** + * Return the key size of the given key object. + * + * @param key the McElieceKeyParameters object + * @return the keysize of the given key object + */ + + public int getKeySize(McElieceKeyParameters key) + { + + if (key instanceof McEliecePublicKeyParameters) + { + return ((McEliecePublicKeyParameters)key).getN(); + + } + if (key instanceof McEliecePrivateKeyParameters) + { + return ((McEliecePrivateKeyParameters)key).getN(); + } + throw new IllegalArgumentException("unsupported type"); + + } + + + public void initCipherEncrypt(McEliecePublicKeyParameters pubKey) + { + this.sr = sr != null ? sr : new SecureRandom(); + n = pubKey.getN(); + k = pubKey.getK(); + t = pubKey.getT(); + cipherTextSize = n >> 3; + maxPlainTextSize = (k >> 3); + } + + + public void initCipherDecrypt(McEliecePrivateKeyParameters privKey) + { + n = privKey.getN(); + k = privKey.getK(); + + maxPlainTextSize = (k >> 3); + cipherTextSize = n >> 3; + } + + /** + * Encrypt a plain text. + * + * @param input the plain text + * @return the cipher text + */ + public byte[] messageEncrypt(byte[] input) + { + GF2Vector m = computeMessageRepresentative(input); + GF2Vector z = new GF2Vector(n, t, sr); + + GF2Matrix g = ((McEliecePublicKeyParameters)key).getG(); + Vector mG = g.leftMultiply(m); + GF2Vector mGZ = (GF2Vector)mG.add(z); + + return mGZ.getEncoded(); + } + + private GF2Vector computeMessageRepresentative(byte[] input) + { + byte[] data = new byte[maxPlainTextSize + ((k & 0x07) != 0 ? 1 : 0)]; + System.arraycopy(input, 0, data, 0, input.length); + data[input.length] = 0x01; + return GF2Vector.OS2VP(k, data); + } + + /** + * Decrypt a cipher text. + * + * @param input the cipher text + * @return the plain text + * @throws Exception if the cipher text is invalid. + */ + public byte[] messageDecrypt(byte[] input) + throws Exception + { + GF2Vector vec = GF2Vector.OS2VP(n, input); + McEliecePrivateKeyParameters privKey = (McEliecePrivateKeyParameters)key; + GF2mField field = privKey.getField(); + PolynomialGF2mSmallM gp = privKey.getGoppaPoly(); + GF2Matrix sInv = privKey.getSInv(); + Permutation p1 = privKey.getP1(); + Permutation p2 = privKey.getP2(); + GF2Matrix h = privKey.getH(); + PolynomialGF2mSmallM[] qInv = privKey.getQInv(); + + // compute permutation P = P1 * P2 + Permutation p = p1.rightMultiply(p2); + + // compute P^-1 + Permutation pInv = p.computeInverse(); + + // compute c P^-1 + GF2Vector cPInv = (GF2Vector)vec.multiply(pInv); + + // compute syndrome of c P^-1 + GF2Vector syndrome = (GF2Vector)h.rightMultiply(cPInv); + + // decode syndrome + GF2Vector z = GoppaCode.syndromeDecode(syndrome, field, gp, qInv); + GF2Vector mSG = (GF2Vector)cPInv.add(z); + + // multiply codeword with P1 and error vector with P + mSG = (GF2Vector)mSG.multiply(p1); + z = (GF2Vector)z.multiply(p); + + // extract mS (last k columns of mSG) + GF2Vector mS = mSG.extractRightVector(k); + + // compute plaintext vector + GF2Vector mVec = (GF2Vector)sInv.leftMultiply(mS); + + // compute and return plaintext + return computeMessage(mVec); + } + + private byte[] computeMessage(GF2Vector mr) + throws Exception + { + byte[] mrBytes = mr.getEncoded(); + // find first non-zero byte + int index; + for (index = mrBytes.length - 1; index >= 0 && mrBytes[index] == 0; index--) + { + ; + } + + // check if padding byte is valid + if (mrBytes[index] != 0x01) + { + throw new Exception("Bad Padding: invalid ciphertext"); + } + + // extract and return message + byte[] mBytes = new byte[index]; + System.arraycopy(mrBytes, 0, mBytes, 0, index); + return mBytes; + } + + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePKCSDigestCipher.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePKCSDigestCipher.java new file mode 100644 index 00000000..901fe688 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePKCSDigestCipher.java @@ -0,0 +1,128 @@ +package org.spongycastle.pqc.crypto.mceliece; + + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.pqc.crypto.MessageEncryptor; + +// TODO should implement some interface? +public class McEliecePKCSDigestCipher +{ + + private final Digest messDigest; + + private final MessageEncryptor mcElieceCipher; + + private boolean forEncrypting; + + + public McEliecePKCSDigestCipher(MessageEncryptor mcElieceCipher, Digest messDigest) + { + this.mcElieceCipher = mcElieceCipher; + this.messDigest = messDigest; + } + + + public void init(boolean forEncrypting, + CipherParameters param) + { + + this.forEncrypting = forEncrypting; + AsymmetricKeyParameter k; + + if (param instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)param).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)param; + } + + if (forEncrypting && k.isPrivate()) + { + throw new IllegalArgumentException("Encrypting Requires Public Key."); + } + + if (!forEncrypting && !k.isPrivate()) + { + throw new IllegalArgumentException("Decrypting Requires Private Key."); + } + + reset(); + + mcElieceCipher.init(forEncrypting, param); + } + + + public byte[] messageEncrypt() + { + if (!forEncrypting) + { + throw new IllegalStateException("McEliecePKCSDigestCipher not initialised for encrypting."); + } + + byte[] hash = new byte[messDigest.getDigestSize()]; + messDigest.doFinal(hash, 0); + byte[] enc = null; + + try + { + enc = mcElieceCipher.messageEncrypt(hash); + } + catch (Exception e) + { + e.printStackTrace(); + } + + + return enc; + } + + + public byte[] messageDecrypt(byte[] ciphertext) + { + byte[] output = null; + if (forEncrypting) + { + throw new IllegalStateException("McEliecePKCSDigestCipher not initialised for decrypting."); + } + + + try + { + output = mcElieceCipher.messageDecrypt(ciphertext); + } + catch (Exception e) + { + e.printStackTrace(); + } + + + return output; + } + + + public void update(byte b) + { + messDigest.update(b); + + } + + public void update(byte[] in, int off, int len) + { + messDigest.update(in, off, len); + + } + + + public void reset() + { + messDigest.reset(); + + } + + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceParameters.java new file mode 100644 index 00000000..d49f9279 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McElieceParameters.java @@ -0,0 +1,181 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.pqc.math.linearalgebra.PolynomialRingGF2; + +public class McElieceParameters + implements CipherParameters +{ + + /** + * The default extension degree + */ + public static final int DEFAULT_M = 11; + + /** + * The default error correcting capability. + */ + public static final int DEFAULT_T = 50; + + /** + * extension degree of the finite field GF(2^m) + */ + private int m; + + /** + * error correction capability of the code + */ + private int t; + + /** + * length of the code + */ + private int n; + + /** + * the field polynomial + */ + private int fieldPoly; + + /** + * Constructor. Set the default parameters: extension degree. + */ + public McElieceParameters() + { + this(DEFAULT_M, DEFAULT_T); + } + + /** + * Constructor. + * + * @param keysize the length of a Goppa code + * @throws IllegalArgumentException if <tt>keysize < 1</tt>. + */ + public McElieceParameters(int keysize) + throws IllegalArgumentException + { + if (keysize < 1) + { + throw new IllegalArgumentException("key size must be positive"); + } + m = 0; + n = 1; + while (n < keysize) + { + n <<= 1; + m++; + } + t = n >>> 1; + t /= m; + fieldPoly = PolynomialRingGF2.getIrreduciblePolynomial(m); + } + + /** + * Constructor. + * + * @param m degree of the finite field GF(2^m) + * @param t error correction capability of the code + * @throws IllegalArgumentException if <tt>m < 1</tt> or <tt>m > 32</tt> or + * <tt>t < 0</tt> or <tt>t > n</tt>. + */ + public McElieceParameters(int m, int t) + throws IllegalArgumentException + { + if (m < 1) + { + throw new IllegalArgumentException("m must be positive"); + } + if (m > 32) + { + throw new IllegalArgumentException("m is too large"); + } + this.m = m; + n = 1 << m; + if (t < 0) + { + throw new IllegalArgumentException("t must be positive"); + } + if (t > n) + { + throw new IllegalArgumentException("t must be less than n = 2^m"); + } + this.t = t; + fieldPoly = PolynomialRingGF2.getIrreduciblePolynomial(m); + } + + /** + * Constructor. + * + * @param m degree of the finite field GF(2^m) + * @param t error correction capability of the code + * @param poly the field polynomial + * @throws IllegalArgumentException if <tt>m < 1</tt> or <tt>m > 32</tt> or + * <tt>t < 0</tt> or <tt>t > n</tt> or + * <tt>poly</tt> is not an irreducible field polynomial. + */ + public McElieceParameters(int m, int t, int poly) + throws IllegalArgumentException + { + this.m = m; + if (m < 1) + { + throw new IllegalArgumentException("m must be positive"); + } + if (m > 32) + { + throw new IllegalArgumentException(" m is too large"); + } + this.n = 1 << m; + this.t = t; + if (t < 0) + { + throw new IllegalArgumentException("t must be positive"); + } + if (t > n) + { + throw new IllegalArgumentException("t must be less than n = 2^m"); + } + if ((PolynomialRingGF2.degree(poly) == m) + && (PolynomialRingGF2.isIrreducible(poly))) + { + this.fieldPoly = poly; + } + else + { + throw new IllegalArgumentException( + "polynomial is not a field polynomial for GF(2^m)"); + } + } + + /** + * @return the extension degree of the finite field GF(2^m) + */ + public int getM() + { + return m; + } + + /** + * @return the length of the code + */ + public int getN() + { + return n; + } + + /** + * @return the error correction capability of the code + */ + public int getT() + { + return t; + } + + /** + * @return the field polynomial + */ + public int getFieldPoly() + { + return fieldPoly; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePointchevalCipher.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePointchevalCipher.java new file mode 100644 index 00000000..ba58258b --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePointchevalCipher.java @@ -0,0 +1,241 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.crypto.prng.DigestRandomGenerator; +import org.spongycastle.pqc.crypto.MessageEncryptor; +import org.spongycastle.pqc.math.linearalgebra.ByteUtils; +import org.spongycastle.pqc.math.linearalgebra.GF2Vector; + +/** + * This class implements the Pointcheval conversion of the McEliecePKCS. + * Pointcheval presents a generic technique to make a CCA2-secure cryptosystem + * from any partially trapdoor one-way function in the random oracle model. For + * details, see D. Engelbert, R. Overbeck, A. Schmidt, "A summary of the + * development of the McEliece Cryptosystem", technical report. + */ +public class McEliecePointchevalCipher + implements MessageEncryptor +{ + + + /** + * The OID of the algorithm. + */ + public static final String OID = "1.3.6.1.4.1.8301.3.1.3.4.2.2"; + + private Digest messDigest; + + private SecureRandom sr; + + /** + * The McEliece main parameters + */ + private int n, k, t; + + McElieceCCA2KeyParameters key; + + public void init(boolean forSigning, + CipherParameters param) + { + + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.sr = rParam.getRandom(); + this.key = (McElieceCCA2PublicKeyParameters)rParam.getParameters(); + this.initCipherEncrypt((McElieceCCA2PublicKeyParameters)key); + + } + else + { + this.sr = new SecureRandom(); + this.key = (McElieceCCA2PublicKeyParameters)param; + this.initCipherEncrypt((McElieceCCA2PublicKeyParameters)key); + } + } + else + { + this.key = (McElieceCCA2PrivateKeyParameters)param; + this.initCipherDecrypt((McElieceCCA2PrivateKeyParameters)key); + } + + } + + /** + * Return the key size of the given key object. + * + * @param key the McElieceCCA2KeyParameters object + * @return the key size of the given key object + * @throws IllegalArgumentException if the key is invalid + */ + public int getKeySize(McElieceCCA2KeyParameters key) + throws IllegalArgumentException + { + + if (key instanceof McElieceCCA2PublicKeyParameters) + { + return ((McElieceCCA2PublicKeyParameters)key).getN(); + + } + if (key instanceof McElieceCCA2PrivateKeyParameters) + { + return ((McElieceCCA2PrivateKeyParameters)key).getN(); + } + throw new IllegalArgumentException("unsupported type"); + + } + + + protected int decryptOutputSize(int inLen) + { + return 0; + } + + protected int encryptOutputSize(int inLen) + { + return 0; + } + + + public void initCipherEncrypt(McElieceCCA2PublicKeyParameters pubKey) + { + this.sr = sr != null ? sr : new SecureRandom(); + this.messDigest = pubKey.getParameters().getDigest(); + n = pubKey.getN(); + k = pubKey.getK(); + t = pubKey.getT(); + } + + public void initCipherDecrypt(McElieceCCA2PrivateKeyParameters privKey) + { + this.messDigest = privKey.getParameters().getDigest(); + n = privKey.getN(); + k = privKey.getK(); + t = privKey.getT(); + } + + public byte[] messageEncrypt(byte[] input) + throws Exception + { + + int kDiv8 = k >> 3; + + // generate random r of length k div 8 bytes + byte[] r = new byte[kDiv8]; + sr.nextBytes(r); + + // generate random vector r' of length k bits + GF2Vector rPrime = new GF2Vector(k, sr); + + // convert r' to byte array + byte[] rPrimeBytes = rPrime.getEncoded(); + + // compute (input||r) + byte[] mr = ByteUtils.concatenate(input, r); + + // compute H(input||r) + messDigest.update(mr, 0, mr.length); + byte[] hmr = new byte[messDigest.getDigestSize()]; + messDigest.doFinal(hmr, 0); + + + // convert H(input||r) to error vector z + GF2Vector z = Conversions.encode(n, t, hmr); + + // compute c1 = E(rPrime, z) + byte[] c1 = McElieceCCA2Primitives.encryptionPrimitive((McElieceCCA2PublicKeyParameters)key, rPrime, + z).getEncoded(); + + // get PRNG object + DigestRandomGenerator sr0 = new DigestRandomGenerator(new SHA1Digest()); + + // seed PRNG with r' + sr0.addSeedMaterial(rPrimeBytes); + + // generate random c2 + byte[] c2 = new byte[input.length + kDiv8]; + sr0.nextBytes(c2); + + // XOR with input + for (int i = 0; i < input.length; i++) + { + c2[i] ^= input[i]; + } + // XOR with r + for (int i = 0; i < kDiv8; i++) + { + c2[input.length + i] ^= r[i]; + } + + // return (c1||c2) + return ByteUtils.concatenate(c1, c2); + } + + public byte[] messageDecrypt(byte[] input) + throws Exception + { + + int c1Len = (n + 7) >> 3; + int c2Len = input.length - c1Len; + + // split cipher text (c1||c2) + byte[][] c1c2 = ByteUtils.split(input, c1Len); + byte[] c1 = c1c2[0]; + byte[] c2 = c1c2[1]; + + // decrypt c1 ... + GF2Vector c1Vec = GF2Vector.OS2VP(n, c1); + GF2Vector[] c1Dec = McElieceCCA2Primitives.decryptionPrimitive((McElieceCCA2PrivateKeyParameters)key, + c1Vec); + byte[] rPrimeBytes = c1Dec[0].getEncoded(); + // ... and obtain error vector z + GF2Vector z = c1Dec[1]; + + // get PRNG object + DigestRandomGenerator sr0 = new DigestRandomGenerator(new SHA1Digest()); + + // seed PRNG with r' + sr0.addSeedMaterial(rPrimeBytes); + + // generate random sequence + byte[] mrBytes = new byte[c2Len]; + sr0.nextBytes(mrBytes); + + // XOR with c2 to obtain (m||r) + for (int i = 0; i < c2Len; i++) + { + mrBytes[i] ^= c2[i]; + } + + // compute H(m||r) + messDigest.update(mrBytes, 0, mrBytes.length); + byte[] hmr = new byte[messDigest.getDigestSize()]; + messDigest.doFinal(hmr, 0); + + // compute Conv(H(m||r)) + c1Vec = Conversions.encode(n, t, hmr); + + // check that Conv(H(m||r)) = z + if (!c1Vec.equals(z)) + { + throw new Exception("Bad Padding: Invalid ciphertext."); + } + + // split (m||r) to obtain m + int kDiv8 = k >> 3; + byte[][] mr = ByteUtils.split(mrBytes, c2Len - kDiv8); + + // return plain text m + return mr[0]; + } + + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePointchevalDigestCipher.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePointchevalDigestCipher.java new file mode 100644 index 00000000..bcfd1d87 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePointchevalDigestCipher.java @@ -0,0 +1,128 @@ +package org.spongycastle.pqc.crypto.mceliece; + + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.pqc.crypto.MessageEncryptor; + +// TODO should implement some interface? +public class McEliecePointchevalDigestCipher +{ + + private final Digest messDigest; + + private final MessageEncryptor mcElieceCCA2Cipher; + + private boolean forEncrypting; + + + public McEliecePointchevalDigestCipher(MessageEncryptor mcElieceCCA2Cipher, Digest messDigest) + { + this.mcElieceCCA2Cipher = mcElieceCCA2Cipher; + this.messDigest = messDigest; + } + + + public void init(boolean forEncrypting, + CipherParameters param) + { + + this.forEncrypting = forEncrypting; + AsymmetricKeyParameter k; + + if (param instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)param).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)param; + } + + if (forEncrypting && k.isPrivate()) + { + throw new IllegalArgumentException("Encrypting Requires Public Key."); + } + + if (!forEncrypting && !k.isPrivate()) + { + throw new IllegalArgumentException("Decrypting Requires Private Key."); + } + + reset(); + + mcElieceCCA2Cipher.init(forEncrypting, param); + } + + + public byte[] messageEncrypt() + { + if (!forEncrypting) + { + throw new IllegalStateException("McEliecePointchevalDigestCipher not initialised for encrypting."); + } + + byte[] hash = new byte[messDigest.getDigestSize()]; + messDigest.doFinal(hash, 0); + byte[] enc = null; + + try + { + enc = mcElieceCCA2Cipher.messageEncrypt(hash); + } + catch (Exception e) + { + e.printStackTrace(); + } + + + return enc; + } + + + public byte[] messageDecrypt(byte[] ciphertext) + { + byte[] output = null; + if (forEncrypting) + { + throw new IllegalStateException("McEliecePointchevalDigestCipher not initialised for decrypting."); + } + + + try + { + output = mcElieceCCA2Cipher.messageDecrypt(ciphertext); + } + catch (Exception e) + { + e.printStackTrace(); + } + + + return output; + } + + + public void update(byte b) + { + messDigest.update(b); + + } + + public void update(byte[] in, int off, int len) + { + messDigest.update(in, off, len); + + } + + + public void reset() + { + messDigest.reset(); + + } + + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePrivateKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePrivateKeyParameters.java new file mode 100644 index 00000000..3c4ef324 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePrivateKeyParameters.java @@ -0,0 +1,197 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import org.spongycastle.pqc.math.linearalgebra.GF2Matrix; +import org.spongycastle.pqc.math.linearalgebra.GF2mField; +import org.spongycastle.pqc.math.linearalgebra.Permutation; +import org.spongycastle.pqc.math.linearalgebra.PolynomialGF2mSmallM; + + +public class McEliecePrivateKeyParameters + extends McElieceKeyParameters +{ + + // the OID of the algorithm + private String oid; + + // the length of the code + private int n; + + // the dimension of the code, where <tt>k >= n - mt</tt> + private int k; + + // the underlying finite field + private GF2mField field; + + // the irreducible Goppa polynomial + private PolynomialGF2mSmallM goppaPoly; + + // a k x k random binary non-singular matrix + private GF2Matrix sInv; + + // the permutation used to generate the systematic check matrix + private Permutation p1; + + // the permutation used to compute the public generator matrix + private Permutation p2; + + // the canonical check matrix of the code + private GF2Matrix h; + + // the matrix used to compute square roots in <tt>(GF(2^m))^t</tt> + private PolynomialGF2mSmallM[] qInv; + + /** + * Constructor. + * + * @param oid + * @param n the length of the code + * @param k the dimension of the code + * @param field the field polynomial defining the finite field + * <tt>GF(2<sup>m</sup>)</tt> + * @param goppaPoly the irreducible Goppa polynomial + * @param sInv the matrix <tt>S<sup>-1</sup></tt> + * @param p1 the permutation used to generate the systematic check + * matrix + * @param p2 the permutation used to compute the public generator + * matrix + * @param h the canonical check matrix + * @param qInv the matrix used to compute square roots in + * <tt>(GF(2<sup>m</sup>))<sup>t</sup></tt> + * @param params McElieceParameters + */ + public McEliecePrivateKeyParameters(String oid, int n, int k, GF2mField field, + PolynomialGF2mSmallM goppaPoly, GF2Matrix sInv, Permutation p1, + Permutation p2, GF2Matrix h, PolynomialGF2mSmallM[] qInv, McElieceParameters params) + { + super(true, params); + this.oid = oid; + this.k = k; + this.n = n; + this.field = field; + this.goppaPoly = goppaPoly; + this.sInv = sInv; + this.p1 = p1; + this.p2 = p2; + this.h = h; + this.qInv = qInv; + } + + /** + * Constructor (used by the {@link McElieceKeyFactory}). + * + * @param oid + * @param n the length of the code + * @param k the dimension of the code + * @param encField the encoded field polynomial defining the finite field + * <tt>GF(2<sup>m</sup>)</tt> + * @param encGoppaPoly the encoded irreducible Goppa polynomial + * @param encSInv the encoded matrix <tt>S<sup>-1</sup></tt> + * @param encP1 the encoded permutation used to generate the systematic + * check matrix + * @param encP2 the encoded permutation used to compute the public + * generator matrix + * @param encH the encoded canonical check matrix + * @param encQInv the encoded matrix used to compute square roots in + * <tt>(GF(2<sup>m</sup>))<sup>t</sup></tt> + * @param params McElieceParameters + */ + public McEliecePrivateKeyParameters(String oid, int n, int k, byte[] encField, + byte[] encGoppaPoly, byte[] encSInv, byte[] encP1, byte[] encP2, + byte[] encH, byte[][] encQInv, McElieceParameters params) + { + super(true, params); + this.oid = oid; + this.n = n; + this.k = k; + field = new GF2mField(encField); + goppaPoly = new PolynomialGF2mSmallM(field, encGoppaPoly); + sInv = new GF2Matrix(encSInv); + p1 = new Permutation(encP1); + p2 = new Permutation(encP2); + h = new GF2Matrix(encH); + qInv = new PolynomialGF2mSmallM[encQInv.length]; + for (int i = 0; i < encQInv.length; i++) + { + qInv[i] = new PolynomialGF2mSmallM(field, encQInv[i]); + } + } + + /** + * @return the length of the code + */ + public int getN() + { + return n; + } + + /** + * @return the dimension of the code + */ + public int getK() + { + return k; + } + + /** + * @return the finite field <tt>GF(2<sup>m</sup>)</tt> + */ + public GF2mField getField() + { + return field; + } + + /** + * @return the irreducible Goppa polynomial + */ + public PolynomialGF2mSmallM getGoppaPoly() + { + return goppaPoly; + } + + /** + * @return the k x k random binary non-singular matrix S^-1 + */ + public GF2Matrix getSInv() + { + return sInv; + } + + /** + * @return the permutation used to generate the systematic check matrix + */ + public Permutation getP1() + { + return p1; + } + + /** + * @return the permutation used to compute the public generator matrix + */ + public Permutation getP2() + { + return p2; + } + + /** + * @return the canonical check matrix H + */ + public GF2Matrix getH() + { + return h; + } + + /** + * @return the matrix used to compute square roots in + * <tt>(GF(2<sup>m</sup>))<sup>t</sup></tt> + */ + public PolynomialGF2mSmallM[] getQInv() + { + return qInv; + } + + public String getOIDString() + { + return oid; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePublicKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePublicKeyParameters.java new file mode 100644 index 00000000..9f011243 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/mceliece/McEliecePublicKeyParameters.java @@ -0,0 +1,96 @@ +package org.spongycastle.pqc.crypto.mceliece; + +import org.spongycastle.pqc.math.linearalgebra.GF2Matrix; + + +public class McEliecePublicKeyParameters + extends McElieceKeyParameters +{ + + // the OID of the algorithm + private String oid; + + // the length of the code + private int n; + + // the error correction capability of the code + private int t; + + // the generator matrix + private GF2Matrix g; + + /** + * Constructor (used by {@link McElieceKeyFactory}). + * + * @param oid + * @param n the length of the code + * @param t the error correction capability of the code + * @param g the generator matrix + * @param params McElieceParameters + */ + public McEliecePublicKeyParameters(String oid, int n, int t, GF2Matrix g, McElieceParameters params) + { + super(false, params); + this.oid = oid; + this.n = n; + this.t = t; + this.g = new GF2Matrix(g); + } + + /** + * Constructor (used by {@link McElieceKeyFactory}). + * + * @param oid + * @param n the length of the code + * @param t the error correction capability of the code + * @param encG the encoded generator matrix + * @param params McElieceParameters + */ + public McEliecePublicKeyParameters(String oid, int t, int n, byte[] encG, McElieceParameters params) + { + super(false, params); + this.oid = oid; + this.n = n; + this.t = t; + this.g = new GF2Matrix(encG); + } + + /** + * @return the length of the code + */ + public int getN() + { + return n; + } + + /** + * @return the error correction capability of the code + */ + public int getT() + { + return t; + } + + /** + * @return the generator matrix + */ + public GF2Matrix getG() + { + return g; + } + + public String getOIDString() + { + return oid; + + } + + /** + * @return the dimension of the code + */ + public int getK() + { + return g.getNumRows(); + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/IndexGenerator.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/IndexGenerator.java new file mode 100644 index 00000000..01caf970 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/IndexGenerator.java @@ -0,0 +1,239 @@ +package org.spongycastle.pqc.crypto.ntru; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.util.Arrays; + +/** + * An implementation of the Index Generation Function in IEEE P1363.1. + */ +public class IndexGenerator +{ + private byte[] seed; + private int N; + private int c; + private int minCallsR; + private int totLen; + private int remLen; + private BitString buf; + private int counter; + private boolean initialized; + private Digest hashAlg; + private int hLen; + + /** + * Constructs a new index generator. + * + * @param seed a seed of arbitrary length to initialize the index generator with + * @param params NtruEncrypt parameters + */ + IndexGenerator(byte[] seed, NTRUEncryptionParameters params) + { + this.seed = seed; + N = params.N; + c = params.c; + minCallsR = params.minCallsR; + + totLen = 0; + remLen = 0; + counter = 0; + hashAlg = params.hashAlg; + + hLen = hashAlg.getDigestSize(); // hash length + initialized = false; + } + + /** + * Returns a number <code>i</code> such that <code>0 <= i < N</code>. + * + * @return + */ + int nextIndex() + { + if (!initialized) + { + buf = new BitString(); + byte[] hash = new byte[hashAlg.getDigestSize()]; + while (counter < minCallsR) + { + appendHash(buf, hash); + counter++; + } + totLen = minCallsR * 8 * hLen; + remLen = totLen; + initialized = true; + } + + while (true) + { + totLen += c; + BitString M = buf.getTrailing(remLen); + if (remLen < c) + { + int tmpLen = c - remLen; + int cThreshold = counter + (tmpLen + hLen - 1) / hLen; + byte[] hash = new byte[hashAlg.getDigestSize()]; + while (counter < cThreshold) + { + appendHash(M, hash); + counter++; + if (tmpLen > 8 * hLen) + { + tmpLen -= 8 * hLen; + } + } + remLen = 8 * hLen - tmpLen; + buf = new BitString(); + buf.appendBits(hash); + } + else + { + remLen -= c; + } + + int i = M.getLeadingAsInt(c); // assume c<32 + if (i < (1 << c) - ((1 << c) % N)) + { + return i % N; + } + } + } + + private void appendHash(BitString m, byte[] hash) + { + hashAlg.update(seed, 0, seed.length); + + putInt(hashAlg, counter); + + hashAlg.doFinal(hash, 0); + + m.appendBits(hash); + } + + private void putInt(Digest hashAlg, int counter) + { + hashAlg.update((byte)(counter >> 24)); + hashAlg.update((byte)(counter >> 16)); + hashAlg.update((byte)(counter >> 8)); + hashAlg.update((byte)counter); + } + + /** + * Represents a string of bits and supports appending, reading the head, and reading the tail. + */ + public static class BitString + { + byte[] bytes = new byte[4]; + int numBytes; // includes the last byte even if only some of its bits are used + int lastByteBits; // lastByteBits <= 8 + + /** + * Appends all bits in a byte array to the end of the bit string. + * + * @param bytes a byte array + */ + void appendBits(byte[] bytes) + { + for (int i = 0; i != bytes.length; i++) + { + appendBits(bytes[i]); + } + } + + /** + * Appends all bits in a byte to the end of the bit string. + * + * @param b a byte + */ + public void appendBits(byte b) + { + if (numBytes == bytes.length) + { + bytes = copyOf(bytes, 2 * bytes.length); + } + + if (numBytes == 0) + { + numBytes = 1; + bytes[0] = b; + lastByteBits = 8; + } + else if (lastByteBits == 8) + { + bytes[numBytes++] = b; + } + else + { + int s = 8 - lastByteBits; + bytes[numBytes - 1] |= (b & 0xFF) << lastByteBits; + bytes[numBytes++] = (byte)((b & 0xFF) >> s); + } + } + + /** + * Returns the last <code>numBits</code> bits from the end of the bit string. + * + * @param numBits number of bits + * @return a new <code>BitString</code> of length <code>numBits</code> + */ + public BitString getTrailing(int numBits) + { + BitString newStr = new BitString(); + newStr.numBytes = (numBits + 7) / 8; + newStr.bytes = new byte[newStr.numBytes]; + for (int i = 0; i < newStr.numBytes; i++) + { + newStr.bytes[i] = bytes[i]; + } + + newStr.lastByteBits = numBits % 8; + if (newStr.lastByteBits == 0) + { + newStr.lastByteBits = 8; + } + else + { + int s = 32 - newStr.lastByteBits; + newStr.bytes[newStr.numBytes - 1] = (byte)(newStr.bytes[newStr.numBytes - 1] << s >>> s); + } + + return newStr; + } + + /** + * Returns up to 32 bits from the beginning of the bit string. + * + * @param numBits number of bits + * @return an <code>int</code> whose lower <code>numBits</code> bits are the beginning of the bit string + */ + public int getLeadingAsInt(int numBits) + { + int startBit = (numBytes - 1) * 8 + lastByteBits - numBits; + int startByte = startBit / 8; + + int startBitInStartByte = startBit % 8; + int sum = (bytes[startByte] & 0xFF) >>> startBitInStartByte; + int shift = 8 - startBitInStartByte; + for (int i = startByte + 1; i < numBytes; i++) + { + sum |= (bytes[i] & 0xFF) << shift; + shift += 8; + } + + return sum; + } + + public byte[] getBytes() + { + return Arrays.clone(bytes); + } + } + + private static byte[] copyOf(byte[] src, int len) + { + byte[] tmp = new byte[len]; + + System.arraycopy(src, 0, tmp, 0, len < src.length ? len : src.length); + + return tmp; + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionKeyGenerationParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionKeyGenerationParameters.java new file mode 100644 index 00000000..76cb839d --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionKeyGenerationParameters.java @@ -0,0 +1,463 @@ +package org.spongycastle.pqc.crypto.ntru; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; +import java.util.Arrays; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.KeyGenerationParameters; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.digests.SHA512Digest; + +/** + * A set of parameters for NtruEncrypt. Several predefined parameter sets are available and new ones can be created as well. + */ +public class NTRUEncryptionKeyGenerationParameters + extends KeyGenerationParameters + implements Cloneable +{ + /** + * A conservative (in terms of security) parameter set that gives 256 bits of security and is optimized for key size. + */ + public static final NTRUEncryptionKeyGenerationParameters EES1087EP2 = new NTRUEncryptionKeyGenerationParameters(1087, 2048, 120, 120, 256, 13, 25, 14, true, new byte[]{0, 6, 3}, true, false, new SHA512Digest()); + + /** + * A conservative (in terms of security) parameter set that gives 256 bits of security and is a tradeoff between key size and encryption/decryption speed. + */ + public static final NTRUEncryptionKeyGenerationParameters EES1171EP1 = new NTRUEncryptionKeyGenerationParameters(1171, 2048, 106, 106, 256, 13, 20, 15, true, new byte[]{0, 6, 4}, true, false, new SHA512Digest()); + + /** + * A conservative (in terms of security) parameter set that gives 256 bits of security and is optimized for encryption/decryption speed. + */ + public static final NTRUEncryptionKeyGenerationParameters EES1499EP1 = new NTRUEncryptionKeyGenerationParameters(1499, 2048, 79, 79, 256, 13, 17, 19, true, new byte[]{0, 6, 5}, true, false, new SHA512Digest()); + + /** + * A parameter set that gives 128 bits of security and uses simple ternary polynomials. + */ + public static final NTRUEncryptionKeyGenerationParameters APR2011_439 = new NTRUEncryptionKeyGenerationParameters(439, 2048, 146, 130, 128, 9, 32, 9, true, new byte[]{0, 7, 101}, true, false, new SHA256Digest()); + + /** + * Like <code>APR2011_439</code>, this parameter set gives 128 bits of security but uses product-form polynomials and <code>f=1+pF</code>. + */ + public static final NTRUEncryptionKeyGenerationParameters APR2011_439_FAST = new NTRUEncryptionKeyGenerationParameters(439, 2048, 9, 8, 5, 130, 128, 9, 32, 9, true, new byte[]{0, 7, 101}, true, true, new SHA256Digest()); + + /** + * A parameter set that gives 256 bits of security and uses simple ternary polynomials. + */ + public static final NTRUEncryptionKeyGenerationParameters APR2011_743 = new NTRUEncryptionKeyGenerationParameters(743, 2048, 248, 220, 256, 10, 27, 14, true, new byte[]{0, 7, 105}, false, false, new SHA512Digest()); + + /** + * Like <code>APR2011_743</code>, this parameter set gives 256 bits of security but uses product-form polynomials and <code>f=1+pF</code>. + */ + public static final NTRUEncryptionKeyGenerationParameters APR2011_743_FAST = new NTRUEncryptionKeyGenerationParameters(743, 2048, 11, 11, 15, 220, 256, 10, 27, 14, true, new byte[]{0, 7, 105}, false, true, new SHA512Digest()); + + public int N, q, df, df1, df2, df3; + public int dr; + public int dr1; + public int dr2; + public int dr3; + public int dg; + int llen; + public int maxMsgLenBytes; + public int db; + public int bufferLenBits; + int bufferLenTrits; + public int dm0; + public int pkLen; + public int c; + public int minCallsR; + public int minCallsMask; + public boolean hashSeed; + public byte[] oid; + public boolean sparse; + public boolean fastFp; + public int polyType; + public Digest hashAlg; + + /** + * Constructs a parameter set that uses ternary private keys (i.e. <code>polyType=SIMPLE</code>). + * + * @param N number of polynomial coefficients + * @param q modulus + * @param df number of ones in the private polynomial <code>f</code> + * @param dm0 minimum acceptable number of -1's, 0's, and 1's in the polynomial <code>m'</code> in the last encryption step + * @param db number of random bits to prepend to the message + * @param c a parameter for the Index Generation Function ({@link org.spongycastle.pqc.crypto.ntru.IndexGenerator}) + * @param minCallsR minimum number of hash calls for the IGF to make + * @param minCallsMask minimum number of calls to generate the masking polynomial + * @param hashSeed whether to hash the seed in the MGF first (true) or use the seed directly (false) + * @param oid three bytes that uniquely identify the parameter set + * @param sparse whether to treat ternary polynomials as sparsely populated ({@link org.spongycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.spongycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial}) + * @param fastFp whether <code>f=1+p*F</code> for a ternary <code>F</code> (true) or <code>f</code> is ternary (false) + * @param hashAlg a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method. + */ + public NTRUEncryptionKeyGenerationParameters(int N, int q, int df, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg) + { + super(new SecureRandom(), db); + this.N = N; + this.q = q; + this.df = df; + this.db = db; + this.dm0 = dm0; + this.c = c; + this.minCallsR = minCallsR; + this.minCallsMask = minCallsMask; + this.hashSeed = hashSeed; + this.oid = oid; + this.sparse = sparse; + this.fastFp = fastFp; + this.polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE; + this.hashAlg = hashAlg; + init(); + } + + /** + * Constructs a parameter set that uses product-form private keys (i.e. <code>polyType=PRODUCT</code>). + * + * @param N number of polynomial coefficients + * @param q modulus + * @param df1 number of ones in the private polynomial <code>f1</code> + * @param df2 number of ones in the private polynomial <code>f2</code> + * @param df3 number of ones in the private polynomial <code>f3</code> + * @param dm0 minimum acceptable number of -1's, 0's, and 1's in the polynomial <code>m'</code> in the last encryption step + * @param db number of random bits to prepend to the message + * @param c a parameter for the Index Generation Function ({@link org.spongycastle.pqc.crypto.ntru.IndexGenerator}) + * @param minCallsR minimum number of hash calls for the IGF to make + * @param minCallsMask minimum number of calls to generate the masking polynomial + * @param hashSeed whether to hash the seed in the MGF first (true) or use the seed directly (false) + * @param oid three bytes that uniquely identify the parameter set + * @param sparse whether to treat ternary polynomials as sparsely populated ({@link org.spongycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.spongycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial}) + * @param fastFp whether <code>f=1+p*F</code> for a ternary <code>F</code> (true) or <code>f</code> is ternary (false) + * @param hashAlg a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code> + */ + public NTRUEncryptionKeyGenerationParameters(int N, int q, int df1, int df2, int df3, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg) + { + super(new SecureRandom(), db); + + this.N = N; + this.q = q; + this.df1 = df1; + this.df2 = df2; + this.df3 = df3; + this.db = db; + this.dm0 = dm0; + this.c = c; + this.minCallsR = minCallsR; + this.minCallsMask = minCallsMask; + this.hashSeed = hashSeed; + this.oid = oid; + this.sparse = sparse; + this.fastFp = fastFp; + this.polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT; + this.hashAlg = hashAlg; + init(); + } + + private void init() + { + dr = df; + dr1 = df1; + dr2 = df2; + dr3 = df3; + dg = N / 3; + llen = 1; // ceil(log2(maxMsgLenBytes)) + maxMsgLenBytes = N * 3 / 2 / 8 - llen - db / 8 - 1; + bufferLenBits = (N * 3 / 2 + 7) / 8 * 8 + 1; + bufferLenTrits = N - 1; + pkLen = db; + } + + /** + * Reads a parameter set from an input stream. + * + * @param is an input stream + * @throws java.io.IOException + */ + public NTRUEncryptionKeyGenerationParameters(InputStream is) + throws IOException + { + super(new SecureRandom(), -1); + DataInputStream dis = new DataInputStream(is); + N = dis.readInt(); + q = dis.readInt(); + df = dis.readInt(); + df1 = dis.readInt(); + df2 = dis.readInt(); + df3 = dis.readInt(); + db = dis.readInt(); + dm0 = dis.readInt(); + c = dis.readInt(); + minCallsR = dis.readInt(); + minCallsMask = dis.readInt(); + hashSeed = dis.readBoolean(); + oid = new byte[3]; + dis.read(oid); + sparse = dis.readBoolean(); + fastFp = dis.readBoolean(); + polyType = dis.read(); + + String alg = dis.readUTF(); + + if ("SHA-512".equals(alg)) + { + hashAlg = new SHA512Digest(); + } + else if ("SHA-256".equals(alg)) + { + hashAlg = new SHA256Digest(); + } + + init(); + } + + public NTRUEncryptionParameters getEncryptionParameters() + { + if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE) + { + return new NTRUEncryptionParameters(N, q, df, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg); + } + else + { + return new NTRUEncryptionParameters(N, q, df1, df2, df3, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg); + } + } + + public NTRUEncryptionKeyGenerationParameters clone() + { + if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE) + { + return new NTRUEncryptionKeyGenerationParameters(N, q, df, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg); + } + else + { + return new NTRUEncryptionKeyGenerationParameters(N, q, df1, df2, df3, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg); + } + } + + /** + * Returns the maximum length a plaintext message can be with this parameter set. + * + * @return the maximum length in bytes + */ + public int getMaxMessageLength() + { + return maxMsgLenBytes; + } + + /** + * Writes the parameter set to an output stream + * + * @param os an output stream + * @throws java.io.IOException + */ + public void writeTo(OutputStream os) + throws IOException + { + DataOutputStream dos = new DataOutputStream(os); + dos.writeInt(N); + dos.writeInt(q); + dos.writeInt(df); + dos.writeInt(df1); + dos.writeInt(df2); + dos.writeInt(df3); + dos.writeInt(db); + dos.writeInt(dm0); + dos.writeInt(c); + dos.writeInt(minCallsR); + dos.writeInt(minCallsMask); + dos.writeBoolean(hashSeed); + dos.write(oid); + dos.writeBoolean(sparse); + dos.writeBoolean(fastFp); + dos.write(polyType); + dos.writeUTF(hashAlg.getAlgorithmName()); + } + + + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + N; + result = prime * result + bufferLenBits; + result = prime * result + bufferLenTrits; + result = prime * result + c; + result = prime * result + db; + result = prime * result + df; + result = prime * result + df1; + result = prime * result + df2; + result = prime * result + df3; + result = prime * result + dg; + result = prime * result + dm0; + result = prime * result + dr; + result = prime * result + dr1; + result = prime * result + dr2; + result = prime * result + dr3; + result = prime * result + (fastFp ? 1231 : 1237); + result = prime * result + ((hashAlg == null) ? 0 : hashAlg.getAlgorithmName().hashCode()); + result = prime * result + (hashSeed ? 1231 : 1237); + result = prime * result + llen; + result = prime * result + maxMsgLenBytes; + result = prime * result + minCallsMask; + result = prime * result + minCallsR; + result = prime * result + Arrays.hashCode(oid); + result = prime * result + pkLen; + result = prime * result + polyType; + result = prime * result + q; + result = prime * result + (sparse ? 1231 : 1237); + return result; + } + + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + NTRUEncryptionKeyGenerationParameters other = (NTRUEncryptionKeyGenerationParameters)obj; + if (N != other.N) + { + return false; + } + if (bufferLenBits != other.bufferLenBits) + { + return false; + } + if (bufferLenTrits != other.bufferLenTrits) + { + return false; + } + if (c != other.c) + { + return false; + } + if (db != other.db) + { + return false; + } + if (df != other.df) + { + return false; + } + if (df1 != other.df1) + { + return false; + } + if (df2 != other.df2) + { + return false; + } + if (df3 != other.df3) + { + return false; + } + if (dg != other.dg) + { + return false; + } + if (dm0 != other.dm0) + { + return false; + } + if (dr != other.dr) + { + return false; + } + if (dr1 != other.dr1) + { + return false; + } + if (dr2 != other.dr2) + { + return false; + } + if (dr3 != other.dr3) + { + return false; + } + if (fastFp != other.fastFp) + { + return false; + } + if (hashAlg == null) + { + if (other.hashAlg != null) + { + return false; + } + } + else if (!hashAlg.getAlgorithmName().equals(other.hashAlg.getAlgorithmName())) + { + return false; + } + if (hashSeed != other.hashSeed) + { + return false; + } + if (llen != other.llen) + { + return false; + } + if (maxMsgLenBytes != other.maxMsgLenBytes) + { + return false; + } + if (minCallsMask != other.minCallsMask) + { + return false; + } + if (minCallsR != other.minCallsR) + { + return false; + } + if (!Arrays.equals(oid, other.oid)) + { + return false; + } + if (pkLen != other.pkLen) + { + return false; + } + if (polyType != other.polyType) + { + return false; + } + if (q != other.q) + { + return false; + } + if (sparse != other.sparse) + { + return false; + } + return true; + } + + public String toString() + { + StringBuilder output = new StringBuilder("EncryptionParameters(N=" + N + " q=" + q); + if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE) + { + output.append(" polyType=SIMPLE df=" + df); + } + else + { + output.append(" polyType=PRODUCT df1=" + df1 + " df2=" + df2 + " df3=" + df3); + } + output.append(" dm0=" + dm0 + " db=" + db + " c=" + c + " minCallsR=" + minCallsR + " minCallsMask=" + minCallsMask + + " hashSeed=" + hashSeed + " hashAlg=" + hashAlg + " oid=" + Arrays.toString(oid) + " sparse=" + sparse + ")"); + return output.toString(); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionKeyPairGenerator.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionKeyPairGenerator.java new file mode 100644 index 00000000..fd483f09 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionKeyPairGenerator.java @@ -0,0 +1,113 @@ +package org.spongycastle.pqc.crypto.ntru; + +import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.spongycastle.crypto.KeyGenerationParameters; +import org.spongycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.IntegerPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.Polynomial; +import org.spongycastle.pqc.math.ntru.polynomial.ProductFormPolynomial; +import org.spongycastle.pqc.math.ntru.util.Util; + +/** + * Generates key pairs.<br> + * The parameter p is hardcoded to 3. + */ +public class NTRUEncryptionKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private NTRUEncryptionKeyGenerationParameters params; + + /** + * Constructs a new instance with a set of encryption parameters. + * + * @param param encryption parameters + */ + public void init(KeyGenerationParameters param) + { + this.params = (NTRUEncryptionKeyGenerationParameters)param; + } + + /** + * Generates a new encryption key pair. + * + * @return a key pair + */ + public AsymmetricCipherKeyPair generateKeyPair() + { + int N = params.N; + int q = params.q; + int df = params.df; + int df1 = params.df1; + int df2 = params.df2; + int df3 = params.df3; + int dg = params.dg; + boolean fastFp = params.fastFp; + boolean sparse = params.sparse; + + Polynomial t; + IntegerPolynomial fq; + IntegerPolynomial fp = null; + + // choose a random f that is invertible mod 3 and q + while (true) + { + IntegerPolynomial f; + + // choose random t, calculate f and fp + if (fastFp) + { + // if fastFp=true, f is always invertible mod 3 + t = params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE ? Util.generateRandomTernary(N, df, df, sparse, params.getRandom()) : ProductFormPolynomial.generateRandom(N, df1, df2, df3, df3, params.getRandom()); + f = t.toIntegerPolynomial(); + f.mult(3); + f.coeffs[0] += 1; + } + else + { + t = params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE ? Util.generateRandomTernary(N, df, df - 1, sparse, params.getRandom()) : ProductFormPolynomial.generateRandom(N, df1, df2, df3, df3 - 1, params.getRandom()); + f = t.toIntegerPolynomial(); + fp = f.invertF3(); + if (fp == null) + { + continue; + } + } + + fq = f.invertFq(q); + if (fq == null) + { + continue; + } + break; + } + + // if fastFp=true, fp=1 + if (fastFp) + { + fp = new IntegerPolynomial(N); + fp.coeffs[0] = 1; + } + + // choose a random g that is invertible mod q + DenseTernaryPolynomial g; + while (true) + { + g = DenseTernaryPolynomial.generateRandom(N, dg, dg - 1, params.getRandom()); + if (g.invertFq(q) != null) + { + break; + } + } + + IntegerPolynomial h = g.mult(fq, q); + h.mult3(q); + h.ensurePositive(q); + g.clear(); + fq.clear(); + + NTRUEncryptionPrivateKeyParameters priv = new NTRUEncryptionPrivateKeyParameters(h, t, fp, params.getEncryptionParameters()); + NTRUEncryptionPublicKeyParameters pub = new NTRUEncryptionPublicKeyParameters(h, params.getEncryptionParameters()); + return new AsymmetricCipherKeyPair(pub, priv); + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionKeyParameters.java new file mode 100644 index 00000000..a22eb286 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionKeyParameters.java @@ -0,0 +1,20 @@ +package org.spongycastle.pqc.crypto.ntru; + +import org.spongycastle.crypto.params.AsymmetricKeyParameter; + +public class NTRUEncryptionKeyParameters + extends AsymmetricKeyParameter +{ + final protected NTRUEncryptionParameters params; + + public NTRUEncryptionKeyParameters(boolean privateKey, NTRUEncryptionParameters params) + { + super(privateKey); + this.params = params; + } + + public NTRUEncryptionParameters getParameters() + { + return params; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionParameters.java new file mode 100644 index 00000000..86dabae8 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionParameters.java @@ -0,0 +1,410 @@ +package org.spongycastle.pqc.crypto.ntru; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.digests.SHA512Digest; + +/** + * A set of parameters for NtruEncrypt. Several predefined parameter sets are available and new ones can be created as well. + */ +public class NTRUEncryptionParameters + implements Cloneable +{ + + public int N, q, df, df1, df2, df3; + public int dr; + public int dr1; + public int dr2; + public int dr3; + public int dg; + int llen; + public int maxMsgLenBytes; + public int db; + public int bufferLenBits; + int bufferLenTrits; + public int dm0; + public int pkLen; + public int c; + public int minCallsR; + public int minCallsMask; + public boolean hashSeed; + public byte[] oid; + public boolean sparse; + public boolean fastFp; + public int polyType; + public Digest hashAlg; + + /** + * Constructs a parameter set that uses ternary private keys (i.e. <code>polyType=SIMPLE</code>). + * + * @param N number of polynomial coefficients + * @param q modulus + * @param df number of ones in the private polynomial <code>f</code> + * @param dm0 minimum acceptable number of -1's, 0's, and 1's in the polynomial <code>m'</code> in the last encryption step + * @param db number of random bits to prepend to the message + * @param c a parameter for the Index Generation Function ({@link org.spongycastle.pqc.crypto.ntru.IndexGenerator}) + * @param minCallsR minimum number of hash calls for the IGF to make + * @param minCallsMask minimum number of calls to generate the masking polynomial + * @param hashSeed whether to hash the seed in the MGF first (true) or use the seed directly (false) + * @param oid three bytes that uniquely identify the parameter set + * @param sparse whether to treat ternary polynomials as sparsely populated ({@link org.spongycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.spongycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial}) + * @param fastFp whether <code>f=1+p*F</code> for a ternary <code>F</code> (true) or <code>f</code> is ternary (false) + * @param hashAlg a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method. + */ + public NTRUEncryptionParameters(int N, int q, int df, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg) + { + this.N = N; + this.q = q; + this.df = df; + this.db = db; + this.dm0 = dm0; + this.c = c; + this.minCallsR = minCallsR; + this.minCallsMask = minCallsMask; + this.hashSeed = hashSeed; + this.oid = oid; + this.sparse = sparse; + this.fastFp = fastFp; + this.polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE; + this.hashAlg = hashAlg; + init(); + } + + /** + * Constructs a parameter set that uses product-form private keys (i.e. <code>polyType=PRODUCT</code>). + * + * @param N number of polynomial coefficients + * @param q modulus + * @param df1 number of ones in the private polynomial <code>f1</code> + * @param df2 number of ones in the private polynomial <code>f2</code> + * @param df3 number of ones in the private polynomial <code>f3</code> + * @param dm0 minimum acceptable number of -1's, 0's, and 1's in the polynomial <code>m'</code> in the last encryption step + * @param db number of random bits to prepend to the message + * @param c a parameter for the Index Generation Function ({@link org.spongycastle.pqc.crypto.ntru.IndexGenerator}) + * @param minCallsR minimum number of hash calls for the IGF to make + * @param minCallsMask minimum number of calls to generate the masking polynomial + * @param hashSeed whether to hash the seed in the MGF first (true) or use the seed directly (false) + * @param oid three bytes that uniquely identify the parameter set + * @param sparse whether to treat ternary polynomials as sparsely populated ({@link org.spongycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.spongycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial}) + * @param fastFp whether <code>f=1+p*F</code> for a ternary <code>F</code> (true) or <code>f</code> is ternary (false) + * @param hashAlg a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code> + */ + public NTRUEncryptionParameters(int N, int q, int df1, int df2, int df3, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg) + { + this.N = N; + this.q = q; + this.df1 = df1; + this.df2 = df2; + this.df3 = df3; + this.db = db; + this.dm0 = dm0; + this.c = c; + this.minCallsR = minCallsR; + this.minCallsMask = minCallsMask; + this.hashSeed = hashSeed; + this.oid = oid; + this.sparse = sparse; + this.fastFp = fastFp; + this.polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT; + this.hashAlg = hashAlg; + init(); + } + + private void init() + { + dr = df; + dr1 = df1; + dr2 = df2; + dr3 = df3; + dg = N / 3; + llen = 1; // ceil(log2(maxMsgLenBytes)) + maxMsgLenBytes = N * 3 / 2 / 8 - llen - db / 8 - 1; + bufferLenBits = (N * 3 / 2 + 7) / 8 * 8 + 1; + bufferLenTrits = N - 1; + pkLen = db; + } + + /** + * Reads a parameter set from an input stream. + * + * @param is an input stream + * @throws IOException + */ + public NTRUEncryptionParameters(InputStream is) + throws IOException + { + DataInputStream dis = new DataInputStream(is); + N = dis.readInt(); + q = dis.readInt(); + df = dis.readInt(); + df1 = dis.readInt(); + df2 = dis.readInt(); + df3 = dis.readInt(); + db = dis.readInt(); + dm0 = dis.readInt(); + c = dis.readInt(); + minCallsR = dis.readInt(); + minCallsMask = dis.readInt(); + hashSeed = dis.readBoolean(); + oid = new byte[3]; + dis.read(oid); + sparse = dis.readBoolean(); + fastFp = dis.readBoolean(); + polyType = dis.read(); + + String alg = dis.readUTF(); + + if ("SHA-512".equals(alg)) + { + hashAlg = new SHA512Digest(); + } + else if ("SHA-256".equals(alg)) + { + hashAlg = new SHA256Digest(); + } + + init(); + } + + public NTRUEncryptionParameters clone() + { + if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE) + { + return new NTRUEncryptionParameters(N, q, df, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg); + } + else + { + return new NTRUEncryptionParameters(N, q, df1, df2, df3, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg); + } + } + + /** + * Returns the maximum length a plaintext message can be with this parameter set. + * + * @return the maximum length in bytes + */ + public int getMaxMessageLength() + { + return maxMsgLenBytes; + } + + /** + * Writes the parameter set to an output stream + * + * @param os an output stream + * @throws IOException + */ + public void writeTo(OutputStream os) + throws IOException + { + DataOutputStream dos = new DataOutputStream(os); + dos.writeInt(N); + dos.writeInt(q); + dos.writeInt(df); + dos.writeInt(df1); + dos.writeInt(df2); + dos.writeInt(df3); + dos.writeInt(db); + dos.writeInt(dm0); + dos.writeInt(c); + dos.writeInt(minCallsR); + dos.writeInt(minCallsMask); + dos.writeBoolean(hashSeed); + dos.write(oid); + dos.writeBoolean(sparse); + dos.writeBoolean(fastFp); + dos.write(polyType); + dos.writeUTF(hashAlg.getAlgorithmName()); + } + + + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + N; + result = prime * result + bufferLenBits; + result = prime * result + bufferLenTrits; + result = prime * result + c; + result = prime * result + db; + result = prime * result + df; + result = prime * result + df1; + result = prime * result + df2; + result = prime * result + df3; + result = prime * result + dg; + result = prime * result + dm0; + result = prime * result + dr; + result = prime * result + dr1; + result = prime * result + dr2; + result = prime * result + dr3; + result = prime * result + (fastFp ? 1231 : 1237); + result = prime * result + ((hashAlg == null) ? 0 : hashAlg.getAlgorithmName().hashCode()); + result = prime * result + (hashSeed ? 1231 : 1237); + result = prime * result + llen; + result = prime * result + maxMsgLenBytes; + result = prime * result + minCallsMask; + result = prime * result + minCallsR; + result = prime * result + Arrays.hashCode(oid); + result = prime * result + pkLen; + result = prime * result + polyType; + result = prime * result + q; + result = prime * result + (sparse ? 1231 : 1237); + return result; + } + + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + NTRUEncryptionParameters other = (NTRUEncryptionParameters)obj; + if (N != other.N) + { + return false; + } + if (bufferLenBits != other.bufferLenBits) + { + return false; + } + if (bufferLenTrits != other.bufferLenTrits) + { + return false; + } + if (c != other.c) + { + return false; + } + if (db != other.db) + { + return false; + } + if (df != other.df) + { + return false; + } + if (df1 != other.df1) + { + return false; + } + if (df2 != other.df2) + { + return false; + } + if (df3 != other.df3) + { + return false; + } + if (dg != other.dg) + { + return false; + } + if (dm0 != other.dm0) + { + return false; + } + if (dr != other.dr) + { + return false; + } + if (dr1 != other.dr1) + { + return false; + } + if (dr2 != other.dr2) + { + return false; + } + if (dr3 != other.dr3) + { + return false; + } + if (fastFp != other.fastFp) + { + return false; + } + if (hashAlg == null) + { + if (other.hashAlg != null) + { + return false; + } + } + else if (!hashAlg.getAlgorithmName().equals(other.hashAlg.getAlgorithmName())) + { + return false; + } + if (hashSeed != other.hashSeed) + { + return false; + } + if (llen != other.llen) + { + return false; + } + if (maxMsgLenBytes != other.maxMsgLenBytes) + { + return false; + } + if (minCallsMask != other.minCallsMask) + { + return false; + } + if (minCallsR != other.minCallsR) + { + return false; + } + if (!Arrays.equals(oid, other.oid)) + { + return false; + } + if (pkLen != other.pkLen) + { + return false; + } + if (polyType != other.polyType) + { + return false; + } + if (q != other.q) + { + return false; + } + if (sparse != other.sparse) + { + return false; + } + return true; + } + + public String toString() + { + StringBuilder output = new StringBuilder("EncryptionParameters(N=" + N + " q=" + q); + if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE) + { + output.append(" polyType=SIMPLE df=" + df); + } + else + { + output.append(" polyType=PRODUCT df1=" + df1 + " df2=" + df2 + " df3=" + df3); + } + output.append(" dm0=" + dm0 + " db=" + db + " c=" + c + " minCallsR=" + minCallsR + " minCallsMask=" + minCallsMask + + " hashSeed=" + hashSeed + " hashAlg=" + hashAlg + " oid=" + Arrays.toString(oid) + " sparse=" + sparse + ")"); + return output.toString(); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionPrivateKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionPrivateKeyParameters.java new file mode 100644 index 00000000..5b0adbea --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionPrivateKeyParameters.java @@ -0,0 +1,199 @@ +package org.spongycastle.pqc.crypto.ntru; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.spongycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.IntegerPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.Polynomial; +import org.spongycastle.pqc.math.ntru.polynomial.ProductFormPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial; + +/** + * A NtruEncrypt private key is essentially a polynomial named <code>f</code> + * which takes different forms depending on whether product-form polynomials are used, + * and on <code>fastP</code><br> + * The inverse of <code>f</code> modulo <code>p</code> is precomputed on initialization. + */ +public class NTRUEncryptionPrivateKeyParameters + extends NTRUEncryptionKeyParameters +{ + public Polynomial t; + public IntegerPolynomial fp; + public IntegerPolynomial h; + + /** + * Constructs a new private key from a polynomial + * + * @param h the public polynomial for the key. + * @param t the polynomial which determines the key: if <code>fastFp=true</code>, <code>f=1+3t</code>; otherwise, <code>f=t</code> + * @param fp the inverse of <code>f</code> + * @param params the NtruEncrypt parameters to use + */ + public NTRUEncryptionPrivateKeyParameters(IntegerPolynomial h, Polynomial t, IntegerPolynomial fp, NTRUEncryptionParameters params) + { + super(true, params); + + this.h = h; + this.t = t; + this.fp = fp; + } + + /** + * Converts a byte array to a polynomial <code>f</code> and constructs a new private key + * + * @param b an encoded polynomial + * @param params the NtruEncrypt parameters to use + * @see #getEncoded() + */ + public NTRUEncryptionPrivateKeyParameters(byte[] b, NTRUEncryptionParameters params) + throws IOException + { + this(new ByteArrayInputStream(b), params); + } + + /** + * Reads a polynomial <code>f</code> from an input stream and constructs a new private key + * + * @param is an input stream + * @param params the NtruEncrypt parameters to use + * @see #writeTo(OutputStream) + */ + public NTRUEncryptionPrivateKeyParameters(InputStream is, NTRUEncryptionParameters params) + throws IOException + { + super(true, params); + + if (params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT) + { + int N = params.N; + int df1 = params.df1; + int df2 = params.df2; + int df3Ones = params.df3; + int df3NegOnes = params.fastFp ? params.df3 : params.df3 - 1; + h = IntegerPolynomial.fromBinary(is, params.N, params.q); + t = ProductFormPolynomial.fromBinary(is, N, df1, df2, df3Ones, df3NegOnes); + } + else + { + h = IntegerPolynomial.fromBinary(is, params.N, params.q); + IntegerPolynomial fInt = IntegerPolynomial.fromBinary3Tight(is, params.N); + t = params.sparse ? new SparseTernaryPolynomial(fInt) : new DenseTernaryPolynomial(fInt); + } + + init(); + } + + /** + * Initializes <code>fp</code> from t. + */ + private void init() + { + if (params.fastFp) + { + fp = new IntegerPolynomial(params.N); + fp.coeffs[0] = 1; + } + else + { + fp = t.toIntegerPolynomial().invertF3(); + } + } + + /** + * Converts the key to a byte array + * + * @return the encoded key + * @see #NTRUEncryptionPrivateKeyParameters(byte[], NTRUEncryptionParameters) + */ + public byte[] getEncoded() + { + byte[] hBytes = h.toBinary(params.q); + byte[] tBytes; + + if (t instanceof ProductFormPolynomial) + { + tBytes = ((ProductFormPolynomial)t).toBinary(); + } + else + { + tBytes = t.toIntegerPolynomial().toBinary3Tight(); + } + + byte[] res = new byte[hBytes.length + tBytes.length]; + + System.arraycopy(hBytes, 0, res, 0, hBytes.length); + System.arraycopy(tBytes, 0, res, hBytes.length, tBytes.length); + + return res; + } + + /** + * Writes the key to an output stream + * + * @param os an output stream + * @throws IOException + * @see #NTRUEncryptionPrivateKeyParameters(InputStream, NTRUEncryptionParameters) + */ + public void writeTo(OutputStream os) + throws IOException + { + os.write(getEncoded()); + } + + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((params == null) ? 0 : params.hashCode()); + result = prime * result + ((t == null) ? 0 : t.hashCode()); + result = prime * result + ((h == null) ? 0 : h.hashCode()); + return result; + } + + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NTRUEncryptionPrivateKeyParameters)) + { + return false; + } + NTRUEncryptionPrivateKeyParameters other = (NTRUEncryptionPrivateKeyParameters)obj; + if (params == null) + { + if (other.params != null) + { + return false; + } + } + else if (!params.equals(other.params)) + { + return false; + } + if (t == null) + { + if (other.t != null) + { + return false; + } + } + else if (!t.equals(other.t)) + { + return false; + } + if (!h.equals(other.h)) + { + return false; + } + return true; + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionPublicKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionPublicKeyParameters.java new file mode 100644 index 00000000..0f3abd9c --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEncryptionPublicKeyParameters.java @@ -0,0 +1,131 @@ +package org.spongycastle.pqc.crypto.ntru; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.spongycastle.pqc.math.ntru.polynomial.IntegerPolynomial; + +/** + * A NtruEncrypt public key is essentially a polynomial named <code>h</code>. + */ +public class NTRUEncryptionPublicKeyParameters + extends NTRUEncryptionKeyParameters +{ + public IntegerPolynomial h; + + /** + * Constructs a new public key from a polynomial + * + * @param h the polynomial <code>h</code> which determines the key + * @param params the NtruEncrypt parameters to use + */ + public NTRUEncryptionPublicKeyParameters(IntegerPolynomial h, NTRUEncryptionParameters params) + { + super(false, params); + + this.h = h; + } + + /** + * Converts a byte array to a polynomial <code>h</code> and constructs a new public key + * + * @param b an encoded polynomial + * @param params the NtruEncrypt parameters to use + * @see #getEncoded() + */ + public NTRUEncryptionPublicKeyParameters(byte[] b, NTRUEncryptionParameters params) + { + super(false, params); + + h = IntegerPolynomial.fromBinary(b, params.N, params.q); + } + + /** + * Reads a polynomial <code>h</code> from an input stream and constructs a new public key + * + * @param is an input stream + * @param params the NtruEncrypt parameters to use + * @see #writeTo(OutputStream) + */ + public NTRUEncryptionPublicKeyParameters(InputStream is, NTRUEncryptionParameters params) + throws IOException + { + super(false, params); + + h = IntegerPolynomial.fromBinary(is, params.N, params.q); + } + + /** + * Converts the key to a byte array + * + * @return the encoded key + * @see #NTRUEncryptionPublicKeyParameters(byte[], NTRUEncryptionParameters) + */ + public byte[] getEncoded() + { + return h.toBinary(params.q); + } + + /** + * Writes the key to an output stream + * + * @param os an output stream + * @throws IOException + * @see #NTRUEncryptionPublicKeyParameters(InputStream, NTRUEncryptionParameters) + */ + public void writeTo(OutputStream os) + throws IOException + { + os.write(getEncoded()); + } + + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((h == null) ? 0 : h.hashCode()); + result = prime * result + ((params == null) ? 0 : params.hashCode()); + return result; + } + + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NTRUEncryptionPublicKeyParameters)) + { + return false; + } + NTRUEncryptionPublicKeyParameters other = (NTRUEncryptionPublicKeyParameters)obj; + if (h == null) + { + if (other.h != null) + { + return false; + } + } + else if (!h.equals(other.h)) + { + return false; + } + if (params == null) + { + if (other.params != null) + { + return false; + } + } + else if (!params.equals(other.params)) + { + return false; + } + return true; + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEngine.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEngine.java new file mode 100644 index 00000000..571057e2 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUEngine.java @@ -0,0 +1,495 @@ +package org.spongycastle.pqc.crypto.ntru; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.DataLengthException; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.IntegerPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.Polynomial; +import org.spongycastle.pqc.math.ntru.polynomial.ProductFormPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.TernaryPolynomial; +import org.spongycastle.util.Arrays; + +/** + * Encrypts, decrypts data and generates key pairs.<br> + * The parameter p is hardcoded to 3. + */ +public class NTRUEngine + implements AsymmetricBlockCipher +{ + private boolean forEncryption; + private NTRUEncryptionParameters params; + private NTRUEncryptionPublicKeyParameters pubKey; + private NTRUEncryptionPrivateKeyParameters privKey; + private SecureRandom random; + + /** + * Constructs a new instance with a set of encryption parameters. + * + */ + public NTRUEngine() + { + } + + public void init(boolean forEncryption, CipherParameters parameters) + { + this.forEncryption = forEncryption; + if (forEncryption) + { + if (parameters instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)parameters; + + this.random = p.getRandom(); + this.pubKey = (NTRUEncryptionPublicKeyParameters)p.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.pubKey = (NTRUEncryptionPublicKeyParameters)parameters; + } + + this.params = pubKey.getParameters(); + } + else + { + this.privKey = (NTRUEncryptionPrivateKeyParameters)parameters; + this.params = privKey.getParameters(); + } + } + + public int getInputBlockSize() + { + return params.maxMsgLenBytes; + } + + public int getOutputBlockSize() + { + return ((params.N * log2(params.q)) + 7) / 8; + } + + public byte[] processBlock(byte[] in, int inOff, int len) + throws InvalidCipherTextException + { + byte[] tmp = new byte[len]; + + System.arraycopy(in, inOff, tmp, 0, len); + + if (forEncryption) + { + return encrypt(tmp, pubKey); + } + else + { + return decrypt(tmp, privKey); + } + } + + /** + * Encrypts a message.<br/> + * See P1363.1 section 9.2.2. + * + * @param m The message to encrypt + * @param pubKey the public key to encrypt the message with + * @return the encrypted message + */ + private byte[] encrypt(byte[] m, NTRUEncryptionPublicKeyParameters pubKey) + { + IntegerPolynomial pub = pubKey.h; + int N = params.N; + int q = params.q; + + int maxLenBytes = params.maxMsgLenBytes; + int db = params.db; + int bufferLenBits = params.bufferLenBits; + int dm0 = params.dm0; + int pkLen = params.pkLen; + int minCallsMask = params.minCallsMask; + boolean hashSeed = params.hashSeed; + byte[] oid = params.oid; + + int l = m.length; + if (maxLenBytes > 255) + { + throw new IllegalArgumentException("llen values bigger than 1 are not supported"); + } + if (l > maxLenBytes) + { + throw new DataLengthException("Message too long: " + l + ">" + maxLenBytes); + } + + while (true) + { + // M = b|octL|m|p0 + byte[] b = new byte[db / 8]; + random.nextBytes(b); + byte[] p0 = new byte[maxLenBytes + 1 - l]; + byte[] M = new byte[bufferLenBits / 8]; + + System.arraycopy(b, 0, M, 0, b.length); + M[b.length] = (byte)l; + System.arraycopy(m, 0, M, b.length + 1, m.length); + System.arraycopy(p0, 0, M, b.length + 1 + m.length, p0.length); + + IntegerPolynomial mTrin = IntegerPolynomial.fromBinary3Sves(M, N); + + // sData = OID|m|b|hTrunc + byte[] bh = pub.toBinary(q); + byte[] hTrunc = copyOf(bh, pkLen / 8); + byte[] sData = buildSData(oid, m, l, b, hTrunc); + + Polynomial r = generateBlindingPoly(sData, M); + IntegerPolynomial R = r.mult(pub, q); + IntegerPolynomial R4 = (IntegerPolynomial)R.clone(); + R4.modPositive(4); + byte[] oR4 = R4.toBinary(4); + IntegerPolynomial mask = MGF(oR4, N, minCallsMask, hashSeed); + mTrin.add(mask); + mTrin.mod3(); + + if (mTrin.count(-1) < dm0) + { + continue; + } + if (mTrin.count(0) < dm0) + { + continue; + } + if (mTrin.count(1) < dm0) + { + continue; + } + + R.add(mTrin, q); + R.ensurePositive(q); + return R.toBinary(q); + } + } + + private byte[] buildSData(byte[] oid, byte[] m, int l, byte[] b, byte[] hTrunc) + { + byte[] sData = new byte[oid.length + l + b.length + hTrunc.length]; + + System.arraycopy(oid, 0, sData, 0, oid.length); + System.arraycopy(m, 0, sData, oid.length, m.length); + System.arraycopy(b, 0, sData, oid.length + m.length, b.length); + System.arraycopy(hTrunc, 0, sData, oid.length + m.length + b.length, hTrunc.length); + return sData; + } + + protected IntegerPolynomial encrypt(IntegerPolynomial m, TernaryPolynomial r, IntegerPolynomial pubKey) + { + IntegerPolynomial e = r.mult(pubKey, params.q); + e.add(m, params.q); + e.ensurePositive(params.q); + return e; + } + + /** + * Deterministically generates a blinding polynomial from a seed and a message representative. + * + * @param seed + * @param M message representative + * @return a blinding polynomial + */ + private Polynomial generateBlindingPoly(byte[] seed, byte[] M) + { + IndexGenerator ig = new IndexGenerator(seed, params); + + if (params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT) + { + SparseTernaryPolynomial r1 = new SparseTernaryPolynomial(generateBlindingCoeffs(ig, params.dr1)); + SparseTernaryPolynomial r2 = new SparseTernaryPolynomial(generateBlindingCoeffs(ig, params.dr2)); + SparseTernaryPolynomial r3 = new SparseTernaryPolynomial(generateBlindingCoeffs(ig, params.dr3)); + return new ProductFormPolynomial(r1, r2, r3); + } + else + { + int dr = params.dr; + boolean sparse = params.sparse; + int[] r = generateBlindingCoeffs(ig, dr); + if (sparse) + { + return new SparseTernaryPolynomial(r); + } + else + { + return new DenseTernaryPolynomial(r); + } + } + } + + /** + * Generates an <code>int</code> array containing <code>dr</code> elements equal to <code>1</code> + * and <code>dr</code> elements equal to <code>-1</code> using an index generator. + * + * @param ig an index generator + * @param dr number of ones / negative ones + * @return an array containing numbers between <code>-1</code> and <code>1</code> + */ + private int[] generateBlindingCoeffs(IndexGenerator ig, int dr) + { + int N = params.N; + + int[] r = new int[N]; + for (int coeff = -1; coeff <= 1; coeff += 2) + { + int t = 0; + while (t < dr) + { + int i = ig.nextIndex(); + if (r[i] == 0) + { + r[i] = coeff; + t++; + } + } + } + + return r; + } + + /** + * An implementation of MGF-TP-1 from P1363.1 section 8.4.1.1. + * + * @param seed + * @param N + * @param minCallsR + * @param hashSeed whether to hash the seed + * @return + */ + private IntegerPolynomial MGF(byte[] seed, int N, int minCallsR, boolean hashSeed) + { + Digest hashAlg = params.hashAlg; + int hashLen = hashAlg.getDigestSize(); + byte[] buf = new byte[minCallsR * hashLen]; + byte[] Z = hashSeed ? calcHash(hashAlg, seed) : seed; + int counter = 0; + while (counter < minCallsR) + { + hashAlg.update(Z, 0, Z.length); + putInt(hashAlg, counter); + + byte[] hash = calcHash(hashAlg); + System.arraycopy(hash, 0, buf, counter * hashLen, hashLen); + counter++; + } + + IntegerPolynomial i = new IntegerPolynomial(N); + while (true) + { + int cur = 0; + for (int index = 0; index != buf.length; index++) + { + int O = (int)buf[index] & 0xFF; + if (O >= 243) // 243 = 3^5 + { + continue; + } + + for (int terIdx = 0; terIdx < 4; terIdx++) + { + int rem3 = O % 3; + i.coeffs[cur] = rem3 - 1; + cur++; + if (cur == N) + { + return i; + } + O = (O - rem3) / 3; + } + + i.coeffs[cur] = O - 1; + cur++; + if (cur == N) + { + return i; + } + } + + if (cur >= N) + { + return i; + } + + hashAlg.update(Z, 0, Z.length); + putInt(hashAlg, counter); + + byte[] hash = calcHash(hashAlg); + + buf = hash; + + counter++; + } + } + + private void putInt(Digest hashAlg, int counter) + { + hashAlg.update((byte)(counter >> 24)); + hashAlg.update((byte)(counter >> 16)); + hashAlg.update((byte)(counter >> 8)); + hashAlg.update((byte)counter); + } + + private byte[] calcHash(Digest hashAlg) + { + byte[] tmp = new byte[hashAlg.getDigestSize()]; + + hashAlg.doFinal(tmp, 0); + + return tmp; + } + + private byte[] calcHash(Digest hashAlg, byte[] input) + { + byte[] tmp = new byte[hashAlg.getDigestSize()]; + + hashAlg.update(input, 0, input.length); + hashAlg.doFinal(tmp, 0); + + return tmp; + } + /** + * Decrypts a message.<br/> + * See P1363.1 section 9.2.3. + * + * @param data The message to decrypt + * @param privKey the corresponding private key + * @return the decrypted message + * @throws InvalidCipherTextException if the encrypted data is invalid, or <code>maxLenBytes</code> is greater than 255 + */ + private byte[] decrypt(byte[] data, NTRUEncryptionPrivateKeyParameters privKey) + throws InvalidCipherTextException + { + Polynomial priv_t = privKey.t; + IntegerPolynomial priv_fp = privKey.fp; + IntegerPolynomial pub = privKey.h; + int N = params.N; + int q = params.q; + int db = params.db; + int maxMsgLenBytes = params.maxMsgLenBytes; + int dm0 = params.dm0; + int pkLen = params.pkLen; + int minCallsMask = params.minCallsMask; + boolean hashSeed = params.hashSeed; + byte[] oid = params.oid; + + if (maxMsgLenBytes > 255) + { + throw new DataLengthException("maxMsgLenBytes values bigger than 255 are not supported"); + } + + int bLen = db / 8; + + IntegerPolynomial e = IntegerPolynomial.fromBinary(data, N, q); + IntegerPolynomial ci = decrypt(e, priv_t, priv_fp); + + if (ci.count(-1) < dm0) + { + throw new InvalidCipherTextException("Less than dm0 coefficients equal -1"); + } + if (ci.count(0) < dm0) + { + throw new InvalidCipherTextException("Less than dm0 coefficients equal 0"); + } + if (ci.count(1) < dm0) + { + throw new InvalidCipherTextException("Less than dm0 coefficients equal 1"); + } + + IntegerPolynomial cR = (IntegerPolynomial)e.clone(); + cR.sub(ci); + cR.modPositive(q); + IntegerPolynomial cR4 = (IntegerPolynomial)cR.clone(); + cR4.modPositive(4); + byte[] coR4 = cR4.toBinary(4); + IntegerPolynomial mask = MGF(coR4, N, minCallsMask, hashSeed); + IntegerPolynomial cMTrin = ci; + cMTrin.sub(mask); + cMTrin.mod3(); + byte[] cM = cMTrin.toBinary3Sves(); + + byte[] cb = new byte[bLen]; + System.arraycopy(cM, 0, cb, 0, bLen); + int cl = cM[bLen] & 0xFF; // llen=1, so read one byte + if (cl > maxMsgLenBytes) + { + throw new InvalidCipherTextException("Message too long: " + cl + ">" + maxMsgLenBytes); + } + byte[] cm = new byte[cl]; + System.arraycopy(cM, bLen + 1, cm, 0, cl); + byte[] p0 = new byte[cM.length - (bLen + 1 + cl)]; + System.arraycopy(cM, bLen + 1 + cl, p0, 0, p0.length); + if (!Arrays.areEqual(p0, new byte[p0.length])) + { + throw new InvalidCipherTextException("The message is not followed by zeroes"); + } + + // sData = OID|m|b|hTrunc + byte[] bh = pub.toBinary(q); + byte[] hTrunc = copyOf(bh, pkLen / 8); + byte[] sData = buildSData(oid, cm, cl, cb, hTrunc); + + Polynomial cr = generateBlindingPoly(sData, cm); + IntegerPolynomial cRPrime = cr.mult(pub); + cRPrime.modPositive(q); + if (!cRPrime.equals(cR)) + { + throw new InvalidCipherTextException("Invalid message encoding"); + } + + return cm; + } + + /** + * @param e + * @param priv_t a polynomial such that if <code>fastFp=true</code>, <code>f=1+3*priv_t</code>; otherwise, <code>f=priv_t</code> + * @param priv_fp + * @return + */ + protected IntegerPolynomial decrypt(IntegerPolynomial e, Polynomial priv_t, IntegerPolynomial priv_fp) + { + IntegerPolynomial a; + if (params.fastFp) + { + a = priv_t.mult(e, params.q); + a.mult(3); + a.add(e); + } + else + { + a = priv_t.mult(e, params.q); + } + a.center0(params.q); + a.mod3(); + + IntegerPolynomial c = params.fastFp ? a : new DenseTernaryPolynomial(a).mult(priv_fp, 3); + c.center0(3); + return c; + } + + private byte[] copyOf(byte[] src, int len) + { + byte[] tmp = new byte[len]; + + System.arraycopy(src, 0, tmp, 0, len < src.length ? len : src.length); + + return tmp; + } + + private int log2(int value) + { + if (value == 2048) + { + return 11; + } + + throw new IllegalStateException("log2 not fully implemented"); + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUParameters.java new file mode 100644 index 00000000..3ce2154d --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUParameters.java @@ -0,0 +1,7 @@ +package org.spongycastle.pqc.crypto.ntru; + +public class NTRUParameters +{ + public static final int TERNARY_POLYNOMIAL_TYPE_SIMPLE = 0; + public static final int TERNARY_POLYNOMIAL_TYPE_PRODUCT = 1; +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigner.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigner.java new file mode 100644 index 00000000..0886cff1 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigner.java @@ -0,0 +1,263 @@ +package org.spongycastle.pqc.crypto.ntru; + +import java.nio.ByteBuffer; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.Digest; +import org.spongycastle.pqc.math.ntru.polynomial.IntegerPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.Polynomial; + +/** +* Signs, verifies data and generates key pairs. +* @deprecated the NTRUSigner algorithm was broken in 2012 by Ducas and Nguyen. See +* <a href="http://www.di.ens.fr/~ducas/NTRUSign_Cryptanalysis/DucasNguyen_Learning.pdf"> +* http://www.di.ens.fr/~ducas/NTRUSign_Cryptanalysis/DucasNguyen_Learning.pdf</a> +* for details. +*/ +public class NTRUSigner +{ + private NTRUSigningParameters params; + private Digest hashAlg; + private NTRUSigningPrivateKeyParameters signingKeyPair; + private NTRUSigningPublicKeyParameters verificationKey; + + /** + * Constructs a new instance with a set of signature parameters. + * + * @param params signature parameters + */ + public NTRUSigner(NTRUSigningParameters params) + { + this.params = params; + } + + /** + * Resets the engine for signing a message. + * + * @param forSigning + * @param params + */ + public void init(boolean forSigning, CipherParameters params) + { + if (forSigning) + { + this.signingKeyPair = (NTRUSigningPrivateKeyParameters)params; + } + else + { + this.verificationKey = (NTRUSigningPublicKeyParameters)params; + } + hashAlg = this.params.hashAlg; + hashAlg.reset(); + } + + /** + * Adds data to sign or verify. + * + * @param b data + */ + public void update(byte b) + { + if (hashAlg == null) + { + throw new IllegalStateException("Call initSign or initVerify first!"); + } + + hashAlg.update(b); + } + + /** + * Adds data to sign or verify. + * + * @param m data + * @param off offset + * @param length number of bytes + */ + public void update(byte[] m, int off, int length) + { + if (hashAlg == null) + { + throw new IllegalStateException("Call initSign or initVerify first!"); + } + + hashAlg.update(m, off, length); + } + + /** + * Adds data to sign and computes a signature over this data and any data previously added via {@link #update(byte[], int, int)}. + * + * @return a signature + * @throws IllegalStateException if <code>initSign</code> was not called + */ + public byte[] generateSignature() + { + if (hashAlg == null || signingKeyPair == null) + { + throw new IllegalStateException("Call initSign first!"); + } + + byte[] msgHash = new byte[hashAlg.getDigestSize()]; + + hashAlg.doFinal(msgHash, 0); + return signHash(msgHash, signingKeyPair); + } + + private byte[] signHash(byte[] msgHash, NTRUSigningPrivateKeyParameters kp) + { + int r = 0; + IntegerPolynomial s; + IntegerPolynomial i; + + NTRUSigningPublicKeyParameters kPub = kp.getPublicKey(); + do + { + r++; + if (r > params.signFailTolerance) + { + throw new IllegalStateException("Signing failed: too many retries (max=" + params.signFailTolerance + ")"); + } + i = createMsgRep(msgHash, r); + s = sign(i, kp); + } + while (!verify(i, s, kPub.h)); + + byte[] rawSig = s.toBinary(params.q); + ByteBuffer sbuf = ByteBuffer.allocate(rawSig.length + 4); + sbuf.put(rawSig); + sbuf.putInt(r); + return sbuf.array(); + } + + private IntegerPolynomial sign(IntegerPolynomial i, NTRUSigningPrivateKeyParameters kp) + { + int N = params.N; + int q = params.q; + int perturbationBases = params.B; + + NTRUSigningPrivateKeyParameters kPriv = kp; + NTRUSigningPublicKeyParameters kPub = kp.getPublicKey(); + + IntegerPolynomial s = new IntegerPolynomial(N); + int iLoop = perturbationBases; + while (iLoop >= 1) + { + Polynomial f = kPriv.getBasis(iLoop).f; + Polynomial fPrime = kPriv.getBasis(iLoop).fPrime; + + IntegerPolynomial y = f.mult(i); + y.div(q); + y = fPrime.mult(y); + + IntegerPolynomial x = fPrime.mult(i); + x.div(q); + x = f.mult(x); + + IntegerPolynomial si = y; + si.sub(x); + s.add(si); + + IntegerPolynomial hi = (IntegerPolynomial)kPriv.getBasis(iLoop).h.clone(); + if (iLoop > 1) + { + hi.sub(kPriv.getBasis(iLoop - 1).h); + } + else + { + hi.sub(kPub.h); + } + i = si.mult(hi, q); + + iLoop--; + } + + Polynomial f = kPriv.getBasis(0).f; + Polynomial fPrime = kPriv.getBasis(0).fPrime; + + IntegerPolynomial y = f.mult(i); + y.div(q); + y = fPrime.mult(y); + + IntegerPolynomial x = fPrime.mult(i); + x.div(q); + x = f.mult(x); + + y.sub(x); + s.add(y); + s.modPositive(q); + return s; + } + + /** + * Verifies a signature for any data previously added via {@link #update(byte[], int, int)}. + * + * @param sig a signature + * @return whether the signature is valid + * @throws IllegalStateException if <code>initVerify</code> was not called + */ + public boolean verifySignature(byte[] sig) + { + if (hashAlg == null || verificationKey == null) + { + throw new IllegalStateException("Call initVerify first!"); + } + + byte[] msgHash = new byte[hashAlg.getDigestSize()]; + + hashAlg.doFinal(msgHash, 0); + + return verifyHash(msgHash, sig, verificationKey); + } + + private boolean verifyHash(byte[] msgHash, byte[] sig, NTRUSigningPublicKeyParameters pub) + { + ByteBuffer sbuf = ByteBuffer.wrap(sig); + byte[] rawSig = new byte[sig.length - 4]; + sbuf.get(rawSig); + IntegerPolynomial s = IntegerPolynomial.fromBinary(rawSig, params.N, params.q); + int r = sbuf.getInt(); + return verify(createMsgRep(msgHash, r), s, pub.h); + } + + private boolean verify(IntegerPolynomial i, IntegerPolynomial s, IntegerPolynomial h) + { + int q = params.q; + double normBoundSq = params.normBoundSq; + double betaSq = params.betaSq; + + IntegerPolynomial t = h.mult(s, q); + t.sub(i); + long centeredNormSq = (long)(s.centeredNormSq(q) + betaSq * t.centeredNormSq(q)); + return centeredNormSq <= normBoundSq; + } + + protected IntegerPolynomial createMsgRep(byte[] msgHash, int r) + { + int N = params.N; + int q = params.q; + + int c = 31 - Integer.numberOfLeadingZeros(q); + int B = (c + 7) / 8; + IntegerPolynomial i = new IntegerPolynomial(N); + + ByteBuffer cbuf = ByteBuffer.allocate(msgHash.length + 4); + cbuf.put(msgHash); + cbuf.putInt(r); + NTRUSignerPrng prng = new NTRUSignerPrng(cbuf.array(), params.hashAlg); + + for (int t = 0; t < N; t++) + { + byte[] o = prng.nextBytes(B); + int hi = o[o.length - 1]; + hi >>= 8 * B - c; + hi <<= 8 * B - c; + o[o.length - 1] = (byte)hi; + + ByteBuffer obuf = ByteBuffer.allocate(4); + obuf.put(o); + obuf.rewind(); + // reverse byte order so it matches the endianness of java ints + i.coeffs[t] = Integer.reverseBytes(obuf.getInt()); + } + return i; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSignerPrng.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSignerPrng.java new file mode 100644 index 00000000..c9278dd5 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSignerPrng.java @@ -0,0 +1,64 @@ +package org.spongycastle.pqc.crypto.ntru; + +import java.nio.ByteBuffer; + +import org.spongycastle.crypto.Digest; + +/** + * An implementation of the deterministic pseudo-random generator in EESS section 3.7.3.1 + */ +public class NTRUSignerPrng +{ + private int counter; + private byte[] seed; + private Digest hashAlg; + + /** + * Constructs a new PRNG and seeds it with a byte array. + * + * @param seed a seed + * @param hashAlg the hash algorithm to use + */ + NTRUSignerPrng(byte[] seed, Digest hashAlg) + { + counter = 0; + this.seed = seed; + this.hashAlg = hashAlg; + } + + /** + * Returns <code>n</code> random bytes + * + * @param n number of bytes to return + * @return the next <code>n</code> random bytes + */ + byte[] nextBytes(int n) + { + ByteBuffer buf = ByteBuffer.allocate(n); + + while (buf.hasRemaining()) + { + ByteBuffer cbuf = ByteBuffer.allocate(seed.length + 4); + cbuf.put(seed); + cbuf.putInt(counter); + byte[] array = cbuf.array(); + byte[] hash = new byte[hashAlg.getDigestSize()]; + + hashAlg.update(array, 0, array.length); + + hashAlg.doFinal(hash, 0); + + if (buf.remaining() < hash.length) + { + buf.put(hash, 0, buf.remaining()); + } + else + { + buf.put(hash); + } + counter++; + } + + return buf.array(); + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningKeyGenerationParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningKeyGenerationParameters.java new file mode 100644 index 00000000..f9c17f1c --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningKeyGenerationParameters.java @@ -0,0 +1,407 @@ +package org.spongycastle.pqc.crypto.ntru; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; +import java.text.DecimalFormat; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.KeyGenerationParameters; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.digests.SHA512Digest; + +/** + * A set of parameters for NtruSign. Several predefined parameter sets are available and new ones can be created as well. + */ +public class NTRUSigningKeyGenerationParameters + extends KeyGenerationParameters + implements Cloneable +{ + public static final int BASIS_TYPE_STANDARD = 0; + public static final int BASIS_TYPE_TRANSPOSE = 1; + + public static final int KEY_GEN_ALG_RESULTANT = 0; + public static final int KEY_GEN_ALG_FLOAT = 1; + + /** + * Gives 128 bits of security + */ + public static final NTRUSigningKeyGenerationParameters APR2011_439 = new NTRUSigningKeyGenerationParameters(439, 2048, 146, 1, BASIS_TYPE_TRANSPOSE, 0.165, 490, 280, false, true, KEY_GEN_ALG_RESULTANT, new SHA256Digest()); + + /** + * Like <code>APR2011_439</code>, this parameter set gives 128 bits of security but uses product-form polynomials + */ + public static final NTRUSigningKeyGenerationParameters APR2011_439_PROD = new NTRUSigningKeyGenerationParameters(439, 2048, 9, 8, 5, 1, BASIS_TYPE_TRANSPOSE, 0.165, 490, 280, false, true, KEY_GEN_ALG_RESULTANT, new SHA256Digest()); + + /** + * Gives 256 bits of security + */ + public static final NTRUSigningKeyGenerationParameters APR2011_743 = new NTRUSigningKeyGenerationParameters(743, 2048, 248, 1, BASIS_TYPE_TRANSPOSE, 0.127, 560, 360, true, false, KEY_GEN_ALG_RESULTANT, new SHA512Digest()); + + /** + * Like <code>APR2011_439</code>, this parameter set gives 256 bits of security but uses product-form polynomials + */ + public static final NTRUSigningKeyGenerationParameters APR2011_743_PROD = new NTRUSigningKeyGenerationParameters(743, 2048, 11, 11, 15, 1, BASIS_TYPE_TRANSPOSE, 0.127, 560, 360, true, false, KEY_GEN_ALG_RESULTANT, new SHA512Digest()); + + /** + * Generates key pairs quickly. Use for testing only. + */ + public static final NTRUSigningKeyGenerationParameters TEST157 = new NTRUSigningKeyGenerationParameters(157, 256, 29, 1, BASIS_TYPE_TRANSPOSE, 0.38, 200, 80, false, false, KEY_GEN_ALG_RESULTANT, new SHA256Digest()); + /** + * Generates key pairs quickly. Use for testing only. + */ + public static final NTRUSigningKeyGenerationParameters TEST157_PROD = new NTRUSigningKeyGenerationParameters(157, 256, 5, 5, 8, 1, BASIS_TYPE_TRANSPOSE, 0.38, 200, 80, false, false, KEY_GEN_ALG_RESULTANT, new SHA256Digest()); + + + public int N; + public int q; + public int d, d1, d2, d3, B; + double beta; + public double betaSq; + double normBound; + public double normBoundSq; + public int signFailTolerance = 100; + double keyNormBound; + public double keyNormBoundSq; + public boolean primeCheck; // true if N and 2N+1 are prime + public int basisType; + int bitsF = 6; // max #bits needed to encode one coefficient of the polynomial F + public boolean sparse; // whether to treat ternary polynomials as sparsely populated + public int keyGenAlg; + public Digest hashAlg; + public int polyType; + + /** + * Constructs a parameter set that uses ternary private keys (i.e. <code>polyType=SIMPLE</code>). + * + * @param N number of polynomial coefficients + * @param q modulus + * @param d number of -1's in the private polynomials <code>f</code> and <code>g</code> + * @param B number of perturbations + * @param basisType whether to use the standard or transpose lattice + * @param beta balancing factor for the transpose lattice + * @param normBound maximum norm for valid signatures + * @param keyNormBound maximum norm for the ploynomials <code>F</code> and <code>G</code> + * @param primeCheck whether <code>2N+1</code> is prime + * @param sparse whether to treat ternary polynomials as sparsely populated ({@link org.spongycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.spongycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial}) + * @param keyGenAlg <code>RESULTANT</code> produces better bases, <code>FLOAT</code> is slightly faster. <code>RESULTANT</code> follows the EESS standard while <code>FLOAT</code> is described in Hoffstein et al: An Introduction to Mathematical Cryptography. + * @param hashAlg a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method. + */ + public NTRUSigningKeyGenerationParameters(int N, int q, int d, int B, int basisType, double beta, double normBound, double keyNormBound, boolean primeCheck, boolean sparse, int keyGenAlg, Digest hashAlg) + { + super(new SecureRandom(), N); + this.N = N; + this.q = q; + this.d = d; + this.B = B; + this.basisType = basisType; + this.beta = beta; + this.normBound = normBound; + this.keyNormBound = keyNormBound; + this.primeCheck = primeCheck; + this.sparse = sparse; + this.keyGenAlg = keyGenAlg; + this.hashAlg = hashAlg; + polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE; + init(); + } + + /** + * Constructs a parameter set that uses product-form private keys (i.e. <code>polyType=PRODUCT</code>). + * + * @param N number of polynomial coefficients + * @param q modulus + * @param d1 number of -1's in the private polynomials <code>f</code> and <code>g</code> + * @param d2 number of -1's in the private polynomials <code>f</code> and <code>g</code> + * @param d3 number of -1's in the private polynomials <code>f</code> and <code>g</code> + * @param B number of perturbations + * @param basisType whether to use the standard or transpose lattice + * @param beta balancing factor for the transpose lattice + * @param normBound maximum norm for valid signatures + * @param keyNormBound maximum norm for the ploynomials <code>F</code> and <code>G</code> + * @param primeCheck whether <code>2N+1</code> is prime + * @param sparse whether to treat ternary polynomials as sparsely populated ({@link org.spongycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.spongycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial}) + * @param keyGenAlg <code>RESULTANT</code> produces better bases, <code>FLOAT</code> is slightly faster. <code>RESULTANT</code> follows the EESS standard while <code>FLOAT</code> is described in Hoffstein et al: An Introduction to Mathematical Cryptography. + * @param hashAlg a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method. + */ + public NTRUSigningKeyGenerationParameters(int N, int q, int d1, int d2, int d3, int B, int basisType, double beta, double normBound, double keyNormBound, boolean primeCheck, boolean sparse, int keyGenAlg, Digest hashAlg) + { + super(new SecureRandom(), N); + this.N = N; + this.q = q; + this.d1 = d1; + this.d2 = d2; + this.d3 = d3; + this.B = B; + this.basisType = basisType; + this.beta = beta; + this.normBound = normBound; + this.keyNormBound = keyNormBound; + this.primeCheck = primeCheck; + this.sparse = sparse; + this.keyGenAlg = keyGenAlg; + this.hashAlg = hashAlg; + polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT; + init(); + } + + private void init() + { + betaSq = beta * beta; + normBoundSq = normBound * normBound; + keyNormBoundSq = keyNormBound * keyNormBound; + } + + /** + * Reads a parameter set from an input stream. + * + * @param is an input stream + * @throws java.io.IOException + */ + public NTRUSigningKeyGenerationParameters(InputStream is) + throws IOException + { + super(new SecureRandom(), 0); // TODO: + DataInputStream dis = new DataInputStream(is); + N = dis.readInt(); + q = dis.readInt(); + d = dis.readInt(); + d1 = dis.readInt(); + d2 = dis.readInt(); + d3 = dis.readInt(); + B = dis.readInt(); + basisType = dis.readInt(); + beta = dis.readDouble(); + normBound = dis.readDouble(); + keyNormBound = dis.readDouble(); + signFailTolerance = dis.readInt(); + primeCheck = dis.readBoolean(); + sparse = dis.readBoolean(); + bitsF = dis.readInt(); + keyGenAlg = dis.read(); + String alg = dis.readUTF(); + if ("SHA-512".equals(alg)) + { + hashAlg = new SHA512Digest(); + } + else if ("SHA-256".equals(alg)) + { + hashAlg = new SHA256Digest(); + } + polyType = dis.read(); + init(); + } + + /** + * Writes the parameter set to an output stream + * + * @param os an output stream + * @throws java.io.IOException + */ + public void writeTo(OutputStream os) + throws IOException + { + DataOutputStream dos = new DataOutputStream(os); + dos.writeInt(N); + dos.writeInt(q); + dos.writeInt(d); + dos.writeInt(d1); + dos.writeInt(d2); + dos.writeInt(d3); + dos.writeInt(B); + dos.writeInt(basisType); + dos.writeDouble(beta); + dos.writeDouble(normBound); + dos.writeDouble(keyNormBound); + dos.writeInt(signFailTolerance); + dos.writeBoolean(primeCheck); + dos.writeBoolean(sparse); + dos.writeInt(bitsF); + dos.write(keyGenAlg); + dos.writeUTF(hashAlg.getAlgorithmName()); + dos.write(polyType); + } + + public NTRUSigningParameters getSigningParameters() + { + return new NTRUSigningParameters(N, q, d, B, beta, normBound, hashAlg); + } + + public NTRUSigningKeyGenerationParameters clone() + { + if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE) + { + return new NTRUSigningKeyGenerationParameters(N, q, d, B, basisType, beta, normBound, keyNormBound, primeCheck, sparse, keyGenAlg, hashAlg); + } + else + { + return new NTRUSigningKeyGenerationParameters(N, q, d1, d2, d3, B, basisType, beta, normBound, keyNormBound, primeCheck, sparse, keyGenAlg, hashAlg); + } + } + + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + B; + result = prime * result + N; + result = prime * result + basisType; + long temp; + temp = Double.doubleToLongBits(beta); + result = prime * result + (int)(temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(betaSq); + result = prime * result + (int)(temp ^ (temp >>> 32)); + result = prime * result + bitsF; + result = prime * result + d; + result = prime * result + d1; + result = prime * result + d2; + result = prime * result + d3; + result = prime * result + ((hashAlg == null) ? 0 : hashAlg.getAlgorithmName().hashCode()); + result = prime * result + keyGenAlg; + temp = Double.doubleToLongBits(keyNormBound); + result = prime * result + (int)(temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(keyNormBoundSq); + result = prime * result + (int)(temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(normBound); + result = prime * result + (int)(temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(normBoundSq); + result = prime * result + (int)(temp ^ (temp >>> 32)); + result = prime * result + polyType; + result = prime * result + (primeCheck ? 1231 : 1237); + result = prime * result + q; + result = prime * result + signFailTolerance; + result = prime * result + (sparse ? 1231 : 1237); + return result; + } + + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NTRUSigningKeyGenerationParameters)) + { + return false; + } + NTRUSigningKeyGenerationParameters other = (NTRUSigningKeyGenerationParameters)obj; + if (B != other.B) + { + return false; + } + if (N != other.N) + { + return false; + } + if (basisType != other.basisType) + { + return false; + } + if (Double.doubleToLongBits(beta) != Double.doubleToLongBits(other.beta)) + { + return false; + } + if (Double.doubleToLongBits(betaSq) != Double.doubleToLongBits(other.betaSq)) + { + return false; + } + if (bitsF != other.bitsF) + { + return false; + } + if (d != other.d) + { + return false; + } + if (d1 != other.d1) + { + return false; + } + if (d2 != other.d2) + { + return false; + } + if (d3 != other.d3) + { + return false; + } + if (hashAlg == null) + { + if (other.hashAlg != null) + { + return false; + } + } + else if (!hashAlg.getAlgorithmName().equals(other.hashAlg.getAlgorithmName())) + { + return false; + } + if (keyGenAlg != other.keyGenAlg) + { + return false; + } + if (Double.doubleToLongBits(keyNormBound) != Double.doubleToLongBits(other.keyNormBound)) + { + return false; + } + if (Double.doubleToLongBits(keyNormBoundSq) != Double.doubleToLongBits(other.keyNormBoundSq)) + { + return false; + } + if (Double.doubleToLongBits(normBound) != Double.doubleToLongBits(other.normBound)) + { + return false; + } + if (Double.doubleToLongBits(normBoundSq) != Double.doubleToLongBits(other.normBoundSq)) + { + return false; + } + if (polyType != other.polyType) + { + return false; + } + if (primeCheck != other.primeCheck) + { + return false; + } + if (q != other.q) + { + return false; + } + if (signFailTolerance != other.signFailTolerance) + { + return false; + } + if (sparse != other.sparse) + { + return false; + } + return true; + } + + public String toString() + { + DecimalFormat format = new DecimalFormat("0.00"); + + StringBuilder output = new StringBuilder("SignatureParameters(N=" + N + " q=" + q); + if (polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE) + { + output.append(" polyType=SIMPLE d=" + d); + } + else + { + output.append(" polyType=PRODUCT d1=" + d1 + " d2=" + d2 + " d3=" + d3); + } + output.append(" B=" + B + " basisType=" + basisType + " beta=" + format.format(beta) + + " normBound=" + format.format(normBound) + " keyNormBound=" + format.format(keyNormBound) + + " prime=" + primeCheck + " sparse=" + sparse + " keyGenAlg=" + keyGenAlg + " hashAlg=" + hashAlg + ")"); + return output.toString(); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningKeyPairGenerator.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningKeyPairGenerator.java new file mode 100644 index 00000000..eac283aa --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningKeyPairGenerator.java @@ -0,0 +1,357 @@ +package org.spongycastle.pqc.crypto.ntru; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.spongycastle.crypto.KeyGenerationParameters; +import org.spongycastle.pqc.math.ntru.euclid.BigIntEuclidean; +import org.spongycastle.pqc.math.ntru.polynomial.BigDecimalPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.BigIntPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.IntegerPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.Polynomial; +import org.spongycastle.pqc.math.ntru.polynomial.ProductFormPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.Resultant; + +import static java.math.BigInteger.ONE; +import static java.math.BigInteger.ZERO; + +public class NTRUSigningKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private NTRUSigningKeyGenerationParameters params; + + public void init(KeyGenerationParameters param) + { + this.params = (NTRUSigningKeyGenerationParameters)param; + } + + /** + * Generates a new signature key pair. Starts <code>B+1</code> threads. + * + * @return a key pair + */ + public AsymmetricCipherKeyPair generateKeyPair() + { + NTRUSigningPublicKeyParameters pub = null; + ExecutorService executor = Executors.newCachedThreadPool(); + List<Future<NTRUSigningPrivateKeyParameters.Basis>> bases = new ArrayList<Future<NTRUSigningPrivateKeyParameters.Basis>>(); + for (int k = params.B; k >= 0; k--) + { + bases.add(executor.submit(new BasisGenerationTask())); + } + executor.shutdown(); + + List<NTRUSigningPrivateKeyParameters.Basis> basises = new ArrayList<NTRUSigningPrivateKeyParameters.Basis>(); + + for (int k = params.B; k >= 0; k--) + { + Future<NTRUSigningPrivateKeyParameters.Basis> basis = bases.get(k); + try + { + basises.add(basis.get()); + if (k == params.B) + { + pub = new NTRUSigningPublicKeyParameters(basis.get().h, params.getSigningParameters()); + } + } + catch (Exception e) + { + throw new IllegalStateException(e); + } + } + NTRUSigningPrivateKeyParameters priv = new NTRUSigningPrivateKeyParameters(basises, pub); + AsymmetricCipherKeyPair kp = new AsymmetricCipherKeyPair(pub, priv); + return kp; + } + + /** + * Generates a new signature key pair. Runs in a single thread. + * + * @return a key pair + */ + public AsymmetricCipherKeyPair generateKeyPairSingleThread() + { + List<NTRUSigningPrivateKeyParameters.Basis> basises = new ArrayList<NTRUSigningPrivateKeyParameters.Basis>(); + NTRUSigningPublicKeyParameters pub = null; + for (int k = params.B; k >= 0; k--) + { + NTRUSigningPrivateKeyParameters.Basis basis = generateBoundedBasis(); + basises.add(basis); + if (k == 0) + { + pub = new NTRUSigningPublicKeyParameters(basis.h, params.getSigningParameters()); + } + } + NTRUSigningPrivateKeyParameters priv = new NTRUSigningPrivateKeyParameters(basises, pub); + return new AsymmetricCipherKeyPair(pub, priv); + } + + + /** + * Implementation of the optional steps 20 through 26 in EESS1v2.pdf, section 3.5.1.1. + * This doesn't seem to have much of an effect and sometimes actually increases the + * norm of F, but on average it slightly reduces the norm.<br/> + * This method changes <code>F</code> and <code>g</code> but leaves <code>f</code> and + * <code>g</code> unchanged. + * + * @param f + * @param g + * @param F + * @param G + * @param N + */ + private void minimizeFG(IntegerPolynomial f, IntegerPolynomial g, IntegerPolynomial F, IntegerPolynomial G, int N) + { + int E = 0; + for (int j = 0; j < N; j++) + { + E += 2 * N * (f.coeffs[j] * f.coeffs[j] + g.coeffs[j] * g.coeffs[j]); + } + + // [f(1)+g(1)]^2 = 4 + E -= 4; + + IntegerPolynomial u = (IntegerPolynomial)f.clone(); + IntegerPolynomial v = (IntegerPolynomial)g.clone(); + int j = 0; + int k = 0; + int maxAdjustment = N; + while (k < maxAdjustment && j < N) + { + int D = 0; + int i = 0; + while (i < N) + { + int D1 = F.coeffs[i] * f.coeffs[i]; + int D2 = G.coeffs[i] * g.coeffs[i]; + int D3 = 4 * N * (D1 + D2); + D += D3; + i++; + } + // f(1)+g(1) = 2 + int D1 = 4 * (F.sumCoeffs() + G.sumCoeffs()); + D -= D1; + + if (D > E) + { + F.sub(u); + G.sub(v); + k++; + j = 0; + } + else if (D < -E) + { + F.add(u); + G.add(v); + k++; + j = 0; + } + j++; + u.rotate1(); + v.rotate1(); + } + } + + /** + * Creates a NTRUSigner basis consisting of polynomials <code>f, g, F, G, h</code>.<br/> + * If <code>KeyGenAlg=FLOAT</code>, the basis may not be valid and this method must be rerun if that is the case.<br/> + * + * @see #generateBoundedBasis() + */ + private FGBasis generateBasis() + { + int N = params.N; + int q = params.q; + int d = params.d; + int d1 = params.d1; + int d2 = params.d2; + int d3 = params.d3; + int basisType = params.basisType; + + Polynomial f; + IntegerPolynomial fInt; + Polynomial g; + IntegerPolynomial gInt; + IntegerPolynomial fq; + Resultant rf; + Resultant rg; + BigIntEuclidean r; + + int _2n1 = 2 * N + 1; + boolean primeCheck = params.primeCheck; + + do + { + do + { + f = params.polyType== NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE ? DenseTernaryPolynomial.generateRandom(N, d + 1, d, new SecureRandom()) : ProductFormPolynomial.generateRandom(N, d1, d2, d3 + 1, d3, new SecureRandom()); + fInt = f.toIntegerPolynomial(); + } + while (primeCheck && fInt.resultant(_2n1).res.equals(ZERO)); + fq = fInt.invertFq(q); + } + while (fq == null); + rf = fInt.resultant(); + + do + { + do + { + do + { + g = params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_SIMPLE ? DenseTernaryPolynomial.generateRandom(N, d + 1, d, new SecureRandom()) : ProductFormPolynomial.generateRandom(N, d1, d2, d3 + 1, d3, new SecureRandom()); + gInt = g.toIntegerPolynomial(); + } + while (primeCheck && gInt.resultant(_2n1).res.equals(ZERO)); + } + while (gInt.invertFq(q) == null); + rg = gInt.resultant(); + r = BigIntEuclidean.calculate(rf.res, rg.res); + } + while (!r.gcd.equals(ONE)); + + BigIntPolynomial A = (BigIntPolynomial)rf.rho.clone(); + A.mult(r.x.multiply(BigInteger.valueOf(q))); + BigIntPolynomial B = (BigIntPolynomial)rg.rho.clone(); + B.mult(r.y.multiply(BigInteger.valueOf(-q))); + + BigIntPolynomial C; + if (params.keyGenAlg == NTRUSigningKeyGenerationParameters.KEY_GEN_ALG_RESULTANT) + { + int[] fRevCoeffs = new int[N]; + int[] gRevCoeffs = new int[N]; + fRevCoeffs[0] = fInt.coeffs[0]; + gRevCoeffs[0] = gInt.coeffs[0]; + for (int i = 1; i < N; i++) + { + fRevCoeffs[i] = fInt.coeffs[N - i]; + gRevCoeffs[i] = gInt.coeffs[N - i]; + } + IntegerPolynomial fRev = new IntegerPolynomial(fRevCoeffs); + IntegerPolynomial gRev = new IntegerPolynomial(gRevCoeffs); + + IntegerPolynomial t = f.mult(fRev); + t.add(g.mult(gRev)); + Resultant rt = t.resultant(); + C = fRev.mult(B); // fRev.mult(B) is actually faster than new SparseTernaryPolynomial(fRev).mult(B), possibly due to cache locality? + C.add(gRev.mult(A)); + C = C.mult(rt.rho); + C.div(rt.res); + } + else + { // KeyGenAlg.FLOAT + // calculate ceil(log10(N)) + int log10N = 0; + for (int i = 1; i < N; i *= 10) + { + log10N++; + } + + // * Cdec needs to be accurate to 1 decimal place so it can be correctly rounded; + // * fInv loses up to (#digits of longest coeff of B) places in fInv.mult(B); + // * multiplying fInv by B also multiplies the rounding error by a factor of N; + // so make #decimal places of fInv the sum of the above. + BigDecimalPolynomial fInv = rf.rho.div(new BigDecimal(rf.res), B.getMaxCoeffLength() + 1 + log10N); + BigDecimalPolynomial gInv = rg.rho.div(new BigDecimal(rg.res), A.getMaxCoeffLength() + 1 + log10N); + + BigDecimalPolynomial Cdec = fInv.mult(B); + Cdec.add(gInv.mult(A)); + Cdec.halve(); + C = Cdec.round(); + } + + BigIntPolynomial F = (BigIntPolynomial)B.clone(); + F.sub(f.mult(C)); + BigIntPolynomial G = (BigIntPolynomial)A.clone(); + G.sub(g.mult(C)); + + IntegerPolynomial FInt = new IntegerPolynomial(F); + IntegerPolynomial GInt = new IntegerPolynomial(G); + minimizeFG(fInt, gInt, FInt, GInt, N); + + Polynomial fPrime; + IntegerPolynomial h; + if (basisType == NTRUSigningKeyGenerationParameters.BASIS_TYPE_STANDARD) + { + fPrime = FInt; + h = g.mult(fq, q); + } + else + { + fPrime = g; + h = FInt.mult(fq, q); + } + h.modPositive(q); + + return new FGBasis(f, fPrime, h, FInt, GInt, params); + } + + /** + * Creates a basis such that <code>|F| < keyNormBound</code> and <code>|G| < keyNormBound</code> + * + * @return a NTRUSigner basis + */ + public NTRUSigningPrivateKeyParameters.Basis generateBoundedBasis() + { + while (true) + { + FGBasis basis = generateBasis(); + if (basis.isNormOk()) + { + return basis; + } + } + } + + private class BasisGenerationTask + implements Callable<NTRUSigningPrivateKeyParameters.Basis> + { + + + public NTRUSigningPrivateKeyParameters.Basis call() + throws Exception + { + return generateBoundedBasis(); + } + } + + /** + * A subclass of Basis that additionally contains the polynomials <code>F</code> and <code>G</code>. + */ + public class FGBasis + extends NTRUSigningPrivateKeyParameters.Basis + { + public IntegerPolynomial F; + public IntegerPolynomial G; + + FGBasis(Polynomial f, Polynomial fPrime, IntegerPolynomial h, IntegerPolynomial F, IntegerPolynomial G, NTRUSigningKeyGenerationParameters params) + { + super(f, fPrime, h, params); + this.F = F; + this.G = G; + } + + /** + * Returns <code>true</code> if the norms of the polynomials <code>F</code> and <code>G</code> + * are within {@link NTRUSigningKeyGenerationParameters#keyNormBound}. + * + * @return + */ + boolean isNormOk() + { + double keyNormBoundSq = params.keyNormBoundSq; + int q = params.q; + return (F.centeredNormSq(q) < keyNormBoundSq && G.centeredNormSq(q) < keyNormBoundSq); + } + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningParameters.java new file mode 100644 index 00000000..bf9ba2cb --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningParameters.java @@ -0,0 +1,269 @@ +package org.spongycastle.pqc.crypto.ntru; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.DecimalFormat; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.digests.SHA512Digest; + +/** + * A set of parameters for NtruSign. Several predefined parameter sets are available and new ones can be created as well. + */ +public class NTRUSigningParameters + implements Cloneable +{ + public int N; + public int q; + public int d, d1, d2, d3, B; + double beta; + public double betaSq; + double normBound; + public double normBoundSq; + public int signFailTolerance = 100; + int bitsF = 6; // max #bits needed to encode one coefficient of the polynomial F + public Digest hashAlg; + + /** + * Constructs a parameter set that uses ternary private keys (i.e. <code>polyType=SIMPLE</code>). + * + * @param N number of polynomial coefficients + * @param q modulus + * @param d number of -1's in the private polynomials <code>f</code> and <code>g</code> + * @param B number of perturbations + * @param beta balancing factor for the transpose lattice + * @param normBound maximum norm for valid signatures + * @param hashAlg a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method. + */ + public NTRUSigningParameters(int N, int q, int d, int B, double beta, double normBound, Digest hashAlg) + { + this.N = N; + this.q = q; + this.d = d; + this.B = B; + this.beta = beta; + this.normBound = normBound; + this.hashAlg = hashAlg; + init(); + } + + /** + * Constructs a parameter set that uses product-form private keys (i.e. <code>polyType=PRODUCT</code>). + * + * @param N number of polynomial coefficients + * @param q modulus + * @param d1 number of -1's in the private polynomials <code>f</code> and <code>g</code> + * @param d2 number of -1's in the private polynomials <code>f</code> and <code>g</code> + * @param d3 number of -1's in the private polynomials <code>f</code> and <code>g</code> + * @param B number of perturbations + * @param beta balancing factor for the transpose lattice + * @param normBound maximum norm for valid signatures + * @param keyNormBound maximum norm for the ploynomials <code>F</code> and <code>G</code> + * @param hashAlg a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method. + */ + public NTRUSigningParameters(int N, int q, int d1, int d2, int d3, int B, double beta, double normBound, double keyNormBound, Digest hashAlg) + { + this.N = N; + this.q = q; + this.d1 = d1; + this.d2 = d2; + this.d3 = d3; + this.B = B; + this.beta = beta; + this.normBound = normBound; + this.hashAlg = hashAlg; + init(); + } + + private void init() + { + betaSq = beta * beta; + normBoundSq = normBound * normBound; + } + + /** + * Reads a parameter set from an input stream. + * + * @param is an input stream + * @throws IOException + */ + public NTRUSigningParameters(InputStream is) + throws IOException + { + DataInputStream dis = new DataInputStream(is); + N = dis.readInt(); + q = dis.readInt(); + d = dis.readInt(); + d1 = dis.readInt(); + d2 = dis.readInt(); + d3 = dis.readInt(); + B = dis.readInt(); + beta = dis.readDouble(); + normBound = dis.readDouble(); + signFailTolerance = dis.readInt(); + bitsF = dis.readInt(); + String alg = dis.readUTF(); + if ("SHA-512".equals(alg)) + { + hashAlg = new SHA512Digest(); + } + else if ("SHA-256".equals(alg)) + { + hashAlg = new SHA256Digest(); + } + init(); + } + + /** + * Writes the parameter set to an output stream + * + * @param os an output stream + * @throws IOException + */ + public void writeTo(OutputStream os) + throws IOException + { + DataOutputStream dos = new DataOutputStream(os); + dos.writeInt(N); + dos.writeInt(q); + dos.writeInt(d); + dos.writeInt(d1); + dos.writeInt(d2); + dos.writeInt(d3); + dos.writeInt(B); + dos.writeDouble(beta); + dos.writeDouble(normBound); + dos.writeInt(signFailTolerance); + dos.writeInt(bitsF); + dos.writeUTF(hashAlg.getAlgorithmName()); + } + + public NTRUSigningParameters clone() + { + return new NTRUSigningParameters(N, q, d, B, beta, normBound, hashAlg); + } + + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + B; + result = prime * result + N; + long temp; + temp = Double.doubleToLongBits(beta); + result = prime * result + (int)(temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(betaSq); + result = prime * result + (int)(temp ^ (temp >>> 32)); + result = prime * result + bitsF; + result = prime * result + d; + result = prime * result + d1; + result = prime * result + d2; + result = prime * result + d3; + result = prime * result + ((hashAlg == null) ? 0 : hashAlg.getAlgorithmName().hashCode()); + temp = Double.doubleToLongBits(normBound); + result = prime * result + (int)(temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(normBoundSq); + result = prime * result + (int)(temp ^ (temp >>> 32)); + result = prime * result + q; + result = prime * result + signFailTolerance; + return result; + } + + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof NTRUSigningParameters)) + { + return false; + } + NTRUSigningParameters other = (NTRUSigningParameters)obj; + if (B != other.B) + { + return false; + } + if (N != other.N) + { + return false; + } + if (Double.doubleToLongBits(beta) != Double.doubleToLongBits(other.beta)) + { + return false; + } + if (Double.doubleToLongBits(betaSq) != Double.doubleToLongBits(other.betaSq)) + { + return false; + } + if (bitsF != other.bitsF) + { + return false; + } + if (d != other.d) + { + return false; + } + if (d1 != other.d1) + { + return false; + } + if (d2 != other.d2) + { + return false; + } + if (d3 != other.d3) + { + return false; + } + if (hashAlg == null) + { + if (other.hashAlg != null) + { + return false; + } + } + else if (!hashAlg.getAlgorithmName().equals(other.hashAlg.getAlgorithmName())) + { + return false; + } + if (Double.doubleToLongBits(normBound) != Double.doubleToLongBits(other.normBound)) + { + return false; + } + if (Double.doubleToLongBits(normBoundSq) != Double.doubleToLongBits(other.normBoundSq)) + { + return false; + } + if (q != other.q) + { + return false; + } + if (signFailTolerance != other.signFailTolerance) + { + return false; + } + + return true; + } + + public String toString() + { + DecimalFormat format = new DecimalFormat("0.00"); + + StringBuilder output = new StringBuilder("SignatureParameters(N=" + N + " q=" + q); + + output.append(" B=" + B + " beta=" + format.format(beta) + + " normBound=" + format.format(normBound) + + " hashAlg=" + hashAlg + ")"); + return output.toString(); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningPrivateKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningPrivateKeyParameters.java new file mode 100644 index 00000000..bbbaaeef --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningPrivateKeyParameters.java @@ -0,0 +1,385 @@ +package org.spongycastle.pqc.crypto.ntru; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.IntegerPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.Polynomial; +import org.spongycastle.pqc.math.ntru.polynomial.ProductFormPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial; + +/** + * A NtruSign private key comprises one or more {@link NTRUSigningPrivateKeyParameters.Basis} of three polynomials each, + * except the zeroth basis for which <code>h</code> is undefined. + */ +public class NTRUSigningPrivateKeyParameters + extends AsymmetricKeyParameter +{ + private List<Basis> bases; + private NTRUSigningPublicKeyParameters publicKey; + + /** + * Constructs a new private key from a byte array + * + * @param b an encoded private key + * @param params the NtruSign parameters to use + */ + public NTRUSigningPrivateKeyParameters(byte[] b, NTRUSigningKeyGenerationParameters params) + throws IOException + { + this(new ByteArrayInputStream(b), params); + } + + /** + * Constructs a new private key from an input stream + * + * @param is an input stream + * @param params the NtruSign parameters to use + */ + public NTRUSigningPrivateKeyParameters(InputStream is, NTRUSigningKeyGenerationParameters params) + throws IOException + { + super(true); + bases = new ArrayList<Basis>(); + for (int i = 0; i <= params.B; i++) + // include a public key h[i] in all bases except for the first one + { + add(new Basis(is, params, i != 0)); + } + publicKey = new NTRUSigningPublicKeyParameters(is, params.getSigningParameters()); + } + + public NTRUSigningPrivateKeyParameters(List<Basis> bases, NTRUSigningPublicKeyParameters publicKey) + { + super(true); + this.bases = new ArrayList<Basis>(bases); + this.publicKey = publicKey; + } + + /** + * Adds a basis to the key. + * + * @param b a NtruSign basis + */ + private void add(Basis b) + { + bases.add(b); + } + + /** + * Returns the <code>i</code>-th basis + * + * @param i the index + * @return the basis at index <code>i</code> + */ + public Basis getBasis(int i) + { + return bases.get(i); + } + + public NTRUSigningPublicKeyParameters getPublicKey() + { + return publicKey; + } + + /** + * Converts the key to a byte array + * + * @return the encoded key + */ + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + for (int i = 0; i < bases.size(); i++) + { + // all bases except for the first one contain a public key + bases.get(i).encode(os, i != 0); + } + + os.write(publicKey.getEncoded()); + + return os.toByteArray(); + } + + /** + * Writes the key to an output stream + * + * @param os an output stream + * @throws IOException + */ + public void writeTo(OutputStream os) + throws IOException + { + os.write(getEncoded()); + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((bases == null) ? 0 : bases.hashCode()); + for (Basis basis : bases) + { + result += basis.hashCode(); + } + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + NTRUSigningPrivateKeyParameters other = (NTRUSigningPrivateKeyParameters)obj; + if (bases == null) + { + if (other.bases != null) + { + return false; + } + } + if (bases.size() != other.bases.size()) + { + return false; + } + for (int i = 0; i < bases.size(); i++) + { + Basis basis1 = bases.get(i); + Basis basis2 = other.bases.get(i); + if (!basis1.f.equals(basis2.f)) + { + return false; + } + if (!basis1.fPrime.equals(basis2.fPrime)) + { + return false; + } + if (i != 0 && !basis1.h.equals(basis2.h)) // don't compare h for the 0th basis + { + return false; + } + if (!basis1.params.equals(basis2.params)) + { + return false; + } + } + return true; + } + + /** + * A NtruSign basis. Contains three polynomials <code>f, f', h</code>. + */ + public static class Basis + { + public Polynomial f; + public Polynomial fPrime; + public IntegerPolynomial h; + NTRUSigningKeyGenerationParameters params; + + /** + * Constructs a new basis from polynomials <code>f, f', h</code>. + * + * @param f + * @param fPrime + * @param h + * @param params NtruSign parameters + */ + protected Basis(Polynomial f, Polynomial fPrime, IntegerPolynomial h, NTRUSigningKeyGenerationParameters params) + { + this.f = f; + this.fPrime = fPrime; + this.h = h; + this.params = params; + } + + /** + * Reads a basis from an input stream and constructs a new basis. + * + * @param is an input stream + * @param params NtruSign parameters + * @param include_h whether to read the polynomial <code>h</code> (<code>true</code>) or only <code>f</code> and <code>f'</code> (<code>false</code>) + */ + Basis(InputStream is, NTRUSigningKeyGenerationParameters params, boolean include_h) + throws IOException + { + int N = params.N; + int q = params.q; + int d1 = params.d1; + int d2 = params.d2; + int d3 = params.d3; + boolean sparse = params.sparse; + this.params = params; + + if (params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT) + { + f = ProductFormPolynomial.fromBinary(is, N, d1, d2, d3 + 1, d3); + } + else + { + IntegerPolynomial fInt = IntegerPolynomial.fromBinary3Tight(is, N); + f = sparse ? new SparseTernaryPolynomial(fInt) : new DenseTernaryPolynomial(fInt); + } + + if (params.basisType == NTRUSigningKeyGenerationParameters.BASIS_TYPE_STANDARD) + { + IntegerPolynomial fPrimeInt = IntegerPolynomial.fromBinary(is, N, q); + for (int i = 0; i < fPrimeInt.coeffs.length; i++) + { + fPrimeInt.coeffs[i] -= q / 2; + } + fPrime = fPrimeInt; + } + else if (params.polyType == NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT) + { + fPrime = ProductFormPolynomial.fromBinary(is, N, d1, d2, d3 + 1, d3); + } + else + { + fPrime = IntegerPolynomial.fromBinary3Tight(is, N); + } + + if (include_h) + { + h = IntegerPolynomial.fromBinary(is, N, q); + } + } + + /** + * Writes the basis to an output stream + * + * @param os an output stream + * @param include_h whether to write the polynomial <code>h</code> (<code>true</code>) or only <code>f</code> and <code>f'</code> (<code>false</code>) + * @throws IOException + */ + void encode(OutputStream os, boolean include_h) + throws IOException + { + int q = params.q; + + os.write(getEncoded(f)); + if (params.basisType == NTRUSigningKeyGenerationParameters.BASIS_TYPE_STANDARD) + { + IntegerPolynomial fPrimeInt = fPrime.toIntegerPolynomial(); + for (int i = 0; i < fPrimeInt.coeffs.length; i++) + { + fPrimeInt.coeffs[i] += q / 2; + } + os.write(fPrimeInt.toBinary(q)); + } + else + { + os.write(getEncoded(fPrime)); + } + if (include_h) + { + os.write(h.toBinary(q)); + } + } + + private byte[] getEncoded(Polynomial p) + { + if (p instanceof ProductFormPolynomial) + { + return ((ProductFormPolynomial)p).toBinary(); + } + else + { + return p.toIntegerPolynomial().toBinary3Tight(); + } + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((f == null) ? 0 : f.hashCode()); + result = prime * result + ((fPrime == null) ? 0 : fPrime.hashCode()); + result = prime * result + ((h == null) ? 0 : h.hashCode()); + result = prime * result + ((params == null) ? 0 : params.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof Basis)) + { + return false; + } + Basis other = (Basis)obj; + if (f == null) + { + if (other.f != null) + { + return false; + } + } + else if (!f.equals(other.f)) + { + return false; + } + if (fPrime == null) + { + if (other.fPrime != null) + { + return false; + } + } + else if (!fPrime.equals(other.fPrime)) + { + return false; + } + if (h == null) + { + if (other.h != null) + { + return false; + } + } + else if (!h.equals(other.h)) + { + return false; + } + if (params == null) + { + if (other.params != null) + { + return false; + } + } + else if (!params.equals(other.params)) + { + return false; + } + return true; + } + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningPublicKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningPublicKeyParameters.java new file mode 100644 index 00000000..d7431b8b --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/ntru/NTRUSigningPublicKeyParameters.java @@ -0,0 +1,132 @@ +package org.spongycastle.pqc.crypto.ntru; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.pqc.math.ntru.polynomial.IntegerPolynomial; + +/** + * A NtruSign public key is essentially a polynomial named <code>h</code>. + */ +public class NTRUSigningPublicKeyParameters + extends AsymmetricKeyParameter +{ + private NTRUSigningParameters params; + public IntegerPolynomial h; + + /** + * Constructs a new public key from a polynomial + * + * @param h the polynomial <code>h</code> which determines the key + * @param params the NtruSign parameters to use + */ + public NTRUSigningPublicKeyParameters(IntegerPolynomial h, NTRUSigningParameters params) + { + super(false); + this.h = h; + this.params = params; + } + + /** + * Converts a byte array to a polynomial <code>h</code> and constructs a new public key + * + * @param b an encoded polynomial + * @param params the NtruSign parameters to use + */ + public NTRUSigningPublicKeyParameters(byte[] b, NTRUSigningParameters params) + { + super(false); + h = IntegerPolynomial.fromBinary(b, params.N, params.q); + this.params = params; + } + + /** + * Reads a polynomial <code>h</code> from an input stream and constructs a new public key + * + * @param is an input stream + * @param params the NtruSign parameters to use + */ + public NTRUSigningPublicKeyParameters(InputStream is, NTRUSigningParameters params) + throws IOException + { + super(false); + h = IntegerPolynomial.fromBinary(is, params.N, params.q); + this.params = params; + } + + + /** + * Converts the key to a byte array + * + * @return the encoded key + */ + public byte[] getEncoded() + { + return h.toBinary(params.q); + } + + /** + * Writes the key to an output stream + * + * @param os an output stream + * @throws IOException + */ + public void writeTo(OutputStream os) + throws IOException + { + os.write(getEncoded()); + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((h == null) ? 0 : h.hashCode()); + result = prime * result + ((params == null) ? 0 : params.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + NTRUSigningPublicKeyParameters other = (NTRUSigningPublicKeyParameters)obj; + if (h == null) + { + if (other.h != null) + { + return false; + } + } + else if (!h.equals(other.h)) + { + return false; + } + if (params == null) + { + if (other.params != null) + { + return false; + } + } + else if (!params.equals(other.params)) + { + return false; + } + return true; + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/Layer.java b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/Layer.java new file mode 100644 index 00000000..0556531e --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/Layer.java @@ -0,0 +1,322 @@ +package org.spongycastle.pqc.crypto.rainbow; + +import java.security.SecureRandom; + +import org.spongycastle.pqc.crypto.rainbow.util.GF2Field; +import org.spongycastle.pqc.crypto.rainbow.util.RainbowUtil; +import org.spongycastle.util.Arrays; + + +/** + * This class represents a layer of the Rainbow Oil- and Vinegar Map. Each Layer + * consists of oi polynomials with their coefficients, generated at random. + * <p> + * To sign a document, we solve a LES (linear equation system) for each layer in + * order to find the oil variables of that layer and to be able to use the + * variables to compute the signature. This functionality is implemented in the + * RainbowSignature-class, by the aid of the private key. + * <p> + * Each layer is a part of the private key. + * <p> + * More information about the layer can be found in the paper of Jintai Ding, + * Dieter Schmidt: Rainbow, a New Multivariable Polynomial Signature Scheme. + * ACNS 2005: 164-175 (http://dx.doi.org/10.1007/11496137_12) + */ +public class Layer +{ + private int vi; // number of vinegars in this layer + private int viNext; // number of vinegars in next layer + private int oi; // number of oils in this layer + + /* + * k : index of polynomial + * + * i,j : indices of oil and vinegar variables + */ + private short[/* k */][/* i */][/* j */] coeff_alpha; + private short[/* k */][/* i */][/* j */] coeff_beta; + private short[/* k */][/* i */] coeff_gamma; + private short[/* k */] coeff_eta; + + /** + * Constructor + * + * @param vi number of vinegar variables of this layer + * @param viNext number of vinegar variables of next layer. It's the same as + * (num of oils) + (num of vinegars) of this layer. + * @param coeffAlpha alpha-coefficients in the polynomials of this layer + * @param coeffBeta beta-coefficients in the polynomials of this layer + * @param coeffGamma gamma-coefficients in the polynomials of this layer + * @param coeffEta eta-coefficients in the polynomials of this layer + */ + public Layer(byte vi, byte viNext, short[][][] coeffAlpha, + short[][][] coeffBeta, short[][] coeffGamma, short[] coeffEta) + { + this.vi = vi & 0xff; + this.viNext = viNext & 0xff; + this.oi = this.viNext - this.vi; + + // the secret coefficients of all polynomials in this layer + this.coeff_alpha = coeffAlpha; + this.coeff_beta = coeffBeta; + this.coeff_gamma = coeffGamma; + this.coeff_eta = coeffEta; + } + + /** + * This function generates the coefficients of all polynomials in this layer + * at random using random generator. + * + * @param sr the random generator which is to be used + */ + public Layer(int vi, int viNext, SecureRandom sr) + { + this.vi = vi; + this.viNext = viNext; + this.oi = viNext - vi; + + // the coefficients of all polynomials in this layer + this.coeff_alpha = new short[this.oi][this.oi][this.vi]; + this.coeff_beta = new short[this.oi][this.vi][this.vi]; + this.coeff_gamma = new short[this.oi][this.viNext]; + this.coeff_eta = new short[this.oi]; + + int numOfPoly = this.oi; // number of polynomials per layer + + // Alpha coeffs + for (int k = 0; k < numOfPoly; k++) + { + for (int i = 0; i < this.oi; i++) + { + for (int j = 0; j < this.vi; j++) + { + coeff_alpha[k][i][j] = (short)(sr.nextInt() & GF2Field.MASK); + } + } + } + // Beta coeffs + for (int k = 0; k < numOfPoly; k++) + { + for (int i = 0; i < this.vi; i++) + { + for (int j = 0; j < this.vi; j++) + { + coeff_beta[k][i][j] = (short)(sr.nextInt() & GF2Field.MASK); + } + } + } + // Gamma coeffs + for (int k = 0; k < numOfPoly; k++) + { + for (int i = 0; i < this.viNext; i++) + { + coeff_gamma[k][i] = (short)(sr.nextInt() & GF2Field.MASK); + } + } + // Eta + for (int k = 0; k < numOfPoly; k++) + { + coeff_eta[k] = (short)(sr.nextInt() & GF2Field.MASK); + } + } + + /** + * This method plugs in the vinegar variables into the polynomials of this + * layer and computes the coefficients of the Oil-variables as well as the + * free coefficient in each polynomial. + * <p> + * It is needed for computing the Oil variables while signing. + * + * @param x vinegar variables of this layer that should be plugged into + * the polynomials. + * @return coeff the coefficients of Oil variables and the free coeff in the + * polynomials of this layer. + */ + public short[][] plugInVinegars(short[] x) + { + // temporary variable needed for the multiplication + short tmpMult = 0; + // coeff: 1st index = which polynomial, 2nd index=which variable + short[][] coeff = new short[oi][oi + 1]; // gets returned + // free coefficient per polynomial + short[] sum = new short[oi]; + + /* + * evaluate the beta-part of the polynomials (it contains no oil + * variables) + */ + for (int k = 0; k < oi; k++) + { + for (int i = 0; i < vi; i++) + { + for (int j = 0; j < vi; j++) + { + // tmp = beta * xi (plug in) + tmpMult = GF2Field.multElem(coeff_beta[k][i][j], x[i]); + // tmp = tmp * xj + tmpMult = GF2Field.multElem(tmpMult, x[j]); + // accumulate into the array for the free coefficients. + sum[k] = GF2Field.addElem(sum[k], tmpMult); + } + } + } + + /* evaluate the alpha-part (it contains oils) */ + for (int k = 0; k < oi; k++) + { + for (int i = 0; i < oi; i++) + { + for (int j = 0; j < vi; j++) + { + // alpha * xj (plug in) + tmpMult = GF2Field.multElem(coeff_alpha[k][i][j], x[j]); + // accumulate + coeff[k][i] = GF2Field.addElem(coeff[k][i], tmpMult); + } + } + } + /* evaluate the gama-part of the polynomial (containing no oils) */ + for (int k = 0; k < oi; k++) + { + for (int i = 0; i < vi; i++) + { + // gamma * xi (plug in) + tmpMult = GF2Field.multElem(coeff_gamma[k][i], x[i]); + // accumulate in the array for the free coefficients (per + // polynomial). + sum[k] = GF2Field.addElem(sum[k], tmpMult); + } + } + /* evaluate the gama-part of the polynomial (but containing oils) */ + for (int k = 0; k < oi; k++) + { + for (int i = vi; i < viNext; i++) + { // oils + // accumulate the coefficients of the oil variables (per + // polynomial). + coeff[k][i - vi] = GF2Field.addElem(coeff_gamma[k][i], + coeff[k][i - vi]); + } + } + /* evaluate the eta-part of the polynomial */ + for (int k = 0; k < oi; k++) + { + // accumulate in the array for the free coefficients per polynomial. + sum[k] = GF2Field.addElem(sum[k], coeff_eta[k]); + } + + /* put the free coefficients (sum) into the coeff-array as last column */ + for (int k = 0; k < oi; k++) + { + coeff[k][oi] = sum[k]; + } + return coeff; + } + + /** + * Getter for the number of vinegar variables of this layer. + * + * @return the number of vinegar variables of this layer. + */ + public int getVi() + { + return vi; + } + + /** + * Getter for the number of vinegar variables of the next layer. + * + * @return the number of vinegar variables of the next layer. + */ + public int getViNext() + { + return viNext; + } + + /** + * Getter for the number of Oil variables of this layer. + * + * @return the number of oil variables of this layer. + */ + public int getOi() + { + return oi; + } + + /** + * Getter for the alpha-coefficients of the polynomials in this layer. + * + * @return the coefficients of alpha-terms of this layer. + */ + public short[][][] getCoeffAlpha() + { + return coeff_alpha; + } + + /** + * Getter for the beta-coefficients of the polynomials in this layer. + * + * @return the coefficients of beta-terms of this layer. + */ + + public short[][][] getCoeffBeta() + { + return coeff_beta; + } + + /** + * Getter for the gamma-coefficients of the polynomials in this layer. + * + * @return the coefficients of gamma-terms of this layer + */ + public short[][] getCoeffGamma() + { + return coeff_gamma; + } + + /** + * Getter for the eta-coefficients of the polynomials in this layer. + * + * @return the coefficients eta of this layer + */ + public short[] getCoeffEta() + { + return coeff_eta; + } + + /** + * This function compares this Layer with another object. + * + * @param other the other object + * @return the result of the comparison + */ + public boolean equals(Object other) + { + if (other == null || !(other instanceof Layer)) + { + return false; + } + Layer otherLayer = (Layer)other; + + return vi == otherLayer.getVi() + && viNext == otherLayer.getViNext() + && oi == otherLayer.getOi() + && RainbowUtil.equals(coeff_alpha, otherLayer.getCoeffAlpha()) + && RainbowUtil.equals(coeff_beta, otherLayer.getCoeffBeta()) + && RainbowUtil.equals(coeff_gamma, otherLayer.getCoeffGamma()) + && RainbowUtil.equals(coeff_eta, otherLayer.getCoeffEta()); + } + + public int hashCode() + { + int hash = vi; + hash = hash * 37 + viNext; + hash = hash * 37 + oi; + hash = hash * 37 + Arrays.hashCode(coeff_alpha); + hash = hash * 37 + Arrays.hashCode(coeff_beta); + hash = hash * 37 + Arrays.hashCode(coeff_gamma); + hash = hash * 37 + Arrays.hashCode(coeff_eta); + + return hash; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowKeyGenerationParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowKeyGenerationParameters.java new file mode 100644 index 00000000..2cbc5b62 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowKeyGenerationParameters.java @@ -0,0 +1,26 @@ +package org.spongycastle.pqc.crypto.rainbow; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.KeyGenerationParameters; + +public class RainbowKeyGenerationParameters + extends KeyGenerationParameters +{ + private RainbowParameters params; + + public RainbowKeyGenerationParameters( + SecureRandom random, + RainbowParameters params) + { + // TODO: key size? + super(random, params.getVi()[params.getVi().length - 1] - params.getVi()[0]); + this.params = params; + } + + public RainbowParameters getParameters() + { + return params; + } +} + diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowKeyPairGenerator.java b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowKeyPairGenerator.java new file mode 100644 index 00000000..1115d649 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowKeyPairGenerator.java @@ -0,0 +1,413 @@ +package org.spongycastle.pqc.crypto.rainbow; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.spongycastle.crypto.KeyGenerationParameters; +import org.spongycastle.pqc.crypto.rainbow.util.ComputeInField; +import org.spongycastle.pqc.crypto.rainbow.util.GF2Field; + +/** + * This class implements AsymmetricCipherKeyPairGenerator. It is used + * as a generator for the private and public key of the Rainbow Signature + * Scheme. + * <p> + * Detailed information about the key generation is to be found in the paper of + * Jintai Ding, Dieter Schmidt: Rainbow, a New Multivariable Polynomial + * Signature Scheme. ACNS 2005: 164-175 (http://dx.doi.org/10.1007/11496137_12) + */ +public class RainbowKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private boolean initialized = false; + private SecureRandom sr; + private RainbowKeyGenerationParameters rainbowParams; + + /* linear affine map L1: */ + private short[][] A1; // matrix of the lin. affine map L1(n-v1 x n-v1 matrix) + private short[][] A1inv; // inverted A1 + private short[] b1; // translation element of the lin.affine map L1 + + /* linear affine map L2: */ + private short[][] A2; // matrix of the lin. affine map (n x n matrix) + private short[][] A2inv; // inverted A2 + private short[] b2; // translation elemt of the lin.affine map L2 + + /* components of F: */ + private int numOfLayers; // u (number of sets S) + private Layer layers[]; // layers of polynomials of F + private int[] vi; // set of vinegar vars per layer. + + /* components of Public Key */ + private short[][] pub_quadratic; // quadratic(mixed) coefficients + private short[][] pub_singular; // singular coefficients + private short[] pub_scalar; // scalars + + // TODO + + /** + * The standard constructor tries to generate the Rainbow algorithm identifier + * with the corresponding OID. + */ + public RainbowKeyPairGenerator() + { + } + + + /** + * This function generates a Rainbow key pair. + * + * @return the generated key pair + */ + public AsymmetricCipherKeyPair genKeyPair() + { + RainbowPrivateKeyParameters privKey; + RainbowPublicKeyParameters pubKey; + + if (!initialized) + { + initializeDefault(); + } + + /* choose all coefficients at random */ + keygen(); + + /* now marshall them to PrivateKey */ + privKey = new RainbowPrivateKeyParameters(A1inv, b1, A2inv, b2, vi, layers); + + + /* marshall to PublicKey */ + pubKey = new RainbowPublicKeyParameters(vi[vi.length - 1] - vi[0], pub_quadratic, pub_singular, pub_scalar); + + return new AsymmetricCipherKeyPair(pubKey, privKey); + } + + // TODO + public void initialize( + KeyGenerationParameters param) + { + this.rainbowParams = (RainbowKeyGenerationParameters)param; + + // set source of randomness + this.sr = new SecureRandom(); + + // unmarshalling: + this.vi = this.rainbowParams.getParameters().getVi(); + this.numOfLayers = this.rainbowParams.getParameters().getNumOfLayers(); + + this.initialized = true; + } + + private void initializeDefault() + { + RainbowKeyGenerationParameters rbKGParams = new RainbowKeyGenerationParameters(new SecureRandom(), new RainbowParameters()); + initialize(rbKGParams); + } + + /** + * This function calls the functions for the random generation of the coefficients + * and the matrices needed for the private key and the method for computing the public key. + */ + private void keygen() + { + generateL1(); + generateL2(); + generateF(); + computePublicKey(); + } + + /** + * This function generates the invertible affine linear map L1 = A1*x + b1 + * <p/> + * The translation part b1, is stored in a separate array. The inverse of + * the matrix-part of L1 A1inv is also computed here. + * <p/> + * This linear map hides the output of the map F. It is on k^(n-v1). + */ + private void generateL1() + { + + // dimension = n-v1 = vi[last] - vi[first] + int dim = vi[vi.length - 1] - vi[0]; + this.A1 = new short[dim][dim]; + this.A1inv = null; + ComputeInField c = new ComputeInField(); + + /* generation of A1 at random */ + while (A1inv == null) + { + for (int i = 0; i < dim; i++) + { + for (int j = 0; j < dim; j++) + { + A1[i][j] = (short)(sr.nextInt() & GF2Field.MASK); + } + } + A1inv = c.inverse(A1); + } + + /* generation of the translation vector at random */ + b1 = new short[dim]; + for (int i = 0; i < dim; i++) + { + b1[i] = (short)(sr.nextInt() & GF2Field.MASK); + } + } + + /** + * This function generates the invertible affine linear map L2 = A2*x + b2 + * <p/> + * The translation part b2, is stored in a separate array. The inverse of + * the matrix-part of L2 A2inv is also computed here. + * <p/> + * This linear map hides the output of the map F. It is on k^(n). + */ + private void generateL2() + { + + // dimension = n = vi[last] + int dim = vi[vi.length - 1]; + this.A2 = new short[dim][dim]; + this.A2inv = null; + ComputeInField c = new ComputeInField(); + + /* generation of A2 at random */ + while (this.A2inv == null) + { + for (int i = 0; i < dim; i++) + { + for (int j = 0; j < dim; j++) + { // one col extra for b + A2[i][j] = (short)(sr.nextInt() & GF2Field.MASK); + } + } + this.A2inv = c.inverse(A2); + } + /* generation of the translation vector at random */ + b2 = new short[dim]; + for (int i = 0; i < dim; i++) + { + b2[i] = (short)(sr.nextInt() & GF2Field.MASK); + } + + } + + /** + * This function generates the private map F, which consists of u-1 layers. + * Each layer consists of oi polynomials where oi = vi[i+1]-vi[i]. + * <p/> + * The methods for the generation of the coefficients of these polynomials + * are called here. + */ + private void generateF() + { + + this.layers = new Layer[this.numOfLayers]; + for (int i = 0; i < this.numOfLayers; i++) + { + layers[i] = new Layer(this.vi[i], this.vi[i + 1], sr); + } + } + + /** + * This function computes the public key from the private key. + * <p/> + * The composition of F with L2 is computed, followed by applying L1 to the + * composition's result. The singular and scalar values constitute to the + * public key as is, the quadratic terms are compacted in + * <tt>compactPublicKey()</tt> + */ + private void computePublicKey() + { + + ComputeInField c = new ComputeInField(); + int rows = this.vi[this.vi.length - 1] - this.vi[0]; + int vars = this.vi[this.vi.length - 1]; + // Fpub + short[][][] coeff_quadratic_3dim = new short[rows][vars][vars]; + this.pub_singular = new short[rows][vars]; + this.pub_scalar = new short[rows]; + + // Coefficients of layers of Private Key F + short[][][] coeff_alpha; + short[][][] coeff_beta; + short[][] coeff_gamma; + short[] coeff_eta; + + // Needed for counters; + int oils = 0; + int vins = 0; + int crnt_row = 0; // current row (polynomial) + + short vect_tmp[] = new short[vars]; // vector tmp; + short sclr_tmp = 0; + + // Composition of F and L2: Insert L2 = A2*x+b2 in F + for (int l = 0; l < this.layers.length; l++) + { + // get coefficients of current layer + coeff_alpha = this.layers[l].getCoeffAlpha(); + coeff_beta = this.layers[l].getCoeffBeta(); + coeff_gamma = this.layers[l].getCoeffGamma(); + coeff_eta = this.layers[l].getCoeffEta(); + oils = coeff_alpha[0].length;// this.layers[l].getOi(); + vins = coeff_beta[0].length;// this.layers[l].getVi(); + // compute polynomials of layer + for (int p = 0; p < oils; p++) + { + // multiply alphas + for (int x1 = 0; x1 < oils; x1++) + { + for (int x2 = 0; x2 < vins; x2++) + { + // multiply polynomial1 with polynomial2 + vect_tmp = c.multVect(coeff_alpha[p][x1][x2], + this.A2[x1 + vins]); + coeff_quadratic_3dim[crnt_row + p] = c.addSquareMatrix( + coeff_quadratic_3dim[crnt_row + p], c + .multVects(vect_tmp, this.A2[x2])); + // mul poly1 with scalar2 + vect_tmp = c.multVect(this.b2[x2], vect_tmp); + this.pub_singular[crnt_row + p] = c.addVect(vect_tmp, + this.pub_singular[crnt_row + p]); + // mul scalar1 with poly2 + vect_tmp = c.multVect(coeff_alpha[p][x1][x2], + this.A2[x2]); + vect_tmp = c.multVect(b2[x1 + vins], vect_tmp); + this.pub_singular[crnt_row + p] = c.addVect(vect_tmp, + this.pub_singular[crnt_row + p]); + // mul scalar1 with scalar2 + sclr_tmp = GF2Field.multElem(coeff_alpha[p][x1][x2], + this.b2[x1 + vins]); + this.pub_scalar[crnt_row + p] = GF2Field.addElem( + this.pub_scalar[crnt_row + p], GF2Field + .multElem(sclr_tmp, this.b2[x2])); + } + } + // multiply betas + for (int x1 = 0; x1 < vins; x1++) + { + for (int x2 = 0; x2 < vins; x2++) + { + // multiply polynomial1 with polynomial2 + vect_tmp = c.multVect(coeff_beta[p][x1][x2], + this.A2[x1]); + coeff_quadratic_3dim[crnt_row + p] = c.addSquareMatrix( + coeff_quadratic_3dim[crnt_row + p], c + .multVects(vect_tmp, this.A2[x2])); + // mul poly1 with scalar2 + vect_tmp = c.multVect(this.b2[x2], vect_tmp); + this.pub_singular[crnt_row + p] = c.addVect(vect_tmp, + this.pub_singular[crnt_row + p]); + // mul scalar1 with poly2 + vect_tmp = c.multVect(coeff_beta[p][x1][x2], + this.A2[x2]); + vect_tmp = c.multVect(this.b2[x1], vect_tmp); + this.pub_singular[crnt_row + p] = c.addVect(vect_tmp, + this.pub_singular[crnt_row + p]); + // mul scalar1 with scalar2 + sclr_tmp = GF2Field.multElem(coeff_beta[p][x1][x2], + this.b2[x1]); + this.pub_scalar[crnt_row + p] = GF2Field.addElem( + this.pub_scalar[crnt_row + p], GF2Field + .multElem(sclr_tmp, this.b2[x2])); + } + } + // multiply gammas + for (int n = 0; n < vins + oils; n++) + { + // mul poly with scalar + vect_tmp = c.multVect(coeff_gamma[p][n], this.A2[n]); + this.pub_singular[crnt_row + p] = c.addVect(vect_tmp, + this.pub_singular[crnt_row + p]); + // mul scalar with scalar + this.pub_scalar[crnt_row + p] = GF2Field.addElem( + this.pub_scalar[crnt_row + p], GF2Field.multElem( + coeff_gamma[p][n], this.b2[n])); + } + // add eta + this.pub_scalar[crnt_row + p] = GF2Field.addElem( + this.pub_scalar[crnt_row + p], coeff_eta[p]); + } + crnt_row = crnt_row + oils; + } + + // Apply L1 = A1*x+b1 to composition of F and L2 + { + // temporary coefficient arrays + short[][][] tmp_c_quad = new short[rows][vars][vars]; + short[][] tmp_c_sing = new short[rows][vars]; + short[] tmp_c_scal = new short[rows]; + for (int r = 0; r < rows; r++) + { + for (int q = 0; q < A1.length; q++) + { + tmp_c_quad[r] = c.addSquareMatrix(tmp_c_quad[r], c + .multMatrix(A1[r][q], coeff_quadratic_3dim[q])); + tmp_c_sing[r] = c.addVect(tmp_c_sing[r], c.multVect( + A1[r][q], this.pub_singular[q])); + tmp_c_scal[r] = GF2Field.addElem(tmp_c_scal[r], GF2Field + .multElem(A1[r][q], this.pub_scalar[q])); + } + tmp_c_scal[r] = GF2Field.addElem(tmp_c_scal[r], b1[r]); + } + // set public key + coeff_quadratic_3dim = tmp_c_quad; + this.pub_singular = tmp_c_sing; + this.pub_scalar = tmp_c_scal; + } + compactPublicKey(coeff_quadratic_3dim); + } + + /** + * The quadratic (or mixed) terms of the public key are compacted from a n x + * n matrix per polynomial to an upper diagonal matrix stored in one integer + * array of n (n + 1) / 2 elements per polynomial. The ordering of elements + * is lexicographic and the result is updating <tt>this.pub_quadratic</tt>, + * which stores the quadratic elements of the public key. + * + * @param coeff_quadratic_to_compact 3-dimensional array containing a n x n Matrix for each of the + * n - v1 polynomials + */ + private void compactPublicKey(short[][][] coeff_quadratic_to_compact) + { + int polynomials = coeff_quadratic_to_compact.length; + int n = coeff_quadratic_to_compact[0].length; + int entries = n * (n + 1) / 2;// the small gauss + this.pub_quadratic = new short[polynomials][entries]; + int offset = 0; + + for (int p = 0; p < polynomials; p++) + { + offset = 0; + for (int x = 0; x < n; x++) + { + for (int y = x; y < n; y++) + { + if (y == x) + { + this.pub_quadratic[p][offset] = coeff_quadratic_to_compact[p][x][y]; + } + else + { + this.pub_quadratic[p][offset] = GF2Field.addElem( + coeff_quadratic_to_compact[p][x][y], + coeff_quadratic_to_compact[p][y][x]); + } + offset++; + } + } + } + } + + public void init(KeyGenerationParameters param) + { + this.initialize(param); + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + return genKeyPair(); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowKeyParameters.java new file mode 100644 index 00000000..075afda4 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowKeyParameters.java @@ -0,0 +1,25 @@ +package org.spongycastle.pqc.crypto.rainbow; + +import org.spongycastle.crypto.params.AsymmetricKeyParameter; + +public class RainbowKeyParameters + extends AsymmetricKeyParameter +{ + private int docLength; + + public RainbowKeyParameters( + boolean isPrivate, + int docLength) + { + super(isPrivate); + this.docLength = docLength; + } + + /** + * @return the docLength + */ + public int getDocLength() + { + return this.docLength; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowParameters.java new file mode 100644 index 00000000..edc0d023 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowParameters.java @@ -0,0 +1,111 @@ +package org.spongycastle.pqc.crypto.rainbow; + +import org.spongycastle.crypto.CipherParameters; + +public class RainbowParameters + implements CipherParameters +{ + + /** + * DEFAULT PARAMS + */ + /* + * Vi = vinegars per layer whereas n is vu (vu = 33 = n) such that + * + * v1 = 6; o1 = 12-6 = 6 + * + * v2 = 12; o2 = 17-12 = 5 + * + * v3 = 17; o3 = 22-17 = 5 + * + * v4 = 22; o4 = 33-22 = 11 + * + * v5 = 33; (o5 = 0) + */ + private final int[] DEFAULT_VI = {6, 12, 17, 22, 33}; + + private int[] vi;// set of vinegar vars per layer. + + /** + * Default Constructor The elements of the array containing the number of + * Vinegar variables in each layer are set to the default values here. + */ + public RainbowParameters() + { + this.vi = this.DEFAULT_VI; + } + + /** + * Constructor with parameters + * + * @param vi The elements of the array containing the number of Vinegar + * variables per layer are set to the values of the input array. + */ + public RainbowParameters(int[] vi) + { + this.vi = vi; + try + { + checkParams(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + private void checkParams() + throws Exception + { + if (vi == null) + { + throw new Exception("no layers defined."); + } + if (vi.length > 1) + { + for (int i = 0; i < vi.length - 1; i++) + { + if (vi[i] >= vi[i + 1]) + { + throw new Exception( + "v[i] has to be smaller than v[i+1]"); + } + } + } + else + { + throw new Exception( + "Rainbow needs at least 1 layer, such that v1 < v2."); + } + } + + /** + * Getter for the number of layers + * + * @return the number of layers + */ + public int getNumOfLayers() + { + return this.vi.length - 1; + } + + /** + * Getter for the number of all the polynomials in Rainbow + * + * @return the number of the polynomials + */ + public int getDocLength() + { + return vi[vi.length - 1] - vi[0]; + } + + /** + * Getter for the array containing the number of Vinegar-variables per layer + * + * @return the numbers of vinegars per layer + */ + public int[] getVi() + { + return this.vi; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowPrivateKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowPrivateKeyParameters.java new file mode 100644 index 00000000..5d501a53 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowPrivateKeyParameters.java @@ -0,0 +1,117 @@ +package org.spongycastle.pqc.crypto.rainbow; + +public class RainbowPrivateKeyParameters + extends RainbowKeyParameters +{ + /** + * Constructor + * + * @param A1inv the inverse of A1(the matrix part of the affine linear map L1) + * (n-v1 x n-v1 matrix) + * @param b1 translation vector, part of the linear affine map L1 + * @param A2inv the inverse of A2(the matrix part of the affine linear map L2) + * (n x n matrix) + * @param b2 translation vector, part of the linear affine map L2 + * @param vi the number of Vinegar-variables per layer + * @param layers the polynomials with their coefficients of private map F + */ + public RainbowPrivateKeyParameters(short[][] A1inv, short[] b1, + short[][] A2inv, short[] b2, int[] vi, Layer[] layers) + { + super(true, vi[vi.length - 1] - vi[0]); + + this.A1inv = A1inv; + this.b1 = b1; + this.A2inv = A2inv; + this.b2 = b2; + this.vi = vi; + this.layers = layers; + } + + /* + * invertible affine linear map L1 + */ + // the inverse of A1, (n-v1 x n-v1 matrix) + private short[][] A1inv; + + // translation vector of L1 + private short[] b1; + + /* + * invertible affine linear map L2 + */ + // the inverse of A2, (n x n matrix) + private short[][] A2inv; + + // translation vector of L2 + private short[] b2; + + /* + * components of F + */ + // the number of Vinegar-variables per layer. + private int[] vi; + + // contains the polynomials with their coefficients of private map F + private Layer[] layers; + + /** + * Getter for the translation part of the private quadratic map L1. + * + * @return b1 the translation part of L1 + */ + public short[] getB1() + { + return this.b1; + } + + /** + * Getter for the inverse matrix of A1. + * + * @return the A1inv inverse + */ + public short[][] getInvA1() + { + return this.A1inv; + } + + /** + * Getter for the translation part of the private quadratic map L2. + * + * @return b2 the translation part of L2 + */ + public short[] getB2() + { + return this.b2; + } + + /** + * Getter for the inverse matrix of A2 + * + * @return the A2inv + */ + public short[][] getInvA2() + { + return this.A2inv; + } + + /** + * Returns the layers contained in the private key + * + * @return layers + */ + public Layer[] getLayers() + { + return this.layers; + } + + /** + * /** Returns the array of vi-s + * + * @return the vi + */ + public int[] getVi() + { + return vi; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowPublicKeyParameters.java b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowPublicKeyParameters.java new file mode 100644 index 00000000..3a5314e1 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowPublicKeyParameters.java @@ -0,0 +1,53 @@ +package org.spongycastle.pqc.crypto.rainbow; + +public class RainbowPublicKeyParameters + extends RainbowKeyParameters +{ + private short[][] coeffquadratic; + private short[][] coeffsingular; + private short[] coeffscalar; + + /** + * Constructor + * + * @param docLength + * @param coeffQuadratic + * @param coeffSingular + * @param coeffScalar + */ + public RainbowPublicKeyParameters(int docLength, + short[][] coeffQuadratic, short[][] coeffSingular, + short[] coeffScalar) + { + super(false, docLength); + + this.coeffquadratic = coeffQuadratic; + this.coeffsingular = coeffSingular; + this.coeffscalar = coeffScalar; + + } + + /** + * @return the coeffquadratic + */ + public short[][] getCoeffQuadratic() + { + return coeffquadratic; + } + + /** + * @return the coeffsingular + */ + public short[][] getCoeffSingular() + { + return coeffsingular; + } + + /** + * @return the coeffscalar + */ + public short[] getCoeffScalar() + { + return coeffscalar; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowSigner.java b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowSigner.java new file mode 100644 index 00000000..9033b37e --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/RainbowSigner.java @@ -0,0 +1,301 @@ +package org.spongycastle.pqc.crypto.rainbow; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.pqc.crypto.MessageSigner; +import org.spongycastle.pqc.crypto.rainbow.util.ComputeInField; +import org.spongycastle.pqc.crypto.rainbow.util.GF2Field; + +/** + * It implements the sign and verify functions for the Rainbow Signature Scheme. + * Here the message, which has to be signed, is updated. The use of + * different hash functions is possible. + * <p> + * Detailed information about the signature and the verify-method is to be found + * in the paper of Jintai Ding, Dieter Schmidt: Rainbow, a New Multivariable + * Polynomial Signature Scheme. ACNS 2005: 164-175 + * (http://dx.doi.org/10.1007/11496137_12) + */ +public class RainbowSigner + implements MessageSigner +{ + // Source of randomness + private SecureRandom random; + + // The length of a document that can be signed with the privKey + int signableDocumentLength; + + // Container for the oil and vinegar variables of all the layers + private short[] x; + + private ComputeInField cf = new ComputeInField(); + + RainbowKeyParameters key; + + public void init(boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (RainbowPrivateKeyParameters)rParam.getParameters(); + + } + else + { + + this.random = new SecureRandom(); + this.key = (RainbowPrivateKeyParameters)param; + } + } + else + { + this.key = (RainbowPublicKeyParameters)param; + } + + this.signableDocumentLength = this.key.getDocLength(); + } + + + /** + * initial operations before solving the Linear equation system. + * + * @param layer the current layer for which a LES is to be solved. + * @param msg the message that should be signed. + * @return Y_ the modified document needed for solving LES, (Y_ = + * A1^{-1}*(Y-b1)) linear map L1 = A1 x + b1. + */ + private short[] initSign(Layer[] layer, short[] msg) + { + + /* preparation: Modifies the document with the inverse of L1 */ + // tmp = Y - b1: + short[] tmpVec = new short[msg.length]; + + tmpVec = cf.addVect(((RainbowPrivateKeyParameters)this.key).getB1(), msg); + + // Y_ = A1^{-1} * (Y - b1) : + short[] Y_ = cf.multiplyMatrix(((RainbowPrivateKeyParameters)this.key).getInvA1(), tmpVec); + + /* generates the vinegar vars of the first layer at random */ + for (int i = 0; i < layer[0].getVi(); i++) + { + x[i] = (short)random.nextInt(); + x[i] = (short)(x[i] & GF2Field.MASK); + } + + return Y_; + } + + /** + * This function signs the message that has been updated, making use of the + * private key. + * <p> + * For computing the signature, L1 and L2 are needed, as well as LES should + * be solved for each layer in order to find the Oil-variables in the layer. + * <p> + * The Vinegar-variables of the first layer are random generated. + * + * @param message the message + * @return the signature of the message. + */ + public byte[] generateSignature(byte[] message) + { + Layer[] layer = ((RainbowPrivateKeyParameters)this.key).getLayers(); + int numberOfLayers = layer.length; + + x = new short[((RainbowPrivateKeyParameters)this.key).getInvA2().length]; // all variables + + short[] Y_; // modified document + short[] y_i; // part of Y_ each polynomial + int counter; // index of the current part of the doc + + short[] solVec; // the solution of LES pro layer + short[] tmpVec; + + // the signature as an array of shorts: + short[] signature; + // the signature as a byte-array: + byte[] S = new byte[layer[numberOfLayers - 1].getViNext()]; + + short[] msgHashVals = makeMessageRepresentative(message); + + // shows if an exception is caught + boolean ok; + do + { + ok = true; + counter = 0; + try + { + Y_ = initSign(layer, msgHashVals); + + for (int i = 0; i < numberOfLayers; i++) + { + + y_i = new short[layer[i].getOi()]; + solVec = new short[layer[i].getOi()]; // solution of LES + + /* copy oi elements of Y_ into y_i */ + for (int k = 0; k < layer[i].getOi(); k++) + { + y_i[k] = Y_[counter]; + counter++; // current index of Y_ + } + + /* + * plug in the vars of the previous layer in order to get + * the vars of the current layer + */ + solVec = cf.solveEquation(layer[i].plugInVinegars(x), y_i); + + if (solVec == null) + { // LES is not solveable + throw new Exception("LES is not solveable!"); + } + + /* copy the new vars into the x-array */ + for (int j = 0; j < solVec.length; j++) + { + x[layer[i].getVi() + j] = solVec[j]; + } + } + + /* apply the inverse of L2: (signature = A2^{-1}*(b2+x)) */ + tmpVec = cf.addVect(((RainbowPrivateKeyParameters)this.key).getB2(), x); + signature = cf.multiplyMatrix(((RainbowPrivateKeyParameters)this.key).getInvA2(), tmpVec); + + /* cast signature from short[] to byte[] */ + for (int i = 0; i < S.length; i++) + { + S[i] = ((byte)signature[i]); + } + } + catch (Exception se) + { + // if one of the LESs was not solveable - sign again + ok = false; + } + } + while (!ok); + /* return the signature in bytes */ + return S; + } + + /** + * This function verifies the signature of the message that has been + * updated, with the aid of the public key. + * + * @param message the message + * @param signature the signature of the message + * @return true if the signature has been verified, false otherwise. + */ + public boolean verifySignature(byte[] message, byte[] signature) + { + short[] sigInt = new short[signature.length]; + short tmp; + + for (int i = 0; i < signature.length; i++) + { + tmp = (short)signature[i]; + tmp &= (short)0xff; + sigInt[i] = tmp; + } + + short[] msgHashVal = makeMessageRepresentative(message); + + // verify + short[] verificationResult = verifySignatureIntern(sigInt); + + // compare + boolean verified = true; + if (msgHashVal.length != verificationResult.length) + { + return false; + } + for (int i = 0; i < msgHashVal.length; i++) + { + verified = verified && msgHashVal[i] == verificationResult[i]; + } + + return verified; + } + + /** + * Signature verification using public key + * + * @param signature vector of dimension n + * @return document hash of length n - v1 + */ + private short[] verifySignatureIntern(short[] signature) + { + + short[][] coeff_quadratic = ((RainbowPublicKeyParameters)this.key).getCoeffQuadratic(); + short[][] coeff_singular = ((RainbowPublicKeyParameters)this.key).getCoeffSingular(); + short[] coeff_scalar = ((RainbowPublicKeyParameters)this.key).getCoeffScalar(); + + short[] rslt = new short[coeff_quadratic.length];// n - v1 + int n = coeff_singular[0].length; + int offset = 0; // array position + short tmp = 0; // for scalar + + for (int p = 0; p < coeff_quadratic.length; p++) + { // no of polynomials + offset = 0; + for (int x = 0; x < n; x++) + { + // calculate quadratic terms + for (int y = x; y < n; y++) + { + tmp = GF2Field.multElem(coeff_quadratic[p][offset], + GF2Field.multElem(signature[x], signature[y])); + rslt[p] = GF2Field.addElem(rslt[p], tmp); + offset++; + } + // calculate singular terms + tmp = GF2Field.multElem(coeff_singular[p][x], signature[x]); + rslt[p] = GF2Field.addElem(rslt[p], tmp); + } + // add scalar + rslt[p] = GF2Field.addElem(rslt[p], coeff_scalar[p]); + } + + return rslt; + } + + /** + * This function creates the representative of the message which gets signed + * or verified. + * + * @param message the message + * @return message representative + */ + private short[] makeMessageRepresentative(byte[] message) + { + // the message representative + short[] output = new short[this.signableDocumentLength]; + + int h = 0; + int i = 0; + do + { + if (i >= message.length) + { + break; + } + output[i] = (short)message[h]; + output[i] &= (short)0xff; + h++; + i++; + } + while (i < output.length); + + return output; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/util/ComputeInField.java b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/util/ComputeInField.java new file mode 100644 index 00000000..1e4b6c0f --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/util/ComputeInField.java @@ -0,0 +1,490 @@ +package org.spongycastle.pqc.crypto.rainbow.util; + +/** + * This class offers different operations on matrices in field GF2^8. + * <p> + * Implemented are functions: + * - finding inverse of a matrix + * - solving linear equation systems using the Gauss-Elimination method + * - basic operations like matrix multiplication, addition and so on. + */ + +public class ComputeInField +{ + + private short[][] A; // used by solveEquation and inverse + short[] x; + + /** + * Constructor with no parameters + */ + public ComputeInField() + { + } + + + /** + * This function finds a solution of the equation Bx = b. + * Exception is thrown if the linear equation system has no solution + * + * @param B this matrix is the left part of the + * equation (B in the equation above) + * @param b the right part of the equation + * (b in the equation above) + * @return x the solution of the equation if it is solvable + * null otherwise + * @throws RuntimeException if LES is not solvable + */ + public short[] solveEquation(short[][] B, short[] b) + { + try + { + + if (B.length != b.length) + { + throw new RuntimeException( + "The equation system is not solvable"); + } + + /** initialize **/ + // this matrix stores B and b from the equation B*x = b + // b is stored as the last column. + // B contains one column more than rows. + // In this column we store a free coefficient that should be later subtracted from b + A = new short[B.length][B.length + 1]; + // stores the solution of the LES + x = new short[B.length]; + + /** copy B into the global matrix A **/ + for (int i = 0; i < B.length; i++) + { // rows + for (int j = 0; j < B[0].length; j++) + { // cols + A[i][j] = B[i][j]; + } + } + + /** copy the vector b into the global A **/ + //the free coefficient, stored in the last column of A( A[i][b.length] + // is to be subtracted from b + for (int i = 0; i < b.length; i++) + { + A[i][b.length] = GF2Field.addElem(b[i], A[i][b.length]); + } + + /** call the methods for gauss elimination and backward substitution **/ + computeZerosUnder(false); // obtain zeros under the diagonal + substitute(); + + return x; + + } + catch (RuntimeException rte) + { + return null; // the LES is not solvable! + } + } + + /** + * This function computes the inverse of a given matrix using the Gauss- + * Elimination method. + * <p> + * An exception is thrown if the matrix has no inverse + * + * @param coef the matrix which inverse matrix is needed + * @return inverse matrix of the input matrix. + * If the matrix is singular, null is returned. + * @throws RuntimeException if the given matrix is not invertible + */ + public short[][] inverse(short[][] coef) + { + try + { + /** Initialization: **/ + short factor; + short[][] inverse; + A = new short[coef.length][2 * coef.length]; + if (coef.length != coef[0].length) + { + throw new RuntimeException( + "The matrix is not invertible. Please choose another one!"); + } + + /** prepare: Copy coef and the identity matrix into the global A. **/ + for (int i = 0; i < coef.length; i++) + { + for (int j = 0; j < coef.length; j++) + { + //copy the input matrix coef into A + A[i][j] = coef[i][j]; + } + // copy the identity matrix into A. + for (int j = coef.length; j < 2 * coef.length; j++) + { + A[i][j] = 0; + } + A[i][i + A.length] = 1; + } + + /** Elimination operations to get the identity matrix from the left side of A. **/ + // modify A to get 0s under the diagonal. + computeZerosUnder(true); + + // modify A to get only 1s on the diagonal: A[i][j] =A[i][j]/A[i][i]. + for (int i = 0; i < A.length; i++) + { + factor = GF2Field.invElem(A[i][i]); + for (int j = i; j < 2 * A.length; j++) + { + A[i][j] = GF2Field.multElem(A[i][j], factor); + } + } + + //modify A to get only 0s above the diagonal. + computeZerosAbove(); + + // copy the result (the second half of A) in the matrix inverse. + inverse = new short[A.length][A.length]; + for (int i = 0; i < A.length; i++) + { + for (int j = A.length; j < 2 * A.length; j++) + { + inverse[i][j - A.length] = A[i][j]; + } + } + return inverse; + + } + catch (RuntimeException rte) + { + // The matrix is not invertible! A new one should be generated! + return null; + } + } + + /** + * Elimination under the diagonal. + * This function changes a matrix so that it contains only zeros under the + * diagonal(Ai,i) using only Gauss-Elimination operations. + * <p/> + * It is used in solveEquaton as well as in the function for + * finding an inverse of a matrix: {@link}inverse. Both of them use the + * Gauss-Elimination Method. + * <p/> + * The result is stored in the global matrix A + * + * @param usedForInverse This parameter shows if the function is used by the + * solveEquation-function or by the inverse-function and according + * to this creates matrices of different sizes. + * @throws RuntimeException in case a multiplicative inverse of 0 is needed + */ + private void computeZerosUnder(boolean usedForInverse) + throws RuntimeException + { + + //the number of columns in the global A where the tmp results are stored + int length; + short tmp = 0; + + //the function is used in inverse() - A should have 2 times more columns than rows + if (usedForInverse) + { + length = 2 * A.length; + } + //the function is used in solveEquation - A has 1 column more than rows + else + { + length = A.length + 1; + } + + //elimination operations to modify A so that that it contains only 0s under the diagonal + for (int k = 0; k < A.length - 1; k++) + { // the fixed row + for (int i = k + 1; i < A.length; i++) + { // rows + short factor1 = A[i][k]; + short factor2 = GF2Field.invElem(A[k][k]); + + //The element which multiplicative inverse is needed, is 0 + //in this case is the input matrix not invertible + if (factor2 == 0) + { + throw new RuntimeException("Matrix not invertible! We have to choose another one!"); + } + + for (int j = k; j < length; j++) + {// columns + // tmp=A[k,j] / A[k,k] + tmp = GF2Field.multElem(A[k][j], factor2); + // tmp = A[i,k] * A[k,j] / A[k,k] + tmp = GF2Field.multElem(factor1, tmp); + // A[i,j]=A[i,j]-A[i,k]/A[k,k]*A[k,j]; + A[i][j] = GF2Field.addElem(A[i][j], tmp); + } + } + } + } + + /** + * Elimination above the diagonal. + * This function changes a matrix so that it contains only zeros above the + * diagonal(Ai,i) using only Gauss-Elimination operations. + * <p/> + * It is used in the inverse-function + * The result is stored in the global matrix A + * + * @throws RuntimeException in case a multiplicative inverse of 0 is needed + */ + private void computeZerosAbove() + throws RuntimeException + { + short tmp = 0; + for (int k = A.length - 1; k > 0; k--) + { // the fixed row + for (int i = k - 1; i >= 0; i--) + { // rows + short factor1 = A[i][k]; + short factor2 = GF2Field.invElem(A[k][k]); + if (factor2 == 0) + { + throw new RuntimeException("The matrix is not invertible"); + } + for (int j = k; j < 2 * A.length; j++) + { // columns + // tmp = A[k,j] / A[k,k] + tmp = GF2Field.multElem(A[k][j], factor2); + // tmp = A[i,k] * A[k,j] / A[k,k] + tmp = GF2Field.multElem(factor1, tmp); + // A[i,j] = A[i,j] - A[i,k] / A[k,k] * A[k,j]; + A[i][j] = GF2Field.addElem(A[i][j], tmp); + } + } + } + } + + + /** + * This function uses backward substitution to find x + * of the linear equation system (LES) B*x = b, + * where A a triangle-matrix is (contains only zeros under the diagonal) + * and b is a vector + * <p/> + * If the multiplicative inverse of 0 is needed, an exception is thrown. + * In this case is the LES not solvable + * + * @throws RuntimeException in case a multiplicative inverse of 0 is needed + */ + private void substitute() + throws RuntimeException + { + + // for the temporary results of the operations in field + short tmp, temp; + + temp = GF2Field.invElem(A[A.length - 1][A.length - 1]); + if (temp == 0) + { + throw new RuntimeException("The equation system is not solvable"); + } + + /** backward substitution **/ + x[A.length - 1] = GF2Field.multElem(A[A.length - 1][A.length], temp); + for (int i = A.length - 2; i >= 0; i--) + { + tmp = A[i][A.length]; + for (int j = A.length - 1; j > i; j--) + { + temp = GF2Field.multElem(A[i][j], x[j]); + tmp = GF2Field.addElem(tmp, temp); + } + + temp = GF2Field.invElem(A[i][i]); + if (temp == 0) + { + throw new RuntimeException("Not solvable equation system"); + } + x[i] = GF2Field.multElem(tmp, temp); + } + } + + + /** + * This function multiplies two given matrices. + * If the given matrices cannot be multiplied due + * to different sizes, an exception is thrown. + * + * @param M1 -the 1st matrix + * @param M2 -the 2nd matrix + * @return A = M1*M2 + * @throws RuntimeException in case the given matrices cannot be multiplied + * due to different dimensions. + */ + public short[][] multiplyMatrix(short[][] M1, short[][] M2) + throws RuntimeException + { + + if (M1[0].length != M2.length) + { + throw new RuntimeException("Multiplication is not possible!"); + } + short tmp = 0; + A = new short[M1.length][M2[0].length]; + for (int i = 0; i < M1.length; i++) + { + for (int j = 0; j < M2.length; j++) + { + for (int k = 0; k < M2[0].length; k++) + { + tmp = GF2Field.multElem(M1[i][j], M2[j][k]); + A[i][k] = GF2Field.addElem(A[i][k], tmp); + } + } + } + return A; + } + + /** + * This function multiplies a given matrix with a one-dimensional array. + * <p> + * An exception is thrown, if the number of columns in the matrix and + * the number of rows in the one-dim. array differ. + * + * @param M1 the matrix to be multiplied + * @param m the one-dimensional array to be multiplied + * @return M1*m + * @throws RuntimeException in case of dimension inconsistency + */ + public short[] multiplyMatrix(short[][] M1, short[] m) + throws RuntimeException + { + if (M1[0].length != m.length) + { + throw new RuntimeException("Multiplication is not possible!"); + } + short tmp = 0; + short[] B = new short[M1.length]; + for (int i = 0; i < M1.length; i++) + { + for (int j = 0; j < m.length; j++) + { + tmp = GF2Field.multElem(M1[i][j], m[j]); + B[i] = GF2Field.addElem(B[i], tmp); + } + } + return B; + } + + /** + * Addition of two vectors + * + * @param vector1 first summand, always of dim n + * @param vector2 second summand, always of dim n + * @return addition of vector1 and vector2 + * @throws RuntimeException in case the addition is impossible + * due to inconsistency in the dimensions + */ + public short[] addVect(short[] vector1, short[] vector2) + { + if (vector1.length != vector2.length) + { + throw new RuntimeException("Multiplication is not possible!"); + } + short rslt[] = new short[vector1.length]; + for (int n = 0; n < rslt.length; n++) + { + rslt[n] = GF2Field.addElem(vector1[n], vector2[n]); + } + return rslt; + } + + /** + * Multiplication of column vector with row vector + * + * @param vector1 column vector, always n x 1 + * @param vector2 row vector, always 1 x n + * @return resulting n x n matrix of multiplication + * @throws RuntimeException in case the multiplication is impossible due to + * inconsistency in the dimensions + */ + public short[][] multVects(short[] vector1, short[] vector2) + { + if (vector1.length != vector2.length) + { + throw new RuntimeException("Multiplication is not possible!"); + } + short rslt[][] = new short[vector1.length][vector2.length]; + for (int i = 0; i < vector1.length; i++) + { + for (int j = 0; j < vector2.length; j++) + { + rslt[i][j] = GF2Field.multElem(vector1[i], vector2[j]); + } + } + return rslt; + } + + /** + * Multiplies vector with scalar + * + * @param scalar galois element to multiply vector with + * @param vector vector to be multiplied + * @return vector multiplied with scalar + */ + public short[] multVect(short scalar, short[] vector) + { + short rslt[] = new short[vector.length]; + for (int n = 0; n < rslt.length; n++) + { + rslt[n] = GF2Field.multElem(scalar, vector[n]); + } + return rslt; + } + + /** + * Multiplies matrix with scalar + * + * @param scalar galois element to multiply matrix with + * @param matrix 2-dim n x n matrix to be multiplied + * @return matrix multiplied with scalar + */ + public short[][] multMatrix(short scalar, short[][] matrix) + { + short[][] rslt = new short[matrix.length][matrix[0].length]; + for (int i = 0; i < matrix.length; i++) + { + for (int j = 0; j < matrix[0].length; j++) + { + rslt[i][j] = GF2Field.multElem(scalar, matrix[i][j]); + } + } + return rslt; + } + + /** + * Adds the n x n matrices matrix1 and matrix2 + * + * @param matrix1 first summand + * @param matrix2 second summand + * @return addition of matrix1 and matrix2; both having the dimensions n x n + * @throws RuntimeException in case the addition is not possible because of + * different dimensions of the matrices + */ + public short[][] addSquareMatrix(short[][] matrix1, short[][] matrix2) + { + if (matrix1.length != matrix2.length || matrix1[0].length != matrix2[0].length) + { + throw new RuntimeException("Addition is not possible!"); + } + + short[][] rslt = new short[matrix1.length][matrix1.length];// + for (int i = 0; i < matrix1.length; i++) + { + for (int j = 0; j < matrix2.length; j++) + { + rslt[i][j] = GF2Field.addElem(matrix1[i][j], matrix2[i][j]); + } + } + return rslt; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/util/GF2Field.java b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/util/GF2Field.java new file mode 100644 index 00000000..675f0ec5 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/util/GF2Field.java @@ -0,0 +1,139 @@ +package org.spongycastle.pqc.crypto.rainbow.util; + +/** + * This class provides the basic operations like addition, multiplication and + * finding the multiplicative inverse of an element in GF2^8. + * <p> + * The operations are implemented using the irreducible polynomial + * 1+x^2+x^3+x^6+x^8 ( 1 0100 1101 = 0x14d ) + * <p> + * This class makes use of lookup tables(exps and logs) for implementing the + * operations in order to increase the efficiency of Rainbow. + */ +public class GF2Field +{ + + public static final int MASK = 0xff; + + /* + * this lookup table is needed for multiplication and computing the + * multiplicative inverse + */ + static final short exps[] = {1, 2, 4, 8, 16, 32, 64, 128, 77, 154, 121, 242, + 169, 31, 62, 124, 248, 189, 55, 110, 220, 245, 167, 3, 6, 12, 24, + 48, 96, 192, 205, 215, 227, 139, 91, 182, 33, 66, 132, 69, 138, 89, + 178, 41, 82, 164, 5, 10, 20, 40, 80, 160, 13, 26, 52, 104, 208, + 237, 151, 99, 198, 193, 207, 211, 235, 155, 123, 246, 161, 15, 30, + 60, 120, 240, 173, 23, 46, 92, 184, 61, 122, 244, 165, 7, 14, 28, + 56, 112, 224, 141, 87, 174, 17, 34, 68, 136, 93, 186, 57, 114, 228, + 133, 71, 142, 81, 162, 9, 18, 36, 72, 144, 109, 218, 249, 191, 51, + 102, 204, 213, 231, 131, 75, 150, 97, 194, 201, 223, 243, 171, 27, + 54, 108, 216, 253, 183, 35, 70, 140, 85, 170, 25, 50, 100, 200, + 221, 247, 163, 11, 22, 44, 88, 176, 45, 90, 180, 37, 74, 148, 101, + 202, 217, 255, 179, 43, 86, 172, 21, 42, 84, 168, 29, 58, 116, 232, + 157, 119, 238, 145, 111, 222, 241, 175, 19, 38, 76, 152, 125, 250, + 185, 63, 126, 252, 181, 39, 78, 156, 117, 234, 153, 127, 254, 177, + 47, 94, 188, 53, 106, 212, 229, 135, 67, 134, 65, 130, 73, 146, + 105, 210, 233, 159, 115, 230, 129, 79, 158, 113, 226, 137, 95, 190, + 49, 98, 196, 197, 199, 195, 203, 219, 251, 187, 59, 118, 236, 149, + 103, 206, 209, 239, 147, 107, 214, 225, 143, 83, 166, 1}; + + /* + * this lookup table is needed for multiplication and computing the + * multiplicative inverse + */ + static final short logs[] = {0, 0, 1, 23, 2, 46, 24, 83, 3, 106, 47, 147, + 25, 52, 84, 69, 4, 92, 107, 182, 48, 166, 148, 75, 26, 140, 53, + 129, 85, 170, 70, 13, 5, 36, 93, 135, 108, 155, 183, 193, 49, 43, + 167, 163, 149, 152, 76, 202, 27, 230, 141, 115, 54, 205, 130, 18, + 86, 98, 171, 240, 71, 79, 14, 189, 6, 212, 37, 210, 94, 39, 136, + 102, 109, 214, 156, 121, 184, 8, 194, 223, 50, 104, 44, 253, 168, + 138, 164, 90, 150, 41, 153, 34, 77, 96, 203, 228, 28, 123, 231, 59, + 142, 158, 116, 244, 55, 216, 206, 249, 131, 111, 19, 178, 87, 225, + 99, 220, 172, 196, 241, 175, 72, 10, 80, 66, 15, 186, 190, 199, 7, + 222, 213, 120, 38, 101, 211, 209, 95, 227, 40, 33, 137, 89, 103, + 252, 110, 177, 215, 248, 157, 243, 122, 58, 185, 198, 9, 65, 195, + 174, 224, 219, 51, 68, 105, 146, 45, 82, 254, 22, 169, 12, 139, + 128, 165, 74, 91, 181, 151, 201, 42, 162, 154, 192, 35, 134, 78, + 188, 97, 239, 204, 17, 229, 114, 29, 61, 124, 235, 232, 233, 60, + 234, 143, 125, 159, 236, 117, 30, 245, 62, 56, 246, 217, 63, 207, + 118, 250, 31, 132, 160, 112, 237, 20, 144, 179, 126, 88, 251, 226, + 32, 100, 208, 221, 119, 173, 218, 197, 64, 242, 57, 176, 247, 73, + 180, 11, 127, 81, 21, 67, 145, 16, 113, 187, 238, 191, 133, 200, + 161}; + + /** + * This function calculates the sum of two elements as an operation in GF2^8 + * + * @param x the first element that is to be added + * @param y the second element that should be add + * @return the sum of the two elements x and y in GF2^8 + */ + public static short addElem(short x, short y) + { + return (short)(x ^ y); + } + + /** + * This function computes the multiplicative inverse of a given element in + * GF2^8 The 0 has no multiplicative inverse and in this case 0 is returned. + * + * @param x the element which multiplicative inverse is to be computed + * @return the multiplicative inverse of the given element, in case it + * exists or 0, otherwise + */ + public static short invElem(short x) + { + if (x == 0) + { + return 0; + } + return (exps[255 - logs[x]]); + } + + /** + * This function multiplies two elements in GF2^8. If one of the two + * elements is 0, 0 is returned. + * + * @param x the first element to be multiplied. + * @param y the second element to be multiplied. + * @return the product of the two input elements in GF2^8. + */ + public static short multElem(short x, short y) + { + if (x == 0 || y == 0) + { + return 0; + } + else + { + return (exps[(logs[x] + logs[y]) % 255]); + } + } + + /** + * This function returns the values of exps-lookup table which correspond to + * the input + * + * @param x the index in the lookup table exps + * @return exps-value, corresponding to the input + */ + public static short getExp(short x) + { + return exps[x]; + } + + /** + * This function returns the values of logs-lookup table which correspond to + * the input + * + * @param x the index in the lookup table logs + * @return logs-value, corresponding to the input + */ + public static short getLog(short x) + { + return logs[x]; + } + + +} diff --git a/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/util/RainbowUtil.java b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/util/RainbowUtil.java new file mode 100644 index 00000000..1e4f40f4 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/crypto/rainbow/util/RainbowUtil.java @@ -0,0 +1,230 @@ +package org.spongycastle.pqc.crypto.rainbow.util; + +/** + * This class is needed for the conversions while encoding and decoding, as well as for + * comparison between arrays of some dimensions + */ +public class RainbowUtil +{ + + /** + * This function converts an one-dimensional array of bytes into a + * one-dimensional array of int + * + * @param in the array to be converted + * @return out + * the one-dimensional int-array that corresponds the input + */ + public static int[] convertArraytoInt(byte[] in) + { + int[] out = new int[in.length]; + for (int i = 0; i < in.length; i++) + { + out[i] = in[i] & GF2Field.MASK; + } + return out; + } + + /** + * This function converts an one-dimensional array of bytes into a + * one-dimensional array of type short + * + * @param in the array to be converted + * @return out + * one-dimensional short-array that corresponds the input + */ + public static short[] convertArray(byte[] in) + { + short[] out = new short[in.length]; + for (int i = 0; i < in.length; i++) + { + out[i] = (short)(in[i] & GF2Field.MASK); + } + return out; + } + + /** + * This function converts a matrix of bytes into a matrix of type short + * + * @param in the matrix to be converted + * @return out + * short-matrix that corresponds the input + */ + public static short[][] convertArray(byte[][] in) + { + short[][] out = new short[in.length][in[0].length]; + for (int i = 0; i < in.length; i++) + { + for (int j = 0; j < in[0].length; j++) + { + out[i][j] = (short)(in[i][j] & GF2Field.MASK); + } + } + return out; + } + + /** + * This function converts a 3-dimensional array of bytes into a 3-dimensional array of type short + * + * @param in the array to be converted + * @return out + * short-array that corresponds the input + */ + public static short[][][] convertArray(byte[][][] in) + { + short[][][] out = new short[in.length][in[0].length][in[0][0].length]; + for (int i = 0; i < in.length; i++) + { + for (int j = 0; j < in[0].length; j++) + { + for (int k = 0; k < in[0][0].length; k++) + { + out[i][j][k] = (short)(in[i][j][k] & GF2Field.MASK); + } + } + } + return out; + } + + /** + * This function converts an array of type int into an array of type byte + * + * @param in the array to be converted + * @return out + * the byte-array that corresponds the input + */ + public static byte[] convertIntArray(int[] in) + { + byte[] out = new byte[in.length]; + for (int i = 0; i < in.length; i++) + { + out[i] = (byte)in[i]; + } + return out; + } + + + /** + * This function converts an array of type short into an array of type byte + * + * @param in the array to be converted + * @return out + * the byte-array that corresponds the input + */ + public static byte[] convertArray(short[] in) + { + byte[] out = new byte[in.length]; + for (int i = 0; i < in.length; i++) + { + out[i] = (byte)in[i]; + } + return out; + } + + /** + * This function converts a matrix of type short into a matrix of type byte + * + * @param in the matrix to be converted + * @return out + * the byte-matrix that corresponds the input + */ + public static byte[][] convertArray(short[][] in) + { + byte[][] out = new byte[in.length][in[0].length]; + for (int i = 0; i < in.length; i++) + { + for (int j = 0; j < in[0].length; j++) + { + out[i][j] = (byte)in[i][j]; + } + } + return out; + } + + /** + * This function converts a 3-dimensional array of type short into a 3-dimensional array of type byte + * + * @param in the array to be converted + * @return out + * the byte-array that corresponds the input + */ + public static byte[][][] convertArray(short[][][] in) + { + byte[][][] out = new byte[in.length][in[0].length][in[0][0].length]; + for (int i = 0; i < in.length; i++) + { + for (int j = 0; j < in[0].length; j++) + { + for (int k = 0; k < in[0][0].length; k++) + { + out[i][j][k] = (byte)in[i][j][k]; + } + } + } + return out; + } + + /** + * Compare two short arrays. No null checks are performed. + * + * @param left the first short array + * @param right the second short array + * @return the result of the comparison + */ + public static boolean equals(short[] left, short[] right) + { + if (left.length != right.length) + { + return false; + } + boolean result = true; + for (int i = left.length - 1; i >= 0; i--) + { + result &= left[i] == right[i]; + } + return result; + } + + /** + * Compare two two-dimensional short arrays. No null checks are performed. + * + * @param left the first short array + * @param right the second short array + * @return the result of the comparison + */ + public static boolean equals(short[][] left, short[][] right) + { + if (left.length != right.length) + { + return false; + } + boolean result = true; + for (int i = left.length - 1; i >= 0; i--) + { + result &= equals(left[i], right[i]); + } + return result; + } + + /** + * Compare two three-dimensional short arrays. No null checks are performed. + * + * @param left the first short array + * @param right the second short array + * @return the result of the comparison + */ + public static boolean equals(short[][][] left, short[][][] right) + { + if (left.length != right.length) + { + return false; + } + boolean result = true; + for (int i = left.length - 1; i >= 0; i--) + { + result &= equals(left[i], right[i]); + } + return result; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/BigEndianConversions.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/BigEndianConversions.java new file mode 100644 index 00000000..4ed7f1c9 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/BigEndianConversions.java @@ -0,0 +1,306 @@ +package org.spongycastle.pqc.math.linearalgebra; + + +/** + * This is a utility class containing data type conversions using big-endian + * byte order. + * + * @see LittleEndianConversions + */ +public final class BigEndianConversions +{ + + /** + * Default constructor (private). + */ + private BigEndianConversions() + { + // empty + } + + /** + * Convert an integer to an octet string of length 4 according to IEEE 1363, + * Section 5.5.3. + * + * @param x the integer to convert + * @return the converted integer + */ + public static byte[] I2OSP(int x) + { + byte[] result = new byte[4]; + result[0] = (byte)(x >>> 24); + result[1] = (byte)(x >>> 16); + result[2] = (byte)(x >>> 8); + result[3] = (byte)x; + return result; + } + + /** + * Convert an integer to an octet string according to IEEE 1363, Section + * 5.5.3. Length checking is performed. + * + * @param x the integer to convert + * @param oLen the desired length of the octet string + * @return an octet string of length <tt>oLen</tt> representing the + * integer <tt>x</tt>, or <tt>null</tt> if the integer is + * negative + * @throws ArithmeticException if <tt>x</tt> can't be encoded into <tt>oLen</tt> + * octets. + */ + public static byte[] I2OSP(int x, int oLen) + throws ArithmeticException + { + if (x < 0) + { + return null; + } + int octL = IntegerFunctions.ceilLog256(x); + if (octL > oLen) + { + throw new ArithmeticException( + "Cannot encode given integer into specified number of octets."); + } + byte[] result = new byte[oLen]; + for (int i = oLen - 1; i >= oLen - octL; i--) + { + result[i] = (byte)(x >>> (8 * (oLen - 1 - i))); + } + return result; + } + + /** + * Convert an integer to an octet string of length 4 according to IEEE 1363, + * Section 5.5.3. + * + * @param input the integer to convert + * @param output byte array holding the output + * @param outOff offset in output array where the result is stored + */ + public static void I2OSP(int input, byte[] output, int outOff) + { + output[outOff++] = (byte)(input >>> 24); + output[outOff++] = (byte)(input >>> 16); + output[outOff++] = (byte)(input >>> 8); + output[outOff] = (byte)input; + } + + /** + * Convert an integer to an octet string of length 8 according to IEEE 1363, + * Section 5.5.3. + * + * @param input the integer to convert + * @return the converted integer + */ + public static byte[] I2OSP(long input) + { + byte[] output = new byte[8]; + output[0] = (byte)(input >>> 56); + output[1] = (byte)(input >>> 48); + output[2] = (byte)(input >>> 40); + output[3] = (byte)(input >>> 32); + output[4] = (byte)(input >>> 24); + output[5] = (byte)(input >>> 16); + output[6] = (byte)(input >>> 8); + output[7] = (byte)input; + return output; + } + + /** + * Convert an integer to an octet string of length 8 according to IEEE 1363, + * Section 5.5.3. + * + * @param input the integer to convert + * @param output byte array holding the output + * @param outOff offset in output array where the result is stored + */ + public static void I2OSP(long input, byte[] output, int outOff) + { + output[outOff++] = (byte)(input >>> 56); + output[outOff++] = (byte)(input >>> 48); + output[outOff++] = (byte)(input >>> 40); + output[outOff++] = (byte)(input >>> 32); + output[outOff++] = (byte)(input >>> 24); + output[outOff++] = (byte)(input >>> 16); + output[outOff++] = (byte)(input >>> 8); + output[outOff] = (byte)input; + } + + /** + * Convert an integer to an octet string of the specified length according + * to IEEE 1363, Section 5.5.3. No length checking is performed (i.e., if + * the integer cannot be encoded into <tt>length</tt> octets, it is + * truncated). + * + * @param input the integer to convert + * @param output byte array holding the output + * @param outOff offset in output array where the result is stored + * @param length the length of the encoding + */ + public static void I2OSP(int input, byte[] output, int outOff, int length) + { + for (int i = length - 1; i >= 0; i--) + { + output[outOff + i] = (byte)(input >>> (8 * (length - 1 - i))); + } + } + + /** + * Convert an octet string to an integer according to IEEE 1363, Section + * 5.5.3. + * + * @param input the byte array holding the octet string + * @return an integer representing the octet string <tt>input</tt>, or + * <tt>0</tt> if the represented integer is negative or too large + * or the byte array is empty + * @throws ArithmeticException if the length of the given octet string is larger than 4. + */ + public static int OS2IP(byte[] input) + { + if (input.length > 4) + { + throw new ArithmeticException("invalid input length"); + } + if (input.length == 0) + { + return 0; + } + int result = 0; + for (int j = 0; j < input.length; j++) + { + result |= (input[j] & 0xff) << (8 * (input.length - 1 - j)); + } + return result; + } + + /** + * Convert a byte array of length 4 beginning at <tt>offset</tt> into an + * integer. + * + * @param input the byte array + * @param inOff the offset into the byte array + * @return the resulting integer + */ + public static int OS2IP(byte[] input, int inOff) + { + int result = (input[inOff++] & 0xff) << 24; + result |= (input[inOff++] & 0xff) << 16; + result |= (input[inOff++] & 0xff) << 8; + result |= input[inOff] & 0xff; + return result; + } + + /** + * Convert an octet string to an integer according to IEEE 1363, Section + * 5.5.3. + * + * @param input the byte array holding the octet string + * @param inOff the offset in the input byte array where the octet string + * starts + * @param inLen the length of the encoded integer + * @return an integer representing the octet string <tt>bytes</tt>, or + * <tt>0</tt> if the represented integer is negative or too large + * or the byte array is empty + */ + public static int OS2IP(byte[] input, int inOff, int inLen) + { + if ((input.length == 0) || input.length < inOff + inLen - 1) + { + return 0; + } + int result = 0; + for (int j = 0; j < inLen; j++) + { + result |= (input[inOff + j] & 0xff) << (8 * (inLen - j - 1)); + } + return result; + } + + /** + * Convert a byte array of length 8 beginning at <tt>inOff</tt> into a + * long integer. + * + * @param input the byte array + * @param inOff the offset into the byte array + * @return the resulting long integer + */ + public static long OS2LIP(byte[] input, int inOff) + { + long result = ((long)input[inOff++] & 0xff) << 56; + result |= ((long)input[inOff++] & 0xff) << 48; + result |= ((long)input[inOff++] & 0xff) << 40; + result |= ((long)input[inOff++] & 0xff) << 32; + result |= ((long)input[inOff++] & 0xff) << 24; + result |= (input[inOff++] & 0xff) << 16; + result |= (input[inOff++] & 0xff) << 8; + result |= input[inOff] & 0xff; + return result; + } + + /** + * Convert an int array into a byte array. + * + * @param input the int array + * @return the converted array + */ + public static byte[] toByteArray(final int[] input) + { + byte[] result = new byte[input.length << 2]; + for (int i = 0; i < input.length; i++) + { + I2OSP(input[i], result, i << 2); + } + return result; + } + + /** + * Convert an int array into a byte array of the specified length. No length + * checking is performed (i.e., if the last integer cannot be encoded into + * <tt>length % 4</tt> octets, it is truncated). + * + * @param input the int array + * @param length the length of the converted array + * @return the converted array + */ + public static byte[] toByteArray(final int[] input, int length) + { + final int intLen = input.length; + byte[] result = new byte[length]; + int index = 0; + for (int i = 0; i <= intLen - 2; i++, index += 4) + { + I2OSP(input[i], result, index); + } + I2OSP(input[intLen - 1], result, index, length - index); + return result; + } + + /** + * Convert a byte array into an int array. + * + * @param input the byte array + * @return the converted array + */ + public static int[] toIntArray(byte[] input) + { + final int intLen = (input.length + 3) / 4; + final int lastLen = input.length & 0x03; + int[] result = new int[intLen]; + + int index = 0; + for (int i = 0; i <= intLen - 2; i++, index += 4) + { + result[i] = OS2IP(input, index); + } + if (lastLen != 0) + { + result[intLen - 1] = OS2IP(input, index, lastLen); + } + else + { + result[intLen - 1] = OS2IP(input, index); + } + + return result; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/BigIntUtils.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/BigIntUtils.java new file mode 100644 index 00000000..19734977 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/BigIntUtils.java @@ -0,0 +1,138 @@ +package org.spongycastle.pqc.math.linearalgebra; + +import java.math.BigInteger; + +/** + * FIXME: is this really necessary?! + */ +public final class BigIntUtils +{ + + /** + * Default constructor (private). + */ + private BigIntUtils() + { + // empty + } + + /** + * Checks if two BigInteger arrays contain the same entries + * + * @param a first BigInteger array + * @param b second BigInteger array + * @return true or false + */ + public static boolean equals(BigInteger[] a, BigInteger[] b) + { + int flag = 0; + + if (a.length != b.length) + { + return false; + } + for (int i = 0; i < a.length; i++) + { + // avoid branches here! + // problem: compareTo on BigIntegers is not + // guaranteed constant-time! + flag |= a[i].compareTo(b[i]); + } + return flag == 0; + } + + /** + * Fill the given BigInteger array with the given value. + * + * @param array the array + * @param value the value + */ + public static void fill(BigInteger[] array, BigInteger value) + { + for (int i = array.length - 1; i >= 0; i--) + { + array[i] = value; + } + } + + /** + * Generates a subarray of a given BigInteger array. + * + * @param input - + * the input BigInteger array + * @param start - + * the start index + * @param end - + * the end index + * @return a subarray of <tt>input</tt>, ranging from <tt>start</tt> to + * <tt>end</tt> + */ + public static BigInteger[] subArray(BigInteger[] input, int start, int end) + { + BigInteger[] result = new BigInteger[end - start]; + System.arraycopy(input, start, result, 0, end - start); + return result; + } + + /** + * Converts a BigInteger array into an integer array + * + * @param input - + * the BigInteger array + * @return the integer array + */ + public static int[] toIntArray(BigInteger[] input) + { + int[] result = new int[input.length]; + for (int i = 0; i < input.length; i++) + { + result[i] = input[i].intValue(); + } + return result; + } + + /** + * Converts a BigInteger array into an integer array, reducing all + * BigIntegers mod q. + * + * @param q - + * the modulus + * @param input - + * the BigInteger array + * @return the integer array + */ + public static int[] toIntArrayModQ(int q, BigInteger[] input) + { + BigInteger bq = BigInteger.valueOf(q); + int[] result = new int[input.length]; + for (int i = 0; i < input.length; i++) + { + result[i] = input[i].mod(bq).intValue(); + } + return result; + } + + /** + * Return the value of <tt>big</tt> as a byte array. Although BigInteger + * has such a method, it uses an extra bit to indicate the sign of the + * number. For elliptic curve cryptography, the numbers usually are + * positive. Thus, this helper method returns a byte array of minimal + * length, ignoring the sign of the number. + * + * @param value the <tt>BigInteger</tt> value to be converted to a byte + * array + * @return the value <tt>big</tt> as byte array + */ + public static byte[] toMinimalByteArray(BigInteger value) + { + byte[] valBytes = value.toByteArray(); + if ((valBytes.length == 1) || (value.bitLength() & 0x07) != 0) + { + return valBytes; + } + byte[] result = new byte[value.bitLength() >> 3]; + System.arraycopy(valBytes, 1, result, 0, result.length); + return result; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/ByteUtils.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/ByteUtils.java new file mode 100644 index 00000000..0e234ae9 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/ByteUtils.java @@ -0,0 +1,414 @@ +package org.spongycastle.pqc.math.linearalgebra; + +/** + * This class is a utility class for manipulating byte arrays. + */ +public final class ByteUtils +{ + + private static final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + /** + * Default constructor (private) + */ + private ByteUtils() + { + // empty + } + + /** + * Compare two byte arrays (perform null checks beforehand). + * + * @param left the first byte array + * @param right the second byte array + * @return the result of the comparison + */ + public static boolean equals(byte[] left, byte[] right) + { + if (left == null) + { + return right == null; + } + if (right == null) + { + return false; + } + + if (left.length != right.length) + { + return false; + } + boolean result = true; + for (int i = left.length - 1; i >= 0; i--) + { + result &= left[i] == right[i]; + } + return result; + } + + /** + * Compare two two-dimensional byte arrays. No null checks are performed. + * + * @param left the first byte array + * @param right the second byte array + * @return the result of the comparison + */ + public static boolean equals(byte[][] left, byte[][] right) + { + if (left.length != right.length) + { + return false; + } + + boolean result = true; + for (int i = left.length - 1; i >= 0; i--) + { + result &= ByteUtils.equals(left[i], right[i]); + } + + return result; + } + + /** + * Compare two three-dimensional byte arrays. No null checks are performed. + * + * @param left the first byte array + * @param right the second byte array + * @return the result of the comparison + */ + public static boolean equals(byte[][][] left, byte[][][] right) + { + if (left.length != right.length) + { + return false; + } + + boolean result = true; + for (int i = left.length - 1; i >= 0; i--) + { + if (left[i].length != right[i].length) + { + return false; + } + for (int j = left[i].length - 1; j >= 0; j--) + { + result &= ByteUtils.equals(left[i][j], right[i][j]); + } + } + + return result; + } + + /** + * Computes a hashcode based on the contents of a one-dimensional byte array + * rather than its identity. + * + * @param array the array to compute the hashcode of + * @return the hashcode + */ + public static int deepHashCode(byte[] array) + { + int result = 1; + for (int i = 0; i < array.length; i++) + { + result = 31 * result + array[i]; + } + return result; + } + + /** + * Computes a hashcode based on the contents of a two-dimensional byte array + * rather than its identity. + * + * @param array the array to compute the hashcode of + * @return the hashcode + */ + public static int deepHashCode(byte[][] array) + { + int result = 1; + for (int i = 0; i < array.length; i++) + { + result = 31 * result + deepHashCode(array[i]); + } + return result; + } + + /** + * Computes a hashcode based on the contents of a three-dimensional byte + * array rather than its identity. + * + * @param array the array to compute the hashcode of + * @return the hashcode + */ + public static int deepHashCode(byte[][][] array) + { + int result = 1; + for (int i = 0; i < array.length; i++) + { + result = 31 * result + deepHashCode(array[i]); + } + return result; + } + + + /** + * Return a clone of the given byte array (performs null check beforehand). + * + * @param array the array to clone + * @return the clone of the given array, or <tt>null</tt> if the array is + * <tt>null</tt> + */ + public static byte[] clone(byte[] array) + { + if (array == null) + { + return null; + } + byte[] result = new byte[array.length]; + System.arraycopy(array, 0, result, 0, array.length); + return result; + } + + /** + * Convert a string containing hexadecimal characters to a byte-array. + * + * @param s a hex string + * @return a byte array with the corresponding value + */ + public static byte[] fromHexString(String s) + { + char[] rawChars = s.toUpperCase().toCharArray(); + + int hexChars = 0; + for (int i = 0; i < rawChars.length; i++) + { + if ((rawChars[i] >= '0' && rawChars[i] <= '9') + || (rawChars[i] >= 'A' && rawChars[i] <= 'F')) + { + hexChars++; + } + } + + byte[] byteString = new byte[(hexChars + 1) >> 1]; + + int pos = hexChars & 1; + + for (int i = 0; i < rawChars.length; i++) + { + if (rawChars[i] >= '0' && rawChars[i] <= '9') + { + byteString[pos >> 1] <<= 4; + byteString[pos >> 1] |= rawChars[i] - '0'; + } + else if (rawChars[i] >= 'A' && rawChars[i] <= 'F') + { + byteString[pos >> 1] <<= 4; + byteString[pos >> 1] |= rawChars[i] - 'A' + 10; + } + else + { + continue; + } + pos++; + } + + return byteString; + } + + /** + * Convert a byte array to the corresponding hexstring. + * + * @param input the byte array to be converted + * @return the corresponding hexstring + */ + public static String toHexString(byte[] input) + { + String result = ""; + for (int i = 0; i < input.length; i++) + { + result += HEX_CHARS[(input[i] >>> 4) & 0x0f]; + result += HEX_CHARS[(input[i]) & 0x0f]; + } + return result; + } + + /** + * Convert a byte array to the corresponding hex string. + * + * @param input the byte array to be converted + * @param prefix the prefix to put at the beginning of the hex string + * @param seperator a separator string + * @return the corresponding hex string + */ + public static String toHexString(byte[] input, String prefix, + String seperator) + { + String result = new String(prefix); + for (int i = 0; i < input.length; i++) + { + result += HEX_CHARS[(input[i] >>> 4) & 0x0f]; + result += HEX_CHARS[(input[i]) & 0x0f]; + if (i < input.length - 1) + { + result += seperator; + } + } + return result; + } + + /** + * Convert a byte array to the corresponding bit string. + * + * @param input the byte array to be converted + * @return the corresponding bit string + */ + public static String toBinaryString(byte[] input) + { + String result = ""; + int i; + for (i = 0; i < input.length; i++) + { + int e = input[i]; + for (int ii = 0; ii < 8; ii++) + { + int b = (e >>> ii) & 1; + result += b; + } + if (i != input.length - 1) + { + result += " "; + } + } + return result; + } + + /** + * Compute the bitwise XOR of two arrays of bytes. The arrays have to be of + * same length. No length checking is performed. + * + * @param x1 the first array + * @param x2 the second array + * @return x1 XOR x2 + */ + public static byte[] xor(byte[] x1, byte[] x2) + { + byte[] out = new byte[x1.length]; + + for (int i = x1.length - 1; i >= 0; i--) + { + out[i] = (byte)(x1[i] ^ x2[i]); + } + return out; + } + + /** + * Concatenate two byte arrays. No null checks are performed. + * + * @param x1 the first array + * @param x2 the second array + * @return (x2||x1) (little-endian order, i.e. x1 is at lower memory + * addresses) + */ + public static byte[] concatenate(byte[] x1, byte[] x2) + { + byte[] result = new byte[x1.length + x2.length]; + + System.arraycopy(x1, 0, result, 0, x1.length); + System.arraycopy(x2, 0, result, x1.length, x2.length); + + return result; + } + + /** + * Convert a 2-dimensional byte array into a 1-dimensional byte array by + * concatenating all entries. + * + * @param array a 2-dimensional byte array + * @return the concatenated input array + */ + public static byte[] concatenate(byte[][] array) + { + int rowLength = array[0].length; + byte[] result = new byte[array.length * rowLength]; + int index = 0; + for (int i = 0; i < array.length; i++) + { + System.arraycopy(array[i], 0, result, index, rowLength); + index += rowLength; + } + return result; + } + + /** + * Split a byte array <tt>input</tt> into two arrays at <tt>index</tt>, + * i.e. the first array will have the lower <tt>index</tt> bytes, the + * second one the higher <tt>input.length - index</tt> bytes. + * + * @param input the byte array to be split + * @param index the index where the byte array is split + * @return the splitted input array as an array of two byte arrays + * @throws ArrayIndexOutOfBoundsException if <tt>index</tt> is out of bounds + */ + public static byte[][] split(byte[] input, int index) + throws ArrayIndexOutOfBoundsException + { + if (index > input.length) + { + throw new ArrayIndexOutOfBoundsException(); + } + byte[][] result = new byte[2][]; + result[0] = new byte[index]; + result[1] = new byte[input.length - index]; + System.arraycopy(input, 0, result[0], 0, index); + System.arraycopy(input, index, result[1], 0, input.length - index); + return result; + } + + /** + * Generate a subarray of a given byte array. + * + * @param input the input byte array + * @param start the start index + * @param end the end index + * @return a subarray of <tt>input</tt>, ranging from <tt>start</tt> + * (inclusively) to <tt>end</tt> (exclusively) + */ + public static byte[] subArray(byte[] input, int start, int end) + { + byte[] result = new byte[end - start]; + System.arraycopy(input, start, result, 0, end - start); + return result; + } + + /** + * Generate a subarray of a given byte array. + * + * @param input the input byte array + * @param start the start index + * @return a subarray of <tt>input</tt>, ranging from <tt>start</tt> to + * the end of the array + */ + public static byte[] subArray(byte[] input, int start) + { + return subArray(input, start, input.length); + } + + /** + * Rewrite a byte array as a char array + * + * @param input - + * the byte array + * @return char array + */ + public static char[] toCharArray(byte[] input) + { + char[] result = new char[input.length]; + for (int i = 0; i < input.length; i++) + { + result[i] = (char)input[i]; + } + return result; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/CharUtils.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/CharUtils.java new file mode 100644 index 00000000..44f97716 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/CharUtils.java @@ -0,0 +1,98 @@ +package org.spongycastle.pqc.math.linearalgebra; + +public final class CharUtils +{ + + /** + * Default constructor (private) + */ + private CharUtils() + { + // empty + } + + /** + * Return a clone of the given char array. No null checks are performed. + * + * @param array the array to clone + * @return the clone of the given array + */ + public static char[] clone(char[] array) + { + char[] result = new char[array.length]; + System.arraycopy(array, 0, result, 0, array.length); + return result; + } + + /** + * Convert the given char array into a byte array. + * + * @param chars the char array + * @return the converted array + */ + public static byte[] toByteArray(char[] chars) + { + byte[] result = new byte[chars.length]; + for (int i = chars.length - 1; i >= 0; i--) + { + result[i] = (byte)chars[i]; + } + return result; + } + + /** + * Convert the given char array into a + * byte array for use with PBE encryption. + * + * @param chars the char array + * @return the converted array + */ + public static byte[] toByteArrayForPBE(char[] chars) + { + + byte[] out = new byte[chars.length]; + + for (int i = 0; i < chars.length; i++) + { + out[i] = (byte)chars[i]; + } + + int length = out.length * 2; + byte[] ret = new byte[length + 2]; + + int j = 0; + for (int i = 0; i < out.length; i++) + { + j = i * 2; + ret[j] = 0; + ret[j + 1] = out[i]; + } + + ret[length] = 0; + ret[length + 1] = 0; + + return ret; + } + + /** + * Compare two char arrays. No null checks are performed. + * + * @param left the char byte array + * @param right the second char array + * @return the result of the comparison + */ + public static boolean equals(char[] left, char[] right) + { + if (left.length != right.length) + { + return false; + } + boolean result = true; + for (int i = left.length - 1; i >= 0; i--) + { + result &= left[i] == right[i]; + } + return result; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2Matrix.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2Matrix.java new file mode 100644 index 00000000..7e2de19e --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2Matrix.java @@ -0,0 +1,1323 @@ +package org.spongycastle.pqc.math.linearalgebra; + +import java.security.SecureRandom; + +/** + * This class describes some operations with matrices over finite field GF(2) + * and is used in ecc and MQ-PKC (also has some specific methods and + * implementation) + */ +public class GF2Matrix + extends Matrix +{ + + /** + * For the matrix representation the array of type int[][] is used, thus one + * element of the array keeps 32 elements of the matrix (from one row and 32 + * columns) + */ + private int[][] matrix; + + /** + * the length of each array representing a row of this matrix, computed as + * <tt>(numColumns + 31) / 32</tt> + */ + private int length; + + /** + * Create the matrix from encoded form. + * + * @param enc the encoded matrix + */ + public GF2Matrix(byte[] enc) + { + if (enc.length < 9) + { + throw new ArithmeticException( + "given array is not an encoded matrix over GF(2)"); + } + + numRows = LittleEndianConversions.OS2IP(enc, 0); + numColumns = LittleEndianConversions.OS2IP(enc, 4); + + int n = ((numColumns + 7) >>> 3) * numRows; + + if ((numRows <= 0) || (n != (enc.length - 8))) + { + throw new ArithmeticException( + "given array is not an encoded matrix over GF(2)"); + } + + length = (numColumns + 31) >>> 5; + matrix = new int[numRows][length]; + + // number of "full" integer + int q = numColumns >> 5; + // number of bits in non-full integer + int r = numColumns & 0x1f; + + int count = 8; + for (int i = 0; i < numRows; i++) + { + for (int j = 0; j < q; j++, count += 4) + { + matrix[i][j] = LittleEndianConversions.OS2IP(enc, count); + } + for (int j = 0; j < r; j += 8) + { + matrix[i][q] ^= (enc[count++] & 0xff) << j; + } + } + } + + /** + * Create the matrix with the contents of the given array. The matrix is not + * copied. Unused coefficients are masked out. + * + * @param numColumns the number of columns + * @param matrix the element array + */ + public GF2Matrix(int numColumns, int[][] matrix) + { + if (matrix[0].length != (numColumns + 31) >> 5) + { + throw new ArithmeticException( + "Int array does not match given number of columns."); + } + this.numColumns = numColumns; + numRows = matrix.length; + length = matrix[0].length; + int rest = numColumns & 0x1f; + int bitMask; + if (rest == 0) + { + bitMask = 0xffffffff; + } + else + { + bitMask = (1 << rest) - 1; + } + for (int i = 0; i < numRows; i++) + { + matrix[i][length - 1] &= bitMask; + } + this.matrix = matrix; + } + + /** + * Create an nxn matrix of the given type. + * + * @param n the number of rows (and columns) + * @param typeOfMatrix the martix type (see {@link Matrix} for predefined + * constants) + */ + public GF2Matrix(int n, char typeOfMatrix) + { + this(n, typeOfMatrix, new java.security.SecureRandom()); + } + + /** + * Create an nxn matrix of the given type. + * + * @param n the matrix size + * @param typeOfMatrix the matrix type + * @param sr the source of randomness + */ + public GF2Matrix(int n, char typeOfMatrix, SecureRandom sr) + { + if (n <= 0) + { + throw new ArithmeticException("Size of matrix is non-positive."); + } + + switch (typeOfMatrix) + { + + case Matrix.MATRIX_TYPE_ZERO: + assignZeroMatrix(n, n); + break; + + case Matrix.MATRIX_TYPE_UNIT: + assignUnitMatrix(n); + break; + + case Matrix.MATRIX_TYPE_RANDOM_LT: + assignRandomLowerTriangularMatrix(n, sr); + break; + + case Matrix.MATRIX_TYPE_RANDOM_UT: + assignRandomUpperTriangularMatrix(n, sr); + break; + + case Matrix.MATRIX_TYPE_RANDOM_REGULAR: + assignRandomRegularMatrix(n, sr); + break; + + default: + throw new ArithmeticException("Unknown matrix type."); + } + } + + /** + * Copy constructor. + * + * @param a another {@link GF2Matrix} + */ + public GF2Matrix(GF2Matrix a) + { + numColumns = a.getNumColumns(); + numRows = a.getNumRows(); + length = a.length; + matrix = new int[a.matrix.length][]; + for (int i = 0; i < matrix.length; i++) + { + matrix[i] = IntUtils.clone(a.matrix[i]); + } + + } + + /** + * create the mxn zero matrix + */ + private GF2Matrix(int m, int n) + { + if ((n <= 0) || (m <= 0)) + { + throw new ArithmeticException("size of matrix is non-positive"); + } + + assignZeroMatrix(m, n); + } + + /** + * Create the mxn zero matrix. + * + * @param m number of rows + * @param n number of columns + */ + private void assignZeroMatrix(int m, int n) + { + numRows = m; + numColumns = n; + length = (n + 31) >>> 5; + matrix = new int[numRows][length]; + for (int i = 0; i < numRows; i++) + { + for (int j = 0; j < length; j++) + { + matrix[i][j] = 0; + } + } + } + + /** + * Create the mxn unit matrix. + * + * @param n number of rows (and columns) + */ + private void assignUnitMatrix(int n) + { + numRows = n; + numColumns = n; + length = (n + 31) >>> 5; + matrix = new int[numRows][length]; + for (int i = 0; i < numRows; i++) + { + for (int j = 0; j < length; j++) + { + matrix[i][j] = 0; + } + } + for (int i = 0; i < numRows; i++) + { + int rest = i & 0x1f; + matrix[i][i >>> 5] = 1 << rest; + } + } + + /** + * Create a nxn random lower triangular matrix. + * + * @param n number of rows (and columns) + * @param sr source of randomness + */ + private void assignRandomLowerTriangularMatrix(int n, SecureRandom sr) + { + numRows = n; + numColumns = n; + length = (n + 31) >>> 5; + matrix = new int[numRows][length]; + for (int i = 0; i < numRows; i++) + { + int q = i >>> 5; + int r = i & 0x1f; + int s = 31 - r; + r = 1 << r; + for (int j = 0; j < q; j++) + { + matrix[i][j] = sr.nextInt(); + } + matrix[i][q] = (sr.nextInt() >>> s) | r; + for (int j = q + 1; j < length; j++) + { + matrix[i][j] = 0; + } + + } + + } + + /** + * Create a nxn random upper triangular matrix. + * + * @param n number of rows (and columns) + * @param sr source of randomness + */ + private void assignRandomUpperTriangularMatrix(int n, SecureRandom sr) + { + numRows = n; + numColumns = n; + length = (n + 31) >>> 5; + matrix = new int[numRows][length]; + int rest = n & 0x1f; + int help; + if (rest == 0) + { + help = 0xffffffff; + } + else + { + help = (1 << rest) - 1; + } + for (int i = 0; i < numRows; i++) + { + int q = i >>> 5; + int r = i & 0x1f; + int s = r; + r = 1 << r; + for (int j = 0; j < q; j++) + { + matrix[i][j] = 0; + } + matrix[i][q] = (sr.nextInt() << s) | r; + for (int j = q + 1; j < length; j++) + { + matrix[i][j] = sr.nextInt(); + } + matrix[i][length - 1] &= help; + } + + } + + /** + * Create an nxn random regular matrix. + * + * @param n number of rows (and columns) + * @param sr source of randomness + */ + private void assignRandomRegularMatrix(int n, SecureRandom sr) + { + numRows = n; + numColumns = n; + length = (n + 31) >>> 5; + matrix = new int[numRows][length]; + GF2Matrix lm = new GF2Matrix(n, Matrix.MATRIX_TYPE_RANDOM_LT, sr); + GF2Matrix um = new GF2Matrix(n, Matrix.MATRIX_TYPE_RANDOM_UT, sr); + GF2Matrix rm = (GF2Matrix)lm.rightMultiply(um); + Permutation perm = new Permutation(n, sr); + int[] p = perm.getVector(); + for (int i = 0; i < n; i++) + { + System.arraycopy(rm.matrix[i], 0, matrix[p[i]], 0, length); + } + } + + /** + * Create a nxn random regular matrix and its inverse. + * + * @param n number of rows (and columns) + * @param sr source of randomness + * @return the created random regular matrix and its inverse + */ + public static GF2Matrix[] createRandomRegularMatrixAndItsInverse(int n, + SecureRandom sr) + { + + GF2Matrix[] result = new GF2Matrix[2]; + + // ------------------------------------ + // First part: create regular matrix + // ------------------------------------ + + // ------ + int length = (n + 31) >> 5; + GF2Matrix lm = new GF2Matrix(n, Matrix.MATRIX_TYPE_RANDOM_LT, sr); + GF2Matrix um = new GF2Matrix(n, Matrix.MATRIX_TYPE_RANDOM_UT, sr); + GF2Matrix rm = (GF2Matrix)lm.rightMultiply(um); + Permutation p = new Permutation(n, sr); + int[] pVec = p.getVector(); + + int[][] matrix = new int[n][length]; + for (int i = 0; i < n; i++) + { + System.arraycopy(rm.matrix[pVec[i]], 0, matrix[i], 0, length); + } + + result[0] = new GF2Matrix(n, matrix); + + // ------------------------------------ + // Second part: create inverse matrix + // ------------------------------------ + + // inverse to lm + GF2Matrix invLm = new GF2Matrix(n, Matrix.MATRIX_TYPE_UNIT); + for (int i = 0; i < n; i++) + { + int rest = i & 0x1f; + int q = i >>> 5; + int r = 1 << rest; + for (int j = i + 1; j < n; j++) + { + int b = (lm.matrix[j][q]) & r; + if (b != 0) + { + for (int k = 0; k <= q; k++) + { + invLm.matrix[j][k] ^= invLm.matrix[i][k]; + } + } + } + } + // inverse to um + GF2Matrix invUm = new GF2Matrix(n, Matrix.MATRIX_TYPE_UNIT); + for (int i = n - 1; i >= 0; i--) + { + int rest = i & 0x1f; + int q = i >>> 5; + int r = 1 << rest; + for (int j = i - 1; j >= 0; j--) + { + int b = (um.matrix[j][q]) & r; + if (b != 0) + { + for (int k = q; k < length; k++) + { + invUm.matrix[j][k] ^= invUm.matrix[i][k]; + } + } + } + } + + // inverse matrix + result[1] = (GF2Matrix)invUm.rightMultiply(invLm.rightMultiply(p)); + + return result; + } + + /** + * @return the array keeping the matrix elements + */ + public int[][] getIntArray() + { + return matrix; + } + + /** + * @return the length of each array representing a row of this matrix + */ + public int getLength() + { + return length; + } + + /** + * Return the row of this matrix with the given index. + * + * @param index the index + * @return the row of this matrix with the given index + */ + public int[] getRow(int index) + { + return matrix[index]; + } + + /** + * Returns encoded matrix, i.e., this matrix in byte array form + * + * @return the encoded matrix + */ + public byte[] getEncoded() + { + int n = (numColumns + 7) >>> 3; + n *= numRows; + n += 8; + byte[] enc = new byte[n]; + + LittleEndianConversions.I2OSP(numRows, enc, 0); + LittleEndianConversions.I2OSP(numColumns, enc, 4); + + // number of "full" integer + int q = numColumns >>> 5; + // number of bits in non-full integer + int r = numColumns & 0x1f; + + int count = 8; + for (int i = 0; i < numRows; i++) + { + for (int j = 0; j < q; j++, count += 4) + { + LittleEndianConversions.I2OSP(matrix[i][j], enc, count); + } + for (int j = 0; j < r; j += 8) + { + enc[count++] = (byte)((matrix[i][q] >>> j) & 0xff); + } + + } + return enc; + } + + + /** + * Returns the percentage of the number of "ones" in this matrix. + * + * @return the Hamming weight of this matrix (as a ratio). + */ + public double getHammingWeight() + { + double counter = 0.0; + double elementCounter = 0.0; + int rest = numColumns & 0x1f; + int d; + if (rest == 0) + { + d = length; + } + else + { + d = length - 1; + } + + for (int i = 0; i < numRows; i++) + { + + for (int j = 0; j < d; j++) + { + int a = matrix[i][j]; + for (int k = 0; k < 32; k++) + { + int b = (a >>> k) & 1; + counter = counter + b; + elementCounter = elementCounter + 1; + } + } + int a = matrix[i][length - 1]; + for (int k = 0; k < rest; k++) + { + int b = (a >>> k) & 1; + counter = counter + b; + elementCounter = elementCounter + 1; + } + } + + return counter / elementCounter; + } + + /** + * Check if this is the zero matrix (i.e., all entries are zero). + * + * @return <tt>true</tt> if this is the zero matrix + */ + public boolean isZero() + { + for (int i = 0; i < numRows; i++) + { + for (int j = 0; j < length; j++) + { + if (matrix[i][j] != 0) + { + return false; + } + } + } + return true; + } + + /** + * Get the quadratic submatrix of this matrix consisting of the leftmost + * <tt>numRows</tt> columns. + * + * @return the <tt>(numRows x numRows)</tt> submatrix + */ + public GF2Matrix getLeftSubMatrix() + { + if (numColumns <= numRows) + { + throw new ArithmeticException("empty submatrix"); + } + int length = (numRows + 31) >> 5; + int[][] result = new int[numRows][length]; + int bitMask = (1 << (numRows & 0x1f)) - 1; + if (bitMask == 0) + { + bitMask = -1; + } + for (int i = numRows - 1; i >= 0; i--) + { + System.arraycopy(matrix[i], 0, result[i], 0, length); + result[i][length - 1] &= bitMask; + } + return new GF2Matrix(numRows, result); + } + + /** + * Compute the full form matrix <tt>(this | Id)</tt> from this matrix in + * left compact form, where <tt>Id</tt> is the <tt>k x k</tt> identity + * matrix and <tt>k</tt> is the number of rows of this matrix. + * + * @return <tt>(this | Id)</tt> + */ + public GF2Matrix extendLeftCompactForm() + { + int newNumColumns = numColumns + numRows; + GF2Matrix result = new GF2Matrix(numRows, newNumColumns); + + int ind = numRows - 1 + numColumns; + for (int i = numRows - 1; i >= 0; i--, ind--) + { + // copy this matrix to first columns + System.arraycopy(matrix[i], 0, result.matrix[i], 0, length); + // store the identity in last columns + result.matrix[i][ind >> 5] |= 1 << (ind & 0x1f); + } + + return result; + } + + /** + * Get the submatrix of this matrix consisting of the rightmost + * <tt>numColumns-numRows</tt> columns. + * + * @return the <tt>(numRows x (numColumns-numRows))</tt> submatrix + */ + public GF2Matrix getRightSubMatrix() + { + if (numColumns <= numRows) + { + throw new ArithmeticException("empty submatrix"); + } + + int q = numRows >> 5; + int r = numRows & 0x1f; + + GF2Matrix result = new GF2Matrix(numRows, numColumns - numRows); + + for (int i = numRows - 1; i >= 0; i--) + { + // if words have to be shifted + if (r != 0) + { + int ind = q; + // process all but last word + for (int j = 0; j < result.length - 1; j++) + { + // shift to correct position + result.matrix[i][j] = (matrix[i][ind++] >>> r) + | (matrix[i][ind] << (32 - r)); + } + // process last word + result.matrix[i][result.length - 1] = matrix[i][ind++] >>> r; + if (ind < length) + { + result.matrix[i][result.length - 1] |= matrix[i][ind] << (32 - r); + } + } + else + { + // no shifting necessary + System.arraycopy(matrix[i], q, result.matrix[i], 0, + result.length); + } + } + return result; + } + + /** + * Compute the full form matrix <tt>(Id | this)</tt> from this matrix in + * right compact form, where <tt>Id</tt> is the <tt>k x k</tt> identity + * matrix and <tt>k</tt> is the number of rows of this matrix. + * + * @return <tt>(Id | this)</tt> + */ + public GF2Matrix extendRightCompactForm() + { + GF2Matrix result = new GF2Matrix(numRows, numRows + numColumns); + + int q = numRows >> 5; + int r = numRows & 0x1f; + + for (int i = numRows - 1; i >= 0; i--) + { + // store the identity in first columns + result.matrix[i][i >> 5] |= 1 << (i & 0x1f); + + // copy this matrix to last columns + + // if words have to be shifted + if (r != 0) + { + int ind = q; + // process all but last word + for (int j = 0; j < length - 1; j++) + { + // obtain matrix word + int mw = matrix[i][j]; + // shift to correct position + result.matrix[i][ind++] |= mw << r; + result.matrix[i][ind] |= mw >>> (32 - r); + } + // process last word + int mw = matrix[i][length - 1]; + result.matrix[i][ind++] |= mw << r; + if (ind < result.length) + { + result.matrix[i][ind] |= mw >>> (32 - r); + } + } + else + { + // no shifting necessary + System.arraycopy(matrix[i], 0, result.matrix[i], q, length); + } + } + + return result; + } + + /** + * Compute the transpose of this matrix. + * + * @return <tt>(this)<sup>T</sup></tt> + */ + public Matrix computeTranspose() + { + int[][] result = new int[numColumns][(numRows + 31) >>> 5]; + for (int i = 0; i < numRows; i++) + { + for (int j = 0; j < numColumns; j++) + { + int qs = j >>> 5; + int rs = j & 0x1f; + int b = (matrix[i][qs] >>> rs) & 1; + int qt = i >>> 5; + int rt = i & 0x1f; + if (b == 1) + { + result[j][qt] |= 1 << rt; + } + } + } + + return new GF2Matrix(numRows, result); + } + + /** + * Compute the inverse of this matrix. + * + * @return the inverse of this matrix (newly created). + * @throws ArithmeticException if this matrix is not invertible. + */ + public Matrix computeInverse() + { + if (numRows != numColumns) + { + throw new ArithmeticException("Matrix is not invertible."); + } + + // clone this matrix + int[][] tmpMatrix = new int[numRows][length]; + for (int i = numRows - 1; i >= 0; i--) + { + tmpMatrix[i] = IntUtils.clone(matrix[i]); + } + + // initialize inverse matrix as unit matrix + int[][] invMatrix = new int[numRows][length]; + for (int i = numRows - 1; i >= 0; i--) + { + int q = i >> 5; + int r = i & 0x1f; + invMatrix[i][q] = 1 << r; + } + + // simultaneously compute Gaussian reduction of tmpMatrix and unit + // matrix + for (int i = 0; i < numRows; i++) + { + // i = q * 32 + (i mod 32) + int q = i >> 5; + int bitMask = 1 << (i & 0x1f); + // if diagonal element is zero + if ((tmpMatrix[i][q] & bitMask) == 0) + { + boolean foundNonZero = false; + // find a non-zero element in the same column + for (int j = i + 1; j < numRows; j++) + { + if ((tmpMatrix[j][q] & bitMask) != 0) + { + // found it, swap rows ... + foundNonZero = true; + swapRows(tmpMatrix, i, j); + swapRows(invMatrix, i, j); + // ... and quit searching + j = numRows; + continue; + } + } + // if no non-zero element was found ... + if (!foundNonZero) + { + // ... the matrix is not invertible + throw new ArithmeticException("Matrix is not invertible."); + } + } + + // normalize all but i-th row + for (int j = numRows - 1; j >= 0; j--) + { + if ((j != i) && ((tmpMatrix[j][q] & bitMask) != 0)) + { + addToRow(tmpMatrix[i], tmpMatrix[j], q); + addToRow(invMatrix[i], invMatrix[j], 0); + } + } + } + + return new GF2Matrix(numColumns, invMatrix); + } + + /** + * Compute the product of a permutation matrix (which is generated from an + * n-permutation) and this matrix. + * + * @param p the permutation + * @return {@link GF2Matrix} <tt>P*this</tt> + */ + public Matrix leftMultiply(Permutation p) + { + int[] pVec = p.getVector(); + if (pVec.length != numRows) + { + throw new ArithmeticException("length mismatch"); + } + + int[][] result = new int[numRows][]; + + for (int i = numRows - 1; i >= 0; i--) + { + result[i] = IntUtils.clone(matrix[pVec[i]]); + } + + return new GF2Matrix(numRows, result); + } + + /** + * compute product a row vector and this matrix + * + * @param vec a vector over GF(2) + * @return Vector product a*matrix + */ + public Vector leftMultiply(Vector vec) + { + + if (!(vec instanceof GF2Vector)) + { + throw new ArithmeticException("vector is not defined over GF(2)"); + } + + if (vec.length != numRows) + { + throw new ArithmeticException("length mismatch"); + } + + int[] v = ((GF2Vector)vec).getVecArray(); + int[] res = new int[length]; + + int q = numRows >> 5; + int r = 1 << (numRows & 0x1f); + + // compute scalar products with full words of vector + int row = 0; + for (int i = 0; i < q; i++) + { + int bitMask = 1; + do + { + int b = v[i] & bitMask; + if (b != 0) + { + for (int j = 0; j < length; j++) + { + res[j] ^= matrix[row][j]; + } + } + row++; + bitMask <<= 1; + } + while (bitMask != 0); + } + + // compute scalar products with last word of vector + int bitMask = 1; + while (bitMask != r) + { + int b = v[q] & bitMask; + if (b != 0) + { + for (int j = 0; j < length; j++) + { + res[j] ^= matrix[row][j]; + } + } + row++; + bitMask <<= 1; + } + + return new GF2Vector(res, numColumns); + } + + /** + * Compute the product of the matrix <tt>(this | Id)</tt> and a column + * vector, where <tt>Id</tt> is a <tt>(numRows x numRows)</tt> unit + * matrix. + * + * @param vec the vector over GF(2) + * @return <tt>(this | Id)*vector</tt> + */ + public Vector leftMultiplyLeftCompactForm(Vector vec) + { + if (!(vec instanceof GF2Vector)) + { + throw new ArithmeticException("vector is not defined over GF(2)"); + } + + if (vec.length != numRows) + { + throw new ArithmeticException("length mismatch"); + } + + int[] v = ((GF2Vector)vec).getVecArray(); + int[] res = new int[(numRows + numColumns + 31) >>> 5]; + + // process full words of vector + int words = numRows >>> 5; + int row = 0; + for (int i = 0; i < words; i++) + { + int bitMask = 1; + do + { + int b = v[i] & bitMask; + if (b != 0) + { + // compute scalar product part + for (int j = 0; j < length; j++) + { + res[j] ^= matrix[row][j]; + } + // set last bit + int q = (numColumns + row) >>> 5; + int r = (numColumns + row) & 0x1f; + res[q] |= 1 << r; + } + row++; + bitMask <<= 1; + } + while (bitMask != 0); + } + + // process last word of vector + int rem = 1 << (numRows & 0x1f); + int bitMask = 1; + while (bitMask != rem) + { + int b = v[words] & bitMask; + if (b != 0) + { + // compute scalar product part + for (int j = 0; j < length; j++) + { + res[j] ^= matrix[row][j]; + } + // set last bit + int q = (numColumns + row) >>> 5; + int r = (numColumns + row) & 0x1f; + res[q] |= 1 << r; + } + row++; + bitMask <<= 1; + } + + return new GF2Vector(res, numRows + numColumns); + } + + /** + * Compute the product of this matrix and a matrix A over GF(2). + * + * @param mat a matrix A over GF(2) + * @return matrix product <tt>this*matrixA</tt> + */ + public Matrix rightMultiply(Matrix mat) + { + if (!(mat instanceof GF2Matrix)) + { + throw new ArithmeticException("matrix is not defined over GF(2)"); + } + + if (mat.numRows != numColumns) + { + throw new ArithmeticException("length mismatch"); + } + + GF2Matrix a = (GF2Matrix)mat; + GF2Matrix result = new GF2Matrix(numRows, mat.numColumns); + + int d; + int rest = numColumns & 0x1f; + if (rest == 0) + { + d = length; + } + else + { + d = length - 1; + } + for (int i = 0; i < numRows; i++) + { + int count = 0; + for (int j = 0; j < d; j++) + { + int e = matrix[i][j]; + for (int h = 0; h < 32; h++) + { + int b = e & (1 << h); + if (b != 0) + { + for (int g = 0; g < a.length; g++) + { + result.matrix[i][g] ^= a.matrix[count][g]; + } + } + count++; + } + } + int e = matrix[i][length - 1]; + for (int h = 0; h < rest; h++) + { + int b = e & (1 << h); + if (b != 0) + { + for (int g = 0; g < a.length; g++) + { + result.matrix[i][g] ^= a.matrix[count][g]; + } + } + count++; + } + + } + + return result; + } + + /** + * Compute the product of this matrix and a permutation matrix which is + * generated from an n-permutation. + * + * @param p the permutation + * @return {@link GF2Matrix} <tt>this*P</tt> + */ + public Matrix rightMultiply(Permutation p) + { + + int[] pVec = p.getVector(); + if (pVec.length != numColumns) + { + throw new ArithmeticException("length mismatch"); + } + + GF2Matrix result = new GF2Matrix(numRows, numColumns); + + for (int i = numColumns - 1; i >= 0; i--) + { + int q = i >>> 5; + int r = i & 0x1f; + int pq = pVec[i] >>> 5; + int pr = pVec[i] & 0x1f; + for (int j = numRows - 1; j >= 0; j--) + { + result.matrix[j][q] |= ((matrix[j][pq] >>> pr) & 1) << r; + } + } + + return result; + } + + /** + * Compute the product of this matrix and the given column vector. + * + * @param vec the vector over GF(2) + * @return <tt>this*vector</tt> + */ + public Vector rightMultiply(Vector vec) + { + if (!(vec instanceof GF2Vector)) + { + throw new ArithmeticException("vector is not defined over GF(2)"); + } + + if (vec.length != numColumns) + { + throw new ArithmeticException("length mismatch"); + } + + int[] v = ((GF2Vector)vec).getVecArray(); + int[] res = new int[(numRows + 31) >>> 5]; + + for (int i = 0; i < numRows; i++) + { + // compute full word scalar products + int help = 0; + for (int j = 0; j < length; j++) + { + help ^= matrix[i][j] & v[j]; + } + // compute single word scalar product + int bitValue = 0; + for (int j = 0; j < 32; j++) + { + bitValue ^= (help >>> j) & 1; + } + // set result bit + if (bitValue == 1) + { + res[i >>> 5] |= 1 << (i & 0x1f); + } + } + + return new GF2Vector(res, numRows); + } + + /** + * Compute the product of the matrix <tt>(Id | this)</tt> and a column + * vector, where <tt>Id</tt> is a <tt>(numRows x numRows)</tt> unit + * matrix. + * + * @param vec the vector over GF(2) + * @return <tt>(Id | this)*vector</tt> + */ + public Vector rightMultiplyRightCompactForm(Vector vec) + { + if (!(vec instanceof GF2Vector)) + { + throw new ArithmeticException("vector is not defined over GF(2)"); + } + + if (vec.length != numColumns + numRows) + { + throw new ArithmeticException("length mismatch"); + } + + int[] v = ((GF2Vector)vec).getVecArray(); + int[] res = new int[(numRows + 31) >>> 5]; + + int q = numRows >> 5; + int r = numRows & 0x1f; + + // for all rows + for (int i = 0; i < numRows; i++) + { + // get vector bit + int help = (v[i >> 5] >>> (i & 0x1f)) & 1; + + // compute full word scalar products + int vInd = q; + // if words have to be shifted + if (r != 0) + { + int vw = 0; + // process all but last word + for (int j = 0; j < length - 1; j++) + { + // shift to correct position + vw = (v[vInd++] >>> r) | (v[vInd] << (32 - r)); + help ^= matrix[i][j] & vw; + } + // process last word + vw = v[vInd++] >>> r; + if (vInd < v.length) + { + vw |= v[vInd] << (32 - r); + } + help ^= matrix[i][length - 1] & vw; + } + else + { + // no shifting necessary + for (int j = 0; j < length; j++) + { + help ^= matrix[i][j] & v[vInd++]; + } + } + + // compute single word scalar product + int bitValue = 0; + for (int j = 0; j < 32; j++) + { + bitValue ^= help & 1; + help >>>= 1; + } + + // set result bit + if (bitValue == 1) + { + res[i >> 5] |= 1 << (i & 0x1f); + } + } + + return new GF2Vector(res, numRows); + } + + /** + * Compare this matrix with another object. + * + * @param other another object + * @return the result of the comparison + */ + public boolean equals(Object other) + { + + if (!(other instanceof GF2Matrix)) + { + return false; + } + GF2Matrix otherMatrix = (GF2Matrix)other; + + if ((numRows != otherMatrix.numRows) + || (numColumns != otherMatrix.numColumns) + || (length != otherMatrix.length)) + { + return false; + } + + for (int i = 0; i < numRows; i++) + { + if (!IntUtils.equals(matrix[i], otherMatrix.matrix[i])) + { + return false; + } + } + + return true; + } + + /** + * @return the hash code of this matrix + */ + public int hashCode() + { + int hash = (numRows * 31 + numColumns) * 31 + length; + for (int i = 0; i < numRows; i++) + { + hash = hash * 31 + matrix[i].hashCode(); + } + return hash; + } + + /** + * @return a human readable form of the matrix + */ + public String toString() + { + int rest = numColumns & 0x1f; + int d; + if (rest == 0) + { + d = length; + } + else + { + d = length - 1; + } + + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < numRows; i++) + { + buf.append(i + ": "); + for (int j = 0; j < d; j++) + { + int a = matrix[i][j]; + for (int k = 0; k < 32; k++) + { + int b = (a >>> k) & 1; + if (b == 0) + { + buf.append('0'); + } + else + { + buf.append('1'); + } + } + buf.append(' '); + } + int a = matrix[i][length - 1]; + for (int k = 0; k < rest; k++) + { + int b = (a >>> k) & 1; + if (b == 0) + { + buf.append('0'); + } + else + { + buf.append('1'); + } + } + buf.append('\n'); + } + + return buf.toString(); + } + + /** + * Swap two rows of the given matrix. + * + * @param matrix the matrix + * @param first the index of the first row + * @param second the index of the second row + */ + private static void swapRows(int[][] matrix, int first, int second) + { + int[] tmp = matrix[first]; + matrix[first] = matrix[second]; + matrix[second] = tmp; + } + + /** + * Partially add one row to another. + * + * @param fromRow the addend + * @param toRow the row to add to + * @param startIndex the array index to start from + */ + private static void addToRow(int[] fromRow, int[] toRow, int startIndex) + { + for (int i = toRow.length - 1; i >= startIndex; i--) + { + toRow[i] = fromRow[i] ^ toRow[i]; + } + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2Polynomial.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2Polynomial.java new file mode 100644 index 00000000..25bf099a --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2Polynomial.java @@ -0,0 +1,2039 @@ +package org.spongycastle.pqc.math.linearalgebra; + + +import java.math.BigInteger; +import java.util.Random; + + +/** + * This class stores very long strings of bits and does some basic arithmetics. + * It is used by <tt>GF2nField</tt>, <tt>GF2nPolynomialField</tt> and + * <tt>GFnPolynomialElement</tt>. + * + * @see GF2nPolynomialElement + * @see GF2nField + */ +public class GF2Polynomial +{ + + // number of bits stored in this GF2Polynomial + private int len; + + // number of int used in value + private int blocks; + + // storage + private int[] value; + + // Random source + private static Random rand = new Random(); + + // Lookup-Table for vectorMult: parity[a]= #1(a) mod 2 == 1 + private static final boolean[] parity = {false, true, true, false, true, + false, false, true, true, false, false, true, false, true, true, + false, true, false, false, true, false, true, true, false, false, + true, true, false, true, false, false, true, true, false, false, + true, false, true, true, false, false, true, true, false, true, + false, false, true, false, true, true, false, true, false, false, + true, true, false, false, true, false, true, true, false, true, + false, false, true, false, true, true, false, false, true, true, + false, true, false, false, true, false, true, true, false, true, + false, false, true, true, false, false, true, false, true, true, + false, false, true, true, false, true, false, false, true, true, + false, false, true, false, true, true, false, true, false, false, + true, false, true, true, false, false, true, true, false, true, + false, false, true, true, false, false, true, false, true, true, + false, false, true, true, false, true, false, false, true, false, + true, true, false, true, false, false, true, true, false, false, + true, false, true, true, false, false, true, true, false, true, + false, false, true, true, false, false, true, false, true, true, + false, true, false, false, true, false, true, true, false, false, + true, true, false, true, false, false, true, false, true, true, + false, true, false, false, true, true, false, false, true, false, + true, true, false, true, false, false, true, false, true, true, + false, false, true, true, false, true, false, false, true, true, + false, false, true, false, true, true, false, false, true, true, + false, true, false, false, true, false, true, true, false, true, + false, false, true, true, false, false, true, false, true, true, + false}; + + // Lookup-Table for Squaring: squaringTable[a]=a^2 + private static final short[] squaringTable = {0x0000, 0x0001, 0x0004, + 0x0005, 0x0010, 0x0011, 0x0014, 0x0015, 0x0040, 0x0041, 0x0044, + 0x0045, 0x0050, 0x0051, 0x0054, 0x0055, 0x0100, 0x0101, 0x0104, + 0x0105, 0x0110, 0x0111, 0x0114, 0x0115, 0x0140, 0x0141, 0x0144, + 0x0145, 0x0150, 0x0151, 0x0154, 0x0155, 0x0400, 0x0401, 0x0404, + 0x0405, 0x0410, 0x0411, 0x0414, 0x0415, 0x0440, 0x0441, 0x0444, + 0x0445, 0x0450, 0x0451, 0x0454, 0x0455, 0x0500, 0x0501, 0x0504, + 0x0505, 0x0510, 0x0511, 0x0514, 0x0515, 0x0540, 0x0541, 0x0544, + 0x0545, 0x0550, 0x0551, 0x0554, 0x0555, 0x1000, 0x1001, 0x1004, + 0x1005, 0x1010, 0x1011, 0x1014, 0x1015, 0x1040, 0x1041, 0x1044, + 0x1045, 0x1050, 0x1051, 0x1054, 0x1055, 0x1100, 0x1101, 0x1104, + 0x1105, 0x1110, 0x1111, 0x1114, 0x1115, 0x1140, 0x1141, 0x1144, + 0x1145, 0x1150, 0x1151, 0x1154, 0x1155, 0x1400, 0x1401, 0x1404, + 0x1405, 0x1410, 0x1411, 0x1414, 0x1415, 0x1440, 0x1441, 0x1444, + 0x1445, 0x1450, 0x1451, 0x1454, 0x1455, 0x1500, 0x1501, 0x1504, + 0x1505, 0x1510, 0x1511, 0x1514, 0x1515, 0x1540, 0x1541, 0x1544, + 0x1545, 0x1550, 0x1551, 0x1554, 0x1555, 0x4000, 0x4001, 0x4004, + 0x4005, 0x4010, 0x4011, 0x4014, 0x4015, 0x4040, 0x4041, 0x4044, + 0x4045, 0x4050, 0x4051, 0x4054, 0x4055, 0x4100, 0x4101, 0x4104, + 0x4105, 0x4110, 0x4111, 0x4114, 0x4115, 0x4140, 0x4141, 0x4144, + 0x4145, 0x4150, 0x4151, 0x4154, 0x4155, 0x4400, 0x4401, 0x4404, + 0x4405, 0x4410, 0x4411, 0x4414, 0x4415, 0x4440, 0x4441, 0x4444, + 0x4445, 0x4450, 0x4451, 0x4454, 0x4455, 0x4500, 0x4501, 0x4504, + 0x4505, 0x4510, 0x4511, 0x4514, 0x4515, 0x4540, 0x4541, 0x4544, + 0x4545, 0x4550, 0x4551, 0x4554, 0x4555, 0x5000, 0x5001, 0x5004, + 0x5005, 0x5010, 0x5011, 0x5014, 0x5015, 0x5040, 0x5041, 0x5044, + 0x5045, 0x5050, 0x5051, 0x5054, 0x5055, 0x5100, 0x5101, 0x5104, + 0x5105, 0x5110, 0x5111, 0x5114, 0x5115, 0x5140, 0x5141, 0x5144, + 0x5145, 0x5150, 0x5151, 0x5154, 0x5155, 0x5400, 0x5401, 0x5404, + 0x5405, 0x5410, 0x5411, 0x5414, 0x5415, 0x5440, 0x5441, 0x5444, + 0x5445, 0x5450, 0x5451, 0x5454, 0x5455, 0x5500, 0x5501, 0x5504, + 0x5505, 0x5510, 0x5511, 0x5514, 0x5515, 0x5540, 0x5541, 0x5544, + 0x5545, 0x5550, 0x5551, 0x5554, 0x5555}; + + // pre-computed Bitmask for fast masking, bitMask[a]=0x1 << a + private static final int[] bitMask = {0x00000001, 0x00000002, 0x00000004, + 0x00000008, 0x00000010, 0x00000020, 0x00000040, 0x00000080, + 0x00000100, 0x00000200, 0x00000400, 0x00000800, 0x00001000, + 0x00002000, 0x00004000, 0x00008000, 0x00010000, 0x00020000, + 0x00040000, 0x00080000, 0x00100000, 0x00200000, 0x00400000, + 0x00800000, 0x01000000, 0x02000000, 0x04000000, 0x08000000, + 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x00000000}; + + // pre-computed Bitmask for fast masking, rightMask[a]=0xffffffff >>> (32-a) + private static final int[] reverseRightMask = {0x00000000, 0x00000001, + 0x00000003, 0x00000007, 0x0000000f, 0x0000001f, 0x0000003f, + 0x0000007f, 0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff, + 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff, + 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff, 0x001fffff, + 0x003fffff, 0x007fffff, 0x00ffffff, 0x01ffffff, 0x03ffffff, + 0x07ffffff, 0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, + 0xffffffff}; + + /** + * Creates a new GF2Polynomial of the given <i>length</i> and value zero. + * + * @param length the desired number of bits to store + */ + public GF2Polynomial(int length) + { + int l = length; + if (l < 1) + { + l = 1; + } + blocks = ((l - 1) >> 5) + 1; + value = new int[blocks]; + len = l; + } + + /** + * Creates a new GF2Polynomial of the given <i>length</i> and random value. + * + * @param length the desired number of bits to store + * @param rand SecureRandom to use for randomization + */ + public GF2Polynomial(int length, Random rand) + { + int l = length; + if (l < 1) + { + l = 1; + } + blocks = ((l - 1) >> 5) + 1; + value = new int[blocks]; + len = l; + randomize(rand); + } + + /** + * Creates a new GF2Polynomial of the given <i>length</i> and value + * selected by <i>value</i>: + * <UL> + * <LI>ZERO</LI> + * <LI>ONE</LI> + * <LI>RANDOM</LI> + * <LI>X</LI> + * <LI>ALL</LI> + * </UL> + * + * @param length the desired number of bits to store + * @param value the value described by a String + */ + public GF2Polynomial(int length, String value) + { + int l = length; + if (l < 1) + { + l = 1; + } + blocks = ((l - 1) >> 5) + 1; + this.value = new int[blocks]; + len = l; + if (value.equalsIgnoreCase("ZERO")) + { + assignZero(); + } + else if (value.equalsIgnoreCase("ONE")) + { + assignOne(); + } + else if (value.equalsIgnoreCase("RANDOM")) + { + randomize(); + } + else if (value.equalsIgnoreCase("X")) + { + assignX(); + } + else if (value.equalsIgnoreCase("ALL")) + { + assignAll(); + } + else + { + throw new IllegalArgumentException( + "Error: GF2Polynomial was called using " + value + + " as value!"); + } + + } + + /** + * Creates a new GF2Polynomial of the given <i>length</i> using the given + * int[]. LSB is contained in bs[0]. + * + * @param length the desired number of bits to store + * @param bs contains the desired value, LSB in bs[0] + */ + public GF2Polynomial(int length, int[] bs) + { + int leng = length; + if (leng < 1) + { + leng = 1; + } + blocks = ((leng - 1) >> 5) + 1; + value = new int[blocks]; + len = leng; + int l = Math.min(blocks, bs.length); + System.arraycopy(bs, 0, value, 0, l); + zeroUnusedBits(); + } + + /** + * Creates a new GF2Polynomial by converting the given byte[] <i>os</i> + * according to 1363 and using the given <i>length</i>. + * + * @param length the intended length of this polynomial + * @param os the octet string to assign to this polynomial + * @see "P1363 5.5.2 p22f, OS2BSP" + */ + public GF2Polynomial(int length, byte[] os) + { + int l = length; + if (l < 1) + { + l = 1; + } + blocks = ((l - 1) >> 5) + 1; + value = new int[blocks]; + len = l; + int i, m; + int k = Math.min(((os.length - 1) >> 2) + 1, blocks); + for (i = 0; i < k - 1; i++) + { + m = os.length - (i << 2) - 1; + value[i] = (os[m]) & 0x000000ff; + value[i] |= (os[m - 1] << 8) & 0x0000ff00; + value[i] |= (os[m - 2] << 16) & 0x00ff0000; + value[i] |= (os[m - 3] << 24) & 0xff000000; + } + i = k - 1; + m = os.length - (i << 2) - 1; + value[i] = os[m] & 0x000000ff; + if (m > 0) + { + value[i] |= (os[m - 1] << 8) & 0x0000ff00; + } + if (m > 1) + { + value[i] |= (os[m - 2] << 16) & 0x00ff0000; + } + if (m > 2) + { + value[i] |= (os[m - 3] << 24) & 0xff000000; + } + zeroUnusedBits(); + reduceN(); + } + + /** + * Creates a new GF2Polynomial by converting the given FlexiBigInt <i>bi</i> + * according to 1363 and using the given <i>length</i>. + * + * @param length the intended length of this polynomial + * @param bi the FlexiBigInt to assign to this polynomial + * @see "P1363 5.5.1 p22, I2BSP" + */ + public GF2Polynomial(int length, BigInteger bi) + { + int l = length; + if (l < 1) + { + l = 1; + } + blocks = ((l - 1) >> 5) + 1; + value = new int[blocks]; + len = l; + int i; + byte[] val = bi.toByteArray(); + if (val[0] == 0) + { + byte[] dummy = new byte[val.length - 1]; + System.arraycopy(val, 1, dummy, 0, dummy.length); + val = dummy; + } + int ov = val.length & 0x03; + int k = ((val.length - 1) >> 2) + 1; + for (i = 0; i < ov; i++) + { + value[k - 1] |= (val[i] & 0x000000ff) << ((ov - 1 - i) << 3); + } + int m = 0; + for (i = 0; i <= (val.length - 4) >> 2; i++) + { + m = val.length - 1 - (i << 2); + value[i] = (val[m]) & 0x000000ff; + value[i] |= ((val[m - 1]) << 8) & 0x0000ff00; + value[i] |= ((val[m - 2]) << 16) & 0x00ff0000; + value[i] |= ((val[m - 3]) << 24) & 0xff000000; + } + if ((len & 0x1f) != 0) + { + value[blocks - 1] &= reverseRightMask[len & 0x1f]; + } + reduceN(); + } + + /** + * Creates a new GF2Polynomial by cloneing the given GF2Polynomial <i>b</i>. + * + * @param b the GF2Polynomial to clone + */ + public GF2Polynomial(GF2Polynomial b) + { + len = b.len; + blocks = b.blocks; + value = IntUtils.clone(b.value); + } + + /** + * @return a copy of this GF2Polynomial + */ + public Object clone() + { + return new GF2Polynomial(this); + } + + /** + * Returns the length of this GF2Polynomial. The length can be greater than + * the degree. To get the degree call reduceN() before calling getLength(). + * + * @return the length of this GF2Polynomial + */ + public int getLength() + { + return len; + } + + /** + * Returns the value of this GF2Polynomial in an int[]. + * + * @return the value of this GF2Polynomial in a new int[], LSB in int[0] + */ + public int[] toIntegerArray() + { + int[] result; + result = new int[blocks]; + System.arraycopy(value, 0, result, 0, blocks); + return result; + } + + /** + * Returns a string representing this GF2Polynomials value using hexadecimal + * or binary radix in MSB-first order. + * + * @param radix the radix to use (2 or 16, otherwise 2 is used) + * @return a String representing this GF2Polynomials value. + */ + public String toString(int radix) + { + final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + final String[] BIN_CHARS = {"0000", "0001", "0010", "0011", "0100", + "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", + "1101", "1110", "1111"}; + String res; + int i; + res = new String(); + if (radix == 16) + { + for (i = blocks - 1; i >= 0; i--) + { + res += HEX_CHARS[(value[i] >>> 28) & 0x0f]; + res += HEX_CHARS[(value[i] >>> 24) & 0x0f]; + res += HEX_CHARS[(value[i] >>> 20) & 0x0f]; + res += HEX_CHARS[(value[i] >>> 16) & 0x0f]; + res += HEX_CHARS[(value[i] >>> 12) & 0x0f]; + res += HEX_CHARS[(value[i] >>> 8) & 0x0f]; + res += HEX_CHARS[(value[i] >>> 4) & 0x0f]; + res += HEX_CHARS[(value[i]) & 0x0f]; + res += " "; + } + } + else + { + for (i = blocks - 1; i >= 0; i--) + { + res += BIN_CHARS[(value[i] >>> 28) & 0x0f]; + res += BIN_CHARS[(value[i] >>> 24) & 0x0f]; + res += BIN_CHARS[(value[i] >>> 20) & 0x0f]; + res += BIN_CHARS[(value[i] >>> 16) & 0x0f]; + res += BIN_CHARS[(value[i] >>> 12) & 0x0f]; + res += BIN_CHARS[(value[i] >>> 8) & 0x0f]; + res += BIN_CHARS[(value[i] >>> 4) & 0x0f]; + res += BIN_CHARS[(value[i]) & 0x0f]; + res += " "; + } + } + return res; + } + + /** + * Converts this polynomial to a byte[] (octet string) according to 1363. + * + * @return a byte[] representing the value of this polynomial + * @see "P1363 5.5.2 p22f, BS2OSP" + */ + public byte[] toByteArray() + { + int k = ((len - 1) >> 3) + 1; + int ov = k & 0x03; + int m; + byte[] res = new byte[k]; + int i; + for (i = 0; i < (k >> 2); i++) + { + m = k - (i << 2) - 1; + res[m] = (byte)((value[i] & 0x000000ff)); + res[m - 1] = (byte)((value[i] & 0x0000ff00) >>> 8); + res[m - 2] = (byte)((value[i] & 0x00ff0000) >>> 16); + res[m - 3] = (byte)((value[i] & 0xff000000) >>> 24); + } + for (i = 0; i < ov; i++) + { + m = (ov - i - 1) << 3; + res[i] = (byte)((value[blocks - 1] & (0x000000ff << m)) >>> m); + } + return res; + } + + /** + * Converts this polynomial to an integer according to 1363. + * + * @return a FlexiBigInt representing the value of this polynomial + * @see "P1363 5.5.1 p22, BS2IP" + */ + public BigInteger toFlexiBigInt() + { + if (len == 0 || isZero()) + { + return new BigInteger(0, new byte[0]); + } + return new BigInteger(1, toByteArray()); + } + + /** + * Sets the LSB to 1 and all other to 0, assigning 'one' to this + * GF2Polynomial. + */ + public void assignOne() + { + int i; + for (i = 1; i < blocks; i++) + { + value[i] = 0x00; + } + value[0] = 0x01; + } + + /** + * Sets Bit 1 to 1 and all other to 0, assigning 'x' to this GF2Polynomial. + */ + public void assignX() + { + int i; + for (i = 1; i < blocks; i++) + { + value[i] = 0x00; + } + value[0] = 0x02; + } + + /** + * Sets all Bits to 1. + */ + public void assignAll() + { + int i; + for (i = 0; i < blocks; i++) + { + value[i] = 0xffffffff; + } + zeroUnusedBits(); + } + + /** + * Resets all bits to zero. + */ + public void assignZero() + { + int i; + for (i = 0; i < blocks; i++) + { + value[i] = 0x00; + } + } + + /** + * Fills all len bits of this GF2Polynomial with random values. + */ + public void randomize() + { + int i; + for (i = 0; i < blocks; i++) + { + value[i] = rand.nextInt(); + } + zeroUnusedBits(); + } + + /** + * Fills all len bits of this GF2Polynomial with random values using the + * specified source of randomness. + * + * @param rand the source of randomness + */ + public void randomize(Random rand) + { + int i; + for (i = 0; i < blocks; i++) + { + value[i] = rand.nextInt(); + } + zeroUnusedBits(); + } + + /** + * Returns true if two GF2Polynomials have the same size and value and thus + * are equal. + * + * @param other the other GF2Polynomial + * @return true if this GF2Polynomial equals <i>b</i> (<i>this</i> == + * <i>b</i>) + */ + public boolean equals(Object other) + { + if (other == null || !(other instanceof GF2Polynomial)) + { + return false; + } + + GF2Polynomial otherPol = (GF2Polynomial)other; + + if (len != otherPol.len) + { + return false; + } + for (int i = 0; i < blocks; i++) + { + if (value[i] != otherPol.value[i]) + { + return false; + } + } + return true; + } + + /** + * @return the hash code of this polynomial + */ + public int hashCode() + { + return len + value.hashCode(); + } + + /** + * Tests if all bits equal zero. + * + * @return true if this GF2Polynomial equals 'zero' (<i>this</i> == 0) + */ + public boolean isZero() + { + int i; + if (len == 0) + { + return true; + } + for (i = 0; i < blocks; i++) + { + if (value[i] != 0) + { + return false; + } + } + return true; + } + + /** + * Tests if all bits are reset to 0 and LSB is set to 1. + * + * @return true if this GF2Polynomial equals 'one' (<i>this</i> == 1) + */ + public boolean isOne() + { + int i; + for (i = 1; i < blocks; i++) + { + if (value[i] != 0) + { + return false; + } + } + if (value[0] != 0x01) + { + return false; + } + return true; + } + + /** + * Adds <i>b</i> to this GF2Polynomial and assigns the result to this + * GF2Polynomial. <i>b</i> can be of different size. + * + * @param b GF2Polynomial to add to this GF2Polynomial + */ + public void addToThis(GF2Polynomial b) + { + expandN(b.len); + xorThisBy(b); + } + + /** + * Adds two GF2Polynomials, <i>this</i> and <i>b</i>, and returns the + * result. <i>this</i> and <i>b</i> can be of different size. + * + * @param b a GF2Polynomial + * @return a new GF2Polynomial (<i>this</i> + <i>b</i>) + */ + public GF2Polynomial add(GF2Polynomial b) + { + return xor(b); + } + + /** + * Subtracts <i>b</i> from this GF2Polynomial and assigns the result to + * this GF2Polynomial. <i>b</i> can be of different size. + * + * @param b a GF2Polynomial + */ + public void subtractFromThis(GF2Polynomial b) + { + expandN(b.len); + xorThisBy(b); + } + + /** + * Subtracts two GF2Polynomials, <i>this</i> and <i>b</i>, and returns the + * result in a new GF2Polynomial. <i>this</i> and <i>b</i> can be of + * different size. + * + * @param b a GF2Polynomial + * @return a new GF2Polynomial (<i>this</i> - <i>b</i>) + */ + public GF2Polynomial subtract(GF2Polynomial b) + { + return xor(b); + } + + /** + * Toggles the LSB of this GF2Polynomial, increasing its value by 'one'. + */ + public void increaseThis() + { + xorBit(0); + } + + /** + * Toggles the LSB of this GF2Polynomial, increasing the value by 'one' and + * returns the result in a new GF2Polynomial. + * + * @return <tt>this + 1</tt> + */ + public GF2Polynomial increase() + { + GF2Polynomial result = new GF2Polynomial(this); + result.increaseThis(); + return result; + } + + /** + * Multiplies this GF2Polynomial with <i>b</i> and returns the result in a + * new GF2Polynomial. This method does not reduce the result in GF(2^N). + * This method uses classic multiplication (schoolbook). + * + * @param b a GF2Polynomial + * @return a new GF2Polynomial (<i>this</i> * <i>b</i>) + */ + public GF2Polynomial multiplyClassic(GF2Polynomial b) + { + GF2Polynomial result = new GF2Polynomial(Math.max(len, b.len) << 1); + GF2Polynomial[] m = new GF2Polynomial[32]; + int i, j; + m[0] = new GF2Polynomial(this); + for (i = 1; i <= 31; i++) + { + m[i] = m[i - 1].shiftLeft(); + } + for (i = 0; i < b.blocks; i++) + { + for (j = 0; j <= 31; j++) + { + if ((b.value[i] & bitMask[j]) != 0) + { + result.xorThisBy(m[j]); + } + } + for (j = 0; j <= 31; j++) + { + m[j].shiftBlocksLeft(); + } + } + return result; + } + + /** + * Multiplies this GF2Polynomial with <i>b</i> and returns the result in a + * new GF2Polynomial. This method does not reduce the result in GF(2^N). + * This method uses Karatzuba multiplication. + * + * @param b a GF2Polynomial + * @return a new GF2Polynomial (<i>this</i> * <i>b</i>) + */ + public GF2Polynomial multiply(GF2Polynomial b) + { + int n = Math.max(len, b.len); + expandN(n); + b.expandN(n); + return karaMult(b); + } + + /** + * Does the recursion for Karatzuba multiplication. + */ + private GF2Polynomial karaMult(GF2Polynomial b) + { + GF2Polynomial result = new GF2Polynomial(len << 1); + if (len <= 32) + { + result.value = mult32(value[0], b.value[0]); + return result; + } + if (len <= 64) + { + result.value = mult64(value, b.value); + return result; + } + if (len <= 128) + { + result.value = mult128(value, b.value); + return result; + } + if (len <= 256) + { + result.value = mult256(value, b.value); + return result; + } + if (len <= 512) + { + result.value = mult512(value, b.value); + return result; + } + + int n = IntegerFunctions.floorLog(len - 1); + n = bitMask[n]; + + GF2Polynomial a0 = lower(((n - 1) >> 5) + 1); + GF2Polynomial a1 = upper(((n - 1) >> 5) + 1); + GF2Polynomial b0 = b.lower(((n - 1) >> 5) + 1); + GF2Polynomial b1 = b.upper(((n - 1) >> 5) + 1); + + GF2Polynomial c = a1.karaMult(b1); // c = a1*b1 + GF2Polynomial e = a0.karaMult(b0); // e = a0*b0 + a0.addToThis(a1); // a0 = a0 + a1 + b0.addToThis(b1); // b0 = b0 + b1 + GF2Polynomial d = a0.karaMult(b0); // d = (a0+a1)*(b0+b1) + + result.shiftLeftAddThis(c, n << 1); + result.shiftLeftAddThis(c, n); + result.shiftLeftAddThis(d, n); + result.shiftLeftAddThis(e, n); + result.addToThis(e); + return result; + } + + /** + * 16-Integer Version of Karatzuba multiplication. + */ + private static int[] mult512(int[] a, int[] b) + { + int[] result = new int[32]; + int[] a0 = new int[8]; + System.arraycopy(a, 0, a0, 0, Math.min(8, a.length)); + int[] a1 = new int[8]; + if (a.length > 8) + { + System.arraycopy(a, 8, a1, 0, Math.min(8, a.length - 8)); + } + int[] b0 = new int[8]; + System.arraycopy(b, 0, b0, 0, Math.min(8, b.length)); + int[] b1 = new int[8]; + if (b.length > 8) + { + System.arraycopy(b, 8, b1, 0, Math.min(8, b.length - 8)); + } + int[] c = mult256(a1, b1); + result[31] ^= c[15]; + result[30] ^= c[14]; + result[29] ^= c[13]; + result[28] ^= c[12]; + result[27] ^= c[11]; + result[26] ^= c[10]; + result[25] ^= c[9]; + result[24] ^= c[8]; + result[23] ^= c[7] ^ c[15]; + result[22] ^= c[6] ^ c[14]; + result[21] ^= c[5] ^ c[13]; + result[20] ^= c[4] ^ c[12]; + result[19] ^= c[3] ^ c[11]; + result[18] ^= c[2] ^ c[10]; + result[17] ^= c[1] ^ c[9]; + result[16] ^= c[0] ^ c[8]; + result[15] ^= c[7]; + result[14] ^= c[6]; + result[13] ^= c[5]; + result[12] ^= c[4]; + result[11] ^= c[3]; + result[10] ^= c[2]; + result[9] ^= c[1]; + result[8] ^= c[0]; + a1[0] ^= a0[0]; + a1[1] ^= a0[1]; + a1[2] ^= a0[2]; + a1[3] ^= a0[3]; + a1[4] ^= a0[4]; + a1[5] ^= a0[5]; + a1[6] ^= a0[6]; + a1[7] ^= a0[7]; + b1[0] ^= b0[0]; + b1[1] ^= b0[1]; + b1[2] ^= b0[2]; + b1[3] ^= b0[3]; + b1[4] ^= b0[4]; + b1[5] ^= b0[5]; + b1[6] ^= b0[6]; + b1[7] ^= b0[7]; + int[] d = mult256(a1, b1); + result[23] ^= d[15]; + result[22] ^= d[14]; + result[21] ^= d[13]; + result[20] ^= d[12]; + result[19] ^= d[11]; + result[18] ^= d[10]; + result[17] ^= d[9]; + result[16] ^= d[8]; + result[15] ^= d[7]; + result[14] ^= d[6]; + result[13] ^= d[5]; + result[12] ^= d[4]; + result[11] ^= d[3]; + result[10] ^= d[2]; + result[9] ^= d[1]; + result[8] ^= d[0]; + int[] e = mult256(a0, b0); + result[23] ^= e[15]; + result[22] ^= e[14]; + result[21] ^= e[13]; + result[20] ^= e[12]; + result[19] ^= e[11]; + result[18] ^= e[10]; + result[17] ^= e[9]; + result[16] ^= e[8]; + result[15] ^= e[7] ^ e[15]; + result[14] ^= e[6] ^ e[14]; + result[13] ^= e[5] ^ e[13]; + result[12] ^= e[4] ^ e[12]; + result[11] ^= e[3] ^ e[11]; + result[10] ^= e[2] ^ e[10]; + result[9] ^= e[1] ^ e[9]; + result[8] ^= e[0] ^ e[8]; + result[7] ^= e[7]; + result[6] ^= e[6]; + result[5] ^= e[5]; + result[4] ^= e[4]; + result[3] ^= e[3]; + result[2] ^= e[2]; + result[1] ^= e[1]; + result[0] ^= e[0]; + return result; + } + + /** + * 8-Integer Version of Karatzuba multiplication. + */ + private static int[] mult256(int[] a, int[] b) + { + int[] result = new int[16]; + int[] a0 = new int[4]; + System.arraycopy(a, 0, a0, 0, Math.min(4, a.length)); + int[] a1 = new int[4]; + if (a.length > 4) + { + System.arraycopy(a, 4, a1, 0, Math.min(4, a.length - 4)); + } + int[] b0 = new int[4]; + System.arraycopy(b, 0, b0, 0, Math.min(4, b.length)); + int[] b1 = new int[4]; + if (b.length > 4) + { + System.arraycopy(b, 4, b1, 0, Math.min(4, b.length - 4)); + } + if (a1[3] == 0 && a1[2] == 0 && b1[3] == 0 && b1[2] == 0) + { + if (a1[1] == 0 && b1[1] == 0) + { + if (a1[0] != 0 || b1[0] != 0) + { // [3]=[2]=[1]=0, [0]!=0 + int[] c = mult32(a1[0], b1[0]); + result[9] ^= c[1]; + result[8] ^= c[0]; + result[5] ^= c[1]; + result[4] ^= c[0]; + } + } + else + { // [3]=[2]=0 [1]!=0, [0]!=0 + int[] c = mult64(a1, b1); + result[11] ^= c[3]; + result[10] ^= c[2]; + result[9] ^= c[1]; + result[8] ^= c[0]; + result[7] ^= c[3]; + result[6] ^= c[2]; + result[5] ^= c[1]; + result[4] ^= c[0]; + } + } + else + { // [3]!=0 [2]!=0 [1]!=0, [0]!=0 + int[] c = mult128(a1, b1); + result[15] ^= c[7]; + result[14] ^= c[6]; + result[13] ^= c[5]; + result[12] ^= c[4]; + result[11] ^= c[3] ^ c[7]; + result[10] ^= c[2] ^ c[6]; + result[9] ^= c[1] ^ c[5]; + result[8] ^= c[0] ^ c[4]; + result[7] ^= c[3]; + result[6] ^= c[2]; + result[5] ^= c[1]; + result[4] ^= c[0]; + } + a1[0] ^= a0[0]; + a1[1] ^= a0[1]; + a1[2] ^= a0[2]; + a1[3] ^= a0[3]; + b1[0] ^= b0[0]; + b1[1] ^= b0[1]; + b1[2] ^= b0[2]; + b1[3] ^= b0[3]; + int[] d = mult128(a1, b1); + result[11] ^= d[7]; + result[10] ^= d[6]; + result[9] ^= d[5]; + result[8] ^= d[4]; + result[7] ^= d[3]; + result[6] ^= d[2]; + result[5] ^= d[1]; + result[4] ^= d[0]; + int[] e = mult128(a0, b0); + result[11] ^= e[7]; + result[10] ^= e[6]; + result[9] ^= e[5]; + result[8] ^= e[4]; + result[7] ^= e[3] ^ e[7]; + result[6] ^= e[2] ^ e[6]; + result[5] ^= e[1] ^ e[5]; + result[4] ^= e[0] ^ e[4]; + result[3] ^= e[3]; + result[2] ^= e[2]; + result[1] ^= e[1]; + result[0] ^= e[0]; + return result; + } + + /** + * 4-Integer Version of Karatzuba multiplication. + */ + private static int[] mult128(int[] a, int[] b) + { + int[] result = new int[8]; + int[] a0 = new int[2]; + System.arraycopy(a, 0, a0, 0, Math.min(2, a.length)); + int[] a1 = new int[2]; + if (a.length > 2) + { + System.arraycopy(a, 2, a1, 0, Math.min(2, a.length - 2)); + } + int[] b0 = new int[2]; + System.arraycopy(b, 0, b0, 0, Math.min(2, b.length)); + int[] b1 = new int[2]; + if (b.length > 2) + { + System.arraycopy(b, 2, b1, 0, Math.min(2, b.length - 2)); + } + if (a1[1] == 0 && b1[1] == 0) + { + if (a1[0] != 0 || b1[0] != 0) + { + int[] c = mult32(a1[0], b1[0]); + result[5] ^= c[1]; + result[4] ^= c[0]; + result[3] ^= c[1]; + result[2] ^= c[0]; + } + } + else + { + int[] c = mult64(a1, b1); + result[7] ^= c[3]; + result[6] ^= c[2]; + result[5] ^= c[1] ^ c[3]; + result[4] ^= c[0] ^ c[2]; + result[3] ^= c[1]; + result[2] ^= c[0]; + } + a1[0] ^= a0[0]; + a1[1] ^= a0[1]; + b1[0] ^= b0[0]; + b1[1] ^= b0[1]; + if (a1[1] == 0 && b1[1] == 0) + { + int[] d = mult32(a1[0], b1[0]); + result[3] ^= d[1]; + result[2] ^= d[0]; + } + else + { + int[] d = mult64(a1, b1); + result[5] ^= d[3]; + result[4] ^= d[2]; + result[3] ^= d[1]; + result[2] ^= d[0]; + } + if (a0[1] == 0 && b0[1] == 0) + { + int[] e = mult32(a0[0], b0[0]); + result[3] ^= e[1]; + result[2] ^= e[0]; + result[1] ^= e[1]; + result[0] ^= e[0]; + } + else + { + int[] e = mult64(a0, b0); + result[5] ^= e[3]; + result[4] ^= e[2]; + result[3] ^= e[1] ^ e[3]; + result[2] ^= e[0] ^ e[2]; + result[1] ^= e[1]; + result[0] ^= e[0]; + } + return result; + } + + /** + * 2-Integer Version of Karatzuba multiplication. + */ + private static int[] mult64(int[] a, int[] b) + { + int[] result = new int[4]; + int a0 = a[0]; + int a1 = 0; + if (a.length > 1) + { + a1 = a[1]; + } + int b0 = b[0]; + int b1 = 0; + if (b.length > 1) + { + b1 = b[1]; + } + if (a1 != 0 || b1 != 0) + { + int[] c = mult32(a1, b1); + result[3] ^= c[1]; + result[2] ^= c[0] ^ c[1]; + result[1] ^= c[0]; + } + int[] d = mult32(a0 ^ a1, b0 ^ b1); + result[2] ^= d[1]; + result[1] ^= d[0]; + int[] e = mult32(a0, b0); + result[2] ^= e[1]; + result[1] ^= e[0] ^ e[1]; + result[0] ^= e[0]; + return result; + } + + /** + * 4-Byte Version of Karatzuba multiplication. Here the actual work is done. + */ + private static int[] mult32(int a, int b) + { + int[] result = new int[2]; + if (a == 0 || b == 0) + { + return result; + } + long b2 = b; + b2 &= 0x00000000ffffffffL; + int i; + long h = 0; + for (i = 1; i <= 32; i++) + { + if ((a & bitMask[i - 1]) != 0) + { + h ^= b2; + } + b2 <<= 1; + } + result[1] = (int)(h >>> 32); + result[0] = (int)(h & 0x00000000ffffffffL); + return result; + } + + /** + * Returns a new GF2Polynomial containing the upper <i>k</i> bytes of this + * GF2Polynomial. + * + * @param k + * @return a new GF2Polynomial containing the upper <i>k</i> bytes of this + * GF2Polynomial + * @see GF2Polynomial#karaMult + */ + private GF2Polynomial upper(int k) + { + int j = Math.min(k, blocks - k); + GF2Polynomial result = new GF2Polynomial(j << 5); + if (blocks >= k) + { + System.arraycopy(value, k, result.value, 0, j); + } + return result; + } + + /** + * Returns a new GF2Polynomial containing the lower <i>k</i> bytes of this + * GF2Polynomial. + * + * @param k + * @return a new GF2Polynomial containing the lower <i>k</i> bytes of this + * GF2Polynomial + * @see GF2Polynomial#karaMult + */ + private GF2Polynomial lower(int k) + { + GF2Polynomial result = new GF2Polynomial(k << 5); + System.arraycopy(value, 0, result.value, 0, Math.min(k, blocks)); + return result; + } + + /** + * Returns the remainder of <i>this</i> divided by <i>g</i> in a new + * GF2Polynomial. + * + * @param g GF2Polynomial != 0 + * @return a new GF2Polynomial (<i>this</i> % <i>g</i>) + * @throws PolynomialIsZeroException if <i>g</i> equals zero + */ + public GF2Polynomial remainder(GF2Polynomial g) + throws RuntimeException + { + /* a div b = q / r */ + GF2Polynomial a = new GF2Polynomial(this); + GF2Polynomial b = new GF2Polynomial(g); + GF2Polynomial j; + int i; + if (b.isZero()) + { + throw new RuntimeException(); + } + a.reduceN(); + b.reduceN(); + if (a.len < b.len) + { + return a; + } + i = a.len - b.len; + while (i >= 0) + { + j = b.shiftLeft(i); + a.subtractFromThis(j); + a.reduceN(); + i = a.len - b.len; + } + return a; + } + + /** + * Returns the absolute quotient of <i>this</i> divided by <i>g</i> in a + * new GF2Polynomial. + * + * @param g GF2Polynomial != 0 + * @return a new GF2Polynomial |_ <i>this</i> / <i>g</i> _| + * @throws PolynomialIsZeroException if <i>g</i> equals zero + */ + public GF2Polynomial quotient(GF2Polynomial g) + throws RuntimeException + { + /* a div b = q / r */ + GF2Polynomial q = new GF2Polynomial(len); + GF2Polynomial a = new GF2Polynomial(this); + GF2Polynomial b = new GF2Polynomial(g); + GF2Polynomial j; + int i; + if (b.isZero()) + { + throw new RuntimeException(); + } + a.reduceN(); + b.reduceN(); + if (a.len < b.len) + { + return new GF2Polynomial(0); + } + i = a.len - b.len; + q.expandN(i + 1); + + while (i >= 0) + { + j = b.shiftLeft(i); + a.subtractFromThis(j); + a.reduceN(); + q.xorBit(i); + i = a.len - b.len; + } + + return q; + } + + /** + * Divides <i>this</i> by <i>g</i> and returns the quotient and remainder + * in a new GF2Polynomial[2], quotient in [0], remainder in [1]. + * + * @param g GF2Polynomial != 0 + * @return a new GF2Polynomial[2] containing quotient and remainder + * @throws PolynomialIsZeroException if <i>g</i> equals zero + */ + public GF2Polynomial[] divide(GF2Polynomial g) + throws RuntimeException + { + /* a div b = q / r */ + GF2Polynomial[] result = new GF2Polynomial[2]; + GF2Polynomial q = new GF2Polynomial(len); + GF2Polynomial a = new GF2Polynomial(this); + GF2Polynomial b = new GF2Polynomial(g); + GF2Polynomial j; + int i; + if (b.isZero()) + { + throw new RuntimeException(); + } + a.reduceN(); + b.reduceN(); + if (a.len < b.len) + { + result[0] = new GF2Polynomial(0); + result[1] = a; + return result; + } + i = a.len - b.len; + q.expandN(i + 1); + + while (i >= 0) + { + j = b.shiftLeft(i); + a.subtractFromThis(j); + a.reduceN(); + q.xorBit(i); + i = a.len - b.len; + } + + result[0] = q; + result[1] = a; + return result; + } + + /** + * Returns the greatest common divisor of <i>this</i> and <i>g</i> in a + * new GF2Polynomial. + * + * @param g GF2Polynomial != 0 + * @return a new GF2Polynomial gcd(<i>this</i>,<i>g</i>) + * @throws ArithmeticException if <i>this</i> and <i>g</i> both are equal to zero + * @throws PolynomialIsZeroException to be API-compliant (should never be thrown). + */ + public GF2Polynomial gcd(GF2Polynomial g) + throws RuntimeException + { + if (isZero() && g.isZero()) + { + throw new ArithmeticException("Both operands of gcd equal zero."); + } + if (isZero()) + { + return new GF2Polynomial(g); + } + if (g.isZero()) + { + return new GF2Polynomial(this); + } + GF2Polynomial a = new GF2Polynomial(this); + GF2Polynomial b = new GF2Polynomial(g); + GF2Polynomial c; + + while (!b.isZero()) + { + c = a.remainder(b); + a = b; + b = c; + } + + return a; + } + + /** + * Checks if <i>this</i> is irreducible, according to IEEE P1363, A.5.5, + * p103.<br> + * Note: The algorithm from IEEE P1363, A5.5 can be used to check a + * polynomial with coefficients in GF(2^r) for irreducibility. As this class + * only represents polynomials with coefficients in GF(2), the algorithm is + * adapted to the case r=1. + * + * @return true if <i>this</i> is irreducible + * @see "P1363, A.5.5, p103" + */ + public boolean isIrreducible() + { + if (isZero()) + { + return false; + } + GF2Polynomial f = new GF2Polynomial(this); + int d, i; + GF2Polynomial u, g; + GF2Polynomial dummy; + f.reduceN(); + d = f.len - 1; + u = new GF2Polynomial(f.len, "X"); + + for (i = 1; i <= (d >> 1); i++) + { + u.squareThisPreCalc(); + u = u.remainder(f); + dummy = u.add(new GF2Polynomial(32, "X")); + if (!dummy.isZero()) + { + g = f.gcd(dummy); + if (!g.isOne()) + { + return false; + } + } + else + { + return false; + } + } + + return true; + } + + /** + * Reduces this GF2Polynomial using the trinomial x^<i>m</i> + x^<i>tc</i> + + * 1. + * + * @param m the degree of the used field + * @param tc degree of the middle x in the trinomial + */ + void reduceTrinomial(int m, int tc) + { + int i; + int p0, p1; + int q0, q1; + long t; + p0 = m >>> 5; // block which contains 2^m + q0 = 32 - (m & 0x1f); // (32-index) of 2^m within block p0 + p1 = (m - tc) >>> 5; // block which contains 2^tc + q1 = 32 - ((m - tc) & 0x1f); // (32-index) of 2^tc within block q1 + int max = ((m << 1) - 2) >>> 5; // block which contains 2^(2m-2) + int min = p0; // block which contains 2^m + for (i = max; i > min; i--) + { // for i = maxBlock to minBlock + // reduce coefficients contained in t + // t = block[i] + t = value[i] & 0x00000000ffffffffL; + // block[i-p0-1] ^= t << q0 + value[i - p0 - 1] ^= (int)(t << q0); + // block[i-p0] ^= t >>> (32-q0) + value[i - p0] ^= t >>> (32 - q0); + // block[i-p1-1] ^= << q1 + value[i - p1 - 1] ^= (int)(t << q1); + // block[i-p1] ^= t >>> (32-q1) + value[i - p1] ^= t >>> (32 - q1); + value[i] = 0x00; + } + // reduce last coefficients in block containing 2^m + t = value[min] & 0x00000000ffffffffL & (0xffffffffL << (m & 0x1f)); // t + // contains the last coefficients > m + value[0] ^= t >>> (32 - q0); + if (min - p1 - 1 >= 0) + { + value[min - p1 - 1] ^= (int)(t << q1); + } + value[min - p1] ^= t >>> (32 - q1); + + value[min] &= reverseRightMask[m & 0x1f]; + blocks = ((m - 1) >>> 5) + 1; + len = m; + } + + /** + * Reduces this GF2Polynomial using the pentanomial x^<i>m</i> + x^<i>pc[2]</i> + + * x^<i>pc[1]</i> + x^<i>pc[0]</i> + 1. + * + * @param m the degree of the used field + * @param pc degrees of the middle x's in the pentanomial + */ + void reducePentanomial(int m, int[] pc) + { + int i; + int p0, p1, p2, p3; + int q0, q1, q2, q3; + long t; + p0 = m >>> 5; + q0 = 32 - (m & 0x1f); + p1 = (m - pc[0]) >>> 5; + q1 = 32 - ((m - pc[0]) & 0x1f); + p2 = (m - pc[1]) >>> 5; + q2 = 32 - ((m - pc[1]) & 0x1f); + p3 = (m - pc[2]) >>> 5; + q3 = 32 - ((m - pc[2]) & 0x1f); + int max = ((m << 1) - 2) >>> 5; + int min = p0; + for (i = max; i > min; i--) + { + t = value[i] & 0x00000000ffffffffL; + value[i - p0 - 1] ^= (int)(t << q0); + value[i - p0] ^= t >>> (32 - q0); + value[i - p1 - 1] ^= (int)(t << q1); + value[i - p1] ^= t >>> (32 - q1); + value[i - p2 - 1] ^= (int)(t << q2); + value[i - p2] ^= t >>> (32 - q2); + value[i - p3 - 1] ^= (int)(t << q3); + value[i - p3] ^= t >>> (32 - q3); + value[i] = 0; + } + t = value[min] & 0x00000000ffffffffL & (0xffffffffL << (m & 0x1f)); + value[0] ^= t >>> (32 - q0); + if (min - p1 - 1 >= 0) + { + value[min - p1 - 1] ^= (int)(t << q1); + } + value[min - p1] ^= t >>> (32 - q1); + if (min - p2 - 1 >= 0) + { + value[min - p2 - 1] ^= (int)(t << q2); + } + value[min - p2] ^= t >>> (32 - q2); + if (min - p3 - 1 >= 0) + { + value[min - p3 - 1] ^= (int)(t << q3); + } + value[min - p3] ^= t >>> (32 - q3); + value[min] &= reverseRightMask[m & 0x1f]; + + blocks = ((m - 1) >>> 5) + 1; + len = m; + } + + /** + * Reduces len by finding the most significant bit set to one and reducing + * len and blocks. + */ + public void reduceN() + { + int i, j, h; + i = blocks - 1; + while ((value[i] == 0) && (i > 0)) + { + i--; + } + h = value[i]; + j = 0; + while (h != 0) + { + h >>>= 1; + j++; + } + len = (i << 5) + j; + blocks = i + 1; + } + + /** + * Expands len and int[] value to <i>i</i>. This is useful before adding + * two GF2Polynomials of different size. + * + * @param i the intended length + */ + public void expandN(int i) + { + int k; + int[] bs; + if (len >= i) + { + return; + } + len = i; + k = ((i - 1) >>> 5) + 1; + if (blocks >= k) + { + return; + } + if (value.length >= k) + { + int j; + for (j = blocks; j < k; j++) + { + value[j] = 0; + } + blocks = k; + return; + } + bs = new int[k]; + System.arraycopy(value, 0, bs, 0, blocks); + blocks = k; + value = null; + value = bs; + } + + /** + * Squares this GF2Polynomial and expands it accordingly. This method does + * not reduce the result in GF(2^N). There exists a faster method for + * squaring in GF(2^N). + * + * @see GF2nPolynomialElement#square + */ + public void squareThisBitwise() + { + int i, h, j, k; + if (isZero()) + { + return; + } + int[] result = new int[blocks << 1]; + for (i = blocks - 1; i >= 0; i--) + { + h = value[i]; + j = 0x00000001; + for (k = 0; k < 16; k++) + { + if ((h & 0x01) != 0) + { + result[i << 1] |= j; + } + if ((h & 0x00010000) != 0) + { + result[(i << 1) + 1] |= j; + } + j <<= 2; + h >>>= 1; + } + } + value = null; + value = result; + blocks = result.length; + len = (len << 1) - 1; + } + + /** + * Squares this GF2Polynomial by using precomputed values of squaringTable. + * This method does not reduce the result in GF(2^N). + */ + public void squareThisPreCalc() + { + int i; + if (isZero()) + { + return; + } + if (value.length >= (blocks << 1)) + { + for (i = blocks - 1; i >= 0; i--) + { + value[(i << 1) + 1] = GF2Polynomial.squaringTable[(value[i] & 0x00ff0000) >>> 16] + | (GF2Polynomial.squaringTable[(value[i] & 0xff000000) >>> 24] << 16); + value[i << 1] = GF2Polynomial.squaringTable[value[i] & 0x000000ff] + | (GF2Polynomial.squaringTable[(value[i] & 0x0000ff00) >>> 8] << 16); + } + blocks <<= 1; + len = (len << 1) - 1; + } + else + { + int[] result = new int[blocks << 1]; + for (i = 0; i < blocks; i++) + { + result[i << 1] = GF2Polynomial.squaringTable[value[i] & 0x000000ff] + | (GF2Polynomial.squaringTable[(value[i] & 0x0000ff00) >>> 8] << 16); + result[(i << 1) + 1] = GF2Polynomial.squaringTable[(value[i] & 0x00ff0000) >>> 16] + | (GF2Polynomial.squaringTable[(value[i] & 0xff000000) >>> 24] << 16); + } + value = null; + value = result; + blocks <<= 1; + len = (len << 1) - 1; + } + } + + /** + * Does a vector-multiplication modulo 2 and returns the result as boolean. + * + * @param b GF2Polynomial + * @return this x <i>b</i> as boolean (1->true, 0->false) + * @throws PolynomialsHaveDifferentLengthException if <i>this</i> and <i>b</i> have a different length and + * thus cannot be vector-multiplied + */ + public boolean vectorMult(GF2Polynomial b) + throws RuntimeException + { + int i; + int h; + boolean result = false; + if (len != b.len) + { + throw new RuntimeException(); + } + for (i = 0; i < blocks; i++) + { + h = value[i] & b.value[i]; + result ^= parity[h & 0x000000ff]; + result ^= parity[(h >>> 8) & 0x000000ff]; + result ^= parity[(h >>> 16) & 0x000000ff]; + result ^= parity[(h >>> 24) & 0x000000ff]; + } + return result; + } + + /** + * Returns the bitwise exclusive-or of <i>this</i> and <i>b</i> in a new + * GF2Polynomial. <i>this</i> and <i>b</i> can be of different size. + * + * @param b GF2Polynomial + * @return a new GF2Polynomial (<i>this</i> ^ <i>b</i>) + */ + public GF2Polynomial xor(GF2Polynomial b) + { + int i; + GF2Polynomial result; + int k = Math.min(blocks, b.blocks); + if (len >= b.len) + { + result = new GF2Polynomial(this); + for (i = 0; i < k; i++) + { + result.value[i] ^= b.value[i]; + } + } + else + { + result = new GF2Polynomial(b); + for (i = 0; i < k; i++) + { + result.value[i] ^= value[i]; + } + } + // If we xor'ed some bits too many by proceeding blockwise, + // restore them to zero: + result.zeroUnusedBits(); + return result; + } + + /** + * Computes the bitwise exclusive-or of this GF2Polynomial and <i>b</i> and + * stores the result in this GF2Polynomial. <i>b</i> can be of different + * size. + * + * @param b GF2Polynomial + */ + public void xorThisBy(GF2Polynomial b) + { + int i; + for (i = 0; i < Math.min(blocks, b.blocks); i++) + { + value[i] ^= b.value[i]; + } + // If we xor'ed some bits too many by proceeding blockwise, + // restore them to zero: + zeroUnusedBits(); + } + + /** + * If {@link #len} is not a multiple of the block size (32), some extra bits + * of the last block might have been modified during a blockwise operation. + * This method compensates for that by restoring these "extra" bits to zero. + */ + private void zeroUnusedBits() + { + if ((len & 0x1f) != 0) + { + value[blocks - 1] &= reverseRightMask[len & 0x1f]; + } + } + + /** + * Sets the bit at position <i>i</i>. + * + * @param i int + * @throws BitDoesNotExistException if (<i>i</i> < 0) || (<i>i</i> > (len - 1)) + */ + public void setBit(int i) + throws RuntimeException + { + if (i < 0 || i > (len - 1)) + { + throw new RuntimeException(); + } + if (i > (len - 1)) + { + return; + } + value[i >>> 5] |= bitMask[i & 0x1f]; + return; + } + + /** + * Returns the bit at position <i>i</i>. + * + * @param i int + * @return the bit at position <i>i</i> if <i>i</i> is a valid position, 0 + * otherwise. + */ + public int getBit(int i) + { + if (i < 0 || i > (len - 1)) + { + return 0; + } + return ((value[i >>> 5] & bitMask[i & 0x1f]) != 0) ? 1 : 0; + } + + /** + * Resets the bit at position <i>i</i>. + * + * @param i int + * @throws BitDoesNotExistException if (<i>i</i> < 0) || (<i>i</i> > (len - 1)) + */ + public void resetBit(int i) + throws RuntimeException + { + if (i < 0 || i > (len - 1)) + { + throw new RuntimeException(); + } + if (i > (len - 1)) + { + return; + } + value[i >>> 5] &= ~bitMask[i & 0x1f]; + } + + /** + * Xors the bit at position <i>i</i>. + * + * @param i int + * @throws BitDoesNotExistException if (<i>i</i> < 0) || (<i>i</i> > (len - 1)) + */ + public void xorBit(int i) + throws RuntimeException + { + if (i < 0 || i > (len - 1)) + { + throw new RuntimeException(); + } + if (i > (len - 1)) + { + return; + } + value[i >>> 5] ^= bitMask[i & 0x1f]; + } + + /** + * Tests the bit at position <i>i</i>. + * + * @param i the position of the bit to be tested + * @return true if the bit at position <i>i</i> is set (a(<i>i</i>) == + * 1). False if (<i>i</i> < 0) || (<i>i</i> > (len - 1)) + */ + public boolean testBit(int i) + { + if (i < 0 || i > (len - 1)) + { + return false; + } + return (value[i >>> 5] & bitMask[i & 0x1f]) != 0; + } + + /** + * Returns this GF2Polynomial shift-left by 1 in a new GF2Polynomial. + * + * @return a new GF2Polynomial (this << 1) + */ + public GF2Polynomial shiftLeft() + { + GF2Polynomial result = new GF2Polynomial(len + 1, value); + int i; + for (i = result.blocks - 1; i >= 1; i--) + { + result.value[i] <<= 1; + result.value[i] |= result.value[i - 1] >>> 31; + } + result.value[0] <<= 1; + return result; + } + + /** + * Shifts-left this by one and enlarges the size of value if necesary. + */ + public void shiftLeftThis() + { + /** @todo This is untested. */ + int i; + if ((len & 0x1f) == 0) + { // check if blocks increases + len += 1; + blocks += 1; + if (blocks > value.length) + { // enlarge value + int[] bs = new int[blocks]; + System.arraycopy(value, 0, bs, 0, value.length); + value = null; + value = bs; + } + for (i = blocks - 1; i >= 1; i--) + { + value[i] |= value[i - 1] >>> 31; + value[i - 1] <<= 1; + } + } + else + { + len += 1; + for (i = blocks - 1; i >= 1; i--) + { + value[i] <<= 1; + value[i] |= value[i - 1] >>> 31; + } + value[0] <<= 1; + } + } + + /** + * Returns this GF2Polynomial shift-left by <i>k</i> in a new + * GF2Polynomial. + * + * @param k int + * @return a new GF2Polynomial (this << <i>k</i>) + */ + public GF2Polynomial shiftLeft(int k) + { + // Variant 2, requiring a modified shiftBlocksLeft(k) + // In case of modification, consider a rename to doShiftBlocksLeft() + // with an explicit note that this method assumes that the polynomial + // has already been resized. Or consider doing things inline. + // Construct the resulting polynomial of appropriate length: + GF2Polynomial result = new GF2Polynomial(len + k, value); + // Shift left as many multiples of the block size as possible: + if (k >= 32) + { + result.doShiftBlocksLeft(k >>> 5); + } + // Shift left by the remaining (<32) amount: + final int remaining = k & 0x1f; + if (remaining != 0) + { + for (int i = result.blocks - 1; i >= 1; i--) + { + result.value[i] <<= remaining; + result.value[i] |= result.value[i - 1] >>> (32 - remaining); + } + result.value[0] <<= remaining; + } + return result; + } + + /** + * Shifts left b and adds the result to Its a fast version of + * <tt>this = add(b.shl(k));</tt> + * + * @param b GF2Polynomial to shift and add to this + * @param k the amount to shift + * @see GF2nPolynomialElement#invertEEA + */ + public void shiftLeftAddThis(GF2Polynomial b, int k) + { + if (k == 0) + { + addToThis(b); + return; + } + int i; + expandN(b.len + k); + int d = k >>> 5; + for (i = b.blocks - 1; i >= 0; i--) + { + if ((i + d + 1 < blocks) && ((k & 0x1f) != 0)) + { + value[i + d + 1] ^= b.value[i] >>> (32 - (k & 0x1f)); + } + value[i + d] ^= b.value[i] << (k & 0x1f); + } + } + + /** + * Shifts-left this GF2Polynomial's value blockwise 1 block resulting in a + * shift-left by 32. + * + * @see GF2Polynomial#multiply + */ + void shiftBlocksLeft() + { + blocks += 1; + len += 32; + if (blocks <= value.length) + { + int i; + for (i = blocks - 1; i >= 1; i--) + { + value[i] = value[i - 1]; + } + value[0] = 0x00; + } + else + { + int[] result = new int[blocks]; + System.arraycopy(value, 0, result, 1, blocks - 1); + value = null; + value = result; + } + } + + /** + * Shifts left this GF2Polynomial's value blockwise <i>b</i> blocks + * resulting in a shift-left by b*32. This method assumes that {@link #len} + * and {@link #blocks} have already been updated to reflect the final state. + * + * @param b shift amount (in blocks) + */ + private void doShiftBlocksLeft(int b) + { + if (blocks <= value.length) + { + int i; + for (i = blocks - 1; i >= b; i--) + { + value[i] = value[i - b]; + } + for (i = 0; i < b; i++) + { + value[i] = 0x00; + } + } + else + { + int[] result = new int[blocks]; + System.arraycopy(value, 0, result, b, blocks - b); + value = null; + value = result; + } + } + + /** + * Returns this GF2Polynomial shift-right by 1 in a new GF2Polynomial. + * + * @return a new GF2Polynomial (this << 1) + */ + public GF2Polynomial shiftRight() + { + GF2Polynomial result = new GF2Polynomial(len - 1); + int i; + System.arraycopy(value, 0, result.value, 0, result.blocks); + for (i = 0; i <= result.blocks - 2; i++) + { + result.value[i] >>>= 1; + result.value[i] |= result.value[i + 1] << 31; + } + result.value[result.blocks - 1] >>>= 1; + if (result.blocks < blocks) + { + result.value[result.blocks - 1] |= value[result.blocks] << 31; + } + return result; + } + + /** + * Shifts-right this GF2Polynomial by 1. + */ + public void shiftRightThis() + { + int i; + len -= 1; + blocks = ((len - 1) >>> 5) + 1; + for (i = 0; i <= blocks - 2; i++) + { + value[i] >>>= 1; + value[i] |= value[i + 1] << 31; + } + value[blocks - 1] >>>= 1; + if ((len & 0x1f) == 0) + { + value[blocks - 1] |= value[blocks] << 31; + } + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2Vector.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2Vector.java new file mode 100644 index 00000000..560ad216 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2Vector.java @@ -0,0 +1,539 @@ +package org.spongycastle.pqc.math.linearalgebra; + +import java.security.SecureRandom; + +/** + * This class implements the abstract class <tt>Vector</tt> for the case of + * vectors over the finite field GF(2). <br> + * For the vector representation the array of type int[] is used, thus one + * element of the array holds 32 elements of the vector. + * + * @see Vector + */ +public class GF2Vector + extends Vector +{ + + /** + * holds the elements of this vector + */ + private int[] v; + + /** + * Construct the zero vector of the given length. + * + * @param length the length of the vector + */ + public GF2Vector(int length) + { + if (length < 0) + { + throw new ArithmeticException("Negative length."); + } + this.length = length; + v = new int[(length + 31) >> 5]; + } + + /** + * Construct a random GF2Vector of the given length. + * + * @param length the length of the vector + * @param sr the source of randomness + */ + public GF2Vector(int length, SecureRandom sr) + { + this.length = length; + + int size = (length + 31) >> 5; + v = new int[size]; + + // generate random elements + for (int i = size - 1; i >= 0; i--) + { + v[i] = sr.nextInt(); + } + + // erase unused bits + int r = length & 0x1f; + if (r != 0) + { + // erase unused bits + v[size - 1] &= (1 << r) - 1; + } + } + + /** + * Construct a random GF2Vector of the given length with the specified + * number of non-zero coefficients. + * + * @param length the length of the vector + * @param t the number of non-zero coefficients + * @param sr the source of randomness + */ + public GF2Vector(int length, int t, SecureRandom sr) + { + if (t > length) + { + throw new ArithmeticException( + "The hamming weight is greater than the length of vector."); + } + this.length = length; + + int size = (length + 31) >> 5; + v = new int[size]; + + int[] help = new int[length]; + for (int i = 0; i < length; i++) + { + help[i] = i; + } + + int m = length; + for (int i = 0; i < t; i++) + { + int j = RandUtils.nextInt(sr, m); + setBit(help[j]); + m--; + help[j] = help[m]; + } + } + + /** + * Construct a GF2Vector of the given length and with elements from the + * given array. The array is copied and unused bits are masked out. + * + * @param length the length of the vector + * @param v the element array + */ + public GF2Vector(int length, int[] v) + { + if (length < 0) + { + throw new ArithmeticException("negative length"); + } + this.length = length; + + int size = (length + 31) >> 5; + + if (v.length != size) + { + throw new ArithmeticException("length mismatch"); + } + + this.v = IntUtils.clone(v); + + int r = length & 0x1f; + if (r != 0) + { + // erase unused bits + this.v[size - 1] &= (1 << r) - 1; + } + } + + /** + * Copy constructor. + * + * @param other another {@link GF2Vector} + */ + public GF2Vector(GF2Vector other) + { + this.length = other.length; + this.v = IntUtils.clone(other.v); + } + + /** + * Construct a new {@link GF2Vector} of the given length and with the given + * element array. The array is not changed and only a reference to the array + * is stored. No length checking is performed either. + * + * @param v the element array + * @param length the length of the vector + */ + protected GF2Vector(int[] v, int length) + { + this.v = v; + this.length = length; + } + + /** + * Construct a new GF2Vector with the given length out of the encoded + * vector. + * + * @param length the length of the vector + * @param encVec the encoded vector + * @return the decoded vector + */ + public static GF2Vector OS2VP(int length, byte[] encVec) + { + if (length < 0) + { + throw new ArithmeticException("negative length"); + } + + int byteLen = (length + 7) >> 3; + + if (encVec.length > byteLen) + { + throw new ArithmeticException("length mismatch"); + } + + return new GF2Vector(length, LittleEndianConversions.toIntArray(encVec)); + } + + /** + * Encode this vector as byte array. + * + * @return the encoded vector + */ + public byte[] getEncoded() + { + int byteLen = (length + 7) >> 3; + return LittleEndianConversions.toByteArray(v, byteLen); + } + + /** + * @return the int array representation of this vector + */ + public int[] getVecArray() + { + return v; + } + + /** + * Return the Hamming weight of this vector, i.e., compute the number of + * units of this vector. + * + * @return the Hamming weight of this vector + */ + public int getHammingWeight() + { + int weight = 0; + for (int i = 0; i < v.length; i++) + { + int e = v[i]; + for (int j = 0; j < 32; j++) + { + int b = e & 1; + if (b != 0) + { + weight++; + } + e >>>= 1; + } + } + return weight; + } + + /** + * @return whether this is the zero vector (i.e., all elements are zero) + */ + public boolean isZero() + { + for (int i = v.length - 1; i >= 0; i--) + { + if (v[i] != 0) + { + return false; + } + } + return true; + } + + /** + * Return the value of the bit of this vector at the specified index. + * + * @param index the index + * @return the value of the bit (0 or 1) + */ + public int getBit(int index) + { + if (index >= length) + { + throw new IndexOutOfBoundsException(); + } + int q = index >> 5; + int r = index & 0x1f; + return (v[q] & (1 << r)) >>> r; + } + + /** + * Set the coefficient at the given index to 1. If the index is out of + * bounds, do nothing. + * + * @param index the index of the coefficient to set + */ + public void setBit(int index) + { + if (index >= length) + { + throw new IndexOutOfBoundsException(); + } + v[index >> 5] |= 1 << (index & 0x1f); + } + + /** + * Adds another GF2Vector to this vector. + * + * @param other another GF2Vector + * @return <tt>this + other</tt> + * @throws ArithmeticException if the other vector is not a GF2Vector or has another + * length. + */ + public Vector add(Vector other) + { + if (!(other instanceof GF2Vector)) + { + throw new ArithmeticException("vector is not defined over GF(2)"); + } + + GF2Vector otherVec = (GF2Vector)other; + if (length != otherVec.length) + { + throw new ArithmeticException("length mismatch"); + } + + int[] vec = IntUtils.clone(((GF2Vector)other).v); + + for (int i = vec.length - 1; i >= 0; i--) + { + vec[i] ^= v[i]; + } + + return new GF2Vector(length, vec); + } + + /** + * Multiply this vector with a permutation. + * + * @param p the permutation + * @return <tt>this*p = p*this</tt> + */ + public Vector multiply(Permutation p) + { + int[] pVec = p.getVector(); + if (length != pVec.length) + { + throw new ArithmeticException("length mismatch"); + } + + GF2Vector result = new GF2Vector(length); + + for (int i = 0; i < pVec.length; i++) + { + int e = v[pVec[i] >> 5] & (1 << (pVec[i] & 0x1f)); + if (e != 0) + { + result.v[i >> 5] |= 1 << (i & 0x1f); + } + } + + return result; + } + + /** + * Return a new vector consisting of the elements of this vector with the + * indices given by the set <tt>setJ</tt>. + * + * @param setJ the set of indices of elements to extract + * @return the new {@link GF2Vector} + * <tt>[this_setJ[0], this_setJ[1], ..., this_setJ[#setJ-1]]</tt> + */ + public GF2Vector extractVector(int[] setJ) + { + int k = setJ.length; + if (setJ[k - 1] > length) + { + throw new ArithmeticException("invalid index set"); + } + + GF2Vector result = new GF2Vector(k); + + for (int i = 0; i < k; i++) + { + int e = v[setJ[i] >> 5] & (1 << (setJ[i] & 0x1f)); + if (e != 0) + { + result.v[i >> 5] |= 1 << (i & 0x1f); + } + } + + return result; + } + + /** + * Return a new vector consisting of the first <tt>k</tt> elements of this + * vector. + * + * @param k the number of elements to extract + * @return a new {@link GF2Vector} consisting of the first <tt>k</tt> + * elements of this vector + */ + public GF2Vector extractLeftVector(int k) + { + if (k > length) + { + throw new ArithmeticException("invalid length"); + } + + if (k == length) + { + return new GF2Vector(this); + } + + GF2Vector result = new GF2Vector(k); + + int q = k >> 5; + int r = k & 0x1f; + + System.arraycopy(v, 0, result.v, 0, q); + if (r != 0) + { + result.v[q] = v[q] & ((1 << r) - 1); + } + + return result; + } + + /** + * Return a new vector consisting of the last <tt>k</tt> elements of this + * vector. + * + * @param k the number of elements to extract + * @return a new {@link GF2Vector} consisting of the last <tt>k</tt> + * elements of this vector + */ + public GF2Vector extractRightVector(int k) + { + if (k > length) + { + throw new ArithmeticException("invalid length"); + } + + if (k == length) + { + return new GF2Vector(this); + } + + GF2Vector result = new GF2Vector(k); + + int q = (length - k) >> 5; + int r = (length - k) & 0x1f; + int length = (k + 31) >> 5; + + int ind = q; + // if words have to be shifted + if (r != 0) + { + // process all but last word + for (int i = 0; i < length - 1; i++) + { + result.v[i] = (v[ind++] >>> r) | (v[ind] << (32 - r)); + } + // process last word + result.v[length - 1] = v[ind++] >>> r; + if (ind < v.length) + { + result.v[length - 1] |= v[ind] << (32 - r); + } + } + else + { + // no shift necessary + System.arraycopy(v, q, result.v, 0, length); + } + + return result; + } + + /** + * Rewrite this vector as a vector over <tt>GF(2<sup>m</sup>)</tt> with + * <tt>t</tt> elements. + * + * @param field the finite field <tt>GF(2<sup>m</sup>)</tt> + * @return the converted vector over <tt>GF(2<sup>m</sup>)</tt> + */ + public GF2mVector toExtensionFieldVector(GF2mField field) + { + int m = field.getDegree(); + if ((length % m) != 0) + { + throw new ArithmeticException("conversion is impossible"); + } + + int t = length / m; + int[] result = new int[t]; + int count = 0; + for (int i = t - 1; i >= 0; i--) + { + for (int j = field.getDegree() - 1; j >= 0; j--) + { + int q = count >>> 5; + int r = count & 0x1f; + + int e = (v[q] >>> r) & 1; + if (e == 1) + { + result[i] ^= 1 << j; + } + count++; + } + } + return new GF2mVector(field, result); + } + + /** + * Check if the given object is equal to this vector. + * + * @param other vector + * @return the result of the comparison + */ + public boolean equals(Object other) + { + + if (!(other instanceof GF2Vector)) + { + return false; + } + GF2Vector otherVec = (GF2Vector)other; + + return (length == otherVec.length) && IntUtils.equals(v, otherVec.v); + } + + /** + * @return the hash code of this vector + */ + public int hashCode() + { + int hash = length; + hash = hash * 31 + v.hashCode(); + return hash; + } + + /** + * @return a human readable form of this vector + */ + public String toString() + { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < length; i++) + { + if ((i != 0) && ((i & 0x1f) == 0)) + { + buf.append(' '); + } + int q = i >> 5; + int r = i & 0x1f; + int bit = v[q] & (1 << r); + if (bit == 0) + { + buf.append('0'); + } + else + { + buf.append('1'); + } + } + return buf.toString(); + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2mField.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2mField.java new file mode 100644 index 00000000..b60cd598 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2mField.java @@ -0,0 +1,365 @@ +package org.spongycastle.pqc.math.linearalgebra; + +import java.security.SecureRandom; + +/** + * This class describes operations with elements from the finite field F = + * GF(2^m). ( GF(2^m)= GF(2)[A] where A is a root of irreducible polynomial with + * degree m, each field element B has a polynomial basis representation, i.e. it + * is represented by a different binary polynomial of degree less than m, B = + * poly(A) ) All operations are defined only for field with 1< m <32. For the + * representation of field elements the map f: F->Z, poly(A)->poly(2) is used, + * where integers have the binary representation. For example: A^7+A^3+A+1 -> + * (00...0010001011)=139 Also for elements type Integer is used. + * + * @see PolynomialRingGF2 + */ +public class GF2mField +{ + + /* + * degree - degree of the field polynomial - the field polynomial ring - + * polynomial ring over the finite field GF(2) + */ + + private int degree = 0; + + private int polynomial; + + /** + * create a finite field GF(2^m) + * + * @param degree the degree of the field + */ + public GF2mField(int degree) + { + if (degree >= 32) + { + throw new IllegalArgumentException( + " Error: the degree of field is too large "); + } + if (degree < 1) + { + throw new IllegalArgumentException( + " Error: the degree of field is non-positive "); + } + this.degree = degree; + polynomial = PolynomialRingGF2.getIrreduciblePolynomial(degree); + } + + /** + * create a finite field GF(2^m) with the fixed field polynomial + * + * @param degree the degree of the field + * @param poly the field polynomial + */ + public GF2mField(int degree, int poly) + { + if (degree != PolynomialRingGF2.degree(poly)) + { + throw new IllegalArgumentException( + " Error: the degree is not correct"); + } + if (!PolynomialRingGF2.isIrreducible(poly)) + { + throw new IllegalArgumentException( + " Error: given polynomial is reducible"); + } + this.degree = degree; + polynomial = poly; + + } + + public GF2mField(byte[] enc) + { + if (enc.length != 4) + { + throw new IllegalArgumentException( + "byte array is not an encoded finite field"); + } + polynomial = LittleEndianConversions.OS2IP(enc); + if (!PolynomialRingGF2.isIrreducible(polynomial)) + { + throw new IllegalArgumentException( + "byte array is not an encoded finite field"); + } + + degree = PolynomialRingGF2.degree(polynomial); + } + + public GF2mField(GF2mField field) + { + degree = field.degree; + polynomial = field.polynomial; + } + + /** + * return degree of the field + * + * @return degree of the field + */ + public int getDegree() + { + return degree; + } + + /** + * return the field polynomial + * + * @return the field polynomial + */ + public int getPolynomial() + { + return polynomial; + } + + /** + * return the encoded form of this field + * + * @return the field in byte array form + */ + public byte[] getEncoded() + { + return LittleEndianConversions.I2OSP(polynomial); + } + + /** + * Return sum of two elements + * + * @param a + * @param b + * @return a+b + */ + public int add(int a, int b) + { + return a ^ b; + } + + /** + * Return product of two elements + * + * @param a + * @param b + * @return a*b + */ + public int mult(int a, int b) + { + return PolynomialRingGF2.modMultiply(a, b, polynomial); + } + + /** + * compute exponentiation a^k + * + * @param a a field element a + * @param k k degree + * @return a^k + */ + public int exp(int a, int k) + { + if (a == 0) + { + return 0; + } + if (a == 1) + { + return 1; + } + int result = 1; + if (k < 0) + { + a = inverse(a); + k = -k; + } + while (k != 0) + { + if ((k & 1) == 1) + { + result = mult(result, a); + } + a = mult(a, a); + k >>>= 1; + } + return result; + } + + /** + * compute the multiplicative inverse of a + * + * @param a a field element a + * @return a<sup>-1</sup> + */ + public int inverse(int a) + { + int d = (1 << degree) - 2; + + return exp(a, d); + } + + /** + * compute the square root of an integer + * + * @param a a field element a + * @return a<sup>1/2</sup> + */ + public int sqRoot(int a) + { + for (int i = 1; i < degree; i++) + { + a = mult(a, a); + } + return a; + } + + /** + * create a random field element using PRNG sr + * + * @param sr SecureRandom + * @return a random element + */ + public int getRandomElement(SecureRandom sr) + { + int result = RandUtils.nextInt(sr, 1 << degree); + return result; + } + + /** + * create a random non-zero field element + * + * @return a random element + */ + public int getRandomNonZeroElement() + { + return getRandomNonZeroElement(new SecureRandom()); + } + + /** + * create a random non-zero field element using PRNG sr + * + * @param sr SecureRandom + * @return a random non-zero element + */ + public int getRandomNonZeroElement(SecureRandom sr) + { + int controltime = 1 << 20; + int count = 0; + int result = RandUtils.nextInt(sr, 1 << degree); + while ((result == 0) && (count < controltime)) + { + result = RandUtils.nextInt(sr, 1 << degree); + count++; + } + if (count == controltime) + { + result = 1; + } + return result; + } + + /** + * @return true if e is encoded element of this field and false otherwise + */ + public boolean isElementOfThisField(int e) + { + // e is encoded element of this field iff 0<= e < |2^m| + if (degree == 31) + { + return e >= 0; + } + return e >= 0 && e < (1 << degree); + } + + /* + * help method for visual control + */ + public String elementToStr(int a) + { + String s = ""; + for (int i = 0; i < degree; i++) + { + if (((byte)a & 0x01) == 0) + { + s = "0" + s; + } + else + { + s = "1" + s; + } + a >>>= 1; + } + return s; + } + + /** + * checks if given object is equal to this field. + * <p> + * The method returns false whenever the given object is not GF2m. + * + * @param other object + * @return true or false + */ + public boolean equals(Object other) + { + if ((other == null) || !(other instanceof GF2mField)) + { + return false; + } + + GF2mField otherField = (GF2mField)other; + + if ((degree == otherField.degree) + && (polynomial == otherField.polynomial)) + { + return true; + } + + return false; + } + + public int hashCode() + { + return polynomial; + } + + /** + * Returns a human readable form of this field. + * + * @return a human readable form of this field. + */ + public String toString() + { + String str = "Finite Field GF(2^" + degree + ") = " + "GF(2)[X]/<" + + polyToString(polynomial) + "> "; + return str; + } + + private static String polyToString(int p) + { + String str = ""; + if (p == 0) + { + str = "0"; + } + else + { + byte b = (byte)(p & 0x01); + if (b == 1) + { + str = "1"; + } + p >>>= 1; + int i = 1; + while (p != 0) + { + b = (byte)(p & 0x01); + if (b == 1) + { + str = str + "+x^" + i; + } + p >>>= 1; + i++; + } + } + return str; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2mMatrix.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2mMatrix.java new file mode 100644 index 00000000..d2989bcf --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2mMatrix.java @@ -0,0 +1,377 @@ +package org.spongycastle.pqc.math.linearalgebra; + +/** + * This class describes some operations with matrices over finite field <i>GF(2<sup>m</sup>)</i> + * with small <i>m</i> (1< m <32). + * + * @see Matrix + */ +public class GF2mMatrix + extends Matrix +{ + + /** + * finite field GF(2^m) + */ + protected GF2mField field; + + /** + * For the matrix representation the array of type int[][] is used, thus + * every element of the array keeps one element of the matrix (element from + * finite field GF(2^m)) + */ + protected int[][] matrix; + + /** + * Constructor. + * + * @param field a finite field GF(2^m) + * @param enc byte[] matrix in byte array form + */ + public GF2mMatrix(GF2mField field, byte[] enc) + { + + this.field = field; + + // decode matrix + int d = 8; + int count = 1; + while (field.getDegree() > d) + { + count++; + d += 8; + } + + if (enc.length < 5) + { + throw new IllegalArgumentException( + " Error: given array is not encoded matrix over GF(2^m)"); + } + + this.numRows = ((enc[3] & 0xff) << 24) ^ ((enc[2] & 0xff) << 16) + ^ ((enc[1] & 0xff) << 8) ^ (enc[0] & 0xff); + + int n = count * this.numRows; + + if ((this.numRows <= 0) || (((enc.length - 4) % n) != 0)) + { + throw new IllegalArgumentException( + " Error: given array is not encoded matrix over GF(2^m)"); + } + + this.numColumns = (enc.length - 4) / n; + + matrix = new int[this.numRows][this.numColumns]; + count = 4; + for (int i = 0; i < this.numRows; i++) + { + for (int j = 0; j < this.numColumns; j++) + { + for (int jj = 0; jj < d; jj += 8) + { + matrix[i][j] ^= (enc[count++] & 0x000000ff) << jj; + } + if (!this.field.isElementOfThisField(matrix[i][j])) + { + throw new IllegalArgumentException( + " Error: given array is not encoded matrix over GF(2^m)"); + } + } + } + } + + /** + * Copy constructor. + * + * @param other another {@link GF2mMatrix} + */ + public GF2mMatrix(GF2mMatrix other) + { + numRows = other.numRows; + numColumns = other.numColumns; + field = other.field; + matrix = new int[numRows][]; + for (int i = 0; i < numRows; i++) + { + matrix[i] = IntUtils.clone(other.matrix[i]); + } + } + + /** + * Constructor. + * + * @param field a finite field GF(2^m) + * @param matrix the matrix as int array. Only the reference is copied. + */ + protected GF2mMatrix(GF2mField field, int[][] matrix) + { + this.field = field; + this.matrix = matrix; + numRows = matrix.length; + numColumns = matrix[0].length; + } + + /** + * @return a byte array encoding of this matrix + */ + public byte[] getEncoded() + { + int d = 8; + int count = 1; + while (field.getDegree() > d) + { + count++; + d += 8; + } + + byte[] bf = new byte[this.numRows * this.numColumns * count + 4]; + bf[0] = (byte)(this.numRows & 0xff); + bf[1] = (byte)((this.numRows >>> 8) & 0xff); + bf[2] = (byte)((this.numRows >>> 16) & 0xff); + bf[3] = (byte)((this.numRows >>> 24) & 0xff); + + count = 4; + for (int i = 0; i < this.numRows; i++) + { + for (int j = 0; j < this.numColumns; j++) + { + for (int jj = 0; jj < d; jj += 8) + { + bf[count++] = (byte)(matrix[i][j] >>> jj); + } + } + } + + return bf; + } + + /** + * Check if this is the zero matrix (i.e., all entries are zero). + * + * @return <tt>true</tt> if this is the zero matrix + */ + public boolean isZero() + { + for (int i = 0; i < numRows; i++) + { + for (int j = 0; j < numColumns; j++) + { + if (matrix[i][j] != 0) + { + return false; + } + } + } + return true; + } + + /** + * Compute the inverse of this matrix. + * + * @return the inverse of this matrix (newly created). + */ + public Matrix computeInverse() + { + if (numRows != numColumns) + { + throw new ArithmeticException("Matrix is not invertible."); + } + + // clone this matrix + int[][] tmpMatrix = new int[numRows][numRows]; + for (int i = numRows - 1; i >= 0; i--) + { + tmpMatrix[i] = IntUtils.clone(matrix[i]); + } + + // initialize inverse matrix as unit matrix + int[][] invMatrix = new int[numRows][numRows]; + for (int i = numRows - 1; i >= 0; i--) + { + invMatrix[i][i] = 1; + } + + // simultaneously compute Gaussian reduction of tmpMatrix and unit + // matrix + for (int i = 0; i < numRows; i++) + { + // if diagonal element is zero + if (tmpMatrix[i][i] == 0) + { + boolean foundNonZero = false; + // find a non-zero element in the same column + for (int j = i + 1; j < numRows; j++) + { + if (tmpMatrix[j][i] != 0) + { + // found it, swap rows ... + foundNonZero = true; + swapColumns(tmpMatrix, i, j); + swapColumns(invMatrix, i, j); + // ... and quit searching + j = numRows; + continue; + } + } + // if no non-zero element was found + if (!foundNonZero) + { + // the matrix is not invertible + throw new ArithmeticException("Matrix is not invertible."); + } + } + + // normalize i-th row + int coef = tmpMatrix[i][i]; + int invCoef = field.inverse(coef); + multRowWithElementThis(tmpMatrix[i], invCoef); + multRowWithElementThis(invMatrix[i], invCoef); + + // normalize all other rows + for (int j = 0; j < numRows; j++) + { + if (j != i) + { + coef = tmpMatrix[j][i]; + if (coef != 0) + { + int[] tmpRow = multRowWithElement(tmpMatrix[i], coef); + int[] tmpInvRow = multRowWithElement(invMatrix[i], coef); + addToRow(tmpRow, tmpMatrix[j]); + addToRow(tmpInvRow, invMatrix[j]); + } + } + } + } + + return new GF2mMatrix(field, invMatrix); + } + + private static void swapColumns(int[][] matrix, int first, int second) + { + int[] tmp = matrix[first]; + matrix[first] = matrix[second]; + matrix[second] = tmp; + } + + private void multRowWithElementThis(int[] row, int element) + { + for (int i = row.length - 1; i >= 0; i--) + { + row[i] = field.mult(row[i], element); + } + } + + private int[] multRowWithElement(int[] row, int element) + { + int[] result = new int[row.length]; + for (int i = row.length - 1; i >= 0; i--) + { + result[i] = field.mult(row[i], element); + } + return result; + } + + /** + * Add one row to another. + * + * @param fromRow the addend + * @param toRow the row to add to + */ + private void addToRow(int[] fromRow, int[] toRow) + { + for (int i = toRow.length - 1; i >= 0; i--) + { + toRow[i] = field.add(fromRow[i], toRow[i]); + } + } + + public Matrix rightMultiply(Matrix a) + { + throw new RuntimeException("Not implemented."); + } + + public Matrix rightMultiply(Permutation perm) + { + throw new RuntimeException("Not implemented."); + } + + public Vector leftMultiply(Vector vector) + { + throw new RuntimeException("Not implemented."); + } + + public Vector rightMultiply(Vector vector) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Checks if given object is equal to this matrix. The method returns false + * whenever the given object is not a matrix over GF(2^m). + * + * @param other object + * @return true or false + */ + public boolean equals(Object other) + { + + if (other == null || !(other instanceof GF2mMatrix)) + { + return false; + } + + GF2mMatrix otherMatrix = (GF2mMatrix)other; + + if ((!this.field.equals(otherMatrix.field)) + || (otherMatrix.numRows != this.numColumns) + || (otherMatrix.numColumns != this.numColumns)) + { + return false; + } + + for (int i = 0; i < this.numRows; i++) + { + for (int j = 0; j < this.numColumns; j++) + { + if (this.matrix[i][j] != otherMatrix.matrix[i][j]) + { + return false; + } + } + } + + return true; + } + + public int hashCode() + { + int hash = (this.field.hashCode() * 31 + numRows) * 31 + numColumns; + for (int i = 0; i < this.numRows; i++) + { + for (int j = 0; j < this.numColumns; j++) + { + hash = hash * 31 + matrix[i][j]; + } + } + return hash; + } + + public String toString() + { + String str = this.numRows + " x " + this.numColumns + " Matrix over " + + this.field.toString() + ": \n"; + + for (int i = 0; i < this.numRows; i++) + { + for (int j = 0; j < this.numColumns; j++) + { + str = str + this.field.elementToStr(matrix[i][j]) + " : "; + } + str = str + "\n"; + } + + return str; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2mVector.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2mVector.java new file mode 100644 index 00000000..8e613e7b --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2mVector.java @@ -0,0 +1,256 @@ +package org.spongycastle.pqc.math.linearalgebra; + + +/** + * This class implements vectors over the finite field + * <tt>GF(2<sup>m</sup>)</tt> for small <tt>m</tt> (i.e., + * <tt>1<m<32</tt>). It extends the abstract class {@link Vector}. + */ +public class GF2mVector + extends Vector +{ + + /** + * the finite field this vector is defined over + */ + private GF2mField field; + + /** + * the element array + */ + private int[] vector; + + /** + * creates the vector over GF(2^m) of given length and with elements from + * array v (beginning at the first bit) + * + * @param field finite field + * @param v array with elements of vector + */ + public GF2mVector(GF2mField field, byte[] v) + { + this.field = new GF2mField(field); + + // decode vector + int d = 8; + int count = 1; + while (field.getDegree() > d) + { + count++; + d += 8; + } + + if ((v.length % count) != 0) + { + throw new IllegalArgumentException( + "Byte array is not an encoded vector over the given finite field."); + } + + length = v.length / count; + vector = new int[length]; + count = 0; + for (int i = 0; i < vector.length; i++) + { + for (int j = 0; j < d; j += 8) + { + vector[i] |= (v[count++] & 0xff) << j; + } + if (!field.isElementOfThisField(vector[i])) + { + throw new IllegalArgumentException( + "Byte array is not an encoded vector over the given finite field."); + } + } + } + + /** + * Create a new vector over <tt>GF(2<sup>m</sup>)</tt> of the given + * length and element array. + * + * @param field the finite field <tt>GF(2<sup>m</sup>)</tt> + * @param vector the element array + */ + public GF2mVector(GF2mField field, int[] vector) + { + this.field = field; + length = vector.length; + for (int i = vector.length - 1; i >= 0; i--) + { + if (!field.isElementOfThisField(vector[i])) + { + throw new ArithmeticException( + "Element array is not specified over the given finite field."); + } + } + this.vector = IntUtils.clone(vector); + } + + /** + * Copy constructor. + * + * @param other another {@link GF2mVector} + */ + public GF2mVector(GF2mVector other) + { + field = new GF2mField(other.field); + length = other.length; + vector = IntUtils.clone(other.vector); + } + + /** + * @return the finite field this vector is defined over + */ + public GF2mField getField() + { + return field; + } + + /** + * @return int[] form of this vector + */ + public int[] getIntArrayForm() + { + return IntUtils.clone(vector); + } + + /** + * @return a byte array encoding of this vector + */ + public byte[] getEncoded() + { + int d = 8; + int count = 1; + while (field.getDegree() > d) + { + count++; + d += 8; + } + + byte[] res = new byte[vector.length * count]; + count = 0; + for (int i = 0; i < vector.length; i++) + { + for (int j = 0; j < d; j += 8) + { + res[count++] = (byte)(vector[i] >>> j); + } + } + + return res; + } + + /** + * @return whether this is the zero vector (i.e., all elements are zero) + */ + public boolean isZero() + { + for (int i = vector.length - 1; i >= 0; i--) + { + if (vector[i] != 0) + { + return false; + } + } + return true; + } + + /** + * Add another vector to this vector. Method is not yet implemented. + * + * @param addend the other vector + * @return <tt>this + addend</tt> + * @throws ArithmeticException if the other vector is not defined over the same field as + * this vector. + * <p> + * TODO: implement this method + */ + public Vector add(Vector addend) + { + throw new RuntimeException("not implemented"); + } + + /** + * Multiply this vector with a permutation. + * + * @param p the permutation + * @return <tt>this*p = p*this</tt> + */ + public Vector multiply(Permutation p) + { + int[] pVec = p.getVector(); + if (length != pVec.length) + { + throw new ArithmeticException( + "permutation size and vector size mismatch"); + } + + int[] result = new int[length]; + for (int i = 0; i < pVec.length; i++) + { + result[i] = vector[pVec[i]]; + } + + return new GF2mVector(field, result); + } + + /** + * Compare this vector with another object. + * + * @param other the other object + * @return the result of the comparison + */ + public boolean equals(Object other) + { + + if (!(other instanceof GF2mVector)) + { + return false; + } + GF2mVector otherVec = (GF2mVector)other; + + if (!field.equals(otherVec.field)) + { + return false; + } + + return IntUtils.equals(vector, otherVec.vector); + } + + /** + * @return the hash code of this vector + */ + public int hashCode() + { + int hash = this.field.hashCode(); + hash = hash * 31 + vector.hashCode(); + return hash; + } + + /** + * @return a human readable form of this vector + */ + public String toString() + { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < vector.length; i++) + { + for (int j = 0; j < field.getDegree(); j++) + { + int r = j & 0x1f; + int bitMask = 1 << r; + int coeff = vector[i] & bitMask; + if (coeff != 0) + { + buf.append('1'); + } + else + { + buf.append('0'); + } + } + buf.append(' '); + } + return buf.toString(); + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nElement.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nElement.java new file mode 100644 index 00000000..a95b25f8 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nElement.java @@ -0,0 +1,186 @@ +package org.spongycastle.pqc.math.linearalgebra; + + +/** + * This abstract class implements an element of the finite field <i>GF(2)<sup>n + * </sup></i> in either <i>optimal normal basis</i> representation (<i>ONB</i>) + * or in <i>polynomial</i> representation. It is extended by the classes <a + * href = GF2nONBElement.html><tt> GF2nONBElement</tt></a> and <a href = + * GF2nPolynomialElement.html> <tt>GF2nPolynomialElement</tt> </a>. + * + * @see GF2nPolynomialElement + * @see GF2nONBElement + * @see GF2nONBField + */ +public abstract class GF2nElement + implements GFElement +{ + + // ///////////////////////////////////////////////////////////////////// + // member variables + // ///////////////////////////////////////////////////////////////////// + + /** + * holds a pointer to this element's corresponding field. + */ + protected GF2nField mField; + + /** + * holds the extension degree <i>n</i> of this element's corresponding + * field. + */ + protected int mDegree; + + // ///////////////////////////////////////////////////////////////////// + // pseudo-constructors + // ///////////////////////////////////////////////////////////////////// + + /** + * @return a copy of this GF2nElement + */ + public abstract Object clone(); + + // ///////////////////////////////////////////////////////////////////// + // assignments + // ///////////////////////////////////////////////////////////////////// + + /** + * Assign the value 0 to this element. + */ + abstract void assignZero(); + + /** + * Assigns the value 1 to this element. + */ + abstract void assignOne(); + + // ///////////////////////////////////////////////////////////////////// + // access + // ///////////////////////////////////////////////////////////////////// + + /** + * Returns whether the rightmost bit of the bit representation is set. This + * is needed for data conversion according to 1363. + * + * @return true if the rightmost bit of this element is set + */ + public abstract boolean testRightmostBit(); + + /** + * Checks whether the indexed bit of the bit representation is set + * + * @param index the index of the bit to test + * @return <tt>true</tt> if the indexed bit is set + */ + abstract boolean testBit(int index); + + /** + * Returns the field of this element. + * + * @return the field of this element + */ + public final GF2nField getField() + { + return mField; + } + + // ///////////////////////////////////////////////////////////////////// + // arithmetic + // ///////////////////////////////////////////////////////////////////// + + /** + * Returns <tt>this</tt> element + 1. + * + * @return <tt>this</tt> + 1 + */ + public abstract GF2nElement increase(); + + /** + * Increases this element by one. + */ + public abstract void increaseThis(); + + /** + * Compute the difference of this element and <tt>minuend</tt>. + * + * @param minuend the minuend + * @return <tt>this - minuend</tt> (newly created) + * @throws DifferentFieldsException if the elements are of different fields. + */ + public final GFElement subtract(GFElement minuend) + throws RuntimeException + { + return add(minuend); + } + + /** + * Compute the difference of this element and <tt>minuend</tt>, + * overwriting this element. + * + * @param minuend the minuend + * @throws DifferentFieldsException if the elements are of different fields. + */ + public final void subtractFromThis(GFElement minuend) + { + addToThis(minuend); + } + + /** + * Returns <tt>this</tt> element to the power of 2. + * + * @return <tt>this</tt><sup>2</sup> + */ + public abstract GF2nElement square(); + + /** + * Squares <tt>this</tt> element. + */ + public abstract void squareThis(); + + /** + * Compute the square root of this element and return the result in a new + * {@link GF2nElement}. + * + * @return <tt>this<sup>1/2</sup></tt> (newly created) + */ + public abstract GF2nElement squareRoot(); + + /** + * Compute the square root of this element. + */ + public abstract void squareRootThis(); + + /** + * Performs a basis transformation of this element to the given GF2nField + * <tt>basis</tt>. + * + * @param basis the GF2nField representation to transform this element to + * @return this element in the representation of <tt>basis</tt> + * @throws DifferentFieldsException if <tt>this</tt> cannot be converted according to + * <tt>basis</tt>. + */ + public final GF2nElement convert(GF2nField basis) + throws RuntimeException + { + return mField.convert(this, basis); + } + + /** + * Returns the trace of this element. + * + * @return the trace of this element + */ + public abstract int trace(); + + /** + * Solves a quadratic equation.<br> + * Let z<sup>2</sup> + z = <tt>this</tt>. Then this method returns z. + * + * @return z with z<sup>2</sup> + z = <tt>this</tt> + * @throws NoSolutionException if z<sup>2</sup> + z = <tt>this</tt> does not have a + * solution + */ + public abstract GF2nElement solveQuadraticEquation() + throws RuntimeException; + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nField.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nField.java new file mode 100644 index 00000000..d1aa1363 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nField.java @@ -0,0 +1,292 @@ +package org.spongycastle.pqc.math.linearalgebra; + + +import java.util.Vector; + + +/** + * This abstract class defines the finite field <i>GF(2<sup>n</sup>)</i>. It + * holds the extension degree <i>n</i>, the characteristic, the irreducible + * fieldpolynomial and conversion matrices. GF2nField is implemented by the + * classes GF2nPolynomialField and GF2nONBField. + * + * @see GF2nONBField + * @see GF2nPolynomialField + */ +public abstract class GF2nField +{ + + /** + * the degree of this field + */ + protected int mDegree; + + /** + * the irreducible fieldPolynomial stored in normal order (also for ONB) + */ + protected GF2Polynomial fieldPolynomial; + + /** + * holds a list of GF2nFields to which elements have been converted and thus + * a COB-Matrix exists + */ + protected Vector fields; + + /** + * the COB matrices + */ + protected Vector matrices; + + /** + * Returns the degree <i>n</i> of this field. + * + * @return the degree <i>n</i> of this field + */ + public final int getDegree() + { + return mDegree; + } + + /** + * Returns the fieldpolynomial as a new Bitstring. + * + * @return a copy of the fieldpolynomial as a new Bitstring + */ + public final GF2Polynomial getFieldPolynomial() + { + if (fieldPolynomial == null) + { + computeFieldPolynomial(); + } + return new GF2Polynomial(fieldPolynomial); + } + + /** + * Decides whether the given object <tt>other</tt> is the same as this + * field. + * + * @param other another object + * @return (this == other) + */ + public final boolean equals(Object other) + { + if (other == null || !(other instanceof GF2nField)) + { + return false; + } + + GF2nField otherField = (GF2nField)other; + + if (otherField.mDegree != mDegree) + { + return false; + } + if (!fieldPolynomial.equals(otherField.fieldPolynomial)) + { + return false; + } + if ((this instanceof GF2nPolynomialField) + && !(otherField instanceof GF2nPolynomialField)) + { + return false; + } + if ((this instanceof GF2nONBField) + && !(otherField instanceof GF2nONBField)) + { + return false; + } + return true; + } + + /** + * @return the hash code of this field + */ + public int hashCode() + { + return mDegree + fieldPolynomial.hashCode(); + } + + /** + * Computes a random root from the given irreducible fieldpolynomial + * according to IEEE 1363 algorithm A.5.6. This cal take very long for big + * degrees. + * + * @param B0FieldPolynomial the fieldpolynomial if the other basis as a Bitstring + * @return a random root of BOFieldPolynomial in representation according to + * this field + * @see "P1363 A.5.6, p103f" + */ + protected abstract GF2nElement getRandomRoot(GF2Polynomial B0FieldPolynomial); + + /** + * Computes the change-of-basis matrix for basis conversion according to + * 1363. The result is stored in the lists fields and matrices. + * + * @param B1 the GF2nField to convert to + * @see "P1363 A.7.3, p111ff" + */ + protected abstract void computeCOBMatrix(GF2nField B1); + + /** + * Computes the fieldpolynomial. This can take a long time for big degrees. + */ + protected abstract void computeFieldPolynomial(); + + /** + * Inverts the given matrix represented as bitstrings. + * + * @param matrix the matrix to invert as a Bitstring[] + * @return matrix^(-1) + */ + protected final GF2Polynomial[] invertMatrix(GF2Polynomial[] matrix) + { + GF2Polynomial[] a = new GF2Polynomial[matrix.length]; + GF2Polynomial[] inv = new GF2Polynomial[matrix.length]; + GF2Polynomial dummy; + int i, j; + // initialize a as a copy of matrix and inv as E(inheitsmatrix) + for (i = 0; i < mDegree; i++) + { + try + { + a[i] = new GF2Polynomial(matrix[i]); + inv[i] = new GF2Polynomial(mDegree); + inv[i].setBit(mDegree - 1 - i); + } + catch (RuntimeException BDNEExc) + { + BDNEExc.printStackTrace(); + } + } + // construct triangle matrix so that for each a[i] the first i bits are + // zero + for (i = 0; i < mDegree - 1; i++) + { + // find column where bit i is set + j = i; + while ((j < mDegree) && !a[j].testBit(mDegree - 1 - i)) + { + j++; + } + if (j >= mDegree) + { + throw new RuntimeException( + "GF2nField.invertMatrix: Matrix cannot be inverted!"); + } + if (i != j) + { // swap a[i]/a[j] and inv[i]/inv[j] + dummy = a[i]; + a[i] = a[j]; + a[j] = dummy; + dummy = inv[i]; + inv[i] = inv[j]; + inv[j] = dummy; + } + for (j = i + 1; j < mDegree; j++) + { // add column i to all columns>i + // having their i-th bit set + if (a[j].testBit(mDegree - 1 - i)) + { + a[j].addToThis(a[i]); + inv[j].addToThis(inv[i]); + } + } + } + // construct Einheitsmatrix from a + for (i = mDegree - 1; i > 0; i--) + { + for (j = i - 1; j >= 0; j--) + { // eliminate the i-th bit in all + // columns < i + if (a[j].testBit(mDegree - 1 - i)) + { + a[j].addToThis(a[i]); + inv[j].addToThis(inv[i]); + } + } + } + return inv; + } + + /** + * Converts the given element in representation according to this field to a + * new element in representation according to B1 using the change-of-basis + * matrix calculated by computeCOBMatrix. + * + * @param elem the GF2nElement to convert + * @param basis the basis to convert <tt>elem</tt> to + * @return <tt>elem</tt> converted to a new element representation + * according to <tt>basis</tt> + * @throws DifferentFieldsException if <tt>elem</tt> cannot be converted according to + * <tt>basis</tt>. + * @see GF2nField#computeCOBMatrix + * @see GF2nField#getRandomRoot + * @see GF2nPolynomial + * @see "P1363 A.7 p109ff" + */ + public final GF2nElement convert(GF2nElement elem, GF2nField basis) + throws RuntimeException + { + if (basis == this) + { + return (GF2nElement)elem.clone(); + } + if (fieldPolynomial.equals(basis.fieldPolynomial)) + { + return (GF2nElement)elem.clone(); + } + if (mDegree != basis.mDegree) + { + throw new RuntimeException("GF2nField.convert: B1 has a" + + " different degree and thus cannot be coverted to!"); + } + + int i; + GF2Polynomial[] COBMatrix; + i = fields.indexOf(basis); + if (i == -1) + { + computeCOBMatrix(basis); + i = fields.indexOf(basis); + } + COBMatrix = (GF2Polynomial[])matrices.elementAt(i); + + GF2nElement elemCopy = (GF2nElement)elem.clone(); + if (elemCopy instanceof GF2nONBElement) + { + // remember: ONB treats its bits in reverse order + ((GF2nONBElement)elemCopy).reverseOrder(); + } + GF2Polynomial bs = new GF2Polynomial(mDegree, elemCopy.toFlexiBigInt()); + bs.expandN(mDegree); + GF2Polynomial result = new GF2Polynomial(mDegree); + for (i = 0; i < mDegree; i++) + { + if (bs.vectorMult(COBMatrix[i])) + { + result.setBit(mDegree - 1 - i); + } + } + if (basis instanceof GF2nPolynomialField) + { + return new GF2nPolynomialElement((GF2nPolynomialField)basis, + result); + } + else if (basis instanceof GF2nONBField) + { + GF2nONBElement res = new GF2nONBElement((GF2nONBField)basis, + result.toFlexiBigInt()); + // TODO Remember: ONB treats its Bits in reverse order !!! + res.reverseOrder(); + return res; + } + else + { + throw new RuntimeException( + "GF2nField.convert: B1 must be an instance of " + + "GF2nPolynomialField or GF2nONBField!"); + } + + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nONBElement.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nONBElement.java new file mode 100644 index 00000000..b14fdd1a --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nONBElement.java @@ -0,0 +1,1154 @@ +package org.spongycastle.pqc.math.linearalgebra; + + +import java.math.BigInteger; +import java.util.Random; + +/** + * This class implements an element of the finite field <i>GF(2<sup>n </sup>)</i>. + * It is represented in an optimal normal basis representation and holds the + * pointer <tt>mField</tt> to its corresponding field. + * + * @see GF2nField + * @see GF2nElement + */ +public class GF2nONBElement + extends GF2nElement +{ + + // ///////////////////////////////////////////////////////////////////// + // member variables + // ///////////////////////////////////////////////////////////////////// + + private static final long[] mBitmask = new long[]{0x0000000000000001L, + 0x0000000000000002L, 0x0000000000000004L, 0x0000000000000008L, + 0x0000000000000010L, 0x0000000000000020L, 0x0000000000000040L, + 0x0000000000000080L, 0x0000000000000100L, 0x0000000000000200L, + 0x0000000000000400L, 0x0000000000000800L, 0x0000000000001000L, + 0x0000000000002000L, 0x0000000000004000L, 0x0000000000008000L, + 0x0000000000010000L, 0x0000000000020000L, 0x0000000000040000L, + 0x0000000000080000L, 0x0000000000100000L, 0x0000000000200000L, + 0x0000000000400000L, 0x0000000000800000L, 0x0000000001000000L, + 0x0000000002000000L, 0x0000000004000000L, 0x0000000008000000L, + 0x0000000010000000L, 0x0000000020000000L, 0x0000000040000000L, + 0x0000000080000000L, 0x0000000100000000L, 0x0000000200000000L, + 0x0000000400000000L, 0x0000000800000000L, 0x0000001000000000L, + 0x0000002000000000L, 0x0000004000000000L, 0x0000008000000000L, + 0x0000010000000000L, 0x0000020000000000L, 0x0000040000000000L, + 0x0000080000000000L, 0x0000100000000000L, 0x0000200000000000L, + 0x0000400000000000L, 0x0000800000000000L, 0x0001000000000000L, + 0x0002000000000000L, 0x0004000000000000L, 0x0008000000000000L, + 0x0010000000000000L, 0x0020000000000000L, 0x0040000000000000L, + 0x0080000000000000L, 0x0100000000000000L, 0x0200000000000000L, + 0x0400000000000000L, 0x0800000000000000L, 0x1000000000000000L, + 0x2000000000000000L, 0x4000000000000000L, 0x8000000000000000L}; + + private static final long[] mMaxmask = new long[]{0x0000000000000001L, + 0x0000000000000003L, 0x0000000000000007L, 0x000000000000000FL, + 0x000000000000001FL, 0x000000000000003FL, 0x000000000000007FL, + 0x00000000000000FFL, 0x00000000000001FFL, 0x00000000000003FFL, + 0x00000000000007FFL, 0x0000000000000FFFL, 0x0000000000001FFFL, + 0x0000000000003FFFL, 0x0000000000007FFFL, 0x000000000000FFFFL, + 0x000000000001FFFFL, 0x000000000003FFFFL, 0x000000000007FFFFL, + 0x00000000000FFFFFL, 0x00000000001FFFFFL, 0x00000000003FFFFFL, + 0x00000000007FFFFFL, 0x0000000000FFFFFFL, 0x0000000001FFFFFFL, + 0x0000000003FFFFFFL, 0x0000000007FFFFFFL, 0x000000000FFFFFFFL, + 0x000000001FFFFFFFL, 0x000000003FFFFFFFL, 0x000000007FFFFFFFL, + 0x00000000FFFFFFFFL, 0x00000001FFFFFFFFL, 0x00000003FFFFFFFFL, + 0x00000007FFFFFFFFL, 0x0000000FFFFFFFFFL, 0x0000001FFFFFFFFFL, + 0x0000003FFFFFFFFFL, 0x0000007FFFFFFFFFL, 0x000000FFFFFFFFFFL, + 0x000001FFFFFFFFFFL, 0x000003FFFFFFFFFFL, 0x000007FFFFFFFFFFL, + 0x00000FFFFFFFFFFFL, 0x00001FFFFFFFFFFFL, 0x00003FFFFFFFFFFFL, + 0x00007FFFFFFFFFFFL, 0x0000FFFFFFFFFFFFL, 0x0001FFFFFFFFFFFFL, + 0x0003FFFFFFFFFFFFL, 0x0007FFFFFFFFFFFFL, 0x000FFFFFFFFFFFFFL, + 0x001FFFFFFFFFFFFFL, 0x003FFFFFFFFFFFFFL, 0x007FFFFFFFFFFFFFL, + 0x00FFFFFFFFFFFFFFL, 0x01FFFFFFFFFFFFFFL, 0x03FFFFFFFFFFFFFFL, + 0x07FFFFFFFFFFFFFFL, 0x0FFFFFFFFFFFFFFFL, 0x1FFFFFFFFFFFFFFFL, + 0x3FFFFFFFFFFFFFFFL, 0x7FFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL}; + + // mIBy64[j * 16 + i] = (j * 16 + i)/64 + // i = + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + // + private static final int[] mIBY64 = new int[]{ + // j = + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 8 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 9 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 10 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 11 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 12 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 13 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 14 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 15 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 16 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 17 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 18 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 19 + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 20 + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 21 + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 22 + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 // 23 + }; + + private static final int MAXLONG = 64; + + /** + * holds the lenght of the polynomial with 64 bit sized fields. + */ + private int mLength; + + /** + * holds the value of mDeg % MAXLONG. + */ + private int mBit; + + /** + * holds this element in ONB representation. + */ + private long[] mPol; + + // ///////////////////////////////////////////////////////////////////// + // constructors + // ///////////////////////////////////////////////////////////////////// + + /** + * Construct a random element over the field <tt>gf2n</tt>, using the + * specified source of randomness. + * + * @param gf2n the field + * @param rand the source of randomness + */ + public GF2nONBElement(GF2nONBField gf2n, Random rand) + { + mField = gf2n; + mDegree = mField.getDegree(); + mLength = gf2n.getONBLength(); + mBit = gf2n.getONBBit(); + mPol = new long[mLength]; + if (mLength > 1) + { + for (int j = 0; j < mLength - 1; j++) + { + mPol[j] = rand.nextLong(); + } + long last = rand.nextLong(); + mPol[mLength - 1] = last >>> (MAXLONG - mBit); + } + else + { + mPol[0] = rand.nextLong(); + mPol[0] = mPol[0] >>> (MAXLONG - mBit); + } + } + + /** + * Construct a new GF2nONBElement from its encoding. + * + * @param gf2n the field + * @param e the encoded element + */ + public GF2nONBElement(GF2nONBField gf2n, byte[] e) + { + mField = gf2n; + mDegree = mField.getDegree(); + mLength = gf2n.getONBLength(); + mBit = gf2n.getONBBit(); + mPol = new long[mLength]; + assign(e); + } + + /** + * Construct the element of the field <tt>gf2n</tt> with the specified + * value <tt>val</tt>. + * + * @param gf2n the field + * @param val the value represented by a BigInteger + */ + public GF2nONBElement(GF2nONBField gf2n, BigInteger val) + { + mField = gf2n; + mDegree = mField.getDegree(); + mLength = gf2n.getONBLength(); + mBit = gf2n.getONBBit(); + mPol = new long[mLength]; + assign(val); + } + + /** + * Construct the element of the field <tt>gf2n</tt> with the specified + * value <tt>val</tt>. + * + * @param gf2n the field + * @param val the value in ONB representation + */ + private GF2nONBElement(GF2nONBField gf2n, long[] val) + { + mField = gf2n; + mDegree = mField.getDegree(); + mLength = gf2n.getONBLength(); + mBit = gf2n.getONBBit(); + mPol = val; + } + + // ///////////////////////////////////////////////////////////////////// + // pseudo-constructors + // ///////////////////////////////////////////////////////////////////// + + /** + * Copy constructor. + * + * @param gf2n the field + */ + public GF2nONBElement(GF2nONBElement gf2n) + { + + mField = gf2n.mField; + mDegree = mField.getDegree(); + mLength = ((GF2nONBField)mField).getONBLength(); + mBit = ((GF2nONBField)mField).getONBBit(); + mPol = new long[mLength]; + assign(gf2n.getElement()); + } + + /** + * Create a new GF2nONBElement by cloning this GF2nPolynomialElement. + * + * @return a copy of this element + */ + public Object clone() + { + return new GF2nONBElement(this); + } + + /** + * Create the zero element. + * + * @param gf2n the finite field + * @return the zero element in the given finite field + */ + public static GF2nONBElement ZERO(GF2nONBField gf2n) + { + long[] polynomial = new long[gf2n.getONBLength()]; + return new GF2nONBElement(gf2n, polynomial); + } + + /** + * Create the one element. + * + * @param gf2n the finite field + * @return the one element in the given finite field + */ + public static GF2nONBElement ONE(GF2nONBField gf2n) + { + int mLength = gf2n.getONBLength(); + long[] polynomial = new long[mLength]; + + // fill mDegree coefficients with one's + for (int i = 0; i < mLength - 1; i++) + { + polynomial[i] = 0xffffffffffffffffL; + } + polynomial[mLength - 1] = mMaxmask[gf2n.getONBBit() - 1]; + + return new GF2nONBElement(gf2n, polynomial); + } + + // ///////////////////////////////////////////////////////////////////// + // assignments + // ///////////////////////////////////////////////////////////////////// + + /** + * assigns to this element the zero element + */ + void assignZero() + { + mPol = new long[mLength]; + } + + /** + * assigns to this element the one element + */ + void assignOne() + { + // fill mDegree coefficients with one's + for (int i = 0; i < mLength - 1; i++) + { + mPol[i] = 0xffffffffffffffffL; + } + mPol[mLength - 1] = mMaxmask[mBit - 1]; + } + + /** + * assigns to this element the value <tt>val</tt>. + * + * @param val the value represented by a BigInteger + */ + private void assign(BigInteger val) + { + assign(val.toByteArray()); + } + + /** + * assigns to this element the value <tt>val</tt>. + * + * @param val the value in ONB representation + */ + private void assign(long[] val) + { + System.arraycopy(val, 0, mPol, 0, mLength); + } + + /** + * assigns to this element the value <tt>val</tt>. First: inverting the + * order of val into reversed[]. That means: reversed[0] = val[length - 1], + * ..., reversed[reversed.length - 1] = val[0]. Second: mPol[0] = sum{i = 0, + * ... 7} (val[i]<<(i*8)) .... mPol[1] = sum{i = 8, ... 15} (val[i]<<(i*8)) + * + * @param val the value in ONB representation + */ + private void assign(byte[] val) + { + int j; + mPol = new long[mLength]; + for (j = 0; j < val.length; j++) + { + mPol[j >>> 3] |= (val[val.length - 1 - j] & 0x00000000000000ffL) << ((j & 0x07) << 3); + } + } + + // ///////////////////////////////////////////////////////////////// + // comparison + // ///////////////////////////////////////////////////////////////// + + /** + * Checks whether this element is zero. + * + * @return <tt>true</tt> if <tt>this</tt> is the zero element + */ + public boolean isZero() + { + + boolean result = true; + + for (int i = 0; i < mLength && result; i++) + { + result = result && ((mPol[i] & 0xFFFFFFFFFFFFFFFFL) == 0); + } + + return result; + } + + /** + * Checks whether this element is one. + * + * @return <tt>true</tt> if <tt>this</tt> is the one element + */ + public boolean isOne() + { + + boolean result = true; + + for (int i = 0; i < mLength - 1 && result; i++) + { + result = result + && ((mPol[i] & 0xFFFFFFFFFFFFFFFFL) == 0xFFFFFFFFFFFFFFFFL); + } + + if (result) + { + result = result + && ((mPol[mLength - 1] & mMaxmask[mBit - 1]) == mMaxmask[mBit - 1]); + } + + return result; + } + + /** + * Compare this element with another object. + * + * @param other the other object + * @return <tt>true</tt> if the two objects are equal, <tt>false</tt> + * otherwise + */ + public boolean equals(Object other) + { + if (other == null || !(other instanceof GF2nONBElement)) + { + return false; + } + + GF2nONBElement otherElem = (GF2nONBElement)other; + + for (int i = 0; i < mLength; i++) + { + if (mPol[i] != otherElem.mPol[i]) + { + return false; + } + } + + return true; + } + + /** + * @return the hash code of this element + */ + public int hashCode() + { + return mPol.hashCode(); + } + + // ///////////////////////////////////////////////////////////////////// + // access + // ///////////////////////////////////////////////////////////////////// + + /** + * Returns whether the highest bit of the bit representation is set + * + * @return true, if the highest bit of mPol is set, false, otherwise + */ + public boolean testRightmostBit() + { + // due to the reverse bit order (compared to 1363) this method returns + // the value of the leftmost bit + return (mPol[mLength - 1] & mBitmask[mBit - 1]) != 0L; + } + + /** + * Checks whether the indexed bit of the bit representation is set. Warning: + * GF2nONBElement currently stores its bits in reverse order (compared to + * 1363) !!! + * + * @param index the index of the bit to test + * @return <tt>true</tt> if the indexed bit of mPol is set, <tt>false</tt> + * otherwise. + */ + boolean testBit(int index) + { + if (index < 0 || index > mDegree) + { + return false; + } + long test = mPol[index >>> 6] & mBitmask[index & 0x3f]; + return test != 0x0L; + } + + /** + * @return this element in its ONB representation + */ + private long[] getElement() + { + + long[] result = new long[mPol.length]; + System.arraycopy(mPol, 0, result, 0, mPol.length); + + return result; + } + + /** + * Returns the ONB representation of this element. The Bit-Order is + * exchanged (according to 1363)! + * + * @return this element in its representation and reverse bit-order + */ + private long[] getElementReverseOrder() + { + long[] result = new long[mPol.length]; + for (int i = 0; i < mDegree; i++) + { + if (testBit(mDegree - i - 1)) + { + result[i >>> 6] |= mBitmask[i & 0x3f]; + } + } + return result; + } + + /** + * Reverses the bit-order in this element(according to 1363). This is a + * hack! + */ + void reverseOrder() + { + mPol = getElementReverseOrder(); + } + + // ///////////////////////////////////////////////////////////////////// + // arithmetic + // ///////////////////////////////////////////////////////////////////// + + /** + * Compute the sum of this element and <tt>addend</tt>. + * + * @param addend the addend + * @return <tt>this + other</tt> (newly created) + * @throws DifferentFieldsException if the elements are of different fields. + */ + public GFElement add(GFElement addend) + throws RuntimeException + { + GF2nONBElement result = new GF2nONBElement(this); + result.addToThis(addend); + return result; + } + + /** + * Compute <tt>this + addend</tt> (overwrite <tt>this</tt>). + * + * @param addend the addend + * @throws DifferentFieldsException if the elements are of different fields. + */ + public void addToThis(GFElement addend) + throws RuntimeException + { + if (!(addend instanceof GF2nONBElement)) + { + throw new RuntimeException(); + } + if (!mField.equals(((GF2nONBElement)addend).mField)) + { + throw new RuntimeException(); + } + + for (int i = 0; i < mLength; i++) + { + mPol[i] ^= ((GF2nONBElement)addend).mPol[i]; + } + } + + /** + * returns <tt>this</tt> element + 1. + * + * @return <tt>this</tt> + 1 + */ + public GF2nElement increase() + { + GF2nONBElement result = new GF2nONBElement(this); + result.increaseThis(); + return result; + } + + /** + * increases <tt>this</tt> element. + */ + public void increaseThis() + { + addToThis(ONE((GF2nONBField)mField)); + } + + /** + * Compute the product of this element and <tt>factor</tt>. + * + * @param factor the factor + * @return <tt>this * factor</tt> (newly created) + * @throws DifferentFieldsException if the elements are of different fields. + */ + public GFElement multiply(GFElement factor) + throws RuntimeException + { + GF2nONBElement result = new GF2nONBElement(this); + result.multiplyThisBy(factor); + return result; + } + + /** + * Compute <tt>this * factor</tt> (overwrite <tt>this</tt>). + * + * @param factor the factor + * @throws DifferentFieldsException if the elements are of different fields. + */ + public void multiplyThisBy(GFElement factor) + throws RuntimeException + { + + if (!(factor instanceof GF2nONBElement)) + { + throw new RuntimeException("The elements have different" + + " representation: not yet" + " implemented"); + } + if (!mField.equals(((GF2nONBElement)factor).mField)) + { + throw new RuntimeException(); + } + + if (equals(factor)) + { + squareThis(); + } + else + { + + long[] a = mPol; + long[] b = ((GF2nONBElement)factor).mPol; + long[] c = new long[mLength]; + + int[][] m = ((GF2nONBField)mField).mMult; + + int degf, degb, s, fielda, fieldb, bita, bitb; + degf = mLength - 1; + degb = mBit - 1; + s = 0; + + long TWOTOMAXLONGM1 = mBitmask[MAXLONG - 1]; + long TWOTODEGB = mBitmask[degb]; + + boolean old, now; + + // the product c of a and b (a*b = c) is calculated in mDegree + // cicles + // in every cicle one coefficient of c is calculated and stored + // k indicates the coefficient + // + for (int k = 0; k < mDegree; k++) + { + + s = 0; + + for (int i = 0; i < mDegree; i++) + { + + // fielda = i / MAXLONG + // + fielda = mIBY64[i]; + + // bita = i % MAXLONG + // + bita = i & (MAXLONG - 1); + + // fieldb = m[i][0] / MAXLONG + // + fieldb = mIBY64[m[i][0]]; + + // bitb = m[i][0] % MAXLONG + // + bitb = m[i][0] & (MAXLONG - 1); + + if ((a[fielda] & mBitmask[bita]) != 0) + { + + if ((b[fieldb] & mBitmask[bitb]) != 0) + { + s ^= 1; + } + + if (m[i][1] != -1) + { + + // fieldb = m[i][1] / MAXLONG + // + fieldb = mIBY64[m[i][1]]; + + // bitb = m[i][1] % MAXLONG + // + bitb = m[i][1] & (MAXLONG - 1); + + if ((b[fieldb] & mBitmask[bitb]) != 0) + { + s ^= 1; + } + + } + } + } + fielda = mIBY64[k]; + bita = k & (MAXLONG - 1); + + if (s != 0) + { + c[fielda] ^= mBitmask[bita]; + } + + // Circular shift of x and y one bit to the right, + // respectively. + + if (mLength > 1) + { + + // Shift x. + // + old = (a[degf] & 1) == 1; + + for (int i = degf - 1; i >= 0; i--) + { + now = (a[i] & 1) != 0; + + a[i] = a[i] >>> 1; + + if (old) + { + a[i] ^= TWOTOMAXLONGM1; + } + + old = now; + } + a[degf] = a[degf] >>> 1; + + if (old) + { + a[degf] ^= TWOTODEGB; + } + + // Shift y. + // + old = (b[degf] & 1) == 1; + + for (int i = degf - 1; i >= 0; i--) + { + now = (b[i] & 1) != 0; + + b[i] = b[i] >>> 1; + + if (old) + { + b[i] ^= TWOTOMAXLONGM1; + } + + old = now; + } + + b[degf] = b[degf] >>> 1; + + if (old) + { + b[degf] ^= TWOTODEGB; + } + } + else + { + old = (a[0] & 1) == 1; + a[0] = a[0] >>> 1; + + if (old) + { + a[0] ^= TWOTODEGB; + } + + old = (b[0] & 1) == 1; + b[0] = b[0] >>> 1; + + if (old) + { + b[0] ^= TWOTODEGB; + } + } + } + assign(c); + } + } + + /** + * returns <tt>this</tt> element to the power of 2. + * + * @return <tt>this</tt><sup>2</sup> + */ + public GF2nElement square() + { + GF2nONBElement result = new GF2nONBElement(this); + result.squareThis(); + return result; + } + + /** + * squares <tt>this</tt> element. + */ + public void squareThis() + { + + long[] pol = getElement(); + + int f = mLength - 1; + int b = mBit - 1; + + // Shift the coefficients one bit to the left. + // + long TWOTOMAXLONGM1 = mBitmask[MAXLONG - 1]; + boolean old, now; + + old = (pol[f] & mBitmask[b]) != 0; + + for (int i = 0; i < f; i++) + { + + now = (pol[i] & TWOTOMAXLONGM1) != 0; + + pol[i] = pol[i] << 1; + + if (old) + { + pol[i] ^= 1; + } + + old = now; + } + now = (pol[f] & mBitmask[b]) != 0; + + pol[f] = pol[f] << 1; + + if (old) + { + pol[f] ^= 1; + } + + // Set the bit with index mDegree to zero. + // + if (now) + { + pol[f] ^= mBitmask[b + 1]; + } + + assign(pol); + } + + /** + * Compute the multiplicative inverse of this element. + * + * @return <tt>this<sup>-1</sup></tt> (newly created) + * @throws ArithmeticException if <tt>this</tt> is the zero element. + */ + public GFElement invert() + throws ArithmeticException + { + GF2nONBElement result = new GF2nONBElement(this); + result.invertThis(); + return result; + } + + /** + * Multiplicatively invert of this element (overwrite <tt>this</tt>). + * + * @throws ArithmeticException if <tt>this</tt> is the zero element. + */ + public void invertThis() + throws ArithmeticException + { + + if (isZero()) + { + throw new ArithmeticException(); + } + int r = 31; // mDegree kann nur 31 Bits lang sein!!! + + // Bitlaenge von mDegree: + for (boolean found = false; !found && r >= 0; r--) + { + + if (((mDegree - 1) & mBitmask[r]) != 0) + { + found = true; + } + } + r++; + + GF2nElement m = ZERO((GF2nONBField)mField); + GF2nElement n = new GF2nONBElement(this); + + int k = 1; + + for (int i = r - 1; i >= 0; i--) + { + m = (GF2nElement)n.clone(); + for (int j = 1; j <= k; j++) + { + m.squareThis(); + } + + n.multiplyThisBy(m); + + k <<= 1; + if (((mDegree - 1) & mBitmask[i]) != 0) + { + n.squareThis(); + + n.multiplyThisBy(this); + + k++; + } + } + n.squareThis(); + } + + /** + * returns the root of<tt>this</tt> element. + * + * @return <tt>this</tt><sup>1/2</sup> + */ + public GF2nElement squareRoot() + { + GF2nONBElement result = new GF2nONBElement(this); + result.squareRootThis(); + return result; + } + + /** + * square roots <tt>this</tt> element. + */ + public void squareRootThis() + { + + long[] pol = getElement(); + + int f = mLength - 1; + int b = mBit - 1; + + // Shift the coefficients one bit to the right. + // + long TWOTOMAXLONGM1 = mBitmask[MAXLONG - 1]; + boolean old, now; + + old = (pol[0] & 1) != 0; + + for (int i = f; i >= 0; i--) + { + now = (pol[i] & 1) != 0; + pol[i] = pol[i] >>> 1; + + if (old) + { + if (i == f) + { + pol[i] ^= mBitmask[b]; + } + else + { + pol[i] ^= TWOTOMAXLONGM1; + } + } + old = now; + } + assign(pol); + } + + /** + * Returns the trace of this element. + * + * @return the trace of this element + */ + public int trace() + { + + // trace = sum of coefficients + // + + int result = 0; + + int max = mLength - 1; + + for (int i = 0; i < max; i++) + { + + for (int j = 0; j < MAXLONG; j++) + { + + if ((mPol[i] & mBitmask[j]) != 0) + { + result ^= 1; + } + } + } + + int b = mBit; + + for (int j = 0; j < b; j++) + { + + if ((mPol[max] & mBitmask[j]) != 0) + { + result ^= 1; + } + } + return result; + } + + /** + * Solves a quadratic equation.<br> + * Let z<sup>2</sup> + z = <tt>this</tt>. Then this method returns z. + * + * @return z with z<sup>2</sup> + z = <tt>this</tt> + * @throws NoSolutionException if z<sup>2</sup> + z = <tt>this</tt> does not have a + * solution + */ + public GF2nElement solveQuadraticEquation() + throws RuntimeException + { + + if (trace() == 1) + { + throw new RuntimeException(); + } + + long TWOTOMAXLONGM1 = mBitmask[MAXLONG - 1]; + long ZERO = 0L; + long ONE = 1L; + + long[] p = new long[mLength]; + long z = 0L; + int j = 1; + for (int i = 0; i < mLength - 1; i++) + { + + for (j = 1; j < MAXLONG; j++) + { + + // + if (!((((mBitmask[j] & mPol[i]) != ZERO) && ((z & mBitmask[j - 1]) != ZERO)) || (((mPol[i] & mBitmask[j]) == ZERO) && ((z & mBitmask[j - 1]) == ZERO)))) + { + z ^= mBitmask[j]; + } + } + p[i] = z; + + if (((TWOTOMAXLONGM1 & z) != ZERO && (ONE & mPol[i + 1]) == ONE) + || ((TWOTOMAXLONGM1 & z) == ZERO && (ONE & mPol[i + 1]) == ZERO)) + { + z = ZERO; + } + else + { + z = ONE; + } + } + + int b = mDegree & (MAXLONG - 1); + + long LASTLONG = mPol[mLength - 1]; + + for (j = 1; j < b; j++) + { + if (!((((mBitmask[j] & LASTLONG) != ZERO) && ((mBitmask[j - 1] & z) != ZERO)) || (((mBitmask[j] & LASTLONG) == ZERO) && ((mBitmask[j - 1] & z) == ZERO)))) + { + z ^= mBitmask[j]; + } + } + p[mLength - 1] = z; + return new GF2nONBElement((GF2nONBField)mField, p); + } + + // ///////////////////////////////////////////////////////////////// + // conversion + // ///////////////////////////////////////////////////////////////// + + /** + * Returns a String representation of this element. + * + * @return String representation of this element with the specified radix + */ + public String toString() + { + return toString(16); + } + + /** + * Returns a String representation of this element. <tt>radix</tt> + * specifies the radix of the String representation.<br> + * NOTE: ONLY <tt>radix = 2</tt> or <tt>radix = 16</tt> IS IMPLEMENTED + * + * @param radix specifies the radix of the String representation + * @return String representation of this element with the specified radix + */ + public String toString(int radix) + { + String s = ""; + + long[] a = getElement(); + int b = mBit; + + if (radix == 2) + { + + for (int j = b - 1; j >= 0; j--) + { + if ((a[a.length - 1] & ((long)1 << j)) == 0) + { + s += "0"; + } + else + { + s += "1"; + } + } + + for (int i = a.length - 2; i >= 0; i--) + { + for (int j = MAXLONG - 1; j >= 0; j--) + { + if ((a[i] & mBitmask[j]) == 0) + { + s += "0"; + } + else + { + s += "1"; + } + } + } + } + else if (radix == 16) + { + final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + for (int i = a.length - 1; i >= 0; i--) + { + s += HEX_CHARS[(int)(a[i] >>> 60) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 56) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 52) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 48) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 44) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 40) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 36) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 32) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 28) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 24) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 20) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 16) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 12) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 8) & 0x0f]; + s += HEX_CHARS[(int)(a[i] >>> 4) & 0x0f]; + s += HEX_CHARS[(int)(a[i]) & 0x0f]; + s += " "; + } + } + return s; + } + + /** + * Returns this element as FlexiBigInt. The conversion is <a href = + * "http://grouper.ieee.org/groups/1363/">P1363</a>-conform. + * + * @return this element as BigInteger + */ + public BigInteger toFlexiBigInt() + { + /** @todo this method does not reverse the bit-order as it should!!! */ + + return new BigInteger(1, toByteArray()); + } + + /** + * Returns this element as byte array. The conversion is <a href = + * "http://grouper.ieee.org/groups/1363/">P1363</a>-conform. + * + * @return this element as byte array + */ + public byte[] toByteArray() + { + /** @todo this method does not reverse the bit-order as it should!!! */ + + int k = ((mDegree - 1) >> 3) + 1; + byte[] result = new byte[k]; + int i; + for (i = 0; i < k; i++) + { + result[k - i - 1] = (byte)((mPol[i >>> 3] & (0x00000000000000ffL << ((i & 0x07) << 3))) >>> ((i & 0x07) << 3)); + } + return result; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nONBField.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nONBField.java new file mode 100644 index 00000000..60e5be41 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nONBField.java @@ -0,0 +1,546 @@ +package org.spongycastle.pqc.math.linearalgebra; + + +import java.util.Random; +import java.util.Vector; + + +/** + * This class implements the abstract class <tt>GF2nField</tt> for ONB + * representation. It computes the fieldpolynomial, multiplication matrix and + * one of its roots mONBRoot, (see for example <a + * href=http://www2.certicom.com/ecc/intro.htm>Certicoms Whitepapers</a>). + * GF2nField is used by GF2nONBElement which implements the elements of this + * field. + * + * @see GF2nField + * @see GF2nONBElement + */ +public class GF2nONBField + extends GF2nField +{ + + // /////////////////////////////////////////////////////////////////// + // Hashtable for irreducible normal polynomials // + // /////////////////////////////////////////////////////////////////// + + // i*5 + 0 i*5 + 1 i*5 + 2 i*5 + 3 i*5 + 4 + /* + * private static int[][] mNB = {{0, 0, 0}, {0, 0, 0}, {1, 0, 0}, {1, 0, 0}, + * {1, 0, 0}, // i = 0 {2, 0, 0}, {1, 0, 0}, {1, 0, 0}, {4, 3, 1}, {1, 0, + * 0}, // i = 1 {3, 0, 0}, {2, 0, 0}, {3, 0, 0}, {4, 3, 1}, {5, 0, 0}, // i = + * 2 {1, 0, 0}, {5, 3, 1}, {3, 0, 0}, {3, 0, 0}, {5, 2, 1}, // i = 3 {3, 0, + * 0}, {2, 0, 0}, {1, 0, 0}, {5, 0, 0}, {4, 3, 1}, // i = 4 {3, 0, 0}, {4, + * 3, 1}, {5, 2, 1}, {1, 0, 0}, {2, 0, 0}, // i = 5 {1, 0, 0}, {3, 0, 0}, + * {7, 3, 2}, {10, 0, 0}, {7, 0, 0}, // i = 6 {2, 0, 0}, {9, 0, 0}, {6, 4, + * 1}, {6, 5, 1}, {4, 0, 0}, // i = 7 {5, 4, 3}, {3, 0, 0}, {7, 0, 0}, {6, + * 4, 3}, {5, 0, 0}, // i = 8 {4, 3, 1}, {1, 0, 0}, {5, 0, 0}, {5, 3, 2}, + * {9, 0, 0}, // i = 9 {4, 3, 2}, {6, 3, 1}, {3, 0, 0}, {6, 2, 1}, {9, 0, + * 0}, // i = 10 {7, 0, 0}, {7, 4, 2}, {4, 0, 0}, {19, 0, 0}, {7, 4, 2}, // + * i = 11 {1, 0, 0}, {5, 2, 1}, {29, 0, 0}, {1, 0, 0}, {4, 3, 1}, // i = 12 + * {18, 0, 0}, {3, 0, 0}, {5, 2, 1}, {9, 0, 0}, {6, 5, 2}, // i = 13 {5, 3, + * 1}, {6, 0, 0}, {10, 9, 3}, {25, 0, 0}, {35, 0, 0}, // i = 14 {6, 3, 1}, + * {21, 0, 0}, {6, 5, 2}, {6, 5, 3}, {9, 0, 0}, // i = 15 {9, 4, 2}, {4, 0, + * 0}, {8, 3, 1}, {7, 4, 2}, {5, 0, 0}, // i = 16 {8, 2, 1}, {21, 0, 0}, + * {13, 0, 0}, {7, 6, 2}, {38, 0, 0}, // i = 17 {27, 0, 0}, {8, 5, 1}, {21, + * 0, 0}, {2, 0, 0}, {21, 0, 0}, // i = 18 {11, 0, 0}, {10, 9, 6}, {6, 0, + * 0}, {11, 0, 0}, {6, 3, 1}, // i = 19 {15, 0, 0}, {7, 6, 1}, {29, 0, 0}, + * {9, 0, 0}, {4, 3, 1}, // i = 20 {4, 0, 0}, {15, 0, 0}, {9, 7, 4}, {17, 0, + * 0}, {5, 4, 2}, // i = 21 {33, 0, 0}, {10, 0, 0}, {5, 4, 3}, {9, 0, 0}, + * {5, 3, 2}, // i = 22 {8, 7, 5}, {4, 2, 1}, {5, 2, 1}, {33, 0, 0}, {8, 0, + * 0}, // i = 23 {4, 3, 1}, {18, 0, 0}, {6, 2, 1}, {2, 0, 0}, {19, 0, 0}, // + * i = 24 {7, 6, 5}, {21, 0, 0}, {1, 0, 0}, {7, 2, 1}, {5, 0, 0}, // i = 25 + * {3, 0, 0}, {8, 3, 2}, {17, 0, 0}, {9, 8, 2}, {57, 0, 0}, // i = 26 {11, + * 0, 0}, {5, 3, 2}, {21, 0, 0}, {8, 7, 1}, {8, 5, 3}, // i = 27 {15, 0, 0}, + * {10, 4, 1}, {21, 0, 0}, {5, 3, 2}, {7, 4, 2}, // i = 28 {52, 0, 0}, {71, + * 0, 0}, {14, 0, 0}, {27, 0, 0}, {10, 9, 7}, // i = 29 {53, 0, 0}, {3, 0, + * 0}, {6, 3, 2}, {1, 0, 0}, {15, 0, 0}, // i = 30 {62, 0, 0}, {9, 0, 0}, + * {6, 5, 2}, {8, 6, 5}, {31, 0, 0}, // i = 31 {5, 3, 2}, {18, 0, 0 }, {27, + * 0, 0}, {7, 6, 3}, {10, 8, 7}, // i = 32 {9, 8, 3}, {37, 0, 0}, {6, 0, 0}, + * {15, 3, 2}, {34, 0, 0}, // i = 33 {11, 0, 0}, {6, 5, 2}, {1, 0, 0}, {8, + * 5, 2}, {13, 0, 0}, // i = 34 {6, 0, 0}, {11, 3, 2}, {8, 0, 0}, {31, 0, + * 0}, {4, 2, 1}, // i = 35 {3, 0, 0}, {7, 6, 1}, {81, 0, 0}, {56, 0, 0}, + * {9, 8, 7}, // i = 36 {24, 0, 0}, {11, 0, 0}, {7, 6, 5}, {6, 5, 2}, {6, 5, + * 2}, // i = 37 {8, 7, 6}, {9, 0, 0}, {7, 2, 1}, {15, 0, 0}, {87, 0, 0}, // + * i = 38 {8, 3, 2}, {3, 0, 0}, {9, 4, 2}, {9, 0, 0}, {34, 0, 0}, // i = 39 + * {5, 3, 2}, {14, 0, 0}, {55, 0, 0}, {8, 7, 1}, {27, 0, 0}, // i = 40 {9, + * 5, 2}, {10, 9, 5}, {43, 0, 0}, {8, 6, 2}, {6, 0, 0}, // i = 41 {7, 0, 0}, + * {11, 10, 8}, {105, 0, 0}, {6, 5, 2}, {73, 0, 0}}; // i = 42 + */ + // ///////////////////////////////////////////////////////////////////// + // member variables + // ///////////////////////////////////////////////////////////////////// + private static final int MAXLONG = 64; + + /** + * holds the length of the array-representation of degree mDegree. + */ + private int mLength; + + /** + * holds the number of relevant bits in mONBPol[mLength-1]. + */ + private int mBit; + + /** + * holds the type of mONB + */ + private int mType; + + /** + * holds the multiplication matrix + */ + int[][] mMult; + + // ///////////////////////////////////////////////////////////////////// + // constructors + // ///////////////////////////////////////////////////////////////////// + + /** + * constructs an instance of the finite field with 2<sup>deg</sup> + * elements and characteristic 2. + * + * @param deg - + * the extention degree of this field + * @throws NoSuchBasisException if an ONB-implementation other than type 1 or type 2 is + * requested. + */ + public GF2nONBField(int deg) + throws RuntimeException + { + if (deg < 3) + { + throw new IllegalArgumentException("k must be at least 3"); + } + + mDegree = deg; + mLength = mDegree / MAXLONG; + mBit = mDegree & (MAXLONG - 1); + if (mBit == 0) + { + mBit = MAXLONG; + } + else + { + mLength++; + } + + computeType(); + + // only ONB-implementations for type 1 and type 2 + // + if (mType < 3) + { + mMult = new int[mDegree][2]; + for (int i = 0; i < mDegree; i++) + { + mMult[i][0] = -1; + mMult[i][1] = -1; + } + computeMultMatrix(); + } + else + { + throw new RuntimeException("\nThe type of this field is " + + mType); + } + computeFieldPolynomial(); + fields = new Vector(); + matrices = new Vector(); + } + + // ///////////////////////////////////////////////////////////////////// + // access + // ///////////////////////////////////////////////////////////////////// + + int getONBLength() + { + return mLength; + } + + int getONBBit() + { + return mBit; + } + + // ///////////////////////////////////////////////////////////////////// + // arithmetic + // ///////////////////////////////////////////////////////////////////// + + /** + * Computes a random root of the given polynomial. + * + * @param polynomial a polynomial + * @return a random root of the polynomial + * @see "P1363 A.5.6, p103f" + */ + protected GF2nElement getRandomRoot(GF2Polynomial polynomial) + { + // We are in B1!!! + GF2nPolynomial c; + GF2nPolynomial ut; + GF2nElement u; + GF2nPolynomial h; + int hDegree; + // 1. Set g(t) <- f(t) + GF2nPolynomial g = new GF2nPolynomial(polynomial, this); + int gDegree = g.getDegree(); + int i; + + // 2. while deg(g) > 1 + while (gDegree > 1) + { + do + { + // 2.1 choose random u (element of) GF(2^m) + u = new GF2nONBElement(this, new Random()); + ut = new GF2nPolynomial(2, GF2nONBElement.ZERO(this)); + // 2.2 Set c(t) <- ut + ut.set(1, u); + c = new GF2nPolynomial(ut); + // 2.3 For i from 1 to m-1 do + for (i = 1; i <= mDegree - 1; i++) + { + // 2.3.1 c(t) <- (c(t)^2 + ut) mod g(t) + c = c.multiplyAndReduce(c, g); + c = c.add(ut); + } + // 2.4 set h(t) <- GCD(c(t), g(t)) + h = c.gcd(g); + // 2.5 if h(t) is constant or deg(g) = deg(h) then go to + // step 2.1 + hDegree = h.getDegree(); + gDegree = g.getDegree(); + } + while ((hDegree == 0) || (hDegree == gDegree)); + // 2.6 If 2deg(h) > deg(g) then set g(t) <- g(t)/h(t) ... + if ((hDegree << 1) > gDegree) + { + g = g.quotient(h); + } + else + { + // ... else g(t) <- h(t) + g = new GF2nPolynomial(h); + } + gDegree = g.getDegree(); + } + // 3. Output g(0) + return g.at(0); + + } + + /** + * Computes the change-of-basis matrix for basis conversion according to + * 1363. The result is stored in the lists fields and matrices. + * + * @param B1 the GF2nField to convert to + * @see "P1363 A.7.3, p111ff" + */ + protected void computeCOBMatrix(GF2nField B1) + { + // we are in B0 here! + if (mDegree != B1.mDegree) + { + throw new IllegalArgumentException( + "GF2nField.computeCOBMatrix: B1 has a " + + "different degree and thus cannot be coverted to!"); + } + int i, j; + GF2nElement[] gamma; + GF2nElement u; + GF2Polynomial[] COBMatrix = new GF2Polynomial[mDegree]; + for (i = 0; i < mDegree; i++) + { + COBMatrix[i] = new GF2Polynomial(mDegree); + } + + // find Random Root + do + { + // u is in representation according to B1 + u = B1.getRandomRoot(fieldPolynomial); + } + while (u.isZero()); + + gamma = new GF2nPolynomialElement[mDegree]; + // build gamma matrix by squaring + gamma[0] = (GF2nElement)u.clone(); + for (i = 1; i < mDegree; i++) + { + gamma[i] = gamma[i - 1].square(); + } + // convert horizontal gamma matrix by vertical Bitstrings + for (i = 0; i < mDegree; i++) + { + for (j = 0; j < mDegree; j++) + { + if (gamma[i].testBit(j)) + { + COBMatrix[mDegree - j - 1].setBit(mDegree - i - 1); + } + } + } + + fields.addElement(B1); + matrices.addElement(COBMatrix); + B1.fields.addElement(this); + B1.matrices.addElement(invertMatrix(COBMatrix)); + } + + /** + * Computes the field polynomial for a ONB according to IEEE 1363 A.7.2 + * (p110f). + * + * @see "P1363 A.7.2, p110f" + */ + protected void computeFieldPolynomial() + { + if (mType == 1) + { + fieldPolynomial = new GF2Polynomial(mDegree + 1, "ALL"); + } + else if (mType == 2) + { + // 1. q = 1 + GF2Polynomial q = new GF2Polynomial(mDegree + 1, "ONE"); + // 2. p = t+1 + GF2Polynomial p = new GF2Polynomial(mDegree + 1, "X"); + p.addToThis(q); + GF2Polynomial r; + int i; + // 3. for i = 1 to (m-1) do + for (i = 1; i < mDegree; i++) + { + // r <- q + r = q; + // q <- p + q = p; + // p = tq+r + p = q.shiftLeft(); + p.addToThis(r); + } + fieldPolynomial = p; + } + } + + /** + * Compute the inverse of a matrix <tt>a</tt>. + * + * @param a the matrix + * @return <tt>a<sup>-1</sup></tt> + */ + int[][] invMatrix(int[][] a) + { + + int[][] A = new int[mDegree][mDegree]; + A = a; + int[][] inv = new int[mDegree][mDegree]; + + for (int i = 0; i < mDegree; i++) + { + inv[i][i] = 1; + } + + for (int i = 0; i < mDegree; i++) + { + for (int j = i; j < mDegree; j++) + { + A[mDegree - 1 - i][j] = A[i][i]; + } + } + return null; + } + + private void computeType() + throws RuntimeException + { + if ((mDegree & 7) == 0) + { + throw new RuntimeException( + "The extension degree is divisible by 8!"); + } + // checking for the type + int s = 0; + int k = 0; + mType = 1; + for (int d = 0; d != 1; mType++) + { + s = mType * mDegree + 1; + if (IntegerFunctions.isPrime(s)) + { + k = IntegerFunctions.order(2, s); + d = IntegerFunctions.gcd(mType * mDegree / k, mDegree); + } + } + mType--; + if (mType == 1) + { + s = (mDegree << 1) + 1; + if (IntegerFunctions.isPrime(s)) + { + k = IntegerFunctions.order(2, s); + int d = IntegerFunctions.gcd((mDegree << 1) / k, mDegree); + if (d == 1) + { + mType++; + } + } + } + } + + private void computeMultMatrix() + { + + if ((mType & 7) != 0) + { + int p = mType * mDegree + 1; + + // compute sequence F[1] ... F[p-1] via A.3.7. of 1363. + // F[0] will not be filled! + // + int[] F = new int[p]; + + int u; + if (mType == 1) + { + u = 1; + } + else if (mType == 2) + { + u = p - 1; + } + else + { + u = elementOfOrder(mType, p); + } + + int w = 1; + int n; + for (int j = 0; j < mType; j++) + { + n = w; + + for (int i = 0; i < mDegree; i++) + { + F[n] = i; + n = (n << 1) % p; + if (n < 0) + { + n += p; + } + } + w = u * w % p; + if (w < 0) + { + w += p; + } + } + + // building the matrix (mDegree * 2) + // + if (mType == 1) + { + for (int k = 1; k < p - 1; k++) + { + if (mMult[F[k + 1]][0] == -1) + { + mMult[F[k + 1]][0] = F[p - k]; + } + else + { + mMult[F[k + 1]][1] = F[p - k]; + } + } + + int m_2 = mDegree >> 1; + for (int k = 1; k <= m_2; k++) + { + + if (mMult[k - 1][0] == -1) + { + mMult[k - 1][0] = m_2 + k - 1; + } + else + { + mMult[k - 1][1] = m_2 + k - 1; + } + + if (mMult[m_2 + k - 1][0] == -1) + { + mMult[m_2 + k - 1][0] = k - 1; + } + else + { + mMult[m_2 + k - 1][1] = k - 1; + } + } + } + else if (mType == 2) + { + for (int k = 1; k < p - 1; k++) + { + if (mMult[F[k + 1]][0] == -1) + { + mMult[F[k + 1]][0] = F[p - k]; + } + else + { + mMult[F[k + 1]][1] = F[p - k]; + } + } + } + else + { + throw new RuntimeException("only type 1 or type 2 implemented"); + } + } + else + { + throw new RuntimeException("bisher nur fuer Gausssche Normalbasen" + + " implementiert"); + } + } + + private int elementOfOrder(int k, int p) + { + Random random = new Random(); + int m = 0; + while (m == 0) + { + m = random.nextInt(); + m %= p - 1; + if (m < 0) + { + m += p - 1; + } + } + + int l = IntegerFunctions.order(m, p); + + while (l % k != 0 || l == 0) + { + while (m == 0) + { + m = random.nextInt(); + m %= p - 1; + if (m < 0) + { + m += p - 1; + } + } + l = IntegerFunctions.order(m, p); + } + int r = m; + + l = k / l; + + for (int i = 2; i <= l; i++) + { + r *= m; + } + + return r; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nPolynomial.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nPolynomial.java new file mode 100644 index 00000000..97ee022c --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nPolynomial.java @@ -0,0 +1,587 @@ +package org.spongycastle.pqc.math.linearalgebra; + + +/** + * This class implements polynomials over GF2nElements. + * + * @see GF2nElement + */ + +public class GF2nPolynomial +{ + + private GF2nElement[] coeff; // keeps the coefficients of this polynomial + + private int size; // the size of this polynomial + + /** + * Creates a new PolynomialGF2n of size <i>deg</i> and elem as + * coefficients. + * + * @param deg - + * the maximum degree + 1 + * @param elem - + * a GF2nElement + */ + public GF2nPolynomial(int deg, GF2nElement elem) + { + size = deg; + coeff = new GF2nElement[size]; + for (int i = 0; i < size; i++) + { + coeff[i] = (GF2nElement)elem.clone(); + } + } + + /** + * Creates a new PolynomialGF2n of size <i>deg</i>. + * + * @param deg the maximum degree + 1 + */ + private GF2nPolynomial(int deg) + { + size = deg; + coeff = new GF2nElement[size]; + } + + /** + * Creates a new PolynomialGF2n by cloning the given PolynomialGF2n <i>a</i>. + * + * @param a the PolynomialGF2n to clone + */ + public GF2nPolynomial(GF2nPolynomial a) + { + int i; + coeff = new GF2nElement[a.size]; + size = a.size; + for (i = 0; i < size; i++) + { + coeff[i] = (GF2nElement)a.coeff[i].clone(); + } + } + + /** + * Creates a new PolynomialGF2n from the given Bitstring <i>polynomial</i> + * over the GF2nField <i>B1</i>. + * + * @param polynomial the Bitstring to use + * @param B1 the field + */ + public GF2nPolynomial(GF2Polynomial polynomial, GF2nField B1) + { + size = B1.getDegree() + 1; + coeff = new GF2nElement[size]; + int i; + if (B1 instanceof GF2nONBField) + { + for (i = 0; i < size; i++) + { + if (polynomial.testBit(i)) + { + coeff[i] = GF2nONBElement.ONE((GF2nONBField)B1); + } + else + { + coeff[i] = GF2nONBElement.ZERO((GF2nONBField)B1); + } + } + } + else if (B1 instanceof GF2nPolynomialField) + { + for (i = 0; i < size; i++) + { + if (polynomial.testBit(i)) + { + coeff[i] = GF2nPolynomialElement + .ONE((GF2nPolynomialField)B1); + } + else + { + coeff[i] = GF2nPolynomialElement + .ZERO((GF2nPolynomialField)B1); + } + } + } + else + { + throw new IllegalArgumentException( + "PolynomialGF2n(Bitstring, GF2nField): B1 must be " + + "an instance of GF2nONBField or GF2nPolynomialField!"); + } + } + + public final void assignZeroToElements() + { + int i; + for (i = 0; i < size; i++) + { + coeff[i].assignZero(); + } + } + + /** + * Returns the size (=maximum degree + 1) of this PolynomialGF2n. This is + * not the degree, use getDegree instead. + * + * @return the size (=maximum degree + 1) of this PolynomialGF2n. + */ + public final int size() + { + return size; + } + + /** + * Returns the degree of this PolynomialGF2n. + * + * @return the degree of this PolynomialGF2n. + */ + public final int getDegree() + { + int i; + for (i = size - 1; i >= 0; i--) + { + if (!coeff[i].isZero()) + { + return i; + } + } + return -1; + } + + /** + * Enlarges the size of this PolynomialGF2n to <i>k</i> + 1. + * + * @param k the new maximum degree + */ + public final void enlarge(int k) + { + if (k <= size) + { + return; + } + int i; + GF2nElement[] res = new GF2nElement[k]; + System.arraycopy(coeff, 0, res, 0, size); + GF2nField f = coeff[0].getField(); + if (coeff[0] instanceof GF2nPolynomialElement) + { + for (i = size; i < k; i++) + { + res[i] = GF2nPolynomialElement.ZERO((GF2nPolynomialField)f); + } + } + else if (coeff[0] instanceof GF2nONBElement) + { + for (i = size; i < k; i++) + { + res[i] = GF2nONBElement.ZERO((GF2nONBField)f); + } + } + size = k; + coeff = res; + } + + public final void shrink() + { + int i = size - 1; + while (coeff[i].isZero() && (i > 0)) + { + i--; + } + i++; + if (i < size) + { + GF2nElement[] res = new GF2nElement[i]; + System.arraycopy(coeff, 0, res, 0, i); + coeff = res; + size = i; + } + } + + /** + * Sets the coefficient at <i>index</i> to <i>elem</i>. + * + * @param index the index + * @param elem the GF2nElement to store as coefficient <i>index</i> + */ + public final void set(int index, GF2nElement elem) + { + if (!(elem instanceof GF2nPolynomialElement) + && !(elem instanceof GF2nONBElement)) + { + throw new IllegalArgumentException( + "PolynomialGF2n.set f must be an " + + "instance of either GF2nPolynomialElement or GF2nONBElement!"); + } + coeff[index] = (GF2nElement)elem.clone(); + } + + /** + * Returns the coefficient at <i>index</i>. + * + * @param index the index + * @return the GF2nElement stored as coefficient <i>index</i> + */ + public final GF2nElement at(int index) + { + return coeff[index]; + } + + /** + * Returns true if all coefficients equal zero. + * + * @return true if all coefficients equal zero. + */ + public final boolean isZero() + { + int i; + for (i = 0; i < size; i++) + { + if (coeff[i] != null) + { + if (!coeff[i].isZero()) + { + return false; + } + } + } + return true; + } + + public final boolean equals(Object other) + { + if (other == null || !(other instanceof GF2nPolynomial)) + { + return false; + } + + GF2nPolynomial otherPol = (GF2nPolynomial)other; + + if (getDegree() != otherPol.getDegree()) + { + return false; + } + int i; + for (i = 0; i < size; i++) + { + if (!coeff[i].equals(otherPol.coeff[i])) + { + return false; + } + } + return true; + } + + /** + * @return the hash code of this polynomial + */ + public int hashCode() + { + return getDegree() + coeff.hashCode(); + } + + /** + * Adds the PolynomialGF2n <tt>b</tt> to <tt>this</tt> and returns the + * result in a new <tt>PolynomialGF2n</tt>. + * + * @param b - + * the <tt>PolynomialGF2n</tt> to add + * @return <tt>this + b</tt> + * @throws DifferentFieldsException if <tt>this</tt> and <tt>b</tt> are not defined over + * the same field. + */ + public final GF2nPolynomial add(GF2nPolynomial b) + throws RuntimeException + { + GF2nPolynomial result; + if (size() >= b.size()) + { + result = new GF2nPolynomial(size()); + int i; + for (i = 0; i < b.size(); i++) + { + result.coeff[i] = (GF2nElement)coeff[i].add(b.coeff[i]); + } + for (; i < size(); i++) + { + result.coeff[i] = coeff[i]; + } + } + else + { + result = new GF2nPolynomial(b.size()); + int i; + for (i = 0; i < size(); i++) + { + result.coeff[i] = (GF2nElement)coeff[i].add(b.coeff[i]); + } + for (; i < b.size(); i++) + { + result.coeff[i] = b.coeff[i]; + } + } + return result; + } + + /** + * Multiplies the scalar <i>s</i> to each coefficient of this + * PolynomialGF2n and returns the result in a new PolynomialGF2n. + * + * @param s the scalar to multiply + * @return <i>this</i> x <i>s</i> + * @throws DifferentFieldsException if <tt>this</tt> and <tt>s</tt> are not defined over + * the same field. + */ + public final GF2nPolynomial scalarMultiply(GF2nElement s) + throws RuntimeException + { + GF2nPolynomial result = new GF2nPolynomial(size()); + int i; + for (i = 0; i < size(); i++) + { + result.coeff[i] = (GF2nElement)coeff[i].multiply(s); // result[i] + // = + // a[i]*s + } + return result; + } + + /** + * Multiplies <i>this</i> by <i>b</i> and returns the result in a new + * PolynomialGF2n. + * + * @param b the PolynomialGF2n to multiply + * @return <i>this</i> * <i>b</i> + * @throws DifferentFieldsException if <tt>this</tt> and <tt>b</tt> are not defined over + * the same field. + */ + public final GF2nPolynomial multiply(GF2nPolynomial b) + throws RuntimeException + { + int i, j; + int aDegree = size(); + int bDegree = b.size(); + if (aDegree != bDegree) + { + throw new IllegalArgumentException( + "PolynomialGF2n.multiply: this and b must " + + "have the same size!"); + } + GF2nPolynomial result = new GF2nPolynomial((aDegree << 1) - 1); + for (i = 0; i < size(); i++) + { + for (j = 0; j < b.size(); j++) + { + if (result.coeff[i + j] == null) + { + result.coeff[i + j] = (GF2nElement)coeff[i] + .multiply(b.coeff[j]); + } + else + { + result.coeff[i + j] = (GF2nElement)result.coeff[i + j] + .add(coeff[i].multiply(b.coeff[j])); + } + } + } + return result; + } + + /** + * Multiplies <i>this</i> by <i>b</i>, reduces the result by <i>g</i> and + * returns it in a new PolynomialGF2n. + * + * @param b the PolynomialGF2n to multiply + * @param g the modul + * @return <i>this</i> * <i>b</i> mod <i>g</i> + * @throws DifferentFieldsException if <tt>this</tt>, <tt>b</tt> and <tt>g</tt> are + * not all defined over the same field. + */ + public final GF2nPolynomial multiplyAndReduce(GF2nPolynomial b, + GF2nPolynomial g) + throws RuntimeException, + ArithmeticException + { + return multiply(b).reduce(g); + } + + /** + * Reduces <i>this</i> by <i>g</i> and returns the result in a new + * PolynomialGF2n. + * + * @param g - + * the modulus + * @return <i>this</i> % <i>g</i> + * @throws DifferentFieldsException if <tt>this</tt> and <tt>g</tt> are not defined over + * the same field. + */ + public final GF2nPolynomial reduce(GF2nPolynomial g) + throws RuntimeException, ArithmeticException + { + return remainder(g); // return this % g + } + + /** + * Shifts left <i>this</i> by <i>amount</i> and stores the result in + * <i>this</i> PolynomialGF2n. + * + * @param amount the amount to shift the coefficients + */ + public final void shiftThisLeft(int amount) + { + if (amount > 0) + { + int i; + int oldSize = size; + GF2nField f = coeff[0].getField(); + enlarge(size + amount); + for (i = oldSize - 1; i >= 0; i--) + { + coeff[i + amount] = coeff[i]; + } + if (coeff[0] instanceof GF2nPolynomialElement) + { + for (i = amount - 1; i >= 0; i--) + { + coeff[i] = GF2nPolynomialElement + .ZERO((GF2nPolynomialField)f); + } + } + else if (coeff[0] instanceof GF2nONBElement) + { + for (i = amount - 1; i >= 0; i--) + { + coeff[i] = GF2nONBElement.ZERO((GF2nONBField)f); + } + } + } + } + + public final GF2nPolynomial shiftLeft(int amount) + { + if (amount <= 0) + { + return new GF2nPolynomial(this); + } + GF2nPolynomial result = new GF2nPolynomial(size + amount, coeff[0]); + result.assignZeroToElements(); + for (int i = 0; i < size; i++) + { + result.coeff[i + amount] = coeff[i]; + } + return result; + } + + /** + * Divides <i>this</i> by <i>b</i> and stores the result in a new + * PolynomialGF2n[2], quotient in result[0] and remainder in result[1]. + * + * @param b the divisor + * @return the quotient and remainder of <i>this</i> / <i>b</i> + * @throws DifferentFieldsException if <tt>this</tt> and <tt>b</tt> are not defined over + * the same field. + */ + public final GF2nPolynomial[] divide(GF2nPolynomial b) + throws RuntimeException, ArithmeticException + { + GF2nPolynomial[] result = new GF2nPolynomial[2]; + GF2nPolynomial a = new GF2nPolynomial(this); + a.shrink(); + GF2nPolynomial shift; + GF2nElement factor; + int bDegree = b.getDegree(); + GF2nElement inv = (GF2nElement)b.coeff[bDegree].invert(); + if (a.getDegree() < bDegree) + { + result[0] = new GF2nPolynomial(this); + result[0].assignZeroToElements(); + result[0].shrink(); + result[1] = new GF2nPolynomial(this); + result[1].shrink(); + return result; + } + result[0] = new GF2nPolynomial(this); + result[0].assignZeroToElements(); + int i = a.getDegree() - bDegree; + while (i >= 0) + { + factor = (GF2nElement)a.coeff[a.getDegree()].multiply(inv); + shift = b.scalarMultiply(factor); + shift.shiftThisLeft(i); + a = a.add(shift); + a.shrink(); + result[0].coeff[i] = (GF2nElement)factor.clone(); + i = a.getDegree() - bDegree; + } + result[1] = a; + result[0].shrink(); + return result; + } + + /** + * Divides <i>this</i> by <i>b</i> and stores the remainder in a new + * PolynomialGF2n. + * + * @param b the divisor + * @return the remainder <i>this</i> % <i>b</i> + * @throws DifferentFieldsException if <tt>this</tt> and <tt>b</tt> are not defined over + * the same field. + */ + public final GF2nPolynomial remainder(GF2nPolynomial b) + throws RuntimeException, ArithmeticException + { + GF2nPolynomial[] result = new GF2nPolynomial[2]; + result = divide(b); + return result[1]; + } + + /** + * Divides <i>this</i> by <i>b</i> and stores the quotient in a new + * PolynomialGF2n. + * + * @param b the divisor + * @return the quotient <i>this</i> / <i>b</i> + * @throws DifferentFieldsException if <tt>this</tt> and <tt>b</tt> are not defined over + * the same field. + */ + public final GF2nPolynomial quotient(GF2nPolynomial b) + throws RuntimeException, ArithmeticException + { + GF2nPolynomial[] result = new GF2nPolynomial[2]; + result = divide(b); + return result[0]; + } + + /** + * Computes the greatest common divisor of <i>this</i> and <i>g</i> and + * returns the result in a new PolynomialGF2n. + * + * @param g - + * a GF2nPolynomial + * @return gcd(<i>this</i>, <i>g</i>) + * @throws DifferentFieldsException if the coefficients of <i>this</i> and <i>g</i> use + * different fields + * @throws ArithmeticException if coefficients are zero. + */ + public final GF2nPolynomial gcd(GF2nPolynomial g) + throws RuntimeException, ArithmeticException + { + GF2nPolynomial a = new GF2nPolynomial(this); + GF2nPolynomial b = new GF2nPolynomial(g); + a.shrink(); + b.shrink(); + GF2nPolynomial c; + GF2nPolynomial result; + GF2nElement alpha; + while (!b.isZero()) + { + c = a.remainder(b); + a = b; + b = c; + } + alpha = a.coeff[a.getDegree()]; + result = a.scalarMultiply((GF2nElement)alpha.invert()); + return result; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nPolynomialElement.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nPolynomialElement.java new file mode 100644 index 00000000..50e9d751 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nPolynomialElement.java @@ -0,0 +1,1021 @@ +package org.spongycastle.pqc.math.linearalgebra; + + +import java.math.BigInteger; +import java.util.Random; + + +/** + * This class implements elements of finite binary fields <i>GF(2<sup>n</sup>)</i> + * using polynomial representation. For more information on the arithmetic see + * for example IEEE Standard 1363 or <a + * href=http://www.certicom.com/research/online.html> Certicom online-tutorial</a>. + * + * @see "GF2nField" + * @see GF2nPolynomialField + * @see GF2nONBElement + * @see GF2Polynomial + */ +public class GF2nPolynomialElement + extends GF2nElement +{ + + // pre-computed Bitmask for fast masking, bitMask[a]=0x1 << a + private static final int[] bitMask = {0x00000001, 0x00000002, 0x00000004, + 0x00000008, 0x00000010, 0x00000020, 0x00000040, 0x00000080, + 0x00000100, 0x00000200, 0x00000400, 0x00000800, 0x00001000, + 0x00002000, 0x00004000, 0x00008000, 0x00010000, 0x00020000, + 0x00040000, 0x00080000, 0x00100000, 0x00200000, 0x00400000, + 0x00800000, 0x01000000, 0x02000000, 0x04000000, 0x08000000, + 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x00000000}; + + // the used GF2Polynomial which stores the coefficients + private GF2Polynomial polynomial; + + /** + * Create a new random GF2nPolynomialElement using the given field and + * source of randomness. + * + * @param f the GF2nField to use + * @param rand the source of randomness + */ + public GF2nPolynomialElement(GF2nPolynomialField f, Random rand) + { + mField = f; + mDegree = mField.getDegree(); + polynomial = new GF2Polynomial(mDegree); + randomize(rand); + } + + /** + * Creates a new GF2nPolynomialElement using the given field and Bitstring. + * + * @param f the GF2nPolynomialField to use + * @param bs the desired value as Bitstring + */ + public GF2nPolynomialElement(GF2nPolynomialField f, GF2Polynomial bs) + { + mField = f; + mDegree = mField.getDegree(); + polynomial = new GF2Polynomial(bs); + polynomial.expandN(mDegree); + } + + /** + * Creates a new GF2nPolynomialElement using the given field <i>f</i> and + * byte[] <i>os</i> as value. The conversion is done according to 1363. + * + * @param f the GF2nField to use + * @param os the octet string to assign to this GF2nPolynomialElement + * @see "P1363 5.5.5 p23, OS2FEP/OS2BSP" + */ + public GF2nPolynomialElement(GF2nPolynomialField f, byte[] os) + { + mField = f; + mDegree = mField.getDegree(); + polynomial = new GF2Polynomial(mDegree, os); + polynomial.expandN(mDegree); + } + + /** + * Creates a new GF2nPolynomialElement using the given field <i>f</i> and + * int[] <i>is</i> as value. + * + * @param f the GF2nField to use + * @param is the integer string to assign to this GF2nPolynomialElement + */ + public GF2nPolynomialElement(GF2nPolynomialField f, int[] is) + { + mField = f; + mDegree = mField.getDegree(); + polynomial = new GF2Polynomial(mDegree, is); + polynomial.expandN(f.mDegree); + } + + /** + * Creates a new GF2nPolynomialElement by cloning the given + * GF2nPolynomialElement <i>b</i>. + * + * @param other the GF2nPolynomialElement to clone + */ + public GF2nPolynomialElement(GF2nPolynomialElement other) + { + mField = other.mField; + mDegree = other.mDegree; + polynomial = new GF2Polynomial(other.polynomial); + } + + // ///////////////////////////////////////////////////////////////////// + // pseudo-constructors + // ///////////////////////////////////////////////////////////////////// + + /** + * Creates a new GF2nPolynomialElement by cloning this + * GF2nPolynomialElement. + * + * @return a copy of this element + */ + public Object clone() + { + return new GF2nPolynomialElement(this); + } + + // ///////////////////////////////////////////////////////////////////// + // assignments + // ///////////////////////////////////////////////////////////////////// + + /** + * Assigns the value 'zero' to this Polynomial. + */ + void assignZero() + { + polynomial.assignZero(); + } + + /** + * Create the zero element. + * + * @param f the finite field + * @return the zero element in the given finite field + */ + public static GF2nPolynomialElement ZERO(GF2nPolynomialField f) + { + GF2Polynomial polynomial = new GF2Polynomial(f.getDegree()); + return new GF2nPolynomialElement(f, polynomial); + } + + /** + * Create the one element. + * + * @param f the finite field + * @return the one element in the given finite field + */ + public static GF2nPolynomialElement ONE(GF2nPolynomialField f) + { + GF2Polynomial polynomial = new GF2Polynomial(f.getDegree(), + new int[]{1}); + return new GF2nPolynomialElement(f, polynomial); + } + + /** + * Assigns the value 'one' to this Polynomial. + */ + void assignOne() + { + polynomial.assignOne(); + } + + /** + * Assign a random value to this GF2nPolynomialElement using the specified + * source of randomness. + * + * @param rand the source of randomness + */ + private void randomize(Random rand) + { + polynomial.expandN(mDegree); + polynomial.randomize(rand); + } + + // ///////////////////////////////////////////////////////////////////// + // comparison + // ///////////////////////////////////////////////////////////////////// + + /** + * Checks whether this element is zero. + * + * @return <tt>true</tt> if <tt>this</tt> is the zero element + */ + public boolean isZero() + { + return polynomial.isZero(); + } + + /** + * Tests if the GF2nPolynomialElement has 'one' as value. + * + * @return true if <i>this</i> equals one (this == 1) + */ + public boolean isOne() + { + return polynomial.isOne(); + } + + /** + * Compare this element with another object. + * + * @param other the other object + * @return <tt>true</tt> if the two objects are equal, <tt>false</tt> + * otherwise + */ + public boolean equals(Object other) + { + if (other == null || !(other instanceof GF2nPolynomialElement)) + { + return false; + } + GF2nPolynomialElement otherElem = (GF2nPolynomialElement)other; + + if (mField != otherElem.mField) + { + if (!mField.getFieldPolynomial().equals( + otherElem.mField.getFieldPolynomial())) + { + return false; + } + } + + return polynomial.equals(otherElem.polynomial); + } + + /** + * @return the hash code of this element + */ + public int hashCode() + { + return mField.hashCode() + polynomial.hashCode(); + } + + // ///////////////////////////////////////////////////////////////////// + // access + // ///////////////////////////////////////////////////////////////////// + + /** + * Returns the value of this GF2nPolynomialElement in a new Bitstring. + * + * @return the value of this GF2nPolynomialElement in a new Bitstring + */ + private GF2Polynomial getGF2Polynomial() + { + return new GF2Polynomial(polynomial); + } + + /** + * Checks whether the indexed bit of the bit representation is set. + * + * @param index the index of the bit to test + * @return <tt>true</tt> if the indexed bit is set + */ + boolean testBit(int index) + { + return polynomial.testBit(index); + } + + /** + * Returns whether the rightmost bit of the bit representation is set. This + * is needed for data conversion according to 1363. + * + * @return true if the rightmost bit of this element is set + */ + public boolean testRightmostBit() + { + return polynomial.testBit(0); + } + + /** + * Compute the sum of this element and <tt>addend</tt>. + * + * @param addend the addend + * @return <tt>this + other</tt> (newly created) + * @throws DifferentFieldsException if the elements are of different fields. + */ + public GFElement add(GFElement addend) + throws RuntimeException + { + GF2nPolynomialElement result = new GF2nPolynomialElement(this); + result.addToThis(addend); + return result; + } + + /** + * Compute <tt>this + addend</tt> (overwrite <tt>this</tt>). + * + * @param addend the addend + * @throws DifferentFieldsException if the elements are of different fields. + */ + public void addToThis(GFElement addend) + throws RuntimeException + { + if (!(addend instanceof GF2nPolynomialElement)) + { + throw new RuntimeException(); + } + if (!mField.equals(((GF2nPolynomialElement)addend).mField)) + { + throw new RuntimeException(); + } + polynomial.addToThis(((GF2nPolynomialElement)addend).polynomial); + } + + /** + * Returns <tt>this</tt> element + 'one". + * + * @return <tt>this</tt> + 'one' + */ + public GF2nElement increase() + { + GF2nPolynomialElement result = new GF2nPolynomialElement(this); + result.increaseThis(); + return result; + } + + /** + * Increases this element by 'one'. + */ + public void increaseThis() + { + polynomial.increaseThis(); + } + + /** + * Compute the product of this element and <tt>factor</tt>. + * + * @param factor the factor + * @return <tt>this * factor</tt> (newly created) + * @throws DifferentFieldsException if the elements are of different fields. + */ + public GFElement multiply(GFElement factor) + throws RuntimeException + { + GF2nPolynomialElement result = new GF2nPolynomialElement(this); + result.multiplyThisBy(factor); + return result; + } + + /** + * Compute <tt>this * factor</tt> (overwrite <tt>this</tt>). + * + * @param factor the factor + * @throws DifferentFieldsException if the elements are of different fields. + */ + public void multiplyThisBy(GFElement factor) + throws RuntimeException + { + if (!(factor instanceof GF2nPolynomialElement)) + { + throw new RuntimeException(); + } + if (!mField.equals(((GF2nPolynomialElement)factor).mField)) + { + throw new RuntimeException(); + } + if (equals(factor)) + { + squareThis(); + return; + } + polynomial = polynomial + .multiply(((GF2nPolynomialElement)factor).polynomial); + reduceThis(); + } + + /** + * Compute the multiplicative inverse of this element. + * + * @return <tt>this<sup>-1</sup></tt> (newly created) + * @throws ArithmeticException if <tt>this</tt> is the zero element. + * @see GF2nPolynomialElement#invertMAIA + * @see GF2nPolynomialElement#invertEEA + * @see GF2nPolynomialElement#invertSquare + */ + public GFElement invert() + throws ArithmeticException + { + return invertMAIA(); + } + + /** + * Calculates the multiplicative inverse of <i>this</i> and returns the + * result in a new GF2nPolynomialElement. + * + * @return <i>this</i>^(-1) + * @throws ArithmeticException if <i>this</i> equals zero + */ + public GF2nPolynomialElement invertEEA() + throws ArithmeticException + { + if (isZero()) + { + throw new ArithmeticException(); + } + GF2Polynomial b = new GF2Polynomial(mDegree + 32, "ONE"); + b.reduceN(); + GF2Polynomial c = new GF2Polynomial(mDegree + 32); + c.reduceN(); + GF2Polynomial u = getGF2Polynomial(); + GF2Polynomial v = mField.getFieldPolynomial(); + GF2Polynomial h; + int j; + u.reduceN(); + while (!u.isOne()) + { + u.reduceN(); + v.reduceN(); + j = u.getLength() - v.getLength(); + if (j < 0) + { + h = u; + u = v; + v = h; + h = b; + b = c; + c = h; + j = -j; + c.reduceN(); // this increases the performance + } + u.shiftLeftAddThis(v, j); + b.shiftLeftAddThis(c, j); + } + b.reduceN(); + return new GF2nPolynomialElement((GF2nPolynomialField)mField, b); + } + + /** + * Calculates the multiplicative inverse of <i>this</i> and returns the + * result in a new GF2nPolynomialElement. + * + * @return <i>this</i>^(-1) + * @throws ArithmeticException if <i>this</i> equals zero + */ + public GF2nPolynomialElement invertSquare() + throws ArithmeticException + { + GF2nPolynomialElement n; + GF2nPolynomialElement u; + int i, j, k, b; + + if (isZero()) + { + throw new ArithmeticException(); + } + // b = (n-1) + b = mField.getDegree() - 1; + // n = a + n = new GF2nPolynomialElement(this); + n.polynomial.expandN((mDegree << 1) + 32); // increase performance + n.polynomial.reduceN(); + // k = 1 + k = 1; + + // for i = (r-1) downto 0 do, r=bitlength(b) + for (i = IntegerFunctions.floorLog(b) - 1; i >= 0; i--) + { + // u = n + u = new GF2nPolynomialElement(n); + // for j = 1 to k do + for (j = 1; j <= k; j++) + { + // u = u^2 + u.squareThisPreCalc(); + } + // n = nu + n.multiplyThisBy(u); + // k = 2k + k <<= 1; + // if b(i)==1 + if ((b & bitMask[i]) != 0) + { + // n = n^2 * b + n.squareThisPreCalc(); + n.multiplyThisBy(this); + // k = k+1 + k += 1; + } + } + + // outpur n^2 + n.squareThisPreCalc(); + return n; + } + + /** + * Calculates the multiplicative inverse of <i>this</i> using the modified + * almost inverse algorithm and returns the result in a new + * GF2nPolynomialElement. + * + * @return <i>this</i>^(-1) + * @throws ArithmeticException if <i>this</i> equals zero + */ + public GF2nPolynomialElement invertMAIA() + throws ArithmeticException + { + if (isZero()) + { + throw new ArithmeticException(); + } + GF2Polynomial b = new GF2Polynomial(mDegree, "ONE"); + GF2Polynomial c = new GF2Polynomial(mDegree); + GF2Polynomial u = getGF2Polynomial(); + GF2Polynomial v = mField.getFieldPolynomial(); + GF2Polynomial h; + while (true) + { + while (!u.testBit(0)) + { // x|u (x divides u) + u.shiftRightThis(); // u = u / x + if (!b.testBit(0)) + { + b.shiftRightThis(); + } + else + { + b.addToThis(mField.getFieldPolynomial()); + b.shiftRightThis(); + } + } + if (u.isOne()) + { + return new GF2nPolynomialElement((GF2nPolynomialField)mField, + b); + } + u.reduceN(); + v.reduceN(); + if (u.getLength() < v.getLength()) + { + h = u; + u = v; + v = h; + h = b; + b = c; + c = h; + } + u.addToThis(v); + b.addToThis(c); + } + } + + /** + * This method is used internally to map the square()-calls within + * GF2nPolynomialElement to one of the possible squaring methods. + * + * @return <tt>this<sup>2</sup></tt> (newly created) + * @see GF2nPolynomialElement#squarePreCalc + */ + public GF2nElement square() + { + return squarePreCalc(); + } + + /** + * This method is used internally to map the square()-calls within + * GF2nPolynomialElement to one of the possible squaring methods. + */ + public void squareThis() + { + squareThisPreCalc(); + } + + /** + * Squares this GF2nPolynomialElement using GF2nField's squaring matrix. + * This is supposed to be fast when using a polynomial (no tri- or + * pentanomial) as fieldpolynomial. Use squarePreCalc when using a tri- or + * pentanomial as fieldpolynomial instead. + * + * @return <tt>this<sup>2</sup></tt> (newly created) + * @see GF2Polynomial#vectorMult + * @see GF2nPolynomialElement#squarePreCalc + * @see GF2nPolynomialElement#squareBitwise + */ + public GF2nPolynomialElement squareMatrix() + { + GF2nPolynomialElement result = new GF2nPolynomialElement(this); + result.squareThisMatrix(); + result.reduceThis(); + return result; + } + + /** + * Squares this GF2nPolynomialElement using GF2nFields squaring matrix. This + * is supposed to be fast when using a polynomial (no tri- or pentanomial) + * as fieldpolynomial. Use squarePreCalc when using a tri- or pentanomial as + * fieldpolynomial instead. + * + * @see GF2Polynomial#vectorMult + * @see GF2nPolynomialElement#squarePreCalc + * @see GF2nPolynomialElement#squareBitwise + */ + public void squareThisMatrix() + { + GF2Polynomial result = new GF2Polynomial(mDegree); + for (int i = 0; i < mDegree; i++) + { + if (polynomial + .vectorMult(((GF2nPolynomialField)mField).squaringMatrix[mDegree + - i - 1])) + { + result.setBit(i); + + } + } + polynomial = result; + } + + /** + * Squares this GF2nPolynomialElement by shifting left its Bitstring and + * reducing. This is supposed to be the slowest method. Use squarePreCalc or + * squareMatrix instead. + * + * @return <tt>this<sup>2</sup></tt> (newly created) + * @see GF2nPolynomialElement#squareMatrix + * @see GF2nPolynomialElement#squarePreCalc + * @see GF2Polynomial#squareThisBitwise + */ + public GF2nPolynomialElement squareBitwise() + { + GF2nPolynomialElement result = new GF2nPolynomialElement(this); + result.squareThisBitwise(); + result.reduceThis(); + return result; + } + + /** + * Squares this GF2nPolynomialElement by shifting left its Bitstring and + * reducing. This is supposed to be the slowest method. Use squarePreCalc or + * squareMatrix instead. + * + * @see GF2nPolynomialElement#squareMatrix + * @see GF2nPolynomialElement#squarePreCalc + * @see GF2Polynomial#squareThisBitwise + */ + public void squareThisBitwise() + { + polynomial.squareThisBitwise(); + reduceThis(); + } + + /** + * Squares this GF2nPolynomialElement by using precalculated values and + * reducing. This is supposed to de fastest when using a trinomial or + * pentanomial as field polynomial. Use squareMatrix when using a ordinary + * polynomial as field polynomial. + * + * @return <tt>this<sup>2</sup></tt> (newly created) + * @see GF2nPolynomialElement#squareMatrix + * @see GF2Polynomial#squareThisPreCalc + */ + public GF2nPolynomialElement squarePreCalc() + { + GF2nPolynomialElement result = new GF2nPolynomialElement(this); + result.squareThisPreCalc(); + result.reduceThis(); + return result; + } + + /** + * Squares this GF2nPolynomialElement by using precalculated values and + * reducing. This is supposed to de fastest when using a tri- or pentanomial + * as fieldpolynomial. Use squareMatrix when using a ordinary polynomial as + * fieldpolynomial. + * + * @see GF2nPolynomialElement#squareMatrix + * @see GF2Polynomial#squareThisPreCalc + */ + public void squareThisPreCalc() + { + polynomial.squareThisPreCalc(); + reduceThis(); + } + + /** + * Calculates <i>this</i> to the power of <i>k</i> and returns the result + * in a new GF2nPolynomialElement. + * + * @param k the power + * @return <i>this</i>^<i>k</i> in a new GF2nPolynomialElement + */ + public GF2nPolynomialElement power(int k) + { + if (k == 1) + { + return new GF2nPolynomialElement(this); + } + + GF2nPolynomialElement result = GF2nPolynomialElement + .ONE((GF2nPolynomialField)mField); + if (k == 0) + { + return result; + } + + GF2nPolynomialElement x = new GF2nPolynomialElement(this); + x.polynomial.expandN((x.mDegree << 1) + 32); // increase performance + x.polynomial.reduceN(); + + for (int i = 0; i < mDegree; i++) + { + if ((k & (1 << i)) != 0) + { + result.multiplyThisBy(x); + } + x.square(); + } + + return result; + } + + /** + * Compute the square root of this element and return the result in a new + * {@link GF2nPolynomialElement}. + * + * @return <tt>this<sup>1/2</sup></tt> (newly created) + */ + public GF2nElement squareRoot() + { + GF2nPolynomialElement result = new GF2nPolynomialElement(this); + result.squareRootThis(); + return result; + } + + /** + * Compute the square root of this element. + */ + public void squareRootThis() + { + // increase performance + polynomial.expandN((mDegree << 1) + 32); + polynomial.reduceN(); + for (int i = 0; i < mField.getDegree() - 1; i++) + { + squareThis(); + } + } + + /** + * Solves the quadratic equation <tt>z<sup>2</sup> + z = this</tt> if + * such a solution exists. This method returns one of the two possible + * solutions. The other solution is <tt>z + 1</tt>. Use z.increase() to + * compute this solution. + * + * @return a GF2nPolynomialElement representing one z satisfying the + * equation <tt>z<sup>2</sup> + z = this</tt> + * @throws NoSolutionException if no solution exists + * @see "IEEE 1363, Annex A.4.7" + */ + public GF2nElement solveQuadraticEquation() + throws RuntimeException + { + if (isZero()) + { + return ZERO((GF2nPolynomialField)mField); + } + + if ((mDegree & 1) == 1) + { + return halfTrace(); + } + + // TODO this can be sped-up by precomputation of p and w's + GF2nPolynomialElement z, w; + do + { + // step 1. + GF2nPolynomialElement p = new GF2nPolynomialElement( + (GF2nPolynomialField)mField, new Random()); + // step 2. + z = ZERO((GF2nPolynomialField)mField); + w = (GF2nPolynomialElement)p.clone(); + // step 3. + for (int i = 1; i < mDegree; i++) + { + // compute z = z^2 + w^2 * this + // and w = w^2 + p + z.squareThis(); + w.squareThis(); + z.addToThis(w.multiply(this)); + w.addToThis(p); + } + } + while (w.isZero()); // step 4. + + if (!equals(z.square().add(z))) + { + throw new RuntimeException(); + } + + // step 5. + return z; + } + + /** + * Returns the trace of this GF2nPolynomialElement. + * + * @return the trace of this GF2nPolynomialElement + */ + public int trace() + { + GF2nPolynomialElement t = new GF2nPolynomialElement(this); + int i; + + for (i = 1; i < mDegree; i++) + { + t.squareThis(); + t.addToThis(this); + } + + if (t.isOne()) + { + return 1; + } + return 0; + } + + /** + * Returns the half-trace of this GF2nPolynomialElement. + * + * @return a GF2nPolynomialElement representing the half-trace of this + * GF2nPolynomialElement. + * @throws DegreeIsEvenException if the degree of this GF2nPolynomialElement is even. + */ + private GF2nPolynomialElement halfTrace() + throws RuntimeException + { + if ((mDegree & 0x01) == 0) + { + throw new RuntimeException(); + } + int i; + GF2nPolynomialElement h = new GF2nPolynomialElement(this); + + for (i = 1; i <= ((mDegree - 1) >> 1); i++) + { + h.squareThis(); + h.squareThis(); + h.addToThis(this); + } + + return h; + } + + /** + * Reduces this GF2nPolynomialElement modulo the field-polynomial. + * + * @see GF2Polynomial#reduceTrinomial + * @see GF2Polynomial#reducePentanomial + */ + private void reduceThis() + { + if (polynomial.getLength() > mDegree) + { // really reduce ? + if (((GF2nPolynomialField)mField).isTrinomial()) + { // fieldpolonomial + // is trinomial + int tc; + try + { + tc = ((GF2nPolynomialField)mField).getTc(); + } + catch (RuntimeException NATExc) + { + throw new RuntimeException( + "GF2nPolynomialElement.reduce: the field" + + " polynomial is not a trinomial"); + } + if (((mDegree - tc) <= 32) // do we have to use slow + // bitwise reduction ? + || (polynomial.getLength() > (mDegree << 1))) + { + reduceTrinomialBitwise(tc); + return; + } + polynomial.reduceTrinomial(mDegree, tc); + return; + } + else if (((GF2nPolynomialField)mField).isPentanomial()) + { // fieldpolynomial + // is + // pentanomial + int[] pc; + try + { + pc = ((GF2nPolynomialField)mField).getPc(); + } + catch (RuntimeException NATExc) + { + throw new RuntimeException( + "GF2nPolynomialElement.reduce: the field" + + " polynomial is not a pentanomial"); + } + if (((mDegree - pc[2]) <= 32) // do we have to use slow + // bitwise reduction ? + || (polynomial.getLength() > (mDegree << 1))) + { + reducePentanomialBitwise(pc); + return; + } + polynomial.reducePentanomial(mDegree, pc); + return; + } + else + { // fieldpolynomial is something else + polynomial = polynomial.remainder(mField.getFieldPolynomial()); + polynomial.expandN(mDegree); + return; + } + } + if (polynomial.getLength() < mDegree) + { + polynomial.expandN(mDegree); + } + } + + /** + * Reduce this GF2nPolynomialElement using the trinomial x^n + x^tc + 1 as + * fieldpolynomial. The coefficients are reduced bit by bit. + */ + private void reduceTrinomialBitwise(int tc) + { + int i; + int k = mDegree - tc; + for (i = polynomial.getLength() - 1; i >= mDegree; i--) + { + if (polynomial.testBit(i)) + { + + polynomial.xorBit(i); + polynomial.xorBit(i - k); + polynomial.xorBit(i - mDegree); + + } + } + polynomial.reduceN(); + polynomial.expandN(mDegree); + } + + /** + * Reduce this GF2nPolynomialElement using the pentanomial x^n + x^pc[2] + + * x^pc[1] + x^pc[0] + 1 as fieldpolynomial. The coefficients are reduced + * bit by bit. + */ + private void reducePentanomialBitwise(int[] pc) + { + int i; + int k = mDegree - pc[2]; + int l = mDegree - pc[1]; + int m = mDegree - pc[0]; + for (i = polynomial.getLength() - 1; i >= mDegree; i--) + { + if (polynomial.testBit(i)) + { + polynomial.xorBit(i); + polynomial.xorBit(i - k); + polynomial.xorBit(i - l); + polynomial.xorBit(i - m); + polynomial.xorBit(i - mDegree); + + } + } + polynomial.reduceN(); + polynomial.expandN(mDegree); + } + + // ///////////////////////////////////////////////////////////////////// + // conversion + // ///////////////////////////////////////////////////////////////////// + + /** + * Returns a string representing this Bitstrings value using hexadecimal + * radix in MSB-first order. + * + * @return a String representing this Bitstrings value. + */ + public String toString() + { + return polynomial.toString(16); + } + + /** + * Returns a string representing this Bitstrings value using hexadecimal or + * binary radix in MSB-first order. + * + * @param radix the radix to use (2 or 16, otherwise 2 is used) + * @return a String representing this Bitstrings value. + */ + public String toString(int radix) + { + return polynomial.toString(radix); + } + + /** + * Converts this GF2nPolynomialElement to a byte[] according to 1363. + * + * @return a byte[] representing the value of this GF2nPolynomialElement + * @see "P1363 5.5.2 p22f BS2OSP, FE2OSP" + */ + public byte[] toByteArray() + { + return polynomial.toByteArray(); + } + + /** + * Converts this GF2nPolynomialElement to an integer according to 1363. + * + * @return a BigInteger representing the value of this + * GF2nPolynomialElement + * @see "P1363 5.5.1 p22 BS2IP" + */ + public BigInteger toFlexiBigInt() + { + return polynomial.toFlexiBigInt(); + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nPolynomialField.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nPolynomialField.java new file mode 100644 index 00000000..f9ec0bca --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GF2nPolynomialField.java @@ -0,0 +1,553 @@ +package org.spongycastle.pqc.math.linearalgebra; + + +import java.util.Random; +import java.util.Vector; + + +/** + * This class implements the abstract class <tt>GF2nField</tt> for polynomial + * representation. It computes the field polynomial and the squaring matrix. + * GF2nField is used by GF2nPolynomialElement which implements the elements of + * this field. + * + * @see GF2nField + * @see GF2nPolynomialElement + */ +public class GF2nPolynomialField + extends GF2nField +{ + + /** + * Matrix used for fast squaring + */ + GF2Polynomial[] squaringMatrix; + + // field polynomial is a trinomial + private boolean isTrinomial = false; + + // field polynomial is a pentanomial + private boolean isPentanomial = false; + + // middle coefficient of the field polynomial in case it is a trinomial + private int tc; + + // middle 3 coefficients of the field polynomial in case it is a pentanomial + private int[] pc = new int[3]; + + /** + * constructs an instance of the finite field with 2<sup>deg</sup> + * elements and characteristic 2. + * + * @param deg the extention degree of this field + */ + public GF2nPolynomialField(int deg) + { + if (deg < 3) + { + throw new IllegalArgumentException("k must be at least 3"); + } + mDegree = deg; + computeFieldPolynomial(); + computeSquaringMatrix(); + fields = new Vector(); + matrices = new Vector(); + } + + /** + * constructs an instance of the finite field with 2<sup>deg</sup> + * elements and characteristic 2. + * + * @param deg the degree of this field + * @param file true if you want to read the field polynomial from the + * file false if you want to use a random fielpolynomial + * (this can take very long for huge degrees) + */ + public GF2nPolynomialField(int deg, boolean file) + { + if (deg < 3) + { + throw new IllegalArgumentException("k must be at least 3"); + } + mDegree = deg; + if (file) + { + computeFieldPolynomial(); + } + else + { + computeFieldPolynomial2(); + } + computeSquaringMatrix(); + fields = new Vector(); + matrices = new Vector(); + } + + /** + * Creates a new GF2nField of degree <i>i</i> and uses the given + * <i>polynomial</i> as field polynomial. The <i>polynomial</i> is checked + * whether it is irreducible. This can take some time if <i>i</i> is huge! + * + * @param deg degree of the GF2nField + * @param polynomial the field polynomial to use + * @throws PolynomialIsNotIrreducibleException if the given polynomial is not irreducible in GF(2^<i>i</i>) + */ + public GF2nPolynomialField(int deg, GF2Polynomial polynomial) + throws RuntimeException + { + if (deg < 3) + { + throw new IllegalArgumentException("degree must be at least 3"); + } + if (polynomial.getLength() != deg + 1) + { + throw new RuntimeException(); + } + if (!polynomial.isIrreducible()) + { + throw new RuntimeException(); + } + mDegree = deg; + // fieldPolynomial = new Bitstring(polynomial); + fieldPolynomial = polynomial; + computeSquaringMatrix(); + int k = 2; // check if the polynomial is a trinomial or pentanomial + for (int j = 1; j < fieldPolynomial.getLength() - 1; j++) + { + if (fieldPolynomial.testBit(j)) + { + k++; + if (k == 3) + { + tc = j; + } + if (k <= 5) + { + pc[k - 3] = j; + } + } + } + if (k == 3) + { + isTrinomial = true; + } + if (k == 5) + { + isPentanomial = true; + } + fields = new Vector(); + matrices = new Vector(); + } + + /** + * Returns true if the field polynomial is a trinomial. The coefficient can + * be retrieved using getTc(). + * + * @return true if the field polynomial is a trinomial + */ + public boolean isTrinomial() + { + return isTrinomial; + } + + /** + * Returns true if the field polynomial is a pentanomial. The coefficients + * can be retrieved using getPc(). + * + * @return true if the field polynomial is a pentanomial + */ + public boolean isPentanomial() + { + return isPentanomial; + } + + /** + * Returns the degree of the middle coefficient of the used field trinomial + * (x^n + x^(getTc()) + 1). + * + * @return the middle coefficient of the used field trinomial + * @throws GFException if the field polynomial is not a trinomial + */ + public int getTc() + throws RuntimeException + { + if (!isTrinomial) + { + throw new RuntimeException(); + } + return tc; + } + + /** + * Returns the degree of the middle coefficients of the used field + * pentanomial (x^n + x^(getPc()[2]) + x^(getPc()[1]) + x^(getPc()[0]) + 1). + * + * @return the middle coefficients of the used field pentanomial + * @throws GFException if the field polynomial is not a pentanomial + */ + public int[] getPc() + throws RuntimeException + { + if (!isPentanomial) + { + throw new RuntimeException(); + } + int[] result = new int[3]; + System.arraycopy(pc, 0, result, 0, 3); + return result; + } + + /** + * Return row vector i of the squaring matrix. + * + * @param i the index of the row vector to return + * @return a copy of squaringMatrix[i] + * @see GF2nPolynomialElement#squareMatrix + */ + public GF2Polynomial getSquaringVector(int i) + { + return new GF2Polynomial(squaringMatrix[i]); + } + + /** + * Compute a random root of the given GF2Polynomial. + * + * @param polynomial the polynomial + * @return a random root of <tt>polynomial</tt> + */ + protected GF2nElement getRandomRoot(GF2Polynomial polynomial) + { + // We are in B1!!! + GF2nPolynomial c; + GF2nPolynomial ut; + GF2nElement u; + GF2nPolynomial h; + int hDegree; + // 1. Set g(t) <- f(t) + GF2nPolynomial g = new GF2nPolynomial(polynomial, this); + int gDegree = g.getDegree(); + int i; + + // 2. while deg(g) > 1 + while (gDegree > 1) + { + do + { + // 2.1 choose random u (element of) GF(2^m) + u = new GF2nPolynomialElement(this, new Random()); + ut = new GF2nPolynomial(2, GF2nPolynomialElement.ZERO(this)); + // 2.2 Set c(t) <- ut + ut.set(1, u); + c = new GF2nPolynomial(ut); + // 2.3 For i from 1 to m-1 do + for (i = 1; i <= mDegree - 1; i++) + { + // 2.3.1 c(t) <- (c(t)^2 + ut) mod g(t) + c = c.multiplyAndReduce(c, g); + c = c.add(ut); + } + // 2.4 set h(t) <- GCD(c(t), g(t)) + h = c.gcd(g); + // 2.5 if h(t) is constant or deg(g) = deg(h) then go to + // step 2.1 + hDegree = h.getDegree(); + gDegree = g.getDegree(); + } + while ((hDegree == 0) || (hDegree == gDegree)); + // 2.6 If 2deg(h) > deg(g) then set g(t) <- g(t)/h(t) ... + if ((hDegree << 1) > gDegree) + { + g = g.quotient(h); + } + else + { + // ... else g(t) <- h(t) + g = new GF2nPolynomial(h); + } + gDegree = g.getDegree(); + } + // 3. Output g(0) + return g.at(0); + + } + + /** + * Computes the change-of-basis matrix for basis conversion according to + * 1363. The result is stored in the lists fields and matrices. + * + * @param B1 the GF2nField to convert to + * @see "P1363 A.7.3, p111ff" + */ + protected void computeCOBMatrix(GF2nField B1) + { + // we are in B0 here! + if (mDegree != B1.mDegree) + { + throw new IllegalArgumentException( + "GF2nPolynomialField.computeCOBMatrix: B1 has a different " + + "degree and thus cannot be coverted to!"); + } + if (B1 instanceof GF2nONBField) + { + // speedup (calculation is done in PolynomialElements instead of + // ONB) + B1.computeCOBMatrix(this); + return; + } + int i, j; + GF2nElement[] gamma; + GF2nElement u; + GF2Polynomial[] COBMatrix = new GF2Polynomial[mDegree]; + for (i = 0; i < mDegree; i++) + { + COBMatrix[i] = new GF2Polynomial(mDegree); + } + + // find Random Root + do + { + // u is in representation according to B1 + u = B1.getRandomRoot(fieldPolynomial); + } + while (u.isZero()); + + // build gamma matrix by multiplying by u + if (u instanceof GF2nONBElement) + { + gamma = new GF2nONBElement[mDegree]; + gamma[mDegree - 1] = GF2nONBElement.ONE((GF2nONBField)B1); + } + else + { + gamma = new GF2nPolynomialElement[mDegree]; + gamma[mDegree - 1] = GF2nPolynomialElement + .ONE((GF2nPolynomialField)B1); + } + gamma[mDegree - 2] = u; + for (i = mDegree - 3; i >= 0; i--) + { + gamma[i] = (GF2nElement)gamma[i + 1].multiply(u); + } + if (B1 instanceof GF2nONBField) + { + // convert horizontal gamma matrix by vertical Bitstrings + for (i = 0; i < mDegree; i++) + { + for (j = 0; j < mDegree; j++) + { + // TODO remember: ONB treats its Bits in reverse order !!! + if (gamma[i].testBit(mDegree - j - 1)) + { + COBMatrix[mDegree - j - 1].setBit(mDegree - i - 1); + } + } + } + } + else + { + // convert horizontal gamma matrix by vertical Bitstrings + for (i = 0; i < mDegree; i++) + { + for (j = 0; j < mDegree; j++) + { + if (gamma[i].testBit(j)) + { + COBMatrix[mDegree - j - 1].setBit(mDegree - i - 1); + } + } + } + } + + // store field and matrix for further use + fields.addElement(B1); + matrices.addElement(COBMatrix); + // store field and inverse matrix for further use in B1 + B1.fields.addElement(this); + B1.matrices.addElement(invertMatrix(COBMatrix)); + } + + /** + * Computes a new squaring matrix used for fast squaring. + * + * @see GF2nPolynomialElement#square + */ + private void computeSquaringMatrix() + { + GF2Polynomial[] d = new GF2Polynomial[mDegree - 1]; + int i, j; + squaringMatrix = new GF2Polynomial[mDegree]; + for (i = 0; i < squaringMatrix.length; i++) + { + squaringMatrix[i] = new GF2Polynomial(mDegree, "ZERO"); + } + + for (i = 0; i < mDegree - 1; i++) + { + d[i] = new GF2Polynomial(1, "ONE").shiftLeft(mDegree + i) + .remainder(fieldPolynomial); + } + for (i = 1; i <= Math.abs(mDegree >> 1); i++) + { + for (j = 1; j <= mDegree; j++) + { + if (d[mDegree - (i << 1)].testBit(mDegree - j)) + { + squaringMatrix[j - 1].setBit(mDegree - i); + } + } + } + for (i = Math.abs(mDegree >> 1) + 1; i <= mDegree; i++) + { + squaringMatrix[(i << 1) - mDegree - 1].setBit(mDegree - i); + } + + } + + /** + * Computes the field polynomial. This can take a long time for big degrees. + */ + protected void computeFieldPolynomial() + { + if (testTrinomials()) + { + return; + } + if (testPentanomials()) + { + return; + } + testRandom(); + } + + /** + * Computes the field polynomial. This can take a long time for big degrees. + */ + protected void computeFieldPolynomial2() + { + if (testTrinomials()) + { + return; + } + if (testPentanomials()) + { + return; + } + testRandom(); + } + + /** + * Tests all trinomials of degree (n+1) until a irreducible is found and + * stores the result in <i>field polynomial</i>. Returns false if no + * irreducible trinomial exists in GF(2^n). This can take very long for huge + * degrees. + * + * @return true if an irreducible trinomial is found + */ + private boolean testTrinomials() + { + int i, l; + boolean done = false; + l = 0; + + fieldPolynomial = new GF2Polynomial(mDegree + 1); + fieldPolynomial.setBit(0); + fieldPolynomial.setBit(mDegree); + for (i = 1; (i < mDegree) && !done; i++) + { + fieldPolynomial.setBit(i); + done = fieldPolynomial.isIrreducible(); + l++; + if (done) + { + isTrinomial = true; + tc = i; + return done; + } + fieldPolynomial.resetBit(i); + done = fieldPolynomial.isIrreducible(); + } + + return done; + } + + /** + * Tests all pentanomials of degree (n+1) until a irreducible is found and + * stores the result in <i>field polynomial</i>. Returns false if no + * irreducible pentanomial exists in GF(2^n). This can take very long for + * huge degrees. + * + * @return true if an irreducible pentanomial is found + */ + private boolean testPentanomials() + { + int i, j, k, l; + boolean done = false; + l = 0; + + fieldPolynomial = new GF2Polynomial(mDegree + 1); + fieldPolynomial.setBit(0); + fieldPolynomial.setBit(mDegree); + for (i = 1; (i <= (mDegree - 3)) && !done; i++) + { + fieldPolynomial.setBit(i); + for (j = i + 1; (j <= (mDegree - 2)) && !done; j++) + { + fieldPolynomial.setBit(j); + for (k = j + 1; (k <= (mDegree - 1)) && !done; k++) + { + fieldPolynomial.setBit(k); + if (((mDegree & 1) != 0) | ((i & 1) != 0) | ((j & 1) != 0) + | ((k & 1) != 0)) + { + done = fieldPolynomial.isIrreducible(); + l++; + if (done) + { + isPentanomial = true; + pc[0] = i; + pc[1] = j; + pc[2] = k; + return done; + } + } + fieldPolynomial.resetBit(k); + } + fieldPolynomial.resetBit(j); + } + fieldPolynomial.resetBit(i); + } + + return done; + } + + /** + * Tests random polynomials of degree (n+1) until an irreducible is found + * and stores the result in <i>field polynomial</i>. This can take very + * long for huge degrees. + * + * @return true + */ + private boolean testRandom() + { + int l; + boolean done = false; + + fieldPolynomial = new GF2Polynomial(mDegree + 1); + l = 0; + while (!done) + { + l++; + fieldPolynomial.randomize(); + fieldPolynomial.setBit(mDegree); + fieldPolynomial.setBit(0); + if (fieldPolynomial.isIrreducible()) + { + done = true; + return done; + } + } + + return done; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GFElement.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GFElement.java new file mode 100644 index 00000000..c33f1956 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GFElement.java @@ -0,0 +1,158 @@ +package org.spongycastle.pqc.math.linearalgebra; + +import java.math.BigInteger; + + +/** + * This interface defines a finite field element. It is implemented by the + * classes {@link GFPElement} and {@link GF2nElement}. + * + * @see GFPElement + * @see GF2nElement + */ +public interface GFElement +{ + + /** + * @return a copy of this GFElement + */ + Object clone(); + + // ///////////////////////////////////////////////////////////////// + // comparison + // ///////////////////////////////////////////////////////////////// + + /** + * Compare this curve with another object. + * + * @param other the other object + * @return the result of the comparison + */ + boolean equals(Object other); + + /** + * @return the hash code of this element + */ + int hashCode(); + + /** + * Checks whether this element is zero. + * + * @return <tt>true</tt> if <tt>this</tt> is the zero element + */ + boolean isZero(); + + /** + * Checks whether this element is one. + * + * @return <tt>true</tt> if <tt>this</tt> is the one element + */ + boolean isOne(); + + // ///////////////////////////////////////////////////////////////////// + // arithmetic + // ///////////////////////////////////////////////////////////////////// + + /** + * Compute the sum of this element and the addend. + * + * @param addend the addend + * @return <tt>this + other</tt> (newly created) + * @throws DifferentFieldsException if the elements are of different fields. + */ + GFElement add(GFElement addend) + throws RuntimeException; + + /** + * Compute the sum of this element and the addend, overwriting this element. + * + * @param addend the addend + * @throws DifferentFieldsException if the elements are of different fields. + */ + void addToThis(GFElement addend) + throws RuntimeException; + + /** + * Compute the difference of this element and <tt>minuend</tt>. + * + * @param minuend the minuend + * @return <tt>this - minuend</tt> (newly created) + * @throws DifferentFieldsException if the elements are of different fields. + */ + GFElement subtract(GFElement minuend) + throws RuntimeException; + + /** + * Compute the difference of this element and <tt>minuend</tt>, + * overwriting this element. + * + * @param minuend the minuend + * @throws DifferentFieldsException if the elements are of different fields. + */ + void subtractFromThis(GFElement minuend); + + /** + * Compute the product of this element and <tt>factor</tt>. + * + * @param factor the factor + * @return <tt>this * factor</tt> (newly created) + * @throws DifferentFieldsException if the elements are of different fields. + */ + GFElement multiply(GFElement factor) + throws RuntimeException; + + /** + * Compute <tt>this * factor</tt> (overwrite <tt>this</tt>). + * + * @param factor the factor + * @throws DifferentFieldsException if the elements are of different fields. + */ + void multiplyThisBy(GFElement factor) + throws RuntimeException; + + /** + * Compute the multiplicative inverse of this element. + * + * @return <tt>this<sup>-1</sup></tt> (newly created) + * @throws ArithmeticException if <tt>this</tt> is the zero element. + */ + GFElement invert() + throws ArithmeticException; + + // ///////////////////////////////////////////////////////////////////// + // conversion + // ///////////////////////////////////////////////////////////////////// + + /** + * Returns this element as FlexiBigInt. The conversion is <a + * href="http://grouper.ieee.org/groups/1363/">P1363</a>-conform. + * + * @return this element as BigInt + */ + BigInteger toFlexiBigInt(); + + /** + * Returns this element as byte array. The conversion is <a href = + * "http://grouper.ieee.org/groups/1363/">P1363</a>-conform. + * + * @return this element as byte array + */ + byte[] toByteArray(); + + /** + * Return a String representation of this element. + * + * @return String representation of this element + */ + String toString(); + + /** + * Return a String representation of this element. <tt>radix</tt> + * specifies the radix of the String representation. + * + * @param radix specifies the radix of the String representation + * @return String representation of this element with the specified radix + */ + String toString(int radix); + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GoppaCode.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GoppaCode.java new file mode 100644 index 00000000..2249d936 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/GoppaCode.java @@ -0,0 +1,310 @@ +package org.spongycastle.pqc.math.linearalgebra; + +import java.security.SecureRandom; + +/** + * This class describes decoding operations of an irreducible binary Goppa code. + * A check matrix H of the Goppa code and an irreducible Goppa polynomial are + * used the operations are worked over a finite field GF(2^m) + * + * @see GF2mField + * @see PolynomialGF2mSmallM + */ +public final class GoppaCode +{ + + /** + * Default constructor (private). + */ + private GoppaCode() + { + // empty + } + + /** + * This class is a container for two instances of {@link GF2Matrix} and one + * instance of {@link Permutation}. It is used to hold the systematic form + * <tt>S*H*P = (Id|M)</tt> of the check matrix <tt>H</tt> as returned by + * {@link GoppaCode#computeSystematicForm(GF2Matrix, SecureRandom)}. + * + * @see GF2Matrix + * @see Permutation + */ + public static class MaMaPe + { + + private GF2Matrix s, h; + + private Permutation p; + + /** + * Construct a new {@link MaMaPe} container with the given parameters. + * + * @param s the first matrix + * @param h the second matrix + * @param p the permutation + */ + public MaMaPe(GF2Matrix s, GF2Matrix h, Permutation p) + { + this.s = s; + this.h = h; + this.p = p; + } + + /** + * @return the first matrix + */ + public GF2Matrix getFirstMatrix() + { + return s; + } + + /** + * @return the second matrix + */ + public GF2Matrix getSecondMatrix() + { + return h; + } + + /** + * @return the permutation + */ + public Permutation getPermutation() + { + return p; + } + } + + /** + * This class is a container for an instance of {@link GF2Matrix} and one + * int[]. It is used to hold a generator matrix and the set of indices such + * that the submatrix of the generator matrix consisting of the specified + * columns is the identity. + * + * @see GF2Matrix + * @see Permutation + */ + public static class MatrixSet + { + + private GF2Matrix g; + + private int[] setJ; + + /** + * Construct a new {@link MatrixSet} container with the given + * parameters. + * + * @param g the generator matrix + * @param setJ the set of indices such that the submatrix of the + * generator matrix consisting of the specified columns + * is the identity + */ + public MatrixSet(GF2Matrix g, int[] setJ) + { + this.g = g; + this.setJ = setJ; + } + + /** + * @return the generator matrix + */ + public GF2Matrix getG() + { + return g; + } + + /** + * @return the set of indices such that the submatrix of the generator + * matrix consisting of the specified columns is the identity + */ + public int[] getSetJ() + { + return setJ; + } + } + + /** + * Construct the check matrix of a Goppa code in canonical form from the + * irreducible Goppa polynomial over the finite field + * <tt>GF(2<sup>m</sup>)</tt>. + * + * @param field the finite field + * @param gp the irreducible Goppa polynomial + */ + public static GF2Matrix createCanonicalCheckMatrix(GF2mField field, + PolynomialGF2mSmallM gp) + { + int m = field.getDegree(); + int n = 1 << m; + int t = gp.getDegree(); + + /* create matrix H over GF(2^m) */ + + int[][] hArray = new int[t][n]; + + // create matrix YZ + int[][] yz = new int[t][n]; + for (int j = 0; j < n; j++) + { + // here j is used as index and as element of field GF(2^m) + yz[0][j] = field.inverse(gp.evaluateAt(j)); + } + + for (int i = 1; i < t; i++) + { + for (int j = 0; j < n; j++) + { + // here j is used as index and as element of field GF(2^m) + yz[i][j] = field.mult(yz[i - 1][j], j); + } + } + + // create matrix H = XYZ + for (int i = 0; i < t; i++) + { + for (int j = 0; j < n; j++) + { + for (int k = 0; k <= i; k++) + { + hArray[i][j] = field.add(hArray[i][j], field.mult(yz[k][j], + gp.getCoefficient(t + k - i))); + } + } + } + + /* convert to matrix over GF(2) */ + + int[][] result = new int[t * m][(n + 31) >>> 5]; + + for (int j = 0; j < n; j++) + { + int q = j >>> 5; + int r = 1 << (j & 0x1f); + for (int i = 0; i < t; i++) + { + int e = hArray[i][j]; + for (int u = 0; u < m; u++) + { + int b = (e >>> u) & 1; + if (b != 0) + { + int ind = (i + 1) * m - u - 1; + result[ind][q] ^= r; + } + } + } + } + + return new GF2Matrix(n, result); + } + + /** + * Given a check matrix <tt>H</tt>, compute matrices <tt>S</tt>, + * <tt>M</tt>, and a random permutation <tt>P</tt> such that + * <tt>S*H*P = (Id|M)</tt>. Return <tt>S^-1</tt>, <tt>M</tt>, and + * <tt>P</tt> as {@link MaMaPe}. The matrix <tt>(Id | M)</tt> is called + * the systematic form of H. + * + * @param h the check matrix + * @param sr a source of randomness + * @return the tuple <tt>(S^-1, M, P)</tt> + */ + public static MaMaPe computeSystematicForm(GF2Matrix h, SecureRandom sr) + { + int n = h.getNumColumns(); + GF2Matrix hp, sInv; + GF2Matrix s = null; + Permutation p; + boolean found = false; + + do + { + p = new Permutation(n, sr); + hp = (GF2Matrix)h.rightMultiply(p); + sInv = hp.getLeftSubMatrix(); + try + { + found = true; + s = (GF2Matrix)sInv.computeInverse(); + } + catch (ArithmeticException ae) + { + found = false; + } + } + while (!found); + + GF2Matrix shp = (GF2Matrix)s.rightMultiply(hp); + GF2Matrix m = shp.getRightSubMatrix(); + + return new MaMaPe(sInv, m, p); + } + + /** + * Find an error vector <tt>e</tt> over <tt>GF(2)</tt> from an input + * syndrome <tt>s</tt> over <tt>GF(2<sup>m</sup>)</tt>. + * + * @param syndVec the syndrome + * @param field the finite field + * @param gp the irreducible Goppa polynomial + * @param sqRootMatrix the matrix for computing square roots in + * <tt>(GF(2<sup>m</sup>))<sup>t</sup></tt> + * @return the error vector + */ + public static GF2Vector syndromeDecode(GF2Vector syndVec, GF2mField field, + PolynomialGF2mSmallM gp, PolynomialGF2mSmallM[] sqRootMatrix) + { + + int n = 1 << field.getDegree(); + + // the error vector + GF2Vector errors = new GF2Vector(n); + + // if the syndrome vector is zero, the error vector is also zero + if (!syndVec.isZero()) + { + // convert syndrome vector to polynomial over GF(2^m) + PolynomialGF2mSmallM syndrome = new PolynomialGF2mSmallM(syndVec + .toExtensionFieldVector(field)); + + // compute T = syndrome^-1 mod gp + PolynomialGF2mSmallM t = syndrome.modInverse(gp); + + // compute tau = sqRoot(T + X) mod gp + PolynomialGF2mSmallM tau = t.addMonomial(1); + tau = tau.modSquareRootMatrix(sqRootMatrix); + + // compute polynomials a and b satisfying a + b*tau = 0 mod gp + PolynomialGF2mSmallM[] ab = tau.modPolynomialToFracton(gp); + + // compute the polynomial a^2 + X*b^2 + PolynomialGF2mSmallM a2 = ab[0].multiply(ab[0]); + PolynomialGF2mSmallM b2 = ab[1].multiply(ab[1]); + PolynomialGF2mSmallM xb2 = b2.multWithMonomial(1); + PolynomialGF2mSmallM a2plusXb2 = a2.add(xb2); + + // normalize a^2 + X*b^2 to obtain the error locator polynomial + int headCoeff = a2plusXb2.getHeadCoefficient(); + int invHeadCoeff = field.inverse(headCoeff); + PolynomialGF2mSmallM elp = a2plusXb2.multWithElement(invHeadCoeff); + + // for all elements i of GF(2^m) + for (int i = 0; i < n; i++) + { + // evaluate the error locator polynomial at i + int z = elp.evaluateAt(i); + // if polynomial evaluates to zero + if (z == 0) + { + // set the i-th coefficient of the error vector + errors.setBit(i); + } + } + } + + return errors; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/IntUtils.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/IntUtils.java new file mode 100644 index 00000000..e586666e --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/IntUtils.java @@ -0,0 +1,202 @@ +package org.spongycastle.pqc.math.linearalgebra; + +import java.math.BigInteger; + +/** + * + * + * + */ +public final class IntUtils +{ + + /** + * Default constructor (private). + */ + private IntUtils() + { + // empty + } + + /** + * Compare two int arrays. No null checks are performed. + * + * @param left the first int array + * @param right the second int array + * @return the result of the comparison + */ + public static boolean equals(int[] left, int[] right) + { + if (left.length != right.length) + { + return false; + } + boolean result = true; + for (int i = left.length - 1; i >= 0; i--) + { + result &= left[i] == right[i]; + } + return result; + } + + /** + * Return a clone of the given int array. No null checks are performed. + * + * @param array the array to clone + * @return the clone of the given array + */ + public static int[] clone(int[] array) + { + int[] result = new int[array.length]; + System.arraycopy(array, 0, result, 0, array.length); + return result; + } + + /** + * Fill the given int array with the given value. + * + * @param array the array + * @param value the value + */ + public static void fill(int[] array, int value) + { + for (int i = array.length - 1; i >= 0; i--) + { + array[i] = value; + } + } + + /** + * Sorts this array of integers according to the Quicksort algorithm. After + * calling this method this array is sorted in ascending order with the + * smallest integer taking position 0 in the array. + * <p> + * This implementation is based on the quicksort algorithm as described in + * <code>Data Structures In Java</code> by Thomas A. Standish, Chapter 10, + * ISBN 0-201-30564-X. + * + * @param source the array of integers that needs to be sorted. + */ + public static void quicksort(int[] source) + { + quicksort(source, 0, source.length - 1); + } + + /** + * Sort a subarray of a source array. The subarray is specified by its start + * and end index. + * + * @param source the int array to be sorted + * @param left the start index of the subarray + * @param right the end index of the subarray + */ + public static void quicksort(int[] source, int left, int right) + { + if (right > left) + { + int index = partition(source, left, right, right); + quicksort(source, left, index - 1); + quicksort(source, index + 1, right); + } + } + + /** + * Split a subarray of a source array into two partitions. The left + * partition contains elements that have value less than or equal to the + * pivot element, the right partition contains the elements that have larger + * value. + * + * @param source the int array whose subarray will be splitted + * @param left the start position of the subarray + * @param right the end position of the subarray + * @param pivotIndex the index of the pivot element inside the array + * @return the new index of the pivot element inside the array + */ + private static int partition(int[] source, int left, int right, + int pivotIndex) + { + + int pivot = source[pivotIndex]; + source[pivotIndex] = source[right]; + source[right] = pivot; + + int index = left; + + for (int i = left; i < right; i++) + { + if (source[i] <= pivot) + { + int tmp = source[index]; + source[index] = source[i]; + source[i] = tmp; + index++; + } + } + + int tmp = source[index]; + source[index] = source[right]; + source[right] = tmp; + + return index; + } + + /** + * Generates a subarray of a given int array. + * + * @param input - + * the input int array + * @param start - + * the start index + * @param end - + * the end index + * @return a subarray of <tt>input</tt>, ranging from <tt>start</tt> to + * <tt>end</tt> + */ + public static int[] subArray(final int[] input, final int start, + final int end) + { + int[] result = new int[end - start]; + System.arraycopy(input, start, result, 0, end - start); + return result; + } + + /** + * Convert an int array to a {@link FlexiBigInt} array. + * + * @param input the int array + * @return the {@link FlexiBigInt} array + */ + public static BigInteger[] toFlexiBigIntArray(int[] input) + { + BigInteger[] result = new BigInteger[input.length]; + for (int i = 0; i < input.length; i++) + { + result[i] = BigInteger.valueOf(input[i]); + } + return result; + } + + /** + * @param input an int array + * @return a human readable form of the given int array + */ + public static String toString(int[] input) + { + String result = ""; + for (int i = 0; i < input.length; i++) + { + result += input[i] + " "; + } + return result; + } + + /** + * @param input an int arary + * @return the int array as hex string + */ + public static String toHexString(int[] input) + { + return ByteUtils.toHexString(BigEndianConversions.toByteArray(input)); + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/IntegerFunctions.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/IntegerFunctions.java new file mode 100644 index 00000000..a6d5a181 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/IntegerFunctions.java @@ -0,0 +1,1423 @@ +package org.spongycastle.pqc.math.linearalgebra; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * Class of number-theory related functions for use with integers represented as + * <tt>int</tt>'s or <tt>BigInteger</tt> objects. + */ +public final class IntegerFunctions +{ + + private static final BigInteger ZERO = BigInteger.valueOf(0); + + private static final BigInteger ONE = BigInteger.valueOf(1); + + private static final BigInteger TWO = BigInteger.valueOf(2); + + private static final BigInteger FOUR = BigInteger.valueOf(4); + + private static final int[] SMALL_PRIMES = {3, 5, 7, 11, 13, 17, 19, 23, + 29, 31, 37, 41}; + + private static final long SMALL_PRIME_PRODUCT = 3L * 5 * 7 * 11 * 13 * 17 + * 19 * 23 * 29 * 31 * 37 * 41; + + private static SecureRandom sr = null; + + // the jacobi function uses this lookup table + private static final int[] jacobiTable = {0, 1, 0, -1, 0, -1, 0, 1}; + + private IntegerFunctions() + { + // empty + } + + /** + * Computes the value of the Jacobi symbol (A|B). The following properties + * hold for the Jacobi symbol which makes it a very efficient way to + * evaluate the Legendre symbol + * <p> + * (A|B) = 0 IF gcd(A,B) > 1<br> + * (-1|B) = 1 IF n = 1 (mod 1)<br> + * (-1|B) = -1 IF n = 3 (mod 4)<br> + * (A|B) (C|B) = (AC|B)<br> + * (A|B) (A|C) = (A|CB)<br> + * (A|B) = (C|B) IF A = C (mod B)<br> + * (2|B) = 1 IF N = 1 OR 7 (mod 8)<br> + * (2|B) = 1 IF N = 3 OR 5 (mod 8) + * + * @param A integer value + * @param B integer value + * @return value of the jacobi symbol (A|B) + */ + public static int jacobi(BigInteger A, BigInteger B) + { + BigInteger a, b, v; + long k = 1; + + k = 1; + + // test trivial cases + if (B.equals(ZERO)) + { + a = A.abs(); + return a.equals(ONE) ? 1 : 0; + } + + if (!A.testBit(0) && !B.testBit(0)) + { + return 0; + } + + a = A; + b = B; + + if (b.signum() == -1) + { // b < 0 + b = b.negate(); // b = -b + if (a.signum() == -1) + { + k = -1; + } + } + + v = ZERO; + while (!b.testBit(0)) + { + v = v.add(ONE); // v = v + 1 + b = b.divide(TWO); // b = b/2 + } + + if (v.testBit(0)) + { + k = k * jacobiTable[a.intValue() & 7]; + } + + if (a.signum() < 0) + { // a < 0 + if (b.testBit(1)) + { + k = -k; // k = -k + } + a = a.negate(); // a = -a + } + + // main loop + while (a.signum() != 0) + { + v = ZERO; + while (!a.testBit(0)) + { // a is even + v = v.add(ONE); + a = a.divide(TWO); + } + if (v.testBit(0)) + { + k = k * jacobiTable[b.intValue() & 7]; + } + + if (a.compareTo(b) < 0) + { // a < b + // swap and correct intermediate result + BigInteger x = a; + a = b; + b = x; + if (a.testBit(1) && b.testBit(1)) + { + k = -k; + } + } + a = a.subtract(b); + } + + return b.equals(ONE) ? (int)k : 0; + } + + /** + * Computes the square root of a BigInteger modulo a prime employing the + * Shanks-Tonelli algorithm. + * + * @param a value out of which we extract the square root + * @param p prime modulus that determines the underlying field + * @return a number <tt>b</tt> such that b<sup>2</sup> = a (mod p) if + * <tt>a</tt> is a quadratic residue modulo <tt>p</tt>. + * @throws NoQuadraticResidueException if <tt>a</tt> is a quadratic non-residue modulo <tt>p</tt> + */ + public static BigInteger ressol(BigInteger a, BigInteger p) + throws IllegalArgumentException + { + + BigInteger v = null; + + if (a.compareTo(ZERO) < 0) + { + a = a.add(p); + } + + if (a.equals(ZERO)) + { + return ZERO; + } + + if (p.equals(TWO)) + { + return a; + } + + // p = 3 mod 4 + if (p.testBit(0) && p.testBit(1)) + { + if (jacobi(a, p) == 1) + { // a quadr. residue mod p + v = p.add(ONE); // v = p+1 + v = v.shiftRight(2); // v = v/4 + return a.modPow(v, p); // return a^v mod p + // return --> a^((p+1)/4) mod p + } + throw new IllegalArgumentException("No quadratic residue: " + a + ", " + p); + } + + long t = 0; + + // initialization + // compute k and s, where p = 2^s (2k+1) +1 + + BigInteger k = p.subtract(ONE); // k = p-1 + long s = 0; + while (!k.testBit(0)) + { // while k is even + s++; // s = s+1 + k = k.shiftRight(1); // k = k/2 + } + + k = k.subtract(ONE); // k = k - 1 + k = k.shiftRight(1); // k = k/2 + + // initial values + BigInteger r = a.modPow(k, p); // r = a^k mod p + + BigInteger n = r.multiply(r).remainder(p); // n = r^2 % p + n = n.multiply(a).remainder(p); // n = n * a % p + r = r.multiply(a).remainder(p); // r = r * a %p + + if (n.equals(ONE)) + { + return r; + } + + // non-quadratic residue + BigInteger z = TWO; // z = 2 + while (jacobi(z, p) == 1) + { + // while z quadratic residue + z = z.add(ONE); // z = z + 1 + } + + v = k; + v = v.multiply(TWO); // v = 2k + v = v.add(ONE); // v = 2k + 1 + BigInteger c = z.modPow(v, p); // c = z^v mod p + + // iteration + while (n.compareTo(ONE) == 1) + { // n > 1 + k = n; // k = n + t = s; // t = s + s = 0; + + while (!k.equals(ONE)) + { // k != 1 + k = k.multiply(k).mod(p); // k = k^2 % p + s++; // s = s + 1 + } + + t -= s; // t = t - s + if (t == 0) + { + throw new IllegalArgumentException("No quadratic residue: " + a + ", " + p); + } + + v = ONE; + for (long i = 0; i < t - 1; i++) + { + v = v.shiftLeft(1); // v = 1 * 2^(t - 1) + } + c = c.modPow(v, p); // c = c^v mod p + r = r.multiply(c).remainder(p); // r = r * c % p + c = c.multiply(c).remainder(p); // c = c^2 % p + n = n.multiply(c).mod(p); // n = n * c % p + } + return r; + } + + /** + * Computes the greatest common divisor of the two specified integers + * + * @param u - first integer + * @param v - second integer + * @return gcd(a, b) + */ + public static int gcd(int u, int v) + { + return BigInteger.valueOf(u).gcd(BigInteger.valueOf(v)).intValue(); + } + + /** + * Extended euclidian algorithm (computes gcd and representation). + * + * @param a the first integer + * @param b the second integer + * @return <tt>(g,u,v)</tt>, where <tt>g = gcd(abs(a),abs(b)) = ua + vb</tt> + */ + public static int[] extGCD(int a, int b) + { + BigInteger ba = BigInteger.valueOf(a); + BigInteger bb = BigInteger.valueOf(b); + BigInteger[] bresult = extgcd(ba, bb); + int[] result = new int[3]; + result[0] = bresult[0].intValue(); + result[1] = bresult[1].intValue(); + result[2] = bresult[2].intValue(); + return result; + } + + public static BigInteger divideAndRound(BigInteger a, BigInteger b) + { + if (a.signum() < 0) + { + return divideAndRound(a.negate(), b).negate(); + } + if (b.signum() < 0) + { + return divideAndRound(a, b.negate()).negate(); + } + return a.shiftLeft(1).add(b).divide(b.shiftLeft(1)); + } + + public static BigInteger[] divideAndRound(BigInteger[] a, BigInteger b) + { + BigInteger[] out = new BigInteger[a.length]; + for (int i = 0; i < a.length; i++) + { + out[i] = divideAndRound(a[i], b); + } + return out; + } + + /** + * Compute the smallest integer that is greater than or equal to the + * logarithm to the base 2 of the given BigInteger. + * + * @param a the integer + * @return ceil[log(a)] + */ + public static int ceilLog(BigInteger a) + { + int result = 0; + BigInteger p = ONE; + while (p.compareTo(a) < 0) + { + result++; + p = p.shiftLeft(1); + } + return result; + } + + /** + * Compute the smallest integer that is greater than or equal to the + * logarithm to the base 2 of the given integer. + * + * @param a the integer + * @return ceil[log(a)] + */ + public static int ceilLog(int a) + { + int log = 0; + int i = 1; + while (i < a) + { + i <<= 1; + log++; + } + return log; + } + + /** + * Compute <tt>ceil(log_256 n)</tt>, the number of bytes needed to encode + * the integer <tt>n</tt>. + * + * @param n the integer + * @return the number of bytes needed to encode <tt>n</tt> + */ + public static int ceilLog256(int n) + { + if (n == 0) + { + return 1; + } + int m; + if (n < 0) + { + m = -n; + } + else + { + m = n; + } + + int d = 0; + while (m > 0) + { + d++; + m >>>= 8; + } + return d; + } + + /** + * Compute <tt>ceil(log_256 n)</tt>, the number of bytes needed to encode + * the long integer <tt>n</tt>. + * + * @param n the long integer + * @return the number of bytes needed to encode <tt>n</tt> + */ + public static int ceilLog256(long n) + { + if (n == 0) + { + return 1; + } + long m; + if (n < 0) + { + m = -n; + } + else + { + m = n; + } + + int d = 0; + while (m > 0) + { + d++; + m >>>= 8; + } + return d; + } + + /** + * Compute the integer part of the logarithm to the base 2 of the given + * integer. + * + * @param a the integer + * @return floor[log(a)] + */ + public static int floorLog(BigInteger a) + { + int result = -1; + BigInteger p = ONE; + while (p.compareTo(a) <= 0) + { + result++; + p = p.shiftLeft(1); + } + return result; + } + + /** + * Compute the integer part of the logarithm to the base 2 of the given + * integer. + * + * @param a the integer + * @return floor[log(a)] + */ + public static int floorLog(int a) + { + int h = 0; + if (a <= 0) + { + return -1; + } + int p = a >>> 1; + while (p > 0) + { + h++; + p >>>= 1; + } + + return h; + } + + /** + * Compute the largest <tt>h</tt> with <tt>2^h | a</tt> if <tt>a!=0</tt>. + * + * @param a an integer + * @return the largest <tt>h</tt> with <tt>2^h | a</tt> if <tt>a!=0</tt>, + * <tt>0</tt> otherwise + */ + public static int maxPower(int a) + { + int h = 0; + if (a != 0) + { + int p = 1; + while ((a & p) == 0) + { + h++; + p <<= 1; + } + } + + return h; + } + + /** + * @param a an integer + * @return the number of ones in the binary representation of an integer + * <tt>a</tt> + */ + public static int bitCount(int a) + { + int h = 0; + while (a != 0) + { + h += a & 1; + a >>>= 1; + } + + return h; + } + + /** + * determines the order of g modulo p, p prime and 1 < g < p. This algorithm + * is only efficient for small p (see X9.62-1998, p. 68). + * + * @param g an integer with 1 < g < p + * @param p a prime + * @return the order k of g (that is k is the smallest integer with + * g<sup>k</sup> = 1 mod p + */ + public static int order(int g, int p) + { + int b, j; + + b = g % p; // Reduce g mod p first. + j = 1; + + // Check whether g == 0 mod p (avoiding endless loop). + if (b == 0) + { + throw new IllegalArgumentException(g + " is not an element of Z/(" + + p + "Z)^*; it is not meaningful to compute its order."); + } + + // Compute the order of g mod p: + while (b != 1) + { + b *= g; + b %= p; + if (b < 0) + { + b += p; + } + j++; + } + + return j; + } + + /** + * Reduces an integer into a given interval + * + * @param n - the integer + * @param begin - left bound of the interval + * @param end - right bound of the interval + * @return <tt>n</tt> reduced into <tt>[begin,end]</tt> + */ + public static BigInteger reduceInto(BigInteger n, BigInteger begin, + BigInteger end) + { + return n.subtract(begin).mod(end.subtract(begin)).add(begin); + } + + /** + * Compute <tt>a<sup>e</sup></tt>. + * + * @param a the base + * @param e the exponent + * @return <tt>a<sup>e</sup></tt> + */ + public static int pow(int a, int e) + { + int result = 1; + while (e > 0) + { + if ((e & 1) == 1) + { + result *= a; + } + a *= a; + e >>>= 1; + } + return result; + } + + /** + * Compute <tt>a<sup>e</sup></tt>. + * + * @param a the base + * @param e the exponent + * @return <tt>a<sup>e</sup></tt> + */ + public static long pow(long a, int e) + { + long result = 1; + while (e > 0) + { + if ((e & 1) == 1) + { + result *= a; + } + a *= a; + e >>>= 1; + } + return result; + } + + /** + * Compute <tt>a<sup>e</sup> mod n</tt>. + * + * @param a the base + * @param e the exponent + * @param n the modulus + * @return <tt>a<sup>e</sup> mod n</tt> + */ + public static int modPow(int a, int e, int n) + { + if (n <= 0 || (n * n) > Integer.MAX_VALUE || e < 0) + { + return 0; + } + int result = 1; + a = (a % n + n) % n; + while (e > 0) + { + if ((e & 1) == 1) + { + result = (result * a) % n; + } + a = (a * a) % n; + e >>>= 1; + } + return result; + } + + /** + * Extended euclidian algorithm (computes gcd and representation). + * + * @param a - the first integer + * @param b - the second integer + * @return <tt>(d,u,v)</tt>, where <tt>d = gcd(a,b) = ua + vb</tt> + */ + public static BigInteger[] extgcd(BigInteger a, BigInteger b) + { + BigInteger u = ONE; + BigInteger v = ZERO; + BigInteger d = a; + if (b.signum() != 0) + { + BigInteger v1 = ZERO; + BigInteger v3 = b; + while (v3.signum() != 0) + { + BigInteger[] tmp = d.divideAndRemainder(v3); + BigInteger q = tmp[0]; + BigInteger t3 = tmp[1]; + BigInteger t1 = u.subtract(q.multiply(v1)); + u = v1; + d = v3; + v1 = t1; + v3 = t3; + } + v = d.subtract(a.multiply(u)).divide(b); + } + return new BigInteger[]{d, u, v}; + } + + /** + * Computation of the least common multiple of a set of BigIntegers. + * + * @param numbers - the set of numbers + * @return the lcm(numbers) + */ + public static BigInteger leastCommonMultiple(BigInteger[] numbers) + { + int n = numbers.length; + BigInteger result = numbers[0]; + for (int i = 1; i < n; i++) + { + BigInteger gcd = result.gcd(numbers[i]); + result = result.multiply(numbers[i]).divide(gcd); + } + return result; + } + + /** + * Returns a long integer whose value is <tt>(a mod m</tt>). This method + * differs from <tt>%</tt> in that it always returns a <i>non-negative</i> + * integer. + * + * @param a value on which the modulo operation has to be performed. + * @param m the modulus. + * @return <tt>a mod m</tt> + */ + public static long mod(long a, long m) + { + long result = a % m; + if (result < 0) + { + result += m; + } + return result; + } + + /** + * Computes the modular inverse of an integer a + * + * @param a - the integer to invert + * @param mod - the modulus + * @return <tt>a<sup>-1</sup> mod n</tt> + */ + public static int modInverse(int a, int mod) + { + return BigInteger.valueOf(a).modInverse(BigInteger.valueOf(mod)) + .intValue(); + } + + /** + * Computes the modular inverse of an integer a + * + * @param a - the integer to invert + * @param mod - the modulus + * @return <tt>a<sup>-1</sup> mod n</tt> + */ + public static long modInverse(long a, long mod) + { + return BigInteger.valueOf(a).modInverse(BigInteger.valueOf(mod)) + .longValue(); + } + + /** + * Tests whether an integer <tt>a</tt> is power of another integer + * <tt>p</tt>. + * + * @param a - the first integer + * @param p - the second integer + * @return n if a = p^n or -1 otherwise + */ + public static int isPower(int a, int p) + { + if (a <= 0) + { + return -1; + } + int n = 0; + int d = a; + while (d > 1) + { + if (d % p != 0) + { + return -1; + } + d /= p; + n++; + } + return n; + } + + /** + * Find and return the least non-trivial divisor of an integer <tt>a</tt>. + * + * @param a - the integer + * @return divisor p >1 or 1 if a = -1,0,1 + */ + public static int leastDiv(int a) + { + if (a < 0) + { + a = -a; + } + if (a == 0) + { + return 1; + } + if ((a & 1) == 0) + { + return 2; + } + int p = 3; + while (p <= (a / p)) + { + if ((a % p) == 0) + { + return p; + } + p += 2; + } + + return a; + } + + /** + * Miller-Rabin-Test, determines wether the given integer is probably prime + * or composite. This method returns <tt>true</tt> if the given integer is + * prime with probability <tt>1 - 2<sup>-20</sup></tt>. + * + * @param n the integer to test for primality + * @return <tt>true</tt> if the given integer is prime with probability + * 2<sup>-100</sup>, <tt>false</tt> otherwise + */ + public static boolean isPrime(int n) + { + if (n < 2) + { + return false; + } + if (n == 2) + { + return true; + } + if ((n & 1) == 0) + { + return false; + } + if (n < 42) + { + for (int i = 0; i < SMALL_PRIMES.length; i++) + { + if (n == SMALL_PRIMES[i]) + { + return true; + } + } + } + + if ((n % 3 == 0) || (n % 5 == 0) || (n % 7 == 0) || (n % 11 == 0) + || (n % 13 == 0) || (n % 17 == 0) || (n % 19 == 0) + || (n % 23 == 0) || (n % 29 == 0) || (n % 31 == 0) + || (n % 37 == 0) || (n % 41 == 0)) + { + return false; + } + + return BigInteger.valueOf(n).isProbablePrime(20); + } + + /** + * Short trial-division test to find out whether a number is not prime. This + * test is usually used before a Miller-Rabin primality test. + * + * @param candidate the number to test + * @return <tt>true</tt> if the number has no factor of the tested primes, + * <tt>false</tt> if the number is definitely composite + */ + public static boolean passesSmallPrimeTest(BigInteger candidate) + { + final int[] smallPrime = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, + 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, + 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, + 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, + 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, + 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, + 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, + 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, + 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, + 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, + 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, + 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, + 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, + 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, + 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, + 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, + 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, + 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, + 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, + 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, + 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499}; + + for (int i = 0; i < smallPrime.length; i++) + { + if (candidate.mod(BigInteger.valueOf(smallPrime[i])).equals( + ZERO)) + { + return false; + } + } + return true; + } + + /** + * Returns the largest prime smaller than the given integer + * + * @param n - upper bound + * @return the largest prime smaller than <tt>n</tt>, or <tt>1</tt> if + * <tt>n <= 2</tt> + */ + public static int nextSmallerPrime(int n) + { + if (n <= 2) + { + return 1; + } + + if (n == 3) + { + return 2; + } + + if ((n & 1) == 0) + { + n--; + } + else + { + n -= 2; + } + + while (n > 3 & !isPrime(n)) + { + n -= 2; + } + return n; + } + + /** + * Compute the next probable prime greater than <tt>n</tt> with the + * specified certainty. + * + * @param n a integer number + * @param certainty the certainty that the generated number is prime + * @return the next prime greater than <tt>n</tt> + */ + public static BigInteger nextProbablePrime(BigInteger n, int certainty) + { + + if (n.signum() < 0 || n.signum() == 0 || n.equals(ONE)) + { + return TWO; + } + + BigInteger result = n.add(ONE); + + // Ensure an odd number + if (!result.testBit(0)) + { + result = result.add(ONE); + } + + while (true) + { + // Do cheap "pre-test" if applicable + if (result.bitLength() > 6) + { + long r = result.remainder( + BigInteger.valueOf(SMALL_PRIME_PRODUCT)).longValue(); + if ((r % 3 == 0) || (r % 5 == 0) || (r % 7 == 0) + || (r % 11 == 0) || (r % 13 == 0) || (r % 17 == 0) + || (r % 19 == 0) || (r % 23 == 0) || (r % 29 == 0) + || (r % 31 == 0) || (r % 37 == 0) || (r % 41 == 0)) + { + result = result.add(TWO); + continue; // Candidate is composite; try another + } + } + + // All candidates of bitLength 2 and 3 are prime by this point + if (result.bitLength() < 4) + { + return result; + } + + // The expensive test + if (result.isProbablePrime(certainty)) + { + return result; + } + + result = result.add(TWO); + } + } + + /** + * Compute the next probable prime greater than <tt>n</tt> with the default + * certainty (20). + * + * @param n a integer number + * @return the next prime greater than <tt>n</tt> + */ + public static BigInteger nextProbablePrime(BigInteger n) + { + return nextProbablePrime(n, 20); + } + + /** + * Computes the next prime greater than n. + * + * @param n a integer number + * @return the next prime greater than n + */ + public static BigInteger nextPrime(long n) + { + long i; + boolean found = false; + long result = 0; + + if (n <= 1) + { + return BigInteger.valueOf(2); + } + if (n == 2) + { + return BigInteger.valueOf(3); + } + + for (i = n + 1 + (n & 1); (i <= n << 1) && !found; i += 2) + { + for (long j = 3; (j <= i >> 1) && !found; j += 2) + { + if (i % j == 0) + { + found = true; + } + } + if (found) + { + found = false; + } + else + { + result = i; + found = true; + } + } + return BigInteger.valueOf(result); + } + + /** + * Computes the binomial coefficient (n|t) ("n over t"). Formula: + * <ul> + * <li>if n !=0 and t != 0 then (n|t) = Mult(i=1, t): (n-(i-1))/i</li> + * <li>if t = 0 then (n|t) = 1</li> + * <li>if n = 0 and t > 0 then (n|t) = 0</li> + * </ul> + * + * @param n - the "upper" integer + * @param t - the "lower" integer + * @return the binomialcoefficient "n over t" as BigInteger + */ + public static BigInteger binomial(int n, int t) + { + + BigInteger result = ONE; + + if (n == 0) + { + if (t == 0) + { + return result; + } + return ZERO; + } + + // the property (n|t) = (n|n-t) be used to reduce numbers of operations + if (t > (n >>> 1)) + { + t = n - t; + } + + for (int i = 1; i <= t; i++) + { + result = (result.multiply(BigInteger.valueOf(n - (i - 1)))) + .divide(BigInteger.valueOf(i)); + } + + return result; + } + + public static BigInteger randomize(BigInteger upperBound) + { + if (sr == null) + { + sr = new SecureRandom(); + } + return randomize(upperBound, sr); + } + + public static BigInteger randomize(BigInteger upperBound, + SecureRandom prng) + { + int blen = upperBound.bitLength(); + BigInteger randomNum = BigInteger.valueOf(0); + + if (prng == null) + { + prng = sr != null ? sr : new SecureRandom(); + } + + for (int i = 0; i < 20; i++) + { + randomNum = new BigInteger(blen, prng); + if (randomNum.compareTo(upperBound) < 0) + { + return randomNum; + } + } + return randomNum.mod(upperBound); + } + + /** + * Extract the truncated square root of a BigInteger. + * + * @param a - value out of which we extract the square root + * @return the truncated square root of <tt>a</tt> + */ + public static BigInteger squareRoot(BigInteger a) + { + int bl; + BigInteger result, remainder, b; + + if (a.compareTo(ZERO) < 0) + { + throw new ArithmeticException( + "cannot extract root of negative number" + a + "."); + } + + bl = a.bitLength(); + result = ZERO; + remainder = ZERO; + + // if the bit length is odd then extra step + if ((bl & 1) != 0) + { + result = result.add(ONE); + bl--; + } + + while (bl > 0) + { + remainder = remainder.multiply(FOUR); + remainder = remainder.add(BigInteger.valueOf((a.testBit(--bl) ? 2 + : 0) + + (a.testBit(--bl) ? 1 : 0))); + b = result.multiply(FOUR).add(ONE); + result = result.multiply(TWO); + if (remainder.compareTo(b) != -1) + { + result = result.add(ONE); + remainder = remainder.subtract(b); + } + } + + return result; + } + + /** + * Takes an approximation of the root from an integer base, using newton's + * algorithm + * + * @param base the base to take the root from + * @param root the root, for example 2 for a square root + */ + public static float intRoot(int base, int root) + { + float gNew = base / root; + float gOld = 0; + int counter = 0; + while (Math.abs(gOld - gNew) > 0.0001) + { + float gPow = floatPow(gNew, root); + while (Float.isInfinite(gPow)) + { + gNew = (gNew + gOld) / 2; + gPow = floatPow(gNew, root); + } + counter += 1; + gOld = gNew; + gNew = gOld - (gPow - base) / (root * floatPow(gOld, root - 1)); + } + return gNew; + } + + /** + * Calculation of a logarithmus of a float param + * + * @param param + * @return + */ + public static float floatLog(float param) + { + double arg = (param - 1) / (param + 1); + double arg2 = arg; + int counter = 1; + float result = (float)arg; + + while (arg2 > 0.001) + { + counter += 2; + arg2 *= arg * arg; + result += (1. / counter) * arg2; + } + return 2 * result; + } + + /** + * int power of a base float, only use for small ints + * + * @param f + * @param i + * @return + */ + public static float floatPow(float f, int i) + { + float g = 1; + for (; i > 0; i--) + { + g *= f; + } + return g; + } + + /** + * calculate the logarithm to the base 2. + * + * @param x any double value + * @return log_2(x) + * @deprecated use MathFunctions.log(double) instead + */ + public static double log(double x) + { + if (x > 0 && x < 1) + { + double d = 1 / x; + double result = -log(d); + return result; + } + + int tmp = 0; + double tmp2 = 1; + double d = x; + + while (d > 2) + { + d = d / 2; + tmp += 1; + tmp2 *= 2; + } + double rem = x / tmp2; + rem = logBKM(rem); + return tmp + rem; + } + + /** + * calculate the logarithm to the base 2. + * + * @param x any long value >=1 + * @return log_2(x) + * @deprecated use MathFunctions.log(long) instead + */ + public static double log(long x) + { + int tmp = floorLog(BigInteger.valueOf(x)); + long tmp2 = 1 << tmp; + double rem = (double)x / (double)tmp2; + rem = logBKM(rem); + return tmp + rem; + } + + /** + * BKM Algorithm to calculate logarithms to the base 2. + * + * @param arg a double value with 1<= arg<= 4.768462058 + * @return log_2(arg) + * @deprecated use MathFunctions.logBKM(double) instead + */ + private static double logBKM(double arg) + { + double ae[] = // A_e[k] = log_2 (1 + 0.5^k) + { + 1.0000000000000000000000000000000000000000000000000000000000000000000000000000, + 0.5849625007211561814537389439478165087598144076924810604557526545410982276485, + 0.3219280948873623478703194294893901758648313930245806120547563958159347765589, + 0.1699250014423123629074778878956330175196288153849621209115053090821964552970, + 0.0874628412503394082540660108104043540112672823448206881266090643866965081686, + 0.0443941193584534376531019906736094674630459333742491317685543002674288465967, + 0.0223678130284545082671320837460849094932677948156179815932199216587899627785, + 0.0112272554232541203378805844158839407281095943600297940811823651462712311786, + 0.0056245491938781069198591026740666017211096815383520359072957784732489771013, + 0.0028150156070540381547362547502839489729507927389771959487826944878598909400, + 0.0014081943928083889066101665016890524233311715793462235597709051792834906001, + 0.0007042690112466432585379340422201964456668872087249334581924550139514213168, + 0.0003521774803010272377989609925281744988670304302127133979341729842842377649, + 0.0001760994864425060348637509459678580940163670081839283659942864068257522373, + 0.0000880524301221769086378699983597183301490534085738474534831071719854721939, + 0.0000440268868273167176441087067175806394819146645511899503059774914593663365, + 0.0000220136113603404964890728830697555571275493801909791504158295359319433723, + 0.0000110068476674814423006223021573490183469930819844945565597452748333526464, + 0.0000055034343306486037230640321058826431606183125807276574241540303833251704, + 0.0000027517197895612831123023958331509538486493412831626219340570294203116559, + 0.0000013758605508411382010566802834037147561973553922354232704569052932922954, + 0.0000006879304394358496786728937442939160483304056131990916985043387874690617, + 0.0000003439652607217645360118314743718005315334062644619363447395987584138324, + 0.0000001719826406118446361936972479533123619972434705828085978955697643547921, + 0.0000000859913228686632156462565208266682841603921494181830811515318381744650, + 0.0000000429956620750168703982940244684787907148132725669106053076409624949917, + 0.0000000214978311976797556164155504126645192380395989504741781512309853438587, + 0.0000000107489156388827085092095702361647949603617203979413516082280717515504, + 0.0000000053744578294520620044408178949217773318785601260677517784797554422804, + 0.0000000026872289172287079490026152352638891824761667284401180026908031182361, + 0.0000000013436144592400232123622589569799954658536700992739887706412976115422, + 0.0000000006718072297764289157920422846078078155859484240808550018085324187007, + 0.0000000003359036149273187853169587152657145221968468364663464125722491530858, + 0.0000000001679518074734354745159899223037458278711244127245990591908996412262, + 0.0000000000839759037391617577226571237484864917411614198675604731728132152582, + 0.0000000000419879518701918839775296677020135040214077417929807824842667285938, + 0.0000000000209939759352486932678195559552767641474249812845414125580747434389, + 0.0000000000104969879676625344536740142096218372850561859495065136990936290929, + 0.0000000000052484939838408141817781356260462777942148580518406975851213868092, + 0.0000000000026242469919227938296243586262369156865545638305682553644113887909, + 0.0000000000013121234959619935994960031017850191710121890821178731821983105443, + 0.0000000000006560617479811459709189576337295395590603644549624717910616347038, + 0.0000000000003280308739906102782522178545328259781415615142931952662153623493, + 0.0000000000001640154369953144623242936888032768768777422997704541618141646683, + 0.0000000000000820077184976595619616930350508356401599552034612281802599177300, + 0.0000000000000410038592488303636807330652208397742314215159774270270147020117, + 0.0000000000000205019296244153275153381695384157073687186580546938331088730952, + 0.0000000000000102509648122077001764119940017243502120046885379813510430378661, + 0.0000000000000051254824061038591928917243090559919209628584150482483994782302, + 0.0000000000000025627412030519318726172939815845367496027046030028595094737777, + 0.0000000000000012813706015259665053515049475574143952543145124550608158430592, + 0.0000000000000006406853007629833949364669629701200556369782295210193569318434, + 0.0000000000000003203426503814917330334121037829290364330169106716787999052925, + 0.0000000000000001601713251907458754080007074659337446341494733882570243497196, + 0.0000000000000000800856625953729399268240176265844257044861248416330071223615, + 0.0000000000000000400428312976864705191179247866966320469710511619971334577509, + 0.0000000000000000200214156488432353984854413866994246781519154793320684126179, + 0.0000000000000000100107078244216177339743404416874899847406043033792202127070, + 0.0000000000000000050053539122108088756700751579281894640362199287591340285355, + 0.0000000000000000025026769561054044400057638132352058574658089256646014899499, + 0.0000000000000000012513384780527022205455634651853807110362316427807660551208, + 0.0000000000000000006256692390263511104084521222346348012116229213309001913762, + 0.0000000000000000003128346195131755552381436585278035120438976487697544916191, + 0.0000000000000000001564173097565877776275512286165232838833090480508502328437, + 0.0000000000000000000782086548782938888158954641464170239072244145219054734086, + 0.0000000000000000000391043274391469444084776945327473574450334092075712154016, + 0.0000000000000000000195521637195734722043713378812583900953755962557525252782, + 0.0000000000000000000097760818597867361022187915943503728909029699365320287407, + 0.0000000000000000000048880409298933680511176764606054809062553340323879609794, + 0.0000000000000000000024440204649466840255609083961603140683286362962192177597, + 0.0000000000000000000012220102324733420127809717395445504379645613448652614939, + 0.0000000000000000000006110051162366710063906152551383735699323415812152114058, + 0.0000000000000000000003055025581183355031953399739107113727036860315024588989, + 0.0000000000000000000001527512790591677515976780735407368332862218276873443537, + 0.0000000000000000000000763756395295838757988410584167137033767056170417508383, + 0.0000000000000000000000381878197647919378994210346199431733717514843471513618, + 0.0000000000000000000000190939098823959689497106436628681671067254111334889005, + 0.0000000000000000000000095469549411979844748553534196582286585751228071408728, + 0.0000000000000000000000047734774705989922374276846068851506055906657137209047, + 0.0000000000000000000000023867387352994961187138442777065843718711089344045782, + 0.0000000000000000000000011933693676497480593569226324192944532044984865894525, + 0.0000000000000000000000005966846838248740296784614396011477934194852481410926, + 0.0000000000000000000000002983423419124370148392307506484490384140516252814304, + 0.0000000000000000000000001491711709562185074196153830361933046331030629430117, + 0.0000000000000000000000000745855854781092537098076934460888486730708440475045, + 0.0000000000000000000000000372927927390546268549038472050424734256652501673274, + 0.0000000000000000000000000186463963695273134274519237230207489851150821191330, + 0.0000000000000000000000000093231981847636567137259618916352525606281553180093, + 0.0000000000000000000000000046615990923818283568629809533488457973317312233323, + 0.0000000000000000000000000023307995461909141784314904785572277779202790023236, + 0.0000000000000000000000000011653997730954570892157452397493151087737428485431, + 0.0000000000000000000000000005826998865477285446078726199923328593402722606924, + 0.0000000000000000000000000002913499432738642723039363100255852559084863397344, + 0.0000000000000000000000000001456749716369321361519681550201473345138307215067, + 0.0000000000000000000000000000728374858184660680759840775119123438968122488047, + 0.0000000000000000000000000000364187429092330340379920387564158411083803465567, + 0.0000000000000000000000000000182093714546165170189960193783228378441837282509, + 0.0000000000000000000000000000091046857273082585094980096891901482445902524441, + 0.0000000000000000000000000000045523428636541292547490048446022564529197237262, + 0.0000000000000000000000000000022761714318270646273745024223029238091160103901}; + int n = 53; + double x = 1; + double y = 0; + double z; + double s = 1; + int k; + + for (k = 0; k < n; k++) + { + z = x + x * s; + if (z <= arg) + { + x = z; + y += ae[k]; + } + s *= 0.5; + } + return y; + } + + public static boolean isIncreasing(int[] a) + { + for (int i = 1; i < a.length; i++) + { + if (a[i - 1] >= a[i]) + { + System.out.println("a[" + (i - 1) + "] = " + a[i - 1] + " >= " + + a[i] + " = a[" + i + "]"); + return false; + } + } + return true; + } + + public static byte[] integerToOctets(BigInteger val) + { + byte[] valBytes = val.abs().toByteArray(); + + // check whether the array includes a sign bit + if ((val.bitLength() & 7) != 0) + { + return valBytes; + } + // get rid of the sign bit (first byte) + byte[] tmp = new byte[val.bitLength() >> 3]; + System.arraycopy(valBytes, 1, tmp, 0, tmp.length); + return tmp; + } + + public static BigInteger octetsToInteger(byte[] data, int offset, + int length) + { + byte[] val = new byte[length + 1]; + + val[0] = 0; + System.arraycopy(data, offset, val, 1, length); + return new BigInteger(val); + } + + public static BigInteger octetsToInteger(byte[] data) + { + return octetsToInteger(data, 0, data.length); + } + + public static void main(String[] args) + { + System.out.println("test"); + // System.out.println(intRoot(37, 5)); + // System.out.println(floatPow((float)2.5, 4)); + System.out.println(floatLog(10)); + System.out.println("test2"); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/LittleEndianConversions.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/LittleEndianConversions.java new file mode 100644 index 00000000..11169521 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/LittleEndianConversions.java @@ -0,0 +1,230 @@ +package org.spongycastle.pqc.math.linearalgebra; + +/** + * This is a utility class containing data type conversions using little-endian + * byte order. + * + * @see BigEndianConversions + */ +public final class LittleEndianConversions +{ + + /** + * Default constructor (private). + */ + private LittleEndianConversions() + { + // empty + } + + /** + * Convert an octet string of length 4 to an integer. No length checking is + * performed. + * + * @param input the byte array holding the octet string + * @return an integer representing the octet string <tt>input</tt> + * @throws ArithmeticException if the length of the given octet string is larger than 4. + */ + public static int OS2IP(byte[] input) + { + return ((input[0] & 0xff)) | ((input[1] & 0xff) << 8) + | ((input[2] & 0xff) << 16) | ((input[3] & 0xff)) << 24; + } + + /** + * Convert an byte array of length 4 beginning at <tt>offset</tt> into an + * integer. + * + * @param input the byte array + * @param inOff the offset into the byte array + * @return the resulting integer + */ + public static int OS2IP(byte[] input, int inOff) + { + int result = input[inOff++] & 0xff; + result |= (input[inOff++] & 0xff) << 8; + result |= (input[inOff++] & 0xff) << 16; + result |= (input[inOff] & 0xff) << 24; + return result; + } + + /** + * Convert a byte array of the given length beginning at <tt>offset</tt> + * into an integer. + * + * @param input the byte array + * @param inOff the offset into the byte array + * @param inLen the length of the encoding + * @return the resulting integer + */ + public static int OS2IP(byte[] input, int inOff, int inLen) + { + int result = 0; + for (int i = inLen - 1; i >= 0; i--) + { + result |= (input[inOff + i] & 0xff) << (8 * i); + } + return result; + } + + /** + * Convert a byte array of length 8 beginning at <tt>inOff</tt> into a + * long integer. + * + * @param input the byte array + * @param inOff the offset into the byte array + * @return the resulting long integer + */ + public static long OS2LIP(byte[] input, int inOff) + { + long result = input[inOff++] & 0xff; + result |= (input[inOff++] & 0xff) << 8; + result |= (input[inOff++] & 0xff) << 16; + result |= ((long)input[inOff++] & 0xff) << 24; + result |= ((long)input[inOff++] & 0xff) << 32; + result |= ((long)input[inOff++] & 0xff) << 40; + result |= ((long)input[inOff++] & 0xff) << 48; + result |= ((long)input[inOff++] & 0xff) << 56; + return result; + } + + /** + * Convert an integer to an octet string of length 4. + * + * @param x the integer to convert + * @return the converted integer + */ + public static byte[] I2OSP(int x) + { + byte[] result = new byte[4]; + result[0] = (byte)x; + result[1] = (byte)(x >>> 8); + result[2] = (byte)(x >>> 16); + result[3] = (byte)(x >>> 24); + return result; + } + + /** + * Convert an integer into a byte array beginning at the specified offset. + * + * @param value the integer to convert + * @param output the byte array to hold the result + * @param outOff the integer offset into the byte array + */ + public static void I2OSP(int value, byte[] output, int outOff) + { + output[outOff++] = (byte)value; + output[outOff++] = (byte)(value >>> 8); + output[outOff++] = (byte)(value >>> 16); + output[outOff++] = (byte)(value >>> 24); + } + + /** + * Convert an integer to a byte array beginning at the specified offset. No + * length checking is performed (i.e., if the integer cannot be encoded with + * <tt>length</tt> octets, it is truncated). + * + * @param value the integer to convert + * @param output the byte array to hold the result + * @param outOff the integer offset into the byte array + * @param outLen the length of the encoding + */ + public static void I2OSP(int value, byte[] output, int outOff, int outLen) + { + for (int i = outLen - 1; i >= 0; i--) + { + output[outOff + i] = (byte)(value >>> (8 * i)); + } + } + + /** + * Convert an integer to a byte array of length 8. + * + * @param input the integer to convert + * @return the converted integer + */ + public static byte[] I2OSP(long input) + { + byte[] output = new byte[8]; + output[0] = (byte)input; + output[1] = (byte)(input >>> 8); + output[2] = (byte)(input >>> 16); + output[3] = (byte)(input >>> 24); + output[4] = (byte)(input >>> 32); + output[5] = (byte)(input >>> 40); + output[6] = (byte)(input >>> 48); + output[7] = (byte)(input >>> 56); + return output; + } + + /** + * Convert an integer to a byte array of length 8. + * + * @param input the integer to convert + * @param output byte array holding the output + * @param outOff offset in output array where the result is stored + */ + public static void I2OSP(long input, byte[] output, int outOff) + { + output[outOff++] = (byte)input; + output[outOff++] = (byte)(input >>> 8); + output[outOff++] = (byte)(input >>> 16); + output[outOff++] = (byte)(input >>> 24); + output[outOff++] = (byte)(input >>> 32); + output[outOff++] = (byte)(input >>> 40); + output[outOff++] = (byte)(input >>> 48); + output[outOff] = (byte)(input >>> 56); + } + + /** + * Convert an int array to a byte array of the specified length. No length + * checking is performed (i.e., if the last integer cannot be encoded with + * <tt>length % 4</tt> octets, it is truncated). + * + * @param input the int array + * @param outLen the length of the converted array + * @return the converted array + */ + public static byte[] toByteArray(int[] input, int outLen) + { + int intLen = input.length; + byte[] result = new byte[outLen]; + int index = 0; + for (int i = 0; i <= intLen - 2; i++, index += 4) + { + I2OSP(input[i], result, index); + } + I2OSP(input[intLen - 1], result, index, outLen - index); + return result; + } + + /** + * Convert a byte array to an int array. + * + * @param input the byte array + * @return the converted array + */ + public static int[] toIntArray(byte[] input) + { + int intLen = (input.length + 3) / 4; + int lastLen = input.length & 0x03; + int[] result = new int[intLen]; + + int index = 0; + for (int i = 0; i <= intLen - 2; i++, index += 4) + { + result[i] = OS2IP(input, index); + } + if (lastLen != 0) + { + result[intLen - 1] = OS2IP(input, index, lastLen); + } + else + { + result[intLen - 1] = OS2IP(input, index); + } + + return result; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/Matrix.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/Matrix.java new file mode 100644 index 00000000..170c5a21 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/Matrix.java @@ -0,0 +1,131 @@ +package org.spongycastle.pqc.math.linearalgebra; + +/** + * This abstract class defines matrices. It holds the number of rows and the + * number of columns of the matrix and defines some basic methods. + */ +public abstract class Matrix +{ + + /** + * number of rows + */ + protected int numRows; + + /** + * number of columns + */ + protected int numColumns; + + // ---------------------------------------------------- + // some constants (matrix types) + // ---------------------------------------------------- + + /** + * zero matrix + */ + public static final char MATRIX_TYPE_ZERO = 'Z'; + + /** + * unit matrix + */ + public static final char MATRIX_TYPE_UNIT = 'I'; + + /** + * random lower triangular matrix + */ + public static final char MATRIX_TYPE_RANDOM_LT = 'L'; + + /** + * random upper triangular matrix + */ + public static final char MATRIX_TYPE_RANDOM_UT = 'U'; + + /** + * random regular matrix + */ + public static final char MATRIX_TYPE_RANDOM_REGULAR = 'R'; + + // ---------------------------------------------------- + // getters + // ---------------------------------------------------- + + /** + * @return the number of rows in the matrix + */ + public int getNumRows() + { + return numRows; + } + + /** + * @return the number of columns in the binary matrix + */ + public int getNumColumns() + { + return numColumns; + } + + /** + * @return the encoded matrix, i.e., this matrix in byte array form. + */ + public abstract byte[] getEncoded(); + + // ---------------------------------------------------- + // arithmetic + // ---------------------------------------------------- + + /** + * Compute the inverse of this matrix. + * + * @return the inverse of this matrix (newly created). + */ + public abstract Matrix computeInverse(); + + /** + * Check if this is the zero matrix (i.e., all entries are zero). + * + * @return <tt>true</tt> if this is the zero matrix + */ + public abstract boolean isZero(); + + /** + * Compute the product of this matrix and another matrix. + * + * @param a the other matrix + * @return <tt>this * a</tt> (newly created) + */ + public abstract Matrix rightMultiply(Matrix a); + + /** + * Compute the product of this matrix and a permutation. + * + * @param p the permutation + * @return <tt>this * p</tt> (newly created) + */ + public abstract Matrix rightMultiply(Permutation p); + + /** + * Compute the product of a vector and this matrix. If the length of the + * vector is greater than the number of rows of this matrix, the matrix is + * multiplied by each m-bit part of the vector. + * + * @param vector a vector + * @return <tt>vector * this</tt> (newly created) + */ + public abstract Vector leftMultiply(Vector vector); + + /** + * Compute the product of this matrix and a vector. + * + * @param vector a vector + * @return <tt>this * vector</tt> (newly created) + */ + public abstract Vector rightMultiply(Vector vector); + + /** + * @return a human readable form of the matrix. + */ + public abstract String toString(); + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/Permutation.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/Permutation.java new file mode 100644 index 00000000..d156672f --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/Permutation.java @@ -0,0 +1,247 @@ +package org.spongycastle.pqc.math.linearalgebra; + +import java.security.SecureRandom; + +/** + * This class implements permutations of the set {0,1,...,n-1} for some given n + * > 0, i.e., ordered sequences containing each number <tt>m</tt> (<tt>0 <= + * m < n</tt>) + * once and only once. + */ +public class Permutation +{ + + /** + * perm holds the elements of the permutation vector, i.e. <tt>[perm(0), + * perm(1), ..., perm(n-1)]</tt> + */ + private int[] perm; + + /** + * Create the identity permutation of the given size. + * + * @param n the size of the permutation + */ + public Permutation(int n) + { + if (n <= 0) + { + throw new IllegalArgumentException("invalid length"); + } + + perm = new int[n]; + for (int i = n - 1; i >= 0; i--) + { + perm[i] = i; + } + } + + /** + * Create a permutation using the given permutation vector. + * + * @param perm the permutation vector + */ + public Permutation(int[] perm) + { + if (!isPermutation(perm)) + { + throw new IllegalArgumentException( + "array is not a permutation vector"); + } + + this.perm = IntUtils.clone(perm); + } + + /** + * Create a permutation from an encoded permutation. + * + * @param enc the encoded permutation + */ + public Permutation(byte[] enc) + { + if (enc.length <= 4) + { + throw new IllegalArgumentException("invalid encoding"); + } + + int n = LittleEndianConversions.OS2IP(enc, 0); + int size = IntegerFunctions.ceilLog256(n - 1); + + if (enc.length != 4 + n * size) + { + throw new IllegalArgumentException("invalid encoding"); + } + + perm = new int[n]; + for (int i = 0; i < n; i++) + { + perm[i] = LittleEndianConversions.OS2IP(enc, 4 + i * size, size); + } + + if (!isPermutation(perm)) + { + throw new IllegalArgumentException("invalid encoding"); + } + + } + + /** + * Create a random permutation of the given size. + * + * @param n the size of the permutation + * @param sr the source of randomness + */ + public Permutation(int n, SecureRandom sr) + { + if (n <= 0) + { + throw new IllegalArgumentException("invalid length"); + } + + perm = new int[n]; + + int[] help = new int[n]; + for (int i = 0; i < n; i++) + { + help[i] = i; + } + + int k = n; + for (int j = 0; j < n; j++) + { + int i = RandUtils.nextInt(sr, k); + k--; + perm[j] = help[i]; + help[i] = help[k]; + } + } + + /** + * Encode this permutation as byte array. + * + * @return the encoded permutation + */ + public byte[] getEncoded() + { + int n = perm.length; + int size = IntegerFunctions.ceilLog256(n - 1); + byte[] result = new byte[4 + n * size]; + LittleEndianConversions.I2OSP(n, result, 0); + for (int i = 0; i < n; i++) + { + LittleEndianConversions.I2OSP(perm[i], result, 4 + i * size, size); + } + return result; + } + + /** + * @return the permutation vector <tt>(perm(0),perm(1),...,perm(n-1))</tt> + */ + public int[] getVector() + { + return IntUtils.clone(perm); + } + + /** + * Compute the inverse permutation <tt>P<sup>-1</sup></tt>. + * + * @return <tt>this<sup>-1</sup></tt> + */ + public Permutation computeInverse() + { + Permutation result = new Permutation(perm.length); + for (int i = perm.length - 1; i >= 0; i--) + { + result.perm[perm[i]] = i; + } + return result; + } + + /** + * Compute the product of this permutation and another permutation. + * + * @param p the other permutation + * @return <tt>this * p</tt> + */ + public Permutation rightMultiply(Permutation p) + { + if (p.perm.length != perm.length) + { + throw new IllegalArgumentException("length mismatch"); + } + Permutation result = new Permutation(perm.length); + for (int i = perm.length - 1; i >= 0; i--) + { + result.perm[i] = perm[p.perm[i]]; + } + return result; + } + + /** + * checks if given object is equal to this permutation. + * <p> + * The method returns false whenever the given object is not permutation. + * + * @param other - + * permutation + * @return true or false + */ + public boolean equals(Object other) + { + + if (!(other instanceof Permutation)) + { + return false; + } + Permutation otherPerm = (Permutation)other; + + return IntUtils.equals(perm, otherPerm.perm); + } + + /** + * @return a human readable form of the permutation + */ + public String toString() + { + String result = "[" + perm[0]; + for (int i = 1; i < perm.length; i++) + { + result += ", " + perm[i]; + } + result += "]"; + return result; + } + + /** + * @return the hash code of this permutation + */ + public int hashCode() + { + return perm.hashCode(); + } + + /** + * Check that the given array corresponds to a permutation of the set + * <tt>{0, 1, ..., n-1}</tt>. + * + * @param perm permutation vector + * @return true if perm represents an n-permutation and false otherwise + */ + private boolean isPermutation(int[] perm) + { + int n = perm.length; + boolean[] onlyOnce = new boolean[n]; + + for (int i = 0; i < n; i++) + { + if ((perm[i] < 0) || (perm[i] >= n) || onlyOnce[perm[i]]) + { + return false; + } + onlyOnce[perm[i]] = true; + } + + return true; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/PolynomialGF2mSmallM.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/PolynomialGF2mSmallM.java new file mode 100644 index 00000000..6426a4ce --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/PolynomialGF2mSmallM.java @@ -0,0 +1,1124 @@ +package org.spongycastle.pqc.math.linearalgebra; + +import java.security.SecureRandom; + +/** + * This class describes operations with polynomials from the ring R = + * GF(2^m)[X], where 2 <= m <=31. + * + * @see GF2mField + * @see PolynomialRingGF2m + */ +public class PolynomialGF2mSmallM +{ + + /** + * the finite field GF(2^m) + */ + private GF2mField field; + + /** + * the degree of this polynomial + */ + private int degree; + + /** + * For the polynomial representation the map f: R->Z*, + * <tt>poly(X) -> [coef_0, coef_1, ...]</tt> is used, where + * <tt>coef_i</tt> is the <tt>i</tt>th coefficient of the polynomial + * represented as int (see {@link GF2mField}). The polynomials are stored + * as int arrays. + */ + private int[] coefficients; + + /* + * some types of polynomials + */ + + /** + * Constant used for polynomial construction (see constructor + * {@link #PolynomialGF2mSmallM(GF2mField, int, char, SecureRandom)}). + */ + public static final char RANDOM_IRREDUCIBLE_POLYNOMIAL = 'I'; + + /** + * Construct the zero polynomial over the finite field GF(2^m). + * + * @param field the finite field GF(2^m) + */ + public PolynomialGF2mSmallM(GF2mField field) + { + this.field = field; + degree = -1; + coefficients = new int[1]; + } + + /** + * Construct a polynomial over the finite field GF(2^m). + * + * @param field the finite field GF(2^m) + * @param deg degree of polynomial + * @param typeOfPolynomial type of polynomial + * @param sr PRNG + */ + public PolynomialGF2mSmallM(GF2mField field, int deg, + char typeOfPolynomial, SecureRandom sr) + { + this.field = field; + + switch (typeOfPolynomial) + { + case PolynomialGF2mSmallM.RANDOM_IRREDUCIBLE_POLYNOMIAL: + coefficients = createRandomIrreduciblePolynomial(deg, sr); + break; + default: + throw new IllegalArgumentException(" Error: type " + + typeOfPolynomial + + " is not defined for GF2smallmPolynomial"); + } + computeDegree(); + } + + /** + * Create an irreducible polynomial with the given degree over the field + * <tt>GF(2^m)</tt>. + * + * @param deg polynomial degree + * @param sr source of randomness + * @return the generated irreducible polynomial + */ + private int[] createRandomIrreduciblePolynomial(int deg, SecureRandom sr) + { + int[] resCoeff = new int[deg + 1]; + resCoeff[deg] = 1; + resCoeff[0] = field.getRandomNonZeroElement(sr); + for (int i = 1; i < deg; i++) + { + resCoeff[i] = field.getRandomElement(sr); + } + while (!isIrreducible(resCoeff)) + { + int n = RandUtils.nextInt(sr, deg); + if (n == 0) + { + resCoeff[0] = field.getRandomNonZeroElement(sr); + } + else + { + resCoeff[n] = field.getRandomElement(sr); + } + } + return resCoeff; + } + + /** + * Construct a monomial of the given degree over the finite field GF(2^m). + * + * @param field the finite field GF(2^m) + * @param degree the degree of the monomial + */ + public PolynomialGF2mSmallM(GF2mField field, int degree) + { + this.field = field; + this.degree = degree; + coefficients = new int[degree + 1]; + coefficients[degree] = 1; + } + + /** + * Construct the polynomial over the given finite field GF(2^m) from the + * given coefficient vector. + * + * @param field finite field GF2m + * @param coeffs the coefficient vector + */ + public PolynomialGF2mSmallM(GF2mField field, int[] coeffs) + { + this.field = field; + coefficients = normalForm(coeffs); + computeDegree(); + } + + /** + * Create a polynomial over the finite field GF(2^m). + * + * @param field the finite field GF(2^m) + * @param enc byte[] polynomial in byte array form + */ + public PolynomialGF2mSmallM(GF2mField field, byte[] enc) + { + this.field = field; + + // decodes polynomial + int d = 8; + int count = 1; + while (field.getDegree() > d) + { + count++; + d += 8; + } + + if ((enc.length % count) != 0) + { + throw new IllegalArgumentException( + " Error: byte array is not encoded polynomial over given finite field GF2m"); + } + + coefficients = new int[enc.length / count]; + count = 0; + for (int i = 0; i < coefficients.length; i++) + { + for (int j = 0; j < d; j += 8) + { + coefficients[i] ^= (enc[count++] & 0x000000ff) << j; + } + if (!this.field.isElementOfThisField(coefficients[i])) + { + throw new IllegalArgumentException( + " Error: byte array is not encoded polynomial over given finite field GF2m"); + } + } + // if HC = 0 for non-zero polynomial, returns error + if ((coefficients.length != 1) + && (coefficients[coefficients.length - 1] == 0)) + { + throw new IllegalArgumentException( + " Error: byte array is not encoded polynomial over given finite field GF2m"); + } + computeDegree(); + } + + /** + * Copy constructor. + * + * @param other another {@link PolynomialGF2mSmallM} + */ + public PolynomialGF2mSmallM(PolynomialGF2mSmallM other) + { + // field needs not to be cloned since it is immutable + field = other.field; + degree = other.degree; + coefficients = IntUtils.clone(other.coefficients); + } + + /** + * Create a polynomial over the finite field GF(2^m) out of the given + * coefficient vector. The finite field is also obtained from the + * {@link GF2mVector}. + * + * @param vect the coefficient vector + */ + public PolynomialGF2mSmallM(GF2mVector vect) + { + this(vect.getField(), vect.getIntArrayForm()); + } + + /* + * ------------------------ + */ + + /** + * Return the degree of this polynomial + * + * @return int degree of this polynomial if this is zero polynomial return + * -1 + */ + public int getDegree() + { + int d = coefficients.length - 1; + if (coefficients[d] == 0) + { + return -1; + } + return d; + } + + /** + * @return the head coefficient of this polynomial + */ + public int getHeadCoefficient() + { + if (degree == -1) + { + return 0; + } + return coefficients[degree]; + } + + /** + * Return the head coefficient of a polynomial. + * + * @param a the polynomial + * @return the head coefficient of <tt>a</tt> + */ + private static int headCoefficient(int[] a) + { + int degree = computeDegree(a); + if (degree == -1) + { + return 0; + } + return a[degree]; + } + + /** + * Return the coefficient with the given index. + * + * @param index the index + * @return the coefficient with the given index + */ + public int getCoefficient(int index) + { + if ((index < 0) || (index > degree)) + { + return 0; + } + return coefficients[index]; + } + + /** + * Returns encoded polynomial, i.e., this polynomial in byte array form + * + * @return the encoded polynomial + */ + public byte[] getEncoded() + { + int d = 8; + int count = 1; + while (field.getDegree() > d) + { + count++; + d += 8; + } + + byte[] res = new byte[coefficients.length * count]; + count = 0; + for (int i = 0; i < coefficients.length; i++) + { + for (int j = 0; j < d; j += 8) + { + res[count++] = (byte)(coefficients[i] >>> j); + } + } + + return res; + } + + /** + * Evaluate this polynomial <tt>p</tt> at a value <tt>e</tt> (in + * <tt>GF(2^m)</tt>) with the Horner scheme. + * + * @param e the element of the finite field GF(2^m) + * @return <tt>this(e)</tt> + */ + public int evaluateAt(int e) + { + int result = coefficients[degree]; + for (int i = degree - 1; i >= 0; i--) + { + result = field.mult(result, e) ^ coefficients[i]; + } + return result; + } + + /** + * Compute the sum of this polynomial and the given polynomial. + * + * @param addend the addend + * @return <tt>this + a</tt> (newly created) + */ + public PolynomialGF2mSmallM add(PolynomialGF2mSmallM addend) + { + int[] resultCoeff = add(coefficients, addend.coefficients); + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Add the given polynomial to this polynomial (overwrite this). + * + * @param addend the addend + */ + public void addToThis(PolynomialGF2mSmallM addend) + { + coefficients = add(coefficients, addend.coefficients); + computeDegree(); + } + + /** + * Compute the sum of two polynomials a and b over the finite field + * <tt>GF(2^m)</tt>. + * + * @param a the first polynomial + * @param b the second polynomial + * @return a + b + */ + private int[] add(int[] a, int[] b) + { + int[] result, addend; + if (a.length < b.length) + { + result = new int[b.length]; + System.arraycopy(b, 0, result, 0, b.length); + addend = a; + } + else + { + result = new int[a.length]; + System.arraycopy(a, 0, result, 0, a.length); + addend = b; + } + + for (int i = addend.length - 1; i >= 0; i--) + { + result[i] = field.add(result[i], addend[i]); + } + + return result; + } + + /** + * Compute the sum of this polynomial and the monomial of the given degree. + * + * @param degree the degree of the monomial + * @return <tt>this + X^k</tt> + */ + public PolynomialGF2mSmallM addMonomial(int degree) + { + int[] monomial = new int[degree + 1]; + monomial[degree] = 1; + int[] resultCoeff = add(coefficients, monomial); + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Compute the product of this polynomial with an element from GF(2^m). + * + * @param element an element of the finite field GF(2^m) + * @return <tt>this * element</tt> (newly created) + * @throws ArithmeticException if <tt>element</tt> is not an element of the finite + * field this polynomial is defined over. + */ + public PolynomialGF2mSmallM multWithElement(int element) + { + if (!field.isElementOfThisField(element)) + { + throw new ArithmeticException( + "Not an element of the finite field this polynomial is defined over."); + } + int[] resultCoeff = multWithElement(coefficients, element); + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Multiply this polynomial with an element from GF(2^m). + * + * @param element an element of the finite field GF(2^m) + * @throws ArithmeticException if <tt>element</tt> is not an element of the finite + * field this polynomial is defined over. + */ + public void multThisWithElement(int element) + { + if (!field.isElementOfThisField(element)) + { + throw new ArithmeticException( + "Not an element of the finite field this polynomial is defined over."); + } + coefficients = multWithElement(coefficients, element); + computeDegree(); + } + + /** + * Compute the product of a polynomial a with an element from the finite + * field <tt>GF(2^m)</tt>. + * + * @param a the polynomial + * @param element an element of the finite field GF(2^m) + * @return <tt>a * element</tt> + */ + private int[] multWithElement(int[] a, int element) + { + int degree = computeDegree(a); + if (degree == -1 || element == 0) + { + return new int[1]; + } + + if (element == 1) + { + return IntUtils.clone(a); + } + + int[] result = new int[degree + 1]; + for (int i = degree; i >= 0; i--) + { + result[i] = field.mult(a[i], element); + } + + return result; + } + + /** + * Compute the product of this polynomial with a monomial X^k. + * + * @param k the degree of the monomial + * @return <tt>this * X^k</tt> + */ + public PolynomialGF2mSmallM multWithMonomial(int k) + { + int[] resultCoeff = multWithMonomial(coefficients, k); + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Compute the product of a polynomial with a monomial X^k. + * + * @param a the polynomial + * @param k the degree of the monomial + * @return <tt>a * X^k</tt> + */ + private static int[] multWithMonomial(int[] a, int k) + { + int d = computeDegree(a); + if (d == -1) + { + return new int[1]; + } + int[] result = new int[d + k + 1]; + System.arraycopy(a, 0, result, k, d + 1); + return result; + } + + /** + * Divide this polynomial by the given polynomial. + * + * @param f a polynomial + * @return polynomial pair = {q,r} where this = q*f+r and deg(r) < + * deg(f); + */ + public PolynomialGF2mSmallM[] div(PolynomialGF2mSmallM f) + { + int[][] resultCoeffs = div(coefficients, f.coefficients); + return new PolynomialGF2mSmallM[]{ + new PolynomialGF2mSmallM(field, resultCoeffs[0]), + new PolynomialGF2mSmallM(field, resultCoeffs[1])}; + } + + /** + * Compute the result of the division of two polynomials over the field + * <tt>GF(2^m)</tt>. + * + * @param a the first polynomial + * @param f the second polynomial + * @return int[][] {q,r}, where a = q*f+r and deg(r) < deg(f); + */ + private int[][] div(int[] a, int[] f) + { + int df = computeDegree(f); + int da = computeDegree(a) + 1; + if (df == -1) + { + throw new ArithmeticException("Division by zero."); + } + int[][] result = new int[2][]; + result[0] = new int[1]; + result[1] = new int[da]; + int hc = headCoefficient(f); + hc = field.inverse(hc); + result[0][0] = 0; + System.arraycopy(a, 0, result[1], 0, result[1].length); + while (df <= computeDegree(result[1])) + { + int[] q; + int[] coeff = new int[1]; + coeff[0] = field.mult(headCoefficient(result[1]), hc); + q = multWithElement(f, coeff[0]); + int n = computeDegree(result[1]) - df; + q = multWithMonomial(q, n); + coeff = multWithMonomial(coeff, n); + result[0] = add(coeff, result[0]); + result[1] = add(q, result[1]); + } + return result; + } + + /** + * Return the greatest common divisor of this and a polynomial <i>f</i> + * + * @param f polynomial + * @return GCD(this, f) + */ + public PolynomialGF2mSmallM gcd(PolynomialGF2mSmallM f) + { + int[] resultCoeff = gcd(coefficients, f.coefficients); + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Return the greatest common divisor of two polynomials over the field + * <tt>GF(2^m)</tt>. + * + * @param f the first polynomial + * @param g the second polynomial + * @return <tt>gcd(f, g)</tt> + */ + private int[] gcd(int[] f, int[] g) + { + int[] a = f; + int[] b = g; + if (computeDegree(a) == -1) + { + return b; + } + while (computeDegree(b) != -1) + { + int[] c = mod(a, b); + a = new int[b.length]; + System.arraycopy(b, 0, a, 0, a.length); + b = new int[c.length]; + System.arraycopy(c, 0, b, 0, b.length); + } + int coeff = field.inverse(headCoefficient(a)); + return multWithElement(a, coeff); + } + + /** + * Compute the product of this polynomial and the given factor using a + * Karatzuba like scheme. + * + * @param factor the polynomial + * @return <tt>this * factor</tt> + */ + public PolynomialGF2mSmallM multiply(PolynomialGF2mSmallM factor) + { + int[] resultCoeff = multiply(coefficients, factor.coefficients); + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Compute the product of two polynomials over the field <tt>GF(2^m)</tt> + * using a Karatzuba like multiplication. + * + * @param a the first polynomial + * @param b the second polynomial + * @return a * b + */ + private int[] multiply(int[] a, int[] b) + { + int[] mult1, mult2; + if (computeDegree(a) < computeDegree(b)) + { + mult1 = b; + mult2 = a; + } + else + { + mult1 = a; + mult2 = b; + } + + mult1 = normalForm(mult1); + mult2 = normalForm(mult2); + + if (mult2.length == 1) + { + return multWithElement(mult1, mult2[0]); + } + + int d1 = mult1.length; + int d2 = mult2.length; + int[] result = new int[d1 + d2 - 1]; + + if (d2 != d1) + { + int[] res1 = new int[d2]; + int[] res2 = new int[d1 - d2]; + System.arraycopy(mult1, 0, res1, 0, res1.length); + System.arraycopy(mult1, d2, res2, 0, res2.length); + res1 = multiply(res1, mult2); + res2 = multiply(res2, mult2); + res2 = multWithMonomial(res2, d2); + result = add(res1, res2); + } + else + { + d2 = (d1 + 1) >>> 1; + int d = d1 - d2; + int[] firstPartMult1 = new int[d2]; + int[] firstPartMult2 = new int[d2]; + int[] secondPartMult1 = new int[d]; + int[] secondPartMult2 = new int[d]; + System + .arraycopy(mult1, 0, firstPartMult1, 0, + firstPartMult1.length); + System.arraycopy(mult1, d2, secondPartMult1, 0, + secondPartMult1.length); + System + .arraycopy(mult2, 0, firstPartMult2, 0, + firstPartMult2.length); + System.arraycopy(mult2, d2, secondPartMult2, 0, + secondPartMult2.length); + int[] helpPoly1 = add(firstPartMult1, secondPartMult1); + int[] helpPoly2 = add(firstPartMult2, secondPartMult2); + int[] res1 = multiply(firstPartMult1, firstPartMult2); + int[] res2 = multiply(helpPoly1, helpPoly2); + int[] res3 = multiply(secondPartMult1, secondPartMult2); + res2 = add(res2, res1); + res2 = add(res2, res3); + res3 = multWithMonomial(res3, d2); + result = add(res2, res3); + result = multWithMonomial(result, d2); + result = add(result, res1); + } + + return result; + } + + /* + * ---------------- PART II ---------------- + * + */ + + /** + * Check a polynomial for irreducibility over the field <tt>GF(2^m)</tt>. + * + * @param a the polynomial to check + * @return true if a is irreducible, false otherwise + */ + private boolean isIrreducible(int[] a) + { + if (a[0] == 0) + { + return false; + } + int d = computeDegree(a) >> 1; + int[] u = {0, 1}; + final int[] Y = {0, 1}; + int fieldDegree = field.getDegree(); + for (int i = 0; i < d; i++) + { + for (int j = fieldDegree - 1; j >= 0; j--) + { + u = modMultiply(u, u, a); + } + u = normalForm(u); + int[] g = gcd(add(u, Y), a); + if (computeDegree(g) != 0) + { + return false; + } + } + return true; + } + + /** + * Reduce this polynomial modulo another polynomial. + * + * @param f the reduction polynomial + * @return <tt>this mod f</tt> + */ + public PolynomialGF2mSmallM mod(PolynomialGF2mSmallM f) + { + int[] resultCoeff = mod(coefficients, f.coefficients); + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Reduce a polynomial modulo another polynomial. + * + * @param a the polynomial + * @param f the reduction polynomial + * @return <tt>a mod f</tt> + */ + private int[] mod(int[] a, int[] f) + { + int df = computeDegree(f); + if (df == -1) + { + throw new ArithmeticException("Division by zero"); + } + int[] result = new int[a.length]; + int hc = headCoefficient(f); + hc = field.inverse(hc); + System.arraycopy(a, 0, result, 0, result.length); + while (df <= computeDegree(result)) + { + int[] q; + int coeff = field.mult(headCoefficient(result), hc); + q = multWithMonomial(f, computeDegree(result) - df); + q = multWithElement(q, coeff); + result = add(q, result); + } + return result; + } + + /** + * Compute the product of this polynomial and another polynomial modulo a + * third polynomial. + * + * @param a another polynomial + * @param b the reduction polynomial + * @return <tt>this * a mod b</tt> + */ + public PolynomialGF2mSmallM modMultiply(PolynomialGF2mSmallM a, + PolynomialGF2mSmallM b) + { + int[] resultCoeff = modMultiply(coefficients, a.coefficients, + b.coefficients); + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Square this polynomial using a squaring matrix. + * + * @param matrix the squaring matrix + * @return <tt>this^2</tt> modulo the reduction polynomial implicitly + * given via the squaring matrix + */ + public PolynomialGF2mSmallM modSquareMatrix(PolynomialGF2mSmallM[] matrix) + { + + int length = matrix.length; + + int[] resultCoeff = new int[length]; + int[] thisSquare = new int[length]; + + // square each entry of this polynomial + for (int i = 0; i < coefficients.length; i++) + { + thisSquare[i] = field.mult(coefficients[i], coefficients[i]); + } + + // do matrix-vector multiplication + for (int i = 0; i < length; i++) + { + // compute scalar product of i-th row and coefficient vector + for (int j = 0; j < length; j++) + { + if (i >= matrix[j].coefficients.length) + { + continue; + } + int scalarTerm = field.mult(matrix[j].coefficients[i], + thisSquare[j]); + resultCoeff[i] = field.add(resultCoeff[i], scalarTerm); + } + } + + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Compute the product of two polynomials modulo a third polynomial over the + * finite field <tt>GF(2^m)</tt>. + * + * @param a the first polynomial + * @param b the second polynomial + * @param g the reduction polynomial + * @return <tt>a * b mod g</tt> + */ + private int[] modMultiply(int[] a, int[] b, int[] g) + { + return mod(multiply(a, b), g); + } + + /** + * Compute the square root of this polynomial modulo the given polynomial. + * + * @param a the reduction polynomial + * @return <tt>this^(1/2) mod a</tt> + */ + public PolynomialGF2mSmallM modSquareRoot(PolynomialGF2mSmallM a) + { + int[] resultCoeff = IntUtils.clone(coefficients); + int[] help = modMultiply(resultCoeff, resultCoeff, a.coefficients); + while (!isEqual(help, coefficients)) + { + resultCoeff = normalForm(help); + help = modMultiply(resultCoeff, resultCoeff, a.coefficients); + } + + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Compute the square root of this polynomial using a square root matrix. + * + * @param matrix the matrix for computing square roots in + * <tt>(GF(2^m))^t</tt> the polynomial ring defining the + * square root matrix + * @return <tt>this^(1/2)</tt> modulo the reduction polynomial implicitly + * given via the square root matrix + */ + public PolynomialGF2mSmallM modSquareRootMatrix( + PolynomialGF2mSmallM[] matrix) + { + + int length = matrix.length; + + int[] resultCoeff = new int[length]; + + // do matrix multiplication + for (int i = 0; i < length; i++) + { + // compute scalar product of i-th row and j-th column + for (int j = 0; j < length; j++) + { + if (i >= matrix[j].coefficients.length) + { + continue; + } + if (j < coefficients.length) + { + int scalarTerm = field.mult(matrix[j].coefficients[i], + coefficients[j]); + resultCoeff[i] = field.add(resultCoeff[i], scalarTerm); + } + } + } + + // compute the square root of each entry of the result coefficients + for (int i = 0; i < length; i++) + { + resultCoeff[i] = field.sqRoot(resultCoeff[i]); + } + + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Compute the result of the division of this polynomial by another + * polynomial modulo a third polynomial. + * + * @param divisor the divisor + * @param modulus the reduction polynomial + * @return <tt>this * divisor^(-1) mod modulus</tt> + */ + public PolynomialGF2mSmallM modDiv(PolynomialGF2mSmallM divisor, + PolynomialGF2mSmallM modulus) + { + int[] resultCoeff = modDiv(coefficients, divisor.coefficients, + modulus.coefficients); + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Compute the result of the division of two polynomials modulo a third + * polynomial over the field <tt>GF(2^m)</tt>. + * + * @param a the first polynomial + * @param b the second polynomial + * @param g the reduction polynomial + * @return <tt>a * b^(-1) mod g</tt> + */ + private int[] modDiv(int[] a, int[] b, int[] g) + { + int[] r0 = normalForm(g); + int[] r1 = mod(b, g); + int[] s0 = {0}; + int[] s1 = mod(a, g); + int[] s2; + int[][] q; + while (computeDegree(r1) != -1) + { + q = div(r0, r1); + r0 = normalForm(r1); + r1 = normalForm(q[1]); + s2 = add(s0, modMultiply(q[0], s1, g)); + s0 = normalForm(s1); + s1 = normalForm(s2); + + } + int hc = headCoefficient(r0); + s0 = multWithElement(s0, field.inverse(hc)); + return s0; + } + + /** + * Compute the inverse of this polynomial modulo the given polynomial. + * + * @param a the reduction polynomial + * @return <tt>this^(-1) mod a</tt> + */ + public PolynomialGF2mSmallM modInverse(PolynomialGF2mSmallM a) + { + int[] unit = {1}; + int[] resultCoeff = modDiv(unit, coefficients, a.coefficients); + return new PolynomialGF2mSmallM(field, resultCoeff); + } + + /** + * Compute a polynomial pair (a,b) from this polynomial and the given + * polynomial g with the property b*this = a mod g and deg(a)<=deg(g)/2. + * + * @param g the reduction polynomial + * @return PolynomialGF2mSmallM[] {a,b} with b*this = a mod g and deg(a)<= + * deg(g)/2 + */ + public PolynomialGF2mSmallM[] modPolynomialToFracton(PolynomialGF2mSmallM g) + { + int dg = g.degree >> 1; + int[] a0 = normalForm(g.coefficients); + int[] a1 = mod(coefficients, g.coefficients); + int[] b0 = {0}; + int[] b1 = {1}; + while (computeDegree(a1) > dg) + { + int[][] q = div(a0, a1); + a0 = a1; + a1 = q[1]; + int[] b2 = add(b0, modMultiply(q[0], b1, g.coefficients)); + b0 = b1; + b1 = b2; + } + + return new PolynomialGF2mSmallM[]{ + new PolynomialGF2mSmallM(field, a1), + new PolynomialGF2mSmallM(field, b1)}; + } + + /** + * checks if given object is equal to this polynomial. + * <p> + * The method returns false whenever the given object is not polynomial over + * GF(2^m). + * + * @param other object + * @return true or false + */ + public boolean equals(Object other) + { + + if (other == null || !(other instanceof PolynomialGF2mSmallM)) + { + return false; + } + + PolynomialGF2mSmallM p = (PolynomialGF2mSmallM)other; + + if ((field.equals(p.field)) && (degree == p.degree) + && (isEqual(coefficients, p.coefficients))) + { + return true; + } + + return false; + } + + /** + * Compare two polynomials given as int arrays. + * + * @param a the first polynomial + * @param b the second polynomial + * @return <tt>true</tt> if <tt>a</tt> and <tt>b</tt> represent the + * same polynomials, <tt>false</tt> otherwise + */ + private static boolean isEqual(int[] a, int[] b) + { + int da = computeDegree(a); + int db = computeDegree(b); + if (da != db) + { + return false; + } + for (int i = 0; i <= da; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + return true; + } + + /** + * @return the hash code of this polynomial + */ + public int hashCode() + { + int hash = field.hashCode(); + for (int j = 0; j < coefficients.length; j++) + { + hash = hash * 31 + coefficients[j]; + } + return hash; + } + + /** + * Returns a human readable form of the polynomial. + * + * @return a human readable form of the polynomial. + */ + public String toString() + { + String str = " Polynomial over " + field.toString() + ": \n"; + + for (int i = 0; i < coefficients.length; i++) + { + str = str + field.elementToStr(coefficients[i]) + "Y^" + i + "+"; + } + str = str + ";"; + + return str; + } + + /** + * Compute the degree of this polynomial. If this is the zero polynomial, + * the degree is -1. + */ + private void computeDegree() + { + for (degree = coefficients.length - 1; degree >= 0 + && coefficients[degree] == 0; degree--) + { + ; + } + } + + /** + * Compute the degree of a polynomial. + * + * @param a the polynomial + * @return the degree of the polynomial <tt>a</tt>. If <tt>a</tt> is + * the zero polynomial, return -1. + */ + private static int computeDegree(int[] a) + { + int degree; + for (degree = a.length - 1; degree >= 0 && a[degree] == 0; degree--) + { + ; + } + return degree; + } + + /** + * Strip leading zero coefficients from the given polynomial. + * + * @param a the polynomial + * @return the reduced polynomial + */ + private static int[] normalForm(int[] a) + { + int d = computeDegree(a); + + // if a is the zero polynomial + if (d == -1) + { + // return new zero polynomial + return new int[1]; + } + + // if a already is in normal form + if (a.length == d + 1) + { + // return a clone of a + return IntUtils.clone(a); + } + + // else, reduce a + int[] result = new int[d + 1]; + System.arraycopy(a, 0, result, 0, d + 1); + return result; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/PolynomialRingGF2.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/PolynomialRingGF2.java new file mode 100644 index 00000000..850b5dbf --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/PolynomialRingGF2.java @@ -0,0 +1,278 @@ +package org.spongycastle.pqc.math.linearalgebra; + +/** + * This class describes operations with polynomials over finite field GF(2), i e + * polynomial ring R = GF(2)[X]. All operations are defined only for polynomials + * with degree <=32. For the polynomial representation the map f: R->Z, + * poly(X)->poly(2) is used, where integers have the binary representation. For + * example: X^7+X^3+X+1 -> (00...0010001011)=139 Also for polynomials type + * Integer is used. + * + * @see GF2mField + */ +public final class PolynomialRingGF2 +{ + + /** + * Default constructor (private). + */ + private PolynomialRingGF2() + { + // empty + } + + /** + * Return sum of two polyomials + * + * @param p polynomial + * @param q polynomial + * @return p+q + */ + + public static int add(int p, int q) + { + return p ^ q; + } + + /** + * Return product of two polynomials + * + * @param p polynomial + * @param q polynomial + * @return p*q + */ + + public static long multiply(int p, int q) + { + long result = 0; + if (q != 0) + { + long q1 = q & 0x00000000ffffffffL; + + while (p != 0) + { + byte b = (byte)(p & 0x01); + if (b == 1) + { + result ^= q1; + } + p >>>= 1; + q1 <<= 1; + + } + } + return result; + } + + /** + * Compute the product of two polynomials modulo a third polynomial. + * + * @param a the first polynomial + * @param b the second polynomial + * @param r the reduction polynomial + * @return <tt>a * b mod r</tt> + */ + public static int modMultiply(int a, int b, int r) + { + int result = 0; + int p = remainder(a, r); + int q = remainder(b, r); + if (q != 0) + { + int d = 1 << degree(r); + + while (p != 0) + { + byte pMod2 = (byte)(p & 0x01); + if (pMod2 == 1) + { + result ^= q; + } + p >>>= 1; + q <<= 1; + if (q >= d) + { + q ^= r; + } + } + } + return result; + } + + /** + * Return the degree of a polynomial + * + * @param p polynomial p + * @return degree(p) + */ + + public static int degree(int p) + { + int result = -1; + while (p != 0) + { + result++; + p >>>= 1; + } + return result; + } + + /** + * Return the degree of a polynomial + * + * @param p polynomial p + * @return degree(p) + */ + + public static int degree(long p) + { + int result = 0; + while (p != 0) + { + result++; + p >>>= 1; + } + return result - 1; + } + + /** + * Return the remainder of a polynomial division of two polynomials. + * + * @param p dividend + * @param q divisor + * @return <tt>p mod q</tt> + */ + public static int remainder(int p, int q) + { + int result = p; + + if (q == 0) + { + System.err.println("Error: to be divided by 0"); + return 0; + } + + while (degree(result) >= degree(q)) + { + result ^= q << (degree(result) - degree(q)); + } + + return result; + } + + /** + * Return the rest of devision two polynomials + * + * @param p polinomial + * @param q polinomial + * @return p mod q + */ + + public static int rest(long p, int q) + { + long p1 = p; + if (q == 0) + { + System.err.println("Error: to be divided by 0"); + return 0; + } + long q1 = q & 0x00000000ffffffffL; + while ((p1 >>> 32) != 0) + { + p1 ^= q1 << (degree(p1) - degree(q1)); + } + + int result = (int)(p1 & 0xffffffff); + while (degree(result) >= degree(q)) + { + result ^= q << (degree(result) - degree(q)); + } + + return result; + } + + /** + * Return the greatest common divisor of two polynomials + * + * @param p polinomial + * @param q polinomial + * @return GCD(p, q) + */ + + public static int gcd(int p, int q) + { + int a, b, c; + a = p; + b = q; + while (b != 0) + { + c = remainder(a, b); + a = b; + b = c; + + } + return a; + } + + /** + * Checking polynomial for irreducibility + * + * @param p polinomial + * @return true if p is irreducible and false otherwise + */ + + public static boolean isIrreducible(int p) + { + if (p == 0) + { + return false; + } + int d = degree(p) >>> 1; + int u = 2; + for (int i = 0; i < d; i++) + { + u = modMultiply(u, u, p); + if (gcd(u ^ 2, p) != 1) + { + return false; + } + } + return true; + } + + /** + * Creates irreducible polynomial with degree d + * + * @param deg polynomial degree + * @return irreducible polynomial p + */ + public static int getIrreduciblePolynomial(int deg) + { + if (deg < 0) + { + System.err.println("The Degree is negative"); + return 0; + } + if (deg > 31) + { + System.err.println("The Degree is more then 31"); + return 0; + } + if (deg == 0) + { + return 1; + } + int a = 1 << deg; + a++; + int b = 1 << (deg + 1); + for (int i = a; i < b; i += 2) + { + if (isIrreducible(i)) + { + return i; + } + } + return 0; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/PolynomialRingGF2m.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/PolynomialRingGF2m.java new file mode 100644 index 00000000..7700fe52 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/PolynomialRingGF2m.java @@ -0,0 +1,175 @@ +package org.spongycastle.pqc.math.linearalgebra; + +/** + * This class represents polynomial rings <tt>GF(2^m)[X]/p(X)</tt> for + * <tt>m<32</tt>. If <tt>p(X)</tt> is irreducible, the polynomial ring + * is in fact an extension field of <tt>GF(2^m)</tt>. + */ +public class PolynomialRingGF2m +{ + + /** + * the finite field this polynomial ring is defined over + */ + private GF2mField field; + + /** + * the reduction polynomial + */ + private PolynomialGF2mSmallM p; + + /** + * the squaring matrix for this polynomial ring (given as the array of its + * row vectors) + */ + protected PolynomialGF2mSmallM[] sqMatrix; + + /** + * the matrix for computing square roots in this polynomial ring (given as + * the array of its row vectors). This matrix is computed as the inverse of + * the squaring matrix. + */ + protected PolynomialGF2mSmallM[] sqRootMatrix; + + /** + * Constructor. + * + * @param field the finite field + * @param p the reduction polynomial + */ + public PolynomialRingGF2m(GF2mField field, PolynomialGF2mSmallM p) + { + this.field = field; + this.p = p; + computeSquaringMatrix(); + computeSquareRootMatrix(); + } + + /** + * @return the squaring matrix for this polynomial ring + */ + public PolynomialGF2mSmallM[] getSquaringMatrix() + { + return sqMatrix; + } + + /** + * @return the matrix for computing square roots for this polynomial ring + */ + public PolynomialGF2mSmallM[] getSquareRootMatrix() + { + return sqRootMatrix; + } + + /** + * Compute the squaring matrix for this polynomial ring, using the base + * field and the reduction polynomial. + */ + private void computeSquaringMatrix() + { + int numColumns = p.getDegree(); + sqMatrix = new PolynomialGF2mSmallM[numColumns]; + for (int i = 0; i < numColumns >> 1; i++) + { + int[] monomCoeffs = new int[(i << 1) + 1]; + monomCoeffs[i << 1] = 1; + sqMatrix[i] = new PolynomialGF2mSmallM(field, monomCoeffs); + } + for (int i = numColumns >> 1; i < numColumns; i++) + { + int[] monomCoeffs = new int[(i << 1) + 1]; + monomCoeffs[i << 1] = 1; + PolynomialGF2mSmallM monomial = new PolynomialGF2mSmallM(field, + monomCoeffs); + sqMatrix[i] = monomial.mod(p); + } + } + + /** + * Compute the matrix for computing square roots in this polynomial ring by + * inverting the squaring matrix. + */ + private void computeSquareRootMatrix() + { + int numColumns = p.getDegree(); + + // clone squaring matrix + PolynomialGF2mSmallM[] tmpMatrix = new PolynomialGF2mSmallM[numColumns]; + for (int i = numColumns - 1; i >= 0; i--) + { + tmpMatrix[i] = new PolynomialGF2mSmallM(sqMatrix[i]); + } + + // initialize square root matrix as unit matrix + sqRootMatrix = new PolynomialGF2mSmallM[numColumns]; + for (int i = numColumns - 1; i >= 0; i--) + { + sqRootMatrix[i] = new PolynomialGF2mSmallM(field, i); + } + + // simultaneously compute Gaussian reduction of squaring matrix and unit + // matrix + for (int i = 0; i < numColumns; i++) + { + // if diagonal element is zero + if (tmpMatrix[i].getCoefficient(i) == 0) + { + boolean foundNonZero = false; + // find a non-zero element in the same row + for (int j = i + 1; j < numColumns; j++) + { + if (tmpMatrix[j].getCoefficient(i) != 0) + { + // found it, swap columns ... + foundNonZero = true; + swapColumns(tmpMatrix, i, j); + swapColumns(sqRootMatrix, i, j); + // ... and quit searching + j = numColumns; + continue; + } + } + // if no non-zero element was found + if (!foundNonZero) + { + // the matrix is not invertible + throw new ArithmeticException( + "Squaring matrix is not invertible."); + } + } + + // normalize i-th column + int coef = tmpMatrix[i].getCoefficient(i); + int invCoef = field.inverse(coef); + tmpMatrix[i].multThisWithElement(invCoef); + sqRootMatrix[i].multThisWithElement(invCoef); + + // normalize all other columns + for (int j = 0; j < numColumns; j++) + { + if (j != i) + { + coef = tmpMatrix[j].getCoefficient(i); + if (coef != 0) + { + PolynomialGF2mSmallM tmpSqColumn = tmpMatrix[i] + .multWithElement(coef); + PolynomialGF2mSmallM tmpInvColumn = sqRootMatrix[i] + .multWithElement(coef); + tmpMatrix[j].addToThis(tmpSqColumn); + sqRootMatrix[j].addToThis(tmpInvColumn); + } + } + } + } + } + + private static void swapColumns(PolynomialGF2mSmallM[] matrix, int first, + int second) + { + PolynomialGF2mSmallM tmp = matrix[first]; + matrix[first] = matrix[second]; + matrix[second] = tmp; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/RandUtils.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/RandUtils.java new file mode 100644 index 00000000..34862d7c --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/RandUtils.java @@ -0,0 +1,25 @@ +package org.spongycastle.pqc.math.linearalgebra; + +import java.security.SecureRandom; + +public class RandUtils +{ + static int nextInt(SecureRandom rand, int n) + { + + if ((n & -n) == n) // i.e., n is a power of 2 + { + return (int)((n * (long)(rand.nextInt() >>> 1)) >> 31); + } + + int bits, value; + do + { + bits = rand.nextInt() >>> 1; + value = bits % n; + } + while (bits - value + (n - 1) < 0); + + return value; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/Vector.java b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/Vector.java new file mode 100644 index 00000000..7f33d735 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/linearalgebra/Vector.java @@ -0,0 +1,69 @@ +package org.spongycastle.pqc.math.linearalgebra; + +/** + * This abstract class defines vectors. It holds the length of vector. + */ +public abstract class Vector +{ + + /** + * the length of this vector + */ + protected int length; + + /** + * @return the length of this vector + */ + public final int getLength() + { + return length; + } + + /** + * @return this vector as byte array + */ + public abstract byte[] getEncoded(); + + /** + * Return whether this is the zero vector (i.e., all elements are zero). + * + * @return <tt>true</tt> if this is the zero vector, <tt>false</tt> + * otherwise + */ + public abstract boolean isZero(); + + /** + * Add another vector to this vector. + * + * @param addend the other vector + * @return <tt>this + addend</tt> + */ + public abstract Vector add(Vector addend); + + /** + * Multiply this vector with a permutation. + * + * @param p the permutation + * @return <tt>this*p = p*this</tt> + */ + public abstract Vector multiply(Permutation p); + + /** + * Check if the given object is equal to this vector. + * + * @param other vector + * @return the result of the comparison + */ + public abstract boolean equals(Object other); + + /** + * @return the hash code of this vector + */ + public abstract int hashCode(); + + /** + * @return a human readable form of this vector + */ + public abstract String toString(); + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/euclid/BigIntEuclidean.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/euclid/BigIntEuclidean.java new file mode 100644 index 00000000..e48d2a0c --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/euclid/BigIntEuclidean.java @@ -0,0 +1,54 @@ +package org.spongycastle.pqc.math.ntru.euclid; + +import java.math.BigInteger; + +/** + * Extended Euclidean Algorithm in <code>BigInteger</code>s + */ +public class BigIntEuclidean +{ + public BigInteger x, y, gcd; + + private BigIntEuclidean() + { + } + + /** + * Runs the EEA on two <code>BigInteger</code>s<br> + * Implemented from pseudocode on <a href="http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm">Wikipedia</a>. + * + * @param a + * @param b + * @return a <code>BigIntEuclidean</code> object that contains the result in the variables <code>x</code>, <code>y</code>, and <code>gcd</code> + */ + public static BigIntEuclidean calculate(BigInteger a, BigInteger b) + { + BigInteger x = BigInteger.ZERO; + BigInteger lastx = BigInteger.ONE; + BigInteger y = BigInteger.ONE; + BigInteger lasty = BigInteger.ZERO; + while (!b.equals(BigInteger.ZERO)) + { + BigInteger[] quotientAndRemainder = a.divideAndRemainder(b); + BigInteger quotient = quotientAndRemainder[0]; + + BigInteger temp = a; + a = b; + b = quotientAndRemainder[1]; + + temp = x; + x = lastx.subtract(quotient.multiply(x)); + lastx = temp; + + temp = y; + y = lasty.subtract(quotient.multiply(y)); + lasty = temp; + } + + BigIntEuclidean result = new BigIntEuclidean(); + result.x = lastx; + result.y = lasty; + result.gcd = a; + return result; + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/euclid/IntEuclidean.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/euclid/IntEuclidean.java new file mode 100644 index 00000000..0791b01e --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/euclid/IntEuclidean.java @@ -0,0 +1,51 @@ +package org.spongycastle.pqc.math.ntru.euclid; + +/** + * Extended Euclidean Algorithm in <code>int</code>s + */ +public class IntEuclidean +{ + public int x, y, gcd; + + private IntEuclidean() + { + } + + /** + * Runs the EEA on two <code>int</code>s<br> + * Implemented from pseudocode on <a href="http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm">Wikipedia</a>. + * + * @param a + * @param b + * @return a <code>IntEuclidean</code> object that contains the result in the variables <code>x</code>, <code>y</code>, and <code>gcd</code> + */ + public static IntEuclidean calculate(int a, int b) + { + int x = 0; + int lastx = 1; + int y = 1; + int lasty = 0; + while (b != 0) + { + int quotient = a / b; + + int temp = a; + a = b; + b = temp % b; + + temp = x; + x = lastx - quotient * x; + lastx = temp; + + temp = y; + y = lasty - quotient * y; + lasty = temp; + } + + IntEuclidean result = new IntEuclidean(); + result.x = lastx; + result.y = lasty; + result.gcd = a; + return result; + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/BigDecimalPolynomial.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/BigDecimalPolynomial.java new file mode 100644 index 00000000..a496060c --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/BigDecimalPolynomial.java @@ -0,0 +1,258 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +import java.math.BigDecimal; + +/** + * A polynomial with {@link BigDecimal} coefficients. + * Some methods (like <code>add</code>) change the polynomial, others (like <code>mult</code>) do + * not but return the result as a new polynomial. + */ +public class BigDecimalPolynomial +{ + private static final BigDecimal ZERO = new BigDecimal("0"); + private static final BigDecimal ONE_HALF = new BigDecimal("0.5"); + + BigDecimal[] coeffs; + + /** + * Constructs a new polynomial with <code>N</code> coefficients initialized to 0. + * + * @param N the number of coefficients + */ + BigDecimalPolynomial(int N) + { + coeffs = new BigDecimal[N]; + for (int i = 0; i < N; i++) + { + coeffs[i] = ZERO; + } + } + + /** + * Constructs a new polynomial with a given set of coefficients. + * + * @param coeffs the coefficients + */ + BigDecimalPolynomial(BigDecimal[] coeffs) + { + this.coeffs = coeffs; + } + + /** + * Constructs a <code>BigDecimalPolynomial</code> from a <code>BigIntPolynomial</code>. The two polynomials are independent of each other. + * + * @param p the original polynomial + */ + public BigDecimalPolynomial(BigIntPolynomial p) + { + int N = p.coeffs.length; + coeffs = new BigDecimal[N]; + for (int i = 0; i < N; i++) + { + coeffs[i] = new BigDecimal(p.coeffs[i]); + } + } + + /** + * Divides all coefficients by 2. + */ + public void halve() + { + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] = coeffs[i].multiply(ONE_HALF); + } + } + + /** + * Multiplies the polynomial by another. Does not change this polynomial + * but returns the result as a new polynomial. + * + * @param poly2 the polynomial to multiply by + * @return a new polynomial + */ + public BigDecimalPolynomial mult(BigIntPolynomial poly2) + { + return mult(new BigDecimalPolynomial(poly2)); + } + + /** + * Multiplies the polynomial by another, taking the indices mod N. Does not + * change this polynomial but returns the result as a new polynomial. + * + * @param poly2 the polynomial to multiply by + * @return a new polynomial + */ + public BigDecimalPolynomial mult(BigDecimalPolynomial poly2) + { + int N = coeffs.length; + if (poly2.coeffs.length != N) + { + throw new IllegalArgumentException("Number of coefficients must be the same"); + } + + BigDecimalPolynomial c = multRecursive(poly2); + + if (c.coeffs.length > N) + { + for (int k = N; k < c.coeffs.length; k++) + { + c.coeffs[k - N] = c.coeffs[k - N].add(c.coeffs[k]); + } + c.coeffs = copyOf(c.coeffs, N); + } + return c; + } + + /** + * Karazuba multiplication + */ + private BigDecimalPolynomial multRecursive(BigDecimalPolynomial poly2) + { + BigDecimal[] a = coeffs; + BigDecimal[] b = poly2.coeffs; + + int n = poly2.coeffs.length; + if (n <= 1) + { + BigDecimal[] c = coeffs.clone(); + for (int i = 0; i < coeffs.length; i++) + { + c[i] = c[i].multiply(poly2.coeffs[0]); + } + return new BigDecimalPolynomial(c); + } + else + { + int n1 = n / 2; + + BigDecimalPolynomial a1 = new BigDecimalPolynomial(copyOf(a, n1)); + BigDecimalPolynomial a2 = new BigDecimalPolynomial(copyOfRange(a, n1, n)); + BigDecimalPolynomial b1 = new BigDecimalPolynomial(copyOf(b, n1)); + BigDecimalPolynomial b2 = new BigDecimalPolynomial(copyOfRange(b, n1, n)); + + BigDecimalPolynomial A = (BigDecimalPolynomial)a1.clone(); + A.add(a2); + BigDecimalPolynomial B = (BigDecimalPolynomial)b1.clone(); + B.add(b2); + + BigDecimalPolynomial c1 = a1.multRecursive(b1); + BigDecimalPolynomial c2 = a2.multRecursive(b2); + BigDecimalPolynomial c3 = A.multRecursive(B); + c3.sub(c1); + c3.sub(c2); + + BigDecimalPolynomial c = new BigDecimalPolynomial(2 * n - 1); + for (int i = 0; i < c1.coeffs.length; i++) + { + c.coeffs[i] = c1.coeffs[i]; + } + for (int i = 0; i < c3.coeffs.length; i++) + { + c.coeffs[n1 + i] = c.coeffs[n1 + i].add(c3.coeffs[i]); + } + for (int i = 0; i < c2.coeffs.length; i++) + { + c.coeffs[2 * n1 + i] = c.coeffs[2 * n1 + i].add(c2.coeffs[i]); + } + return c; + } + } + + /** + * Adds another polynomial which can have a different number of coefficients. + * + * @param b another polynomial + */ + public void add(BigDecimalPolynomial b) + { + if (b.coeffs.length > coeffs.length) + { + int N = coeffs.length; + coeffs = copyOf(coeffs, b.coeffs.length); + for (int i = N; i < coeffs.length; i++) + { + coeffs[i] = ZERO; + } + } + for (int i = 0; i < b.coeffs.length; i++) + { + coeffs[i] = coeffs[i].add(b.coeffs[i]); + } + } + + /** + * Subtracts another polynomial which can have a different number of coefficients. + * + * @param b + */ + void sub(BigDecimalPolynomial b) + { + if (b.coeffs.length > coeffs.length) + { + int N = coeffs.length; + coeffs = copyOf(coeffs, b.coeffs.length); + for (int i = N; i < coeffs.length; i++) + { + coeffs[i] = ZERO; + } + } + for (int i = 0; i < b.coeffs.length; i++) + { + coeffs[i] = coeffs[i].subtract(b.coeffs[i]); + } + } + + /** + * Rounds all coefficients to the nearest integer. + * + * @return a new polynomial with <code>BigInteger</code> coefficients + */ + public BigIntPolynomial round() + { + int N = coeffs.length; + BigIntPolynomial p = new BigIntPolynomial(N); + for (int i = 0; i < N; i++) + { + p.coeffs[i] = coeffs[i].setScale(0, BigDecimal.ROUND_HALF_EVEN).toBigInteger(); + } + return p; + } + + /** + * Makes a copy of the polynomial that is independent of the original. + */ + public Object clone() + { + return new BigDecimalPolynomial(coeffs.clone()); + } + + private BigDecimal[] copyOf(BigDecimal[] a, int length) + { + BigDecimal[] tmp = new BigDecimal[length]; + + System.arraycopy(a, 0, tmp, 0, a.length < length ? a.length : length); + + return tmp; + } + + private BigDecimal[] copyOfRange(BigDecimal[] a, int from, int to) + { + int newLength = to - from; + BigDecimal[] tmp = new BigDecimal[to - from]; + + System.arraycopy(a, from, tmp, 0, (a.length - from) < newLength ? (a.length - from) : newLength); + + return tmp; + } + + public BigDecimal[] getCoeffs() + { + BigDecimal[] tmp = new BigDecimal[coeffs.length]; + + System.arraycopy(coeffs, 0, tmp, 0, coeffs.length); + + return tmp; + } + +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/BigIntPolynomial.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/BigIntPolynomial.java new file mode 100644 index 00000000..2869fec8 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/BigIntPolynomial.java @@ -0,0 +1,394 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.spongycastle.util.Arrays; + +/** + * A polynomial with {@link BigInteger} coefficients.<br> + * Some methods (like <code>add</code>) change the polynomial, others (like <code>mult</code>) do + * not but return the result as a new polynomial. + */ +public class BigIntPolynomial +{ + private final static double LOG_10_2 = Math.log10(2); + + BigInteger[] coeffs; + + /** + * Constructs a new polynomial with <code>N</code> coefficients initialized to 0. + * + * @param N the number of coefficients + */ + BigIntPolynomial(int N) + { + coeffs = new BigInteger[N]; + for (int i = 0; i < N; i++) + { + coeffs[i] = Constants.BIGINT_ZERO; + } + } + + /** + * Constructs a new polynomial with a given set of coefficients. + * + * @param coeffs the coefficients + */ + BigIntPolynomial(BigInteger[] coeffs) + { + this.coeffs = coeffs; + } + + /** + * Constructs a <code>BigIntPolynomial</code> from a <code>IntegerPolynomial</code>. The two polynomials are + * independent of each other. + * + * @param p the original polynomial + */ + public BigIntPolynomial(IntegerPolynomial p) + { + coeffs = new BigInteger[p.coeffs.length]; + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] = BigInteger.valueOf(p.coeffs[i]); + } + } + + /** + * Generates a random polynomial with <code>numOnes</code> coefficients equal to 1, + * <code>numNegOnes</code> coefficients equal to -1, and the rest equal to 0. + * + * @param N number of coefficients + * @param numOnes number of 1's + * @param numNegOnes number of -1's + * @return + */ + static BigIntPolynomial generateRandomSmall(int N, int numOnes, int numNegOnes) + { + List coeffs = new ArrayList(); + for (int i = 0; i < numOnes; i++) + { + coeffs.add(Constants.BIGINT_ONE); + } + for (int i = 0; i < numNegOnes; i++) + { + coeffs.add(BigInteger.valueOf(-1)); + } + while (coeffs.size() < N) + { + coeffs.add(Constants.BIGINT_ZERO); + } + Collections.shuffle(coeffs, new SecureRandom()); + + BigIntPolynomial poly = new BigIntPolynomial(N); + for (int i = 0; i < coeffs.size(); i++) + { + poly.coeffs[i] = (BigInteger)coeffs.get(i); + } + return poly; + } + + /** + * Multiplies the polynomial by another, taking the indices mod N. Does not + * change this polynomial but returns the result as a new polynomial.<br> + * Both polynomials must have the same number of coefficients. + * + * @param poly2 the polynomial to multiply by + * @return a new polynomial + */ + public BigIntPolynomial mult(BigIntPolynomial poly2) + { + int N = coeffs.length; + if (poly2.coeffs.length != N) + { + throw new IllegalArgumentException("Number of coefficients must be the same"); + } + + BigIntPolynomial c = multRecursive(poly2); + + if (c.coeffs.length > N) + { + for (int k = N; k < c.coeffs.length; k++) + { + c.coeffs[k - N] = c.coeffs[k - N].add(c.coeffs[k]); + } + c.coeffs = Arrays.copyOf(c.coeffs, N); + } + return c; + } + + /** + * Karazuba multiplication + */ + private BigIntPolynomial multRecursive(BigIntPolynomial poly2) + { + BigInteger[] a = coeffs; + BigInteger[] b = poly2.coeffs; + + int n = poly2.coeffs.length; + if (n <= 1) + { + BigInteger[] c = Arrays.clone(coeffs); + for (int i = 0; i < coeffs.length; i++) + { + c[i] = c[i].multiply(poly2.coeffs[0]); + } + return new BigIntPolynomial(c); + } + else + { + int n1 = n / 2; + + BigIntPolynomial a1 = new BigIntPolynomial(Arrays.copyOf(a, n1)); + BigIntPolynomial a2 = new BigIntPolynomial(Arrays.copyOfRange(a, n1, n)); + BigIntPolynomial b1 = new BigIntPolynomial(Arrays.copyOf(b, n1)); + BigIntPolynomial b2 = new BigIntPolynomial(Arrays.copyOfRange(b, n1, n)); + + BigIntPolynomial A = (BigIntPolynomial)a1.clone(); + A.add(a2); + BigIntPolynomial B = (BigIntPolynomial)b1.clone(); + B.add(b2); + + BigIntPolynomial c1 = a1.multRecursive(b1); + BigIntPolynomial c2 = a2.multRecursive(b2); + BigIntPolynomial c3 = A.multRecursive(B); + c3.sub(c1); + c3.sub(c2); + + BigIntPolynomial c = new BigIntPolynomial(2 * n - 1); + for (int i = 0; i < c1.coeffs.length; i++) + { + c.coeffs[i] = c1.coeffs[i]; + } + for (int i = 0; i < c3.coeffs.length; i++) + { + c.coeffs[n1 + i] = c.coeffs[n1 + i].add(c3.coeffs[i]); + } + for (int i = 0; i < c2.coeffs.length; i++) + { + c.coeffs[2 * n1 + i] = c.coeffs[2 * n1 + i].add(c2.coeffs[i]); + } + return c; + } + } + + /** + * Adds another polynomial which can have a different number of coefficients, + * and takes the coefficient values mod <code>modulus</code>. + * + * @param b another polynomial + */ + void add(BigIntPolynomial b, BigInteger modulus) + { + add(b); + mod(modulus); + } + + /** + * Adds another polynomial which can have a different number of coefficients. + * + * @param b another polynomial + */ + public void add(BigIntPolynomial b) + { + if (b.coeffs.length > coeffs.length) + { + int N = coeffs.length; + coeffs = Arrays.copyOf(coeffs, b.coeffs.length); + for (int i = N; i < coeffs.length; i++) + { + coeffs[i] = Constants.BIGINT_ZERO; + } + } + for (int i = 0; i < b.coeffs.length; i++) + { + coeffs[i] = coeffs[i].add(b.coeffs[i]); + } + } + + /** + * Subtracts another polynomial which can have a different number of coefficients. + * + * @param b another polynomial + */ + public void sub(BigIntPolynomial b) + { + if (b.coeffs.length > coeffs.length) + { + int N = coeffs.length; + coeffs = Arrays.copyOf(coeffs, b.coeffs.length); + for (int i = N; i < coeffs.length; i++) + { + coeffs[i] = Constants.BIGINT_ZERO; + } + } + for (int i = 0; i < b.coeffs.length; i++) + { + coeffs[i] = coeffs[i].subtract(b.coeffs[i]); + } + } + + /** + * Multiplies each coefficient by a <code>BigInteger</code>. Does not return a new polynomial but modifies this polynomial. + * + * @param factor + */ + public void mult(BigInteger factor) + { + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] = coeffs[i].multiply(factor); + } + } + + /** + * Multiplies each coefficient by a <code>int</code>. Does not return a new polynomial but modifies this polynomial. + * + * @param factor + */ + void mult(int factor) + { + mult(BigInteger.valueOf(factor)); + } + + /** + * Divides each coefficient by a <code>BigInteger</code> and rounds the result to the nearest whole number.<br> + * Does not return a new polynomial but modifies this polynomial. + * + * @param divisor the number to divide by + */ + public void div(BigInteger divisor) + { + BigInteger d = divisor.add(Constants.BIGINT_ONE).divide(BigInteger.valueOf(2)); + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] = coeffs[i].compareTo(Constants.BIGINT_ZERO) > 0 ? coeffs[i].add(d) : coeffs[i].add(d.negate()); + coeffs[i] = coeffs[i].divide(divisor); + } + } + + /** + * Divides each coefficient by a <code>BigDecimal</code> and rounds the result to <code>decimalPlaces</code> places. + * + * @param divisor the number to divide by + * @param decimalPlaces the number of fractional digits to round the result to + * @return a new <code>BigDecimalPolynomial</code> + */ + public BigDecimalPolynomial div(BigDecimal divisor, int decimalPlaces) + { + BigInteger max = maxCoeffAbs(); + int coeffLength = (int)(max.bitLength() * LOG_10_2) + 1; + // factor = 1/divisor + BigDecimal factor = Constants.BIGDEC_ONE.divide(divisor, coeffLength + decimalPlaces + 1, BigDecimal.ROUND_HALF_EVEN); + + // multiply each coefficient by factor + BigDecimalPolynomial p = new BigDecimalPolynomial(coeffs.length); + for (int i = 0; i < coeffs.length; i++) + // multiply, then truncate after decimalPlaces so subsequent operations aren't slowed down + { + p.coeffs[i] = new BigDecimal(coeffs[i]).multiply(factor).setScale(decimalPlaces, BigDecimal.ROUND_HALF_EVEN); + } + + return p; + } + + /** + * Returns the base10 length of the largest coefficient. + * + * @return length of the longest coefficient + */ + public int getMaxCoeffLength() + { + return (int)(maxCoeffAbs().bitLength() * LOG_10_2) + 1; + } + + private BigInteger maxCoeffAbs() + { + BigInteger max = coeffs[0].abs(); + for (int i = 1; i < coeffs.length; i++) + { + BigInteger coeff = coeffs[i].abs(); + if (coeff.compareTo(max) > 0) + { + max = coeff; + } + } + return max; + } + + /** + * Takes each coefficient modulo a number. + * + * @param modulus + */ + public void mod(BigInteger modulus) + { + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] = coeffs[i].mod(modulus); + } + } + + /** + * Returns the sum of all coefficients, i.e. evaluates the polynomial at 0. + * + * @return the sum of all coefficients + */ + BigInteger sumCoeffs() + { + BigInteger sum = Constants.BIGINT_ZERO; + for (int i = 0; i < coeffs.length; i++) + { + sum = sum.add(coeffs[i]); + } + return sum; + } + + /** + * Makes a copy of the polynomial that is independent of the original. + */ + public Object clone() + { + return new BigIntPolynomial(coeffs.clone()); + } + + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(coeffs); + return result; + } + + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + BigIntPolynomial other = (BigIntPolynomial)obj; + if (!Arrays.areEqual(coeffs, other.coeffs)) + { + return false; + } + return true; + } + + public BigInteger[] getCoeffs() + { + return Arrays.clone(coeffs); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/Constants.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/Constants.java new file mode 100644 index 00000000..f29ce066 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/Constants.java @@ -0,0 +1,12 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +import java.math.BigDecimal; +import java.math.BigInteger; + +public class Constants +{ + static final BigInteger BIGINT_ZERO = BigInteger.valueOf(0); + static final BigInteger BIGINT_ONE = BigInteger.valueOf(1); + + static final BigDecimal BIGDEC_ONE = BigDecimal.valueOf(1); +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/DenseTernaryPolynomial.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/DenseTernaryPolynomial.java new file mode 100644 index 00000000..007e7249 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/DenseTernaryPolynomial.java @@ -0,0 +1,142 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +import java.security.SecureRandom; + +import org.spongycastle.pqc.math.ntru.util.Util; +import org.spongycastle.util.Arrays; + +/** + * A <code>TernaryPolynomial</code> with a "high" number of nonzero coefficients. + */ +public class DenseTernaryPolynomial + extends IntegerPolynomial + implements TernaryPolynomial +{ + + /** + * Constructs a new <code>DenseTernaryPolynomial</code> with <code>N</code> coefficients. + * + * @param N the number of coefficients + */ + DenseTernaryPolynomial(int N) + { + super(N); + checkTernarity(); + } + + /** + * Constructs a <code>DenseTernaryPolynomial</code> from a <code>IntegerPolynomial</code>. The two polynomials are + * independent of each other. + * + * @param intPoly the original polynomial + */ + public DenseTernaryPolynomial(IntegerPolynomial intPoly) + { + this(intPoly.coeffs); + } + + /** + * Constructs a new <code>DenseTernaryPolynomial</code> with a given set of coefficients. + * + * @param coeffs the coefficients + */ + public DenseTernaryPolynomial(int[] coeffs) + { + super(coeffs); + checkTernarity(); + } + + private void checkTernarity() + { + for (int i = 0; i != coeffs.length; i++) + { + int c = coeffs[i]; + if (c < -1 || c > 1) + { + throw new IllegalStateException("Illegal value: " + c + ", must be one of {-1, 0, 1}"); + } + } + } + + /** + * Generates a random polynomial with <code>numOnes</code> coefficients equal to 1, + * <code>numNegOnes</code> coefficients equal to -1, and the rest equal to 0. + * + * @param N number of coefficients + * @param numOnes number of 1's + * @param numNegOnes number of -1's + */ + public static DenseTernaryPolynomial generateRandom(int N, int numOnes, int numNegOnes, SecureRandom random) + { + int[] coeffs = Util.generateRandomTernary(N, numOnes, numNegOnes, random); + return new DenseTernaryPolynomial(coeffs); + } + + /** + * Generates a polynomial with coefficients randomly selected from <code>{-1, 0, 1}</code>. + * + * @param N number of coefficients + */ + public static DenseTernaryPolynomial generateRandom(int N, SecureRandom random) + { + DenseTernaryPolynomial poly = new DenseTernaryPolynomial(N); + for (int i = 0; i < N; i++) + { + poly.coeffs[i] = random.nextInt(3) - 1; + } + return poly; + } + + public IntegerPolynomial mult(IntegerPolynomial poly2, int modulus) + { + // even on 32-bit systems, LongPolynomial5 multiplies faster than IntegerPolynomial + if (modulus == 2048) + { + IntegerPolynomial poly2Pos = (IntegerPolynomial)poly2.clone(); + poly2Pos.modPositive(2048); + LongPolynomial5 poly5 = new LongPolynomial5(poly2Pos); + return poly5.mult(this).toIntegerPolynomial(); + } + else + { + return super.mult(poly2, modulus); + } + } + + public int[] getOnes() + { + int N = coeffs.length; + int[] ones = new int[N]; + int onesIdx = 0; + for (int i = 0; i < N; i++) + { + int c = coeffs[i]; + if (c == 1) + { + ones[onesIdx++] = i; + } + } + return Arrays.copyOf(ones, onesIdx); + } + + public int[] getNegOnes() + { + int N = coeffs.length; + int[] negOnes = new int[N]; + int negOnesIdx = 0; + for (int i = 0; i < N; i++) + { + int c = coeffs[i]; + if (c == -1) + { + negOnes[negOnesIdx++] = i; + } + } + return Arrays.copyOf(negOnes, negOnesIdx); + } + + public int size() + { + return coeffs.length; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/IntegerPolynomial.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/IntegerPolynomial.java new file mode 100644 index 00000000..41e921bf --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/IntegerPolynomial.java @@ -0,0 +1,1358 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; + +import org.spongycastle.pqc.math.ntru.euclid.BigIntEuclidean; +import org.spongycastle.pqc.math.ntru.util.ArrayEncoder; +import org.spongycastle.pqc.math.ntru.util.Util; +import org.spongycastle.util.Arrays; + +/** + * A polynomial with <code>int</code> coefficients.<br> + * Some methods (like <code>add</code>) change the polynomial, others (like <code>mult</code>) do + * not but return the result as a new polynomial. + */ +public class IntegerPolynomial + implements Polynomial +{ + private static final int NUM_EQUAL_RESULTANTS = 3; + /** + * Prime numbers > 4500 for resultant computation. Starting them below ~4400 causes incorrect results occasionally. + * Fortunately, 4500 is about the optimum number for performance.<br/> + * This array contains enough prime numbers so primes never have to be computed on-line for any standard {@link org.spongycastle.pqc.crypto.ntru.NTRUSigningParameters}. + */ + private static final int[] PRIMES = new int[]{ + 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583, + 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, + 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, + 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, + 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, + 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, + 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087, + 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, + 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, + 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, + 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, + 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, + 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, + 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693, + 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, + 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, + 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, + 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, + 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133, + 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, + 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, + 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, + 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, + 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, + 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673, + 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, + 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, + 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, + 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, + 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, + 7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, + 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, + 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, + 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, + 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, + 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, + 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, + 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, + 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919, + 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017, + 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, + 8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219, + 8221, 8231, 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291, + 8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387, + 8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501, + 8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597, + 8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677, + 8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741, + 8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, 8821, 8831, + 8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929, + 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011, + 9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109, + 9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199, + 9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283, + 9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377, + 9391, 9397, 9403, 9413, 9419, 9421, 9431, 9433, 9437, 9439, + 9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 9533, + 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631, + 9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733, + 9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811, + 9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887, + 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973}; + private static final List BIGINT_PRIMES; + + static + { + BIGINT_PRIMES = new ArrayList(); + for (int i = 0; i != PRIMES.length; i++) + { + BIGINT_PRIMES.add(BigInteger.valueOf(PRIMES[i])); + } + } + + public int[] coeffs; + + /** + * Constructs a new polynomial with <code>N</code> coefficients initialized to 0. + * + * @param N the number of coefficients + */ + public IntegerPolynomial(int N) + { + coeffs = new int[N]; + } + + /** + * Constructs a new polynomial with a given set of coefficients. + * + * @param coeffs the coefficients + */ + public IntegerPolynomial(int[] coeffs) + { + this.coeffs = coeffs; + } + + /** + * Constructs a <code>IntegerPolynomial</code> from a <code>BigIntPolynomial</code>. The two polynomials are independent of each other. + * + * @param p the original polynomial + */ + public IntegerPolynomial(BigIntPolynomial p) + { + coeffs = new int[p.coeffs.length]; + for (int i = 0; i < p.coeffs.length; i++) + { + coeffs[i] = p.coeffs[i].intValue(); + } + } + + /** + * Decodes a byte array to a polynomial with <code>N</code> ternary coefficients<br> + * Ignores any excess bytes. + * + * @param data an encoded ternary polynomial + * @param N number of coefficients + * @return the decoded polynomial + */ + public static IntegerPolynomial fromBinary3Sves(byte[] data, int N) + { + return new IntegerPolynomial(ArrayEncoder.decodeMod3Sves(data, N)); + } + + /** + * Converts a byte array produced by {@link #toBinary3Tight()} to a polynomial. + * + * @param b a byte array + * @param N number of coefficients + * @return the decoded polynomial + */ + public static IntegerPolynomial fromBinary3Tight(byte[] b, int N) + { + return new IntegerPolynomial(ArrayEncoder.decodeMod3Tight(b, N)); + } + + /** + * Reads data produced by {@link #toBinary3Tight()} from an input stream and converts it to a polynomial. + * + * @param is an input stream + * @param N number of coefficients + * @return the decoded polynomial + */ + public static IntegerPolynomial fromBinary3Tight(InputStream is, int N) + throws IOException + { + return new IntegerPolynomial(ArrayEncoder.decodeMod3Tight(is, N)); + } + + /** + * Returns a polynomial with N coefficients between <code>0</code> and <code>q-1</code>.<br> + * <code>q</code> must be a power of 2.<br> + * Ignores any excess bytes. + * + * @param data an encoded ternary polynomial + * @param N number of coefficients + * @param q + * @return the decoded polynomial + */ + public static IntegerPolynomial fromBinary(byte[] data, int N, int q) + { + return new IntegerPolynomial(ArrayEncoder.decodeModQ(data, N, q)); + } + + /** + * Returns a polynomial with N coefficients between <code>0</code> and <code>q-1</code>.<br> + * <code>q</code> must be a power of 2.<br> + * Ignores any excess bytes. + * + * @param is an encoded ternary polynomial + * @param N number of coefficients + * @param q + * @return the decoded polynomial + */ + public static IntegerPolynomial fromBinary(InputStream is, int N, int q) + throws IOException + { + return new IntegerPolynomial(ArrayEncoder.decodeModQ(is, N, q)); + } + + /** + * Encodes a polynomial with ternary coefficients to binary. + * <code>coeffs[2*i]</code> and <code>coeffs[2*i+1]</code> must not both equal -1 for any integer <code>i</code>, + * so this method is only safe to use with polynomials produced by <code>fromBinary3Sves()</code>. + * + * @return the encoded polynomial + */ + public byte[] toBinary3Sves() + { + return ArrayEncoder.encodeMod3Sves(coeffs); + } + + /** + * Converts a polynomial with ternary coefficients to binary. + * + * @return the encoded polynomial + */ + public byte[] toBinary3Tight() + { + BigInteger sum = Constants.BIGINT_ZERO; + for (int i = coeffs.length - 1; i >= 0; i--) + { + sum = sum.multiply(BigInteger.valueOf(3)); + sum = sum.add(BigInteger.valueOf(coeffs[i] + 1)); + } + + int size = (BigInteger.valueOf(3).pow(coeffs.length).bitLength() + 7) / 8; + byte[] arr = sum.toByteArray(); + + if (arr.length < size) + { + // pad with leading zeros so arr.length==size + byte[] arr2 = new byte[size]; + System.arraycopy(arr, 0, arr2, size - arr.length, arr.length); + return arr2; + } + + if (arr.length > size) + // drop sign bit + { + arr = Arrays.copyOfRange(arr, 1, arr.length); + } + return arr; + } + + /** + * Encodes a polynomial whose coefficients are between 0 and q, to binary. q must be a power of 2. + * + * @param q + * @return the encoded polynomial + */ + public byte[] toBinary(int q) + { + return ArrayEncoder.encodeModQ(coeffs, q); + } + + /** + * Multiplies the polynomial with another, taking the values mod modulus and the indices mod N + */ + public IntegerPolynomial mult(IntegerPolynomial poly2, int modulus) + { + IntegerPolynomial c = mult(poly2); + c.mod(modulus); + return c; + } + + /** + * Multiplies the polynomial with another, taking the indices mod N + */ + public IntegerPolynomial mult(IntegerPolynomial poly2) + { + int N = coeffs.length; + if (poly2.coeffs.length != N) + { + throw new IllegalArgumentException("Number of coefficients must be the same"); + } + + IntegerPolynomial c = multRecursive(poly2); + + if (c.coeffs.length > N) + { + for (int k = N; k < c.coeffs.length; k++) + { + c.coeffs[k - N] += c.coeffs[k]; + } + c.coeffs = Arrays.copyOf(c.coeffs, N); + } + return c; + } + + public BigIntPolynomial mult(BigIntPolynomial poly2) + { + return new BigIntPolynomial(this).mult(poly2); + } + + /** + * Karazuba multiplication + */ + private IntegerPolynomial multRecursive(IntegerPolynomial poly2) + { + int[] a = coeffs; + int[] b = poly2.coeffs; + + int n = poly2.coeffs.length; + if (n <= 32) + { + int cn = 2 * n - 1; + IntegerPolynomial c = new IntegerPolynomial(new int[cn]); + for (int k = 0; k < cn; k++) + { + for (int i = Math.max(0, k - n + 1); i <= Math.min(k, n - 1); i++) + { + c.coeffs[k] += b[i] * a[k - i]; + } + } + return c; + } + else + { + int n1 = n / 2; + + IntegerPolynomial a1 = new IntegerPolynomial(Arrays.copyOf(a, n1)); + IntegerPolynomial a2 = new IntegerPolynomial(Arrays.copyOfRange(a, n1, n)); + IntegerPolynomial b1 = new IntegerPolynomial(Arrays.copyOf(b, n1)); + IntegerPolynomial b2 = new IntegerPolynomial(Arrays.copyOfRange(b, n1, n)); + + IntegerPolynomial A = (IntegerPolynomial)a1.clone(); + A.add(a2); + IntegerPolynomial B = (IntegerPolynomial)b1.clone(); + B.add(b2); + + IntegerPolynomial c1 = a1.multRecursive(b1); + IntegerPolynomial c2 = a2.multRecursive(b2); + IntegerPolynomial c3 = A.multRecursive(B); + c3.sub(c1); + c3.sub(c2); + + IntegerPolynomial c = new IntegerPolynomial(2 * n - 1); + for (int i = 0; i < c1.coeffs.length; i++) + { + c.coeffs[i] = c1.coeffs[i]; + } + for (int i = 0; i < c3.coeffs.length; i++) + { + c.coeffs[n1 + i] += c3.coeffs[i]; + } + for (int i = 0; i < c2.coeffs.length; i++) + { + c.coeffs[2 * n1 + i] += c2.coeffs[i]; + } + return c; + } + } + + /** + * Computes the inverse mod <code>q; q</code> must be a power of 2.<br> + * Returns <code>null</code> if the polynomial is not invertible. + * + * @param q the modulus + * @return a new polynomial + */ + public IntegerPolynomial invertFq(int q) + { + int N = coeffs.length; + int k = 0; + IntegerPolynomial b = new IntegerPolynomial(N + 1); + b.coeffs[0] = 1; + IntegerPolynomial c = new IntegerPolynomial(N + 1); + IntegerPolynomial f = new IntegerPolynomial(N + 1); + f.coeffs = Arrays.copyOf(coeffs, N + 1); + f.modPositive(2); + // set g(x) = x^N − 1 + IntegerPolynomial g = new IntegerPolynomial(N + 1); + g.coeffs[0] = 1; + g.coeffs[N] = 1; + while (true) + { + while (f.coeffs[0] == 0) + { + for (int i = 1; i <= N; i++) + { + f.coeffs[i - 1] = f.coeffs[i]; // f(x) = f(x) / x + c.coeffs[N + 1 - i] = c.coeffs[N - i]; // c(x) = c(x) * x + } + f.coeffs[N] = 0; + c.coeffs[0] = 0; + k++; + if (f.equalsZero()) + { + return null; // not invertible + } + } + if (f.equalsOne()) + { + break; + } + if (f.degree() < g.degree()) + { + // exchange f and g + IntegerPolynomial temp = f; + f = g; + g = temp; + // exchange b and c + temp = b; + b = c; + c = temp; + } + f.add(g, 2); + b.add(c, 2); + } + + if (b.coeffs[N] != 0) + { + return null; + } + // Fq(x) = x^(N-k) * b(x) + IntegerPolynomial Fq = new IntegerPolynomial(N); + int j = 0; + k %= N; + for (int i = N - 1; i >= 0; i--) + { + j = i - k; + if (j < 0) + { + j += N; + } + Fq.coeffs[j] = b.coeffs[i]; + } + + return mod2ToModq(Fq, q); + } + + /** + * Computes the inverse mod q from the inverse mod 2 + * + * @param Fq + * @param q + * @return The inverse of this polynomial mod q + */ + private IntegerPolynomial mod2ToModq(IntegerPolynomial Fq, int q) + { + if (Util.is64BitJVM() && q == 2048) + { + LongPolynomial2 thisLong = new LongPolynomial2(this); + LongPolynomial2 FqLong = new LongPolynomial2(Fq); + int v = 2; + while (v < q) + { + v *= 2; + LongPolynomial2 temp = (LongPolynomial2)FqLong.clone(); + temp.mult2And(v - 1); + FqLong = thisLong.mult(FqLong).mult(FqLong); + temp.subAnd(FqLong, v - 1); + FqLong = temp; + } + return FqLong.toIntegerPolynomial(); + } + else + { + int v = 2; + while (v < q) + { + v *= 2; + IntegerPolynomial temp = new IntegerPolynomial(Arrays.copyOf(Fq.coeffs, Fq.coeffs.length)); + temp.mult2(v); + Fq = mult(Fq, v).mult(Fq, v); + temp.sub(Fq, v); + Fq = temp; + } + return Fq; + } + } + + /** + * Computes the inverse mod 3. + * Returns <code>null</code> if the polynomial is not invertible. + * + * @return a new polynomial + */ + public IntegerPolynomial invertF3() + { + int N = coeffs.length; + int k = 0; + IntegerPolynomial b = new IntegerPolynomial(N + 1); + b.coeffs[0] = 1; + IntegerPolynomial c = new IntegerPolynomial(N + 1); + IntegerPolynomial f = new IntegerPolynomial(N + 1); + f.coeffs = Arrays.copyOf(coeffs, N + 1); + f.modPositive(3); + // set g(x) = x^N − 1 + IntegerPolynomial g = new IntegerPolynomial(N + 1); + g.coeffs[0] = -1; + g.coeffs[N] = 1; + while (true) + { + while (f.coeffs[0] == 0) + { + for (int i = 1; i <= N; i++) + { + f.coeffs[i - 1] = f.coeffs[i]; // f(x) = f(x) / x + c.coeffs[N + 1 - i] = c.coeffs[N - i]; // c(x) = c(x) * x + } + f.coeffs[N] = 0; + c.coeffs[0] = 0; + k++; + if (f.equalsZero()) + { + return null; // not invertible + } + } + if (f.equalsAbsOne()) + { + break; + } + if (f.degree() < g.degree()) + { + // exchange f and g + IntegerPolynomial temp = f; + f = g; + g = temp; + // exchange b and c + temp = b; + b = c; + c = temp; + } + if (f.coeffs[0] == g.coeffs[0]) + { + f.sub(g, 3); + b.sub(c, 3); + } + else + { + f.add(g, 3); + b.add(c, 3); + } + } + + if (b.coeffs[N] != 0) + { + return null; + } + // Fp(x) = [+-] x^(N-k) * b(x) + IntegerPolynomial Fp = new IntegerPolynomial(N); + int j = 0; + k %= N; + for (int i = N - 1; i >= 0; i--) + { + j = i - k; + if (j < 0) + { + j += N; + } + Fp.coeffs[j] = f.coeffs[0] * b.coeffs[i]; + } + + Fp.ensurePositive(3); + return Fp; + } + + /** + * Resultant of this polynomial with <code>x^n-1</code> using a probabilistic algorithm. + * <p> + * Unlike EESS, this implementation does not compute all resultants modulo primes + * such that their product exceeds the maximum possible resultant, but rather stops + * when <code>NUM_EQUAL_RESULTANTS</code> consecutive modular resultants are equal.<br> + * This means the return value may be incorrect. Experiments show this happens in + * about 1 out of 100 cases when <code>N=439</code> and <code>NUM_EQUAL_RESULTANTS=2</code>, + * so the likelyhood of leaving the loop too early is <code>(1/100)^(NUM_EQUAL_RESULTANTS-1)</code>. + * <p> + * Because of the above, callers must verify the output and try a different polynomial if necessary. + * + * @return <code>(rho, res)</code> satisfying <code>res = rho*this + t*(x^n-1)</code> for some integer <code>t</code>. + */ + public Resultant resultant() + { + int N = coeffs.length; + + // Compute resultants modulo prime numbers. Continue until NUM_EQUAL_RESULTANTS consecutive modular resultants are equal. + LinkedList<ModularResultant> modResultants = new LinkedList<ModularResultant>(); + BigInteger prime = null; + BigInteger pProd = Constants.BIGINT_ONE; + BigInteger res = Constants.BIGINT_ONE; + int numEqual = 1; // number of consecutive modular resultants equal to each other + Iterator<BigInteger> primes = BIGINT_PRIMES.iterator(); + while (true) + { + prime = primes.hasNext() ? primes.next() : prime.nextProbablePrime(); + ModularResultant crr = resultant(prime.intValue()); + modResultants.add(crr); + + BigInteger temp = pProd.multiply(prime); + BigIntEuclidean er = BigIntEuclidean.calculate(prime, pProd); + BigInteger resPrev = res; + res = res.multiply(er.x.multiply(prime)); + BigInteger res2 = crr.res.multiply(er.y.multiply(pProd)); + res = res.add(res2).mod(temp); + pProd = temp; + + BigInteger pProd2 = pProd.divide(BigInteger.valueOf(2)); + BigInteger pProd2n = pProd2.negate(); + if (res.compareTo(pProd2) > 0) + { + res = res.subtract(pProd); + } + else if (res.compareTo(pProd2n) < 0) + { + res = res.add(pProd); + } + + if (res.equals(resPrev)) + { + numEqual++; + if (numEqual >= NUM_EQUAL_RESULTANTS) + { + break; + } + } + else + { + numEqual = 1; + } + } + + // Combine modular rho's to obtain the final rho. + // For efficiency, first combine all pairs of small resultants to bigger resultants, + // then combine pairs of those, etc. until only one is left. + while (modResultants.size() > 1) + { + ModularResultant modRes1 = modResultants.removeFirst(); + ModularResultant modRes2 = modResultants.removeFirst(); + ModularResultant modRes3 = ModularResultant.combineRho(modRes1, modRes2); + modResultants.addLast(modRes3); + } + BigIntPolynomial rhoP = modResultants.getFirst().rho; + + BigInteger pProd2 = pProd.divide(BigInteger.valueOf(2)); + BigInteger pProd2n = pProd2.negate(); + if (res.compareTo(pProd2) > 0) + { + res = res.subtract(pProd); + } + if (res.compareTo(pProd2n) < 0) + { + res = res.add(pProd); + } + + for (int i = 0; i < N; i++) + { + BigInteger c = rhoP.coeffs[i]; + if (c.compareTo(pProd2) > 0) + { + rhoP.coeffs[i] = c.subtract(pProd); + } + if (c.compareTo(pProd2n) < 0) + { + rhoP.coeffs[i] = c.add(pProd); + } + } + + return new Resultant(rhoP, res); + } + + /** + * Multithreaded version of {@link #resultant()}. + * + * @return <code>(rho, res)</code> satisfying <code>res = rho*this + t*(x^n-1)</code> for some integer <code>t</code>. + */ + public Resultant resultantMultiThread() + { + int N = coeffs.length; + + // upper bound for resultant(f, g) = ||f, 2||^deg(g) * ||g, 2||^deg(f) = squaresum(f)^(N/2) * 2^(deg(f)/2) because g(x)=x^N-1 + // see http://jondalon.mathematik.uni-osnabrueck.de/staff/phpages/brunsw/CompAlg.pdf chapter 3 + BigInteger max = squareSum().pow((N + 1) / 2); + max = max.multiply(BigInteger.valueOf(2).pow((degree() + 1) / 2)); + BigInteger max2 = max.multiply(BigInteger.valueOf(2)); + + // compute resultants modulo prime numbers + BigInteger prime = BigInteger.valueOf(10000); + BigInteger pProd = Constants.BIGINT_ONE; + LinkedBlockingQueue<Future<ModularResultant>> resultantTasks = new LinkedBlockingQueue<Future<ModularResultant>>(); + Iterator<BigInteger> primes = BIGINT_PRIMES.iterator(); + ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + while (pProd.compareTo(max2) < 0) + { + if (primes.hasNext()) + { + prime = primes.next(); + } + else + { + prime = prime.nextProbablePrime(); + } + Future<ModularResultant> task = executor.submit(new ModResultantTask(prime.intValue())); + resultantTasks.add(task); + pProd = pProd.multiply(prime); + } + + // Combine modular resultants to obtain the resultant. + // For efficiency, first combine all pairs of small resultants to bigger resultants, + // then combine pairs of those, etc. until only one is left. + ModularResultant overallResultant = null; + while (!resultantTasks.isEmpty()) + { + try + { + Future<ModularResultant> modRes1 = resultantTasks.take(); + Future<ModularResultant> modRes2 = resultantTasks.poll(); + if (modRes2 == null) + { + // modRes1 is the only one left + overallResultant = modRes1.get(); + break; + } + Future<ModularResultant> newTask = executor.submit(new CombineTask(modRes1.get(), modRes2.get())); + resultantTasks.add(newTask); + } + catch (Exception e) + { + throw new IllegalStateException(e.toString()); + } + } + executor.shutdown(); + BigInteger res = overallResultant.res; + BigIntPolynomial rhoP = overallResultant.rho; + + BigInteger pProd2 = pProd.divide(BigInteger.valueOf(2)); + BigInteger pProd2n = pProd2.negate(); + + if (res.compareTo(pProd2) > 0) + { + res = res.subtract(pProd); + } + if (res.compareTo(pProd2n) < 0) + { + res = res.add(pProd); + } + + for (int i = 0; i < N; i++) + { + BigInteger c = rhoP.coeffs[i]; + if (c.compareTo(pProd2) > 0) + { + rhoP.coeffs[i] = c.subtract(pProd); + } + if (c.compareTo(pProd2n) < 0) + { + rhoP.coeffs[i] = c.add(pProd); + } + } + + return new Resultant(rhoP, res); + } + + /** + * Resultant of this polynomial with <code>x^n-1 mod p</code>. + * + * @return <code>(rho, res)</code> satisfying <code>res = rho*this + t*(x^n-1) mod p</code> for some integer <code>t</code>. + */ + public ModularResultant resultant(int p) + { + // Add a coefficient as the following operations involve polynomials of degree deg(f)+1 + int[] fcoeffs = Arrays.copyOf(coeffs, coeffs.length + 1); + IntegerPolynomial f = new IntegerPolynomial(fcoeffs); + int N = fcoeffs.length; + + IntegerPolynomial a = new IntegerPolynomial(N); + a.coeffs[0] = -1; + a.coeffs[N - 1] = 1; + IntegerPolynomial b = new IntegerPolynomial(f.coeffs); + IntegerPolynomial v1 = new IntegerPolynomial(N); + IntegerPolynomial v2 = new IntegerPolynomial(N); + v2.coeffs[0] = 1; + int da = N - 1; + int db = b.degree(); + int ta = da; + int c = 0; + int r = 1; + while (db > 0) + { + c = Util.invert(b.coeffs[db], p); + c = (c * a.coeffs[da]) % p; + a.multShiftSub(b, c, da - db, p); + v1.multShiftSub(v2, c, da - db, p); + + da = a.degree(); + if (da < db) + { + r *= Util.pow(b.coeffs[db], ta - da, p); + r %= p; + if (ta % 2 == 1 && db % 2 == 1) + { + r = (-r) % p; + } + IntegerPolynomial temp = a; + a = b; + b = temp; + int tempdeg = da; + da = db; + temp = v1; + v1 = v2; + v2 = temp; + ta = db; + db = tempdeg; + } + } + r *= Util.pow(b.coeffs[0], da, p); + r %= p; + c = Util.invert(b.coeffs[0], p); + v2.mult(c); + v2.mod(p); + v2.mult(r); + v2.mod(p); + + // drop the highest coefficient so #coeffs matches the original input + v2.coeffs = Arrays.copyOf(v2.coeffs, v2.coeffs.length - 1); + return new ModularResultant(new BigIntPolynomial(v2), BigInteger.valueOf(r), BigInteger.valueOf(p)); + } + + /** + * Computes <code>this-b*c*(x^k) mod p</code> and stores the result in this polynomial.<br/> + * See steps 4a,4b in EESS algorithm 2.2.7.1. + * + * @param b + * @param c + * @param k + * @param p + */ + private void multShiftSub(IntegerPolynomial b, int c, int k, int p) + { + int N = coeffs.length; + for (int i = k; i < N; i++) + { + coeffs[i] = (coeffs[i] - b.coeffs[i - k] * c) % p; + } + } + + /** + * Adds the squares of all coefficients. + * + * @return the sum of squares + */ + private BigInteger squareSum() + { + BigInteger sum = Constants.BIGINT_ZERO; + for (int i = 0; i < coeffs.length; i++) + { + sum = sum.add(BigInteger.valueOf(coeffs[i] * coeffs[i])); + } + return sum; + } + + /** + * Returns the degree of the polynomial + * + * @return the degree + */ + int degree() + { + int degree = coeffs.length - 1; + while (degree > 0 && coeffs[degree] == 0) + { + degree--; + } + return degree; + } + + /** + * Adds another polynomial which can have a different number of coefficients, + * and takes the coefficient values mod <code>modulus</code>. + * + * @param b another polynomial + */ + public void add(IntegerPolynomial b, int modulus) + { + add(b); + mod(modulus); + } + + /** + * Adds another polynomial which can have a different number of coefficients. + * + * @param b another polynomial + */ + public void add(IntegerPolynomial b) + { + if (b.coeffs.length > coeffs.length) + { + coeffs = Arrays.copyOf(coeffs, b.coeffs.length); + } + for (int i = 0; i < b.coeffs.length; i++) + { + coeffs[i] += b.coeffs[i]; + } + } + + /** + * Subtracts another polynomial which can have a different number of coefficients, + * and takes the coefficient values mod <code>modulus</code>. + * + * @param b another polynomial + */ + public void sub(IntegerPolynomial b, int modulus) + { + sub(b); + mod(modulus); + } + + /** + * Subtracts another polynomial which can have a different number of coefficients. + * + * @param b another polynomial + */ + public void sub(IntegerPolynomial b) + { + if (b.coeffs.length > coeffs.length) + { + coeffs = Arrays.copyOf(coeffs, b.coeffs.length); + } + for (int i = 0; i < b.coeffs.length; i++) + { + coeffs[i] -= b.coeffs[i]; + } + } + + /** + * Subtracts a <code>int</code> from each coefficient. Does not return a new polynomial but modifies this polynomial. + * + * @param b + */ + void sub(int b) + { + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] -= b; + } + } + + /** + * Multiplies each coefficient by a <code>int</code>. Does not return a new polynomial but modifies this polynomial. + * + * @param factor + */ + public void mult(int factor) + { + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] *= factor; + } + } + + /** + * Multiplies each coefficient by a 2 and applies a modulus. Does not return a new polynomial but modifies this polynomial. + * + * @param modulus a modulus + */ + private void mult2(int modulus) + { + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] *= 2; + coeffs[i] %= modulus; + } + } + + /** + * Multiplies each coefficient by a 2 and applies a modulus. Does not return a new polynomial but modifies this polynomial. + * + * @param modulus a modulus + */ + public void mult3(int modulus) + { + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] *= 3; + coeffs[i] %= modulus; + } + } + + /** + * Divides each coefficient by <code>k</code> and rounds to the nearest integer. Does not return a new polynomial but modifies this polynomial. + * + * @param k the divisor + */ + public void div(int k) + { + int k2 = (k + 1) / 2; + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] += coeffs[i] > 0 ? k2 : -k2; + coeffs[i] /= k; + } + } + + /** + * Takes each coefficient modulo 3 such that all coefficients are ternary. + */ + public void mod3() + { + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] %= 3; + if (coeffs[i] > 1) + { + coeffs[i] -= 3; + } + if (coeffs[i] < -1) + { + coeffs[i] += 3; + } + } + } + + /** + * Ensures all coefficients are between 0 and <code>modulus-1</code> + * + * @param modulus a modulus + */ + public void modPositive(int modulus) + { + mod(modulus); + ensurePositive(modulus); + } + + /** + * Reduces all coefficients to the interval [-modulus/2, modulus/2) + */ + void modCenter(int modulus) + { + mod(modulus); + for (int j = 0; j < coeffs.length; j++) + { + while (coeffs[j] < modulus / 2) + { + coeffs[j] += modulus; + } + while (coeffs[j] >= modulus / 2) + { + coeffs[j] -= modulus; + } + } + } + + /** + * Takes each coefficient modulo <code>modulus</code>. + */ + public void mod(int modulus) + { + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] %= modulus; + } + } + + /** + * Adds <code>modulus</code> until all coefficients are above 0. + * + * @param modulus a modulus + */ + public void ensurePositive(int modulus) + { + for (int i = 0; i < coeffs.length; i++) + { + while (coeffs[i] < 0) + { + coeffs[i] += modulus; + } + } + } + + /** + * Computes the centered euclidean norm of the polynomial. + * + * @param q a modulus + * @return the centered norm + */ + public long centeredNormSq(int q) + { + int N = coeffs.length; + IntegerPolynomial p = (IntegerPolynomial)clone(); + p.shiftGap(q); + + long sum = 0; + long sqSum = 0; + for (int i = 0; i != p.coeffs.length; i++) + { + int c = p.coeffs[i]; + sum += c; + sqSum += c * c; + } + + long centeredNormSq = sqSum - sum * sum / N; + return centeredNormSq; + } + + /** + * Shifts all coefficients so the largest gap is centered around <code>-q/2</code>. + * + * @param q a modulus + */ + void shiftGap(int q) + { + modCenter(q); + + int[] sorted = Arrays.clone(coeffs); + + sort(sorted); + + int maxrange = 0; + int maxrangeStart = 0; + for (int i = 0; i < sorted.length - 1; i++) + { + int range = sorted[i + 1] - sorted[i]; + if (range > maxrange) + { + maxrange = range; + maxrangeStart = sorted[i]; + } + } + + int pmin = sorted[0]; + int pmax = sorted[sorted.length - 1]; + + int j = q - pmax + pmin; + int shift; + if (j > maxrange) + { + shift = (pmax + pmin) / 2; + } + else + { + shift = maxrangeStart + maxrange / 2 + q / 2; + } + + sub(shift); + } + + private void sort(int[] ints) + { + boolean swap = true; + + while (swap) + { + swap = false; + for (int i = 0; i != ints.length - 1; i++) + { + if (ints[i] > ints[i+1]) + { + int tmp = ints[i]; + ints[i] = ints[i+1]; + ints[i+1] = tmp; + swap = true; + } + } + } + } + + /** + * Shifts the values of all coefficients to the interval <code>[-q/2, q/2]</code>. + * + * @param q a modulus + */ + public void center0(int q) + { + for (int i = 0; i < coeffs.length; i++) + { + while (coeffs[i] < -q / 2) + { + coeffs[i] += q; + } + while (coeffs[i] > q / 2) + { + coeffs[i] -= q; + } + } + } + + /** + * Returns the sum of all coefficients, i.e. evaluates the polynomial at 0. + * + * @return the sum of all coefficients + */ + public int sumCoeffs() + { + int sum = 0; + for (int i = 0; i < coeffs.length; i++) + { + sum += coeffs[i]; + } + return sum; + } + + /** + * Tests if <code>p(x) = 0</code>. + * + * @return true iff all coefficients are zeros + */ + private boolean equalsZero() + { + for (int i = 0; i < coeffs.length; i++) + { + if (coeffs[i] != 0) + { + return false; + } + } + return true; + } + + /** + * Tests if <code>p(x) = 1</code>. + * + * @return true iff all coefficients are equal to zero, except for the lowest coefficient which must equal 1 + */ + public boolean equalsOne() + { + for (int i = 1; i < coeffs.length; i++) + { + if (coeffs[i] != 0) + { + return false; + } + } + return coeffs[0] == 1; + } + + /** + * Tests if <code>|p(x)| = 1</code>. + * + * @return true iff all coefficients are equal to zero, except for the lowest coefficient which must equal 1 or -1 + */ + private boolean equalsAbsOne() + { + for (int i = 1; i < coeffs.length; i++) + { + if (coeffs[i] != 0) + { + return false; + } + } + return Math.abs(coeffs[0]) == 1; + } + + /** + * Counts the number of coefficients equal to an integer + * + * @param value an integer + * @return the number of coefficients equal to <code>value</code> + */ + public int count(int value) + { + int count = 0; + for (int i = 0; i != coeffs.length; i++) + { + if (coeffs[i] == value) + { + count++; + } + } + return count; + } + + /** + * Multiplication by <code>X</code> in <code>Z[X]/Z[X^n-1]</code>. + */ + public void rotate1() + { + int clast = coeffs[coeffs.length - 1]; + for (int i = coeffs.length - 1; i > 0; i--) + { + coeffs[i] = coeffs[i - 1]; + } + coeffs[0] = clast; + } + + public void clear() + { + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] = 0; + } + } + + public IntegerPolynomial toIntegerPolynomial() + { + return (IntegerPolynomial)clone(); + } + + public Object clone() + { + return new IntegerPolynomial(coeffs.clone()); + } + + public boolean equals(Object obj) + { + if (obj instanceof IntegerPolynomial) + { + return Arrays.areEqual(coeffs, ((IntegerPolynomial)obj).coeffs); + } + else + { + return false; + } + } + + /** + * Calls {@link IntegerPolynomial#resultant(int) + */ + private class ModResultantTask + implements Callable<ModularResultant> + { + private int modulus; + + private ModResultantTask(int modulus) + { + this.modulus = modulus; + } + + public ModularResultant call() + { + return resultant(modulus); + } + } + + /** + * Calls {@link ModularResultant#combineRho(ModularResultant, ModularResultant) + */ + private class CombineTask + implements Callable<ModularResultant> + { + private ModularResultant modRes1; + private ModularResultant modRes2; + + private CombineTask(ModularResultant modRes1, ModularResultant modRes2) + { + this.modRes1 = modRes1; + this.modRes2 = modRes2; + } + + public ModularResultant call() + { + return ModularResultant.combineRho(modRes1, modRes2); + } + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/LongPolynomial2.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/LongPolynomial2.java new file mode 100644 index 00000000..513cac50 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/LongPolynomial2.java @@ -0,0 +1,255 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +import org.spongycastle.util.Arrays; + +/** + * A polynomial class that combines two coefficients into one <code>long</code> value for + * faster multiplication in 64 bit environments.<br> + * Coefficients can be between 0 and 2047 and are stored in pairs in the bits 0..10 and 24..34 of a <code>long</code> number. + */ +public class LongPolynomial2 +{ + private long[] coeffs; // each representing two coefficients in the original IntegerPolynomial + private int numCoeffs; + + /** + * Constructs a <code>LongPolynomial2</code> from a <code>IntegerPolynomial</code>. The two polynomials are independent of each other. + * + * @param p the original polynomial. Coefficients must be between 0 and 2047. + */ + public LongPolynomial2(IntegerPolynomial p) + { + numCoeffs = p.coeffs.length; + coeffs = new long[(numCoeffs + 1) / 2]; + int idx = 0; + for (int pIdx = 0; pIdx < numCoeffs; ) + { + int c0 = p.coeffs[pIdx++]; + while (c0 < 0) + { + c0 += 2048; + } + long c1 = pIdx < numCoeffs ? p.coeffs[pIdx++] : 0; + while (c1 < 0) + { + c1 += 2048; + } + coeffs[idx] = c0 + (c1 << 24); + idx++; + } + } + + private LongPolynomial2(long[] coeffs) + { + this.coeffs = coeffs; + } + + private LongPolynomial2(int N) + { + coeffs = new long[N]; + } + + /** + * Multiplies the polynomial with another, taking the indices mod N and the values mod 2048. + */ + public LongPolynomial2 mult(LongPolynomial2 poly2) + { + int N = coeffs.length; + if (poly2.coeffs.length != N || numCoeffs != poly2.numCoeffs) + { + throw new IllegalArgumentException("Number of coefficients must be the same"); + } + + LongPolynomial2 c = multRecursive(poly2); + + if (c.coeffs.length > N) + { + if (numCoeffs % 2 == 0) + { + for (int k = N; k < c.coeffs.length; k++) + { + c.coeffs[k - N] = (c.coeffs[k - N] + c.coeffs[k]) & 0x7FF0007FFL; + } + c.coeffs = Arrays.copyOf(c.coeffs, N); + } + else + { + for (int k = N; k < c.coeffs.length; k++) + { + c.coeffs[k - N] = c.coeffs[k - N] + (c.coeffs[k - 1] >> 24); + c.coeffs[k - N] = c.coeffs[k - N] + ((c.coeffs[k] & 2047) << 24); + c.coeffs[k - N] &= 0x7FF0007FFL; + } + c.coeffs = Arrays.copyOf(c.coeffs, N); + c.coeffs[c.coeffs.length - 1] &= 2047; + } + } + + c = new LongPolynomial2(c.coeffs); + c.numCoeffs = numCoeffs; + return c; + } + + public IntegerPolynomial toIntegerPolynomial() + { + int[] intCoeffs = new int[numCoeffs]; + int uIdx = 0; + for (int i = 0; i < coeffs.length; i++) + { + intCoeffs[uIdx++] = (int)(coeffs[i] & 2047); + if (uIdx < numCoeffs) + { + intCoeffs[uIdx++] = (int)((coeffs[i] >> 24) & 2047); + } + } + return new IntegerPolynomial(intCoeffs); + } + + /** + * Karazuba multiplication + */ + private LongPolynomial2 multRecursive(LongPolynomial2 poly2) + { + long[] a = coeffs; + long[] b = poly2.coeffs; + + int n = poly2.coeffs.length; + if (n <= 32) + { + int cn = 2 * n; + LongPolynomial2 c = new LongPolynomial2(new long[cn]); + for (int k = 0; k < cn; k++) + { + for (int i = Math.max(0, k - n + 1); i <= Math.min(k, n - 1); i++) + { + long c0 = a[k - i] * b[i]; + long cu = c0 & 0x7FF000000L + (c0 & 2047); + long co = (c0 >>> 48) & 2047; + + c.coeffs[k] = (c.coeffs[k] + cu) & 0x7FF0007FFL; + c.coeffs[k + 1] = (c.coeffs[k + 1] + co) & 0x7FF0007FFL; + } + } + return c; + } + else + { + int n1 = n / 2; + + LongPolynomial2 a1 = new LongPolynomial2(Arrays.copyOf(a, n1)); + LongPolynomial2 a2 = new LongPolynomial2(Arrays.copyOfRange(a, n1, n)); + LongPolynomial2 b1 = new LongPolynomial2(Arrays.copyOf(b, n1)); + LongPolynomial2 b2 = new LongPolynomial2(Arrays.copyOfRange(b, n1, n)); + + LongPolynomial2 A = (LongPolynomial2)a1.clone(); + A.add(a2); + LongPolynomial2 B = (LongPolynomial2)b1.clone(); + B.add(b2); + + LongPolynomial2 c1 = a1.multRecursive(b1); + LongPolynomial2 c2 = a2.multRecursive(b2); + LongPolynomial2 c3 = A.multRecursive(B); + c3.sub(c1); + c3.sub(c2); + + LongPolynomial2 c = new LongPolynomial2(2 * n); + for (int i = 0; i < c1.coeffs.length; i++) + { + c.coeffs[i] = c1.coeffs[i] & 0x7FF0007FFL; + } + for (int i = 0; i < c3.coeffs.length; i++) + { + c.coeffs[n1 + i] = (c.coeffs[n1 + i] + c3.coeffs[i]) & 0x7FF0007FFL; + } + for (int i = 0; i < c2.coeffs.length; i++) + { + c.coeffs[2 * n1 + i] = (c.coeffs[2 * n1 + i] + c2.coeffs[i]) & 0x7FF0007FFL; + } + return c; + } + } + + /** + * Adds another polynomial which can have a different number of coefficients. + * + * @param b another polynomial + */ + private void add(LongPolynomial2 b) + { + if (b.coeffs.length > coeffs.length) + { + coeffs = Arrays.copyOf(coeffs, b.coeffs.length); + } + for (int i = 0; i < b.coeffs.length; i++) + { + coeffs[i] = (coeffs[i] + b.coeffs[i]) & 0x7FF0007FFL; + } + } + + /** + * Subtracts another polynomial which can have a different number of coefficients. + * + * @param b another polynomial + */ + private void sub(LongPolynomial2 b) + { + if (b.coeffs.length > coeffs.length) + { + coeffs = Arrays.copyOf(coeffs, b.coeffs.length); + } + for (int i = 0; i < b.coeffs.length; i++) + { + coeffs[i] = (0x0800000800000L + coeffs[i] - b.coeffs[i]) & 0x7FF0007FFL; + } + } + + /** + * Subtracts another polynomial which must have the same number of coefficients, + * and applies an AND mask to the upper and lower halves of each coefficients. + * + * @param b another polynomial + * @param mask a bit mask less than 2048 to apply to each 11-bit coefficient + */ + public void subAnd(LongPolynomial2 b, int mask) + { + long longMask = (((long)mask) << 24) + mask; + for (int i = 0; i < b.coeffs.length; i++) + { + coeffs[i] = (0x0800000800000L + coeffs[i] - b.coeffs[i]) & longMask; + } + } + + /** + * Multiplies this polynomial by 2 and applies an AND mask to the upper and + * lower halves of each coefficients. + * + * @param mask a bit mask less than 2048 to apply to each 11-bit coefficient + */ + public void mult2And(int mask) + { + long longMask = (((long)mask) << 24) + mask; + for (int i = 0; i < coeffs.length; i++) + { + coeffs[i] = (coeffs[i] << 1) & longMask; + } + } + + public Object clone() + { + LongPolynomial2 p = new LongPolynomial2(coeffs.clone()); + p.numCoeffs = numCoeffs; + return p; + } + + public boolean equals(Object obj) + { + if (obj instanceof LongPolynomial2) + { + return Arrays.areEqual(coeffs, ((LongPolynomial2)obj).coeffs); + } + else + { + return false; + } + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/LongPolynomial5.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/LongPolynomial5.java new file mode 100644 index 00000000..f8764c68 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/LongPolynomial5.java @@ -0,0 +1,149 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +import org.spongycastle.util.Arrays; + +/** + * A polynomial class that combines five coefficients into one <code>long</code> value for + * faster multiplication by a ternary polynomial.<br> + * Coefficients can be between 0 and 2047 and are stored in bits 0..11, 12..23, ..., 48..59 of a <code>long</code> number. + */ +public class LongPolynomial5 +{ + private long[] coeffs; // groups of 5 coefficients + private int numCoeffs; + + /** + * Constructs a <code>LongPolynomial5</code> from a <code>IntegerPolynomial</code>. The two polynomials are independent of each other. + * + * @param p the original polynomial. Coefficients must be between 0 and 2047. + */ + public LongPolynomial5(IntegerPolynomial p) + { + numCoeffs = p.coeffs.length; + + coeffs = new long[(numCoeffs + 4) / 5]; + int cIdx = 0; + int shift = 0; + for (int i = 0; i < numCoeffs; i++) + { + coeffs[cIdx] |= ((long)p.coeffs[i]) << shift; + shift += 12; + if (shift >= 60) + { + shift = 0; + cIdx++; + } + } + } + + private LongPolynomial5(long[] coeffs, int numCoeffs) + { + this.coeffs = coeffs; + this.numCoeffs = numCoeffs; + } + + /** + * Multiplies the polynomial with a <code>TernaryPolynomial</code>, taking the indices mod N and the values mod 2048. + */ + public LongPolynomial5 mult(TernaryPolynomial poly2) + { + long[][] prod = new long[5][coeffs.length + (poly2.size() + 4) / 5 - 1]; // intermediate results, the subarrays are shifted by 0,...,4 coefficients + + // multiply ones + int[] ones = poly2.getOnes(); + for (int idx = 0; idx != ones.length; idx++) + { + int pIdx = ones[idx]; + int cIdx = pIdx / 5; + int m = pIdx - cIdx * 5; // m = pIdx % 5 + for (int i = 0; i < coeffs.length; i++) + { + prod[m][cIdx] = (prod[m][cIdx] + coeffs[i]) & 0x7FF7FF7FF7FF7FFL; + cIdx++; + } + } + + // multiply negative ones + int[] negOnes = poly2.getNegOnes(); + for (int idx = 0; idx != negOnes.length; idx++) + { + int pIdx = negOnes[idx]; + int cIdx = pIdx / 5; + int m = pIdx - cIdx * 5; // m = pIdx % 5 + for (int i = 0; i < coeffs.length; i++) + { + prod[m][cIdx] = (0x800800800800800L + prod[m][cIdx] - coeffs[i]) & 0x7FF7FF7FF7FF7FFL; + cIdx++; + } + } + + // combine shifted coefficients (5 arrays) into a single array of length prod[*].length+1 + long[] cCoeffs = Arrays.copyOf(prod[0], prod[0].length + 1); + for (int m = 1; m <= 4; m++) + { + int shift = m * 12; + int shift60 = 60 - shift; + long mask = (1L << shift60) - 1; + int pLen = prod[m].length; + for (int i = 0; i < pLen; i++) + { + long upper, lower; + upper = prod[m][i] >> shift60; + lower = prod[m][i] & mask; + + cCoeffs[i] = (cCoeffs[i] + (lower << shift)) & 0x7FF7FF7FF7FF7FFL; + int nextIdx = i + 1; + cCoeffs[nextIdx] = (cCoeffs[nextIdx] + upper) & 0x7FF7FF7FF7FF7FFL; + } + } + + // reduce indices of cCoeffs modulo numCoeffs + int shift = 12 * (numCoeffs % 5); + for (int cIdx = coeffs.length - 1; cIdx < cCoeffs.length; cIdx++) + { + long iCoeff; // coefficient to shift into the [0..numCoeffs-1] range + int newIdx; + if (cIdx == coeffs.length - 1) + { + iCoeff = numCoeffs == 5 ? 0 : cCoeffs[cIdx] >> shift; + newIdx = 0; + } + else + { + iCoeff = cCoeffs[cIdx]; + newIdx = cIdx * 5 - numCoeffs; + } + + int base = newIdx / 5; + int m = newIdx - base * 5; // m = newIdx % 5 + long lower = iCoeff << (12 * m); + long upper = iCoeff >> (12 * (5 - m)); + cCoeffs[base] = (cCoeffs[base] + lower) & 0x7FF7FF7FF7FF7FFL; + int base1 = base + 1; + if (base1 < coeffs.length) + { + cCoeffs[base1] = (cCoeffs[base1] + upper) & 0x7FF7FF7FF7FF7FFL; + } + } + + return new LongPolynomial5(cCoeffs, numCoeffs); + } + + public IntegerPolynomial toIntegerPolynomial() + { + int[] intCoeffs = new int[numCoeffs]; + int cIdx = 0; + int shift = 0; + for (int i = 0; i < numCoeffs; i++) + { + intCoeffs[i] = (int)((coeffs[cIdx] >> shift) & 2047); + shift += 12; + if (shift >= 60) + { + shift = 0; + cIdx++; + } + } + return new IntegerPolynomial(intCoeffs); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/ModularResultant.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/ModularResultant.java new file mode 100644 index 00000000..7e8031fe --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/ModularResultant.java @@ -0,0 +1,46 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +import java.math.BigInteger; + +import org.spongycastle.pqc.math.ntru.euclid.BigIntEuclidean; + +/** + * A resultant modulo a <code>BigInteger</code> + */ +public class ModularResultant + extends Resultant +{ + BigInteger modulus; + + ModularResultant(BigIntPolynomial rho, BigInteger res, BigInteger modulus) + { + super(rho, res); + this.modulus = modulus; + } + + /** + * Calculates a <code>rho</code> modulo <code>m1*m2</code> from + * two resultants whose <code>rho</code>s are modulo <code>m1</code> and <code>m2</code>.<br/> + * </code>res</code> is set to <code>null</code>. + * + * @param modRes1 + * @param modRes2 + * @return <code>rho</code> modulo <code>modRes1.modulus * modRes2.modulus</code>, and <code>null</code> for </code>res</code>. + */ + static ModularResultant combineRho(ModularResultant modRes1, ModularResultant modRes2) + { + BigInteger mod1 = modRes1.modulus; + BigInteger mod2 = modRes2.modulus; + BigInteger prod = mod1.multiply(mod2); + BigIntEuclidean er = BigIntEuclidean.calculate(mod2, mod1); + + BigIntPolynomial rho1 = (BigIntPolynomial)modRes1.rho.clone(); + rho1.mult(er.x.multiply(mod2)); + BigIntPolynomial rho2 = (BigIntPolynomial)modRes2.rho.clone(); + rho2.mult(er.y.multiply(mod1)); + rho1.add(rho2); + rho1.mod(prod); + + return new ModularResultant(rho1, null, prod); + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/Polynomial.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/Polynomial.java new file mode 100644 index 00000000..1d1f1dfb --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/Polynomial.java @@ -0,0 +1,42 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +public interface Polynomial +{ + + /** + * Multiplies the polynomial by an <code>IntegerPolynomial</code>, + * taking the indices mod <code>N</code>. + * + * @param poly2 a polynomial + * @return the product of the two polynomials + */ + IntegerPolynomial mult(IntegerPolynomial poly2); + + /** + * Multiplies the polynomial by an <code>IntegerPolynomial</code>, + * taking the coefficient values mod <code>modulus</code> and the indices mod <code>N</code>. + * + * @param poly2 a polynomial + * @param modulus a modulus to apply + * @return the product of the two polynomials + */ + IntegerPolynomial mult(IntegerPolynomial poly2, int modulus); + + /** + * Returns a polynomial that is equal to this polynomial (in the sense that {@link #mult(IntegerPolynomial, int)} + * returns equal <code>IntegerPolynomial</code>s). The new polynomial is guaranteed to be independent of the original. + * + * @return a new <code>IntegerPolynomial</code>. + */ + IntegerPolynomial toIntegerPolynomial(); + + /** + * Multiplies the polynomial by a <code>BigIntPolynomial</code>, taking the indices mod N. Does not + * change this polynomial but returns the result as a new polynomial.<br> + * Both polynomials must have the same number of coefficients. + * + * @param poly2 the polynomial to multiply by + * @return a new polynomial + */ + BigIntPolynomial mult(BigIntPolynomial poly2); +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/ProductFormPolynomial.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/ProductFormPolynomial.java new file mode 100644 index 00000000..e60ce8b8 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/ProductFormPolynomial.java @@ -0,0 +1,153 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.SecureRandom; + +import org.spongycastle.util.Arrays; + +/** + * A polynomial of the form <code>f1*f2+f3</code>, where + * <code>f1,f2,f3</code> are very sparsely populated ternary polynomials. + */ +public class ProductFormPolynomial + implements Polynomial +{ + private SparseTernaryPolynomial f1, f2, f3; + + public ProductFormPolynomial(SparseTernaryPolynomial f1, SparseTernaryPolynomial f2, SparseTernaryPolynomial f3) + { + this.f1 = f1; + this.f2 = f2; + this.f3 = f3; + } + + public static ProductFormPolynomial generateRandom(int N, int df1, int df2, int df3Ones, int df3NegOnes, SecureRandom random) + { + SparseTernaryPolynomial f1 = SparseTernaryPolynomial.generateRandom(N, df1, df1, random); + SparseTernaryPolynomial f2 = SparseTernaryPolynomial.generateRandom(N, df2, df2, random); + SparseTernaryPolynomial f3 = SparseTernaryPolynomial.generateRandom(N, df3Ones, df3NegOnes, random); + return new ProductFormPolynomial(f1, f2, f3); + } + + public static ProductFormPolynomial fromBinary(byte[] data, int N, int df1, int df2, int df3Ones, int df3NegOnes) + throws IOException + { + return fromBinary(new ByteArrayInputStream(data), N, df1, df2, df3Ones, df3NegOnes); + } + + public static ProductFormPolynomial fromBinary(InputStream is, int N, int df1, int df2, int df3Ones, int df3NegOnes) + throws IOException + { + SparseTernaryPolynomial f1; + + f1 = SparseTernaryPolynomial.fromBinary(is, N, df1, df1); + SparseTernaryPolynomial f2 = SparseTernaryPolynomial.fromBinary(is, N, df2, df2); + SparseTernaryPolynomial f3 = SparseTernaryPolynomial.fromBinary(is, N, df3Ones, df3NegOnes); + return new ProductFormPolynomial(f1, f2, f3); + } + + public byte[] toBinary() + { + byte[] f1Bin = f1.toBinary(); + byte[] f2Bin = f2.toBinary(); + byte[] f3Bin = f3.toBinary(); + + byte[] all = Arrays.copyOf(f1Bin, f1Bin.length + f2Bin.length + f3Bin.length); + System.arraycopy(f2Bin, 0, all, f1Bin.length, f2Bin.length); + System.arraycopy(f3Bin, 0, all, f1Bin.length + f2Bin.length, f3Bin.length); + return all; + } + + public IntegerPolynomial mult(IntegerPolynomial b) + { + IntegerPolynomial c = f1.mult(b); + c = f2.mult(c); + c.add(f3.mult(b)); + return c; + } + + public BigIntPolynomial mult(BigIntPolynomial b) + { + BigIntPolynomial c = f1.mult(b); + c = f2.mult(c); + c.add(f3.mult(b)); + return c; + } + + public IntegerPolynomial toIntegerPolynomial() + { + IntegerPolynomial i = f1.mult(f2.toIntegerPolynomial()); + i.add(f3.toIntegerPolynomial()); + return i; + } + + public IntegerPolynomial mult(IntegerPolynomial poly2, int modulus) + { + IntegerPolynomial c = mult(poly2); + c.mod(modulus); + return c; + } + + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((f1 == null) ? 0 : f1.hashCode()); + result = prime * result + ((f2 == null) ? 0 : f2.hashCode()); + result = prime * result + ((f3 == null) ? 0 : f3.hashCode()); + return result; + } + + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + ProductFormPolynomial other = (ProductFormPolynomial)obj; + if (f1 == null) + { + if (other.f1 != null) + { + return false; + } + } + else if (!f1.equals(other.f1)) + { + return false; + } + if (f2 == null) + { + if (other.f2 != null) + { + return false; + } + } + else if (!f2.equals(other.f2)) + { + return false; + } + if (f3 == null) + { + if (other.f3 != null) + { + return false; + } + } + else if (!f3.equals(other.f3)) + { + return false; + } + return true; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/Resultant.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/Resultant.java new file mode 100644 index 00000000..8fa13328 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/Resultant.java @@ -0,0 +1,28 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +import java.math.BigInteger; + +/** + * Contains a resultant and a polynomial <code>rho</code> such that + * <code>res = rho*this + t*(x^n-1) for some integer t</code>. + * + * @see IntegerPolynomial#resultant() + * @see IntegerPolynomial#resultant(int) + */ +public class Resultant +{ + /** + * A polynomial such that <code>res = rho*this + t*(x^n-1) for some integer t</code> + */ + public BigIntPolynomial rho; + /** + * Resultant of a polynomial with <code>x^n-1</code> + */ + public BigInteger res; + + Resultant(BigIntPolynomial rho, BigInteger res) + { + this.rho = rho; + this.res = res; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/SparseTernaryPolynomial.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/SparseTernaryPolynomial.java new file mode 100644 index 00000000..2fbd70a4 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/SparseTernaryPolynomial.java @@ -0,0 +1,320 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.spongycastle.pqc.math.ntru.util.ArrayEncoder; +import org.spongycastle.pqc.math.ntru.util.Util; +import org.spongycastle.util.Arrays; + +/** + * A <code>TernaryPolynomial</code> with a "low" number of nonzero coefficients. + */ +public class SparseTernaryPolynomial + implements TernaryPolynomial +{ + /** + * Number of bits to use for each coefficient. Determines the upper bound for <code>N</code>. + */ + private static final int BITS_PER_INDEX = 11; + + private int N; + private int[] ones; + private int[] negOnes; + + /** + * Constructs a new polynomial. + * + * @param N total number of coefficients including zeros + * @param ones indices of coefficients equal to 1 + * @param negOnes indices of coefficients equal to -1 + */ + SparseTernaryPolynomial(int N, int[] ones, int[] negOnes) + { + this.N = N; + this.ones = ones; + this.negOnes = negOnes; + } + + /** + * Constructs a <code>DenseTernaryPolynomial</code> from a <code>IntegerPolynomial</code>. The two polynomials are + * independent of each other. + * + * @param intPoly the original polynomial + */ + public SparseTernaryPolynomial(IntegerPolynomial intPoly) + { + this(intPoly.coeffs); + } + + /** + * Constructs a new <code>SparseTernaryPolynomial</code> with a given set of coefficients. + * + * @param coeffs the coefficients + */ + public SparseTernaryPolynomial(int[] coeffs) + { + N = coeffs.length; + ones = new int[N]; + negOnes = new int[N]; + int onesIdx = 0; + int negOnesIdx = 0; + for (int i = 0; i < N; i++) + { + int c = coeffs[i]; + switch (c) + { + case 1: + ones[onesIdx++] = i; + break; + case -1: + negOnes[negOnesIdx++] = i; + break; + case 0: + break; + default: + throw new IllegalArgumentException("Illegal value: " + c + ", must be one of {-1, 0, 1}"); + } + } + ones = Arrays.copyOf(ones, onesIdx); + negOnes = Arrays.copyOf(negOnes, negOnesIdx); + } + + /** + * Decodes a byte array encoded with {@link #toBinary()} to a ploynomial. + * + * @param is an input stream containing an encoded polynomial + * @param N number of coefficients including zeros + * @param numOnes number of coefficients equal to 1 + * @param numNegOnes number of coefficients equal to -1 + * @return the decoded polynomial + * @throws IOException + */ + public static SparseTernaryPolynomial fromBinary(InputStream is, int N, int numOnes, int numNegOnes) + throws IOException + { + int maxIndex = 1 << BITS_PER_INDEX; + int bitsPerIndex = 32 - Integer.numberOfLeadingZeros(maxIndex - 1); + + int data1Len = (numOnes * bitsPerIndex + 7) / 8; + byte[] data1 = Util.readFullLength(is, data1Len); + int[] ones = ArrayEncoder.decodeModQ(data1, numOnes, maxIndex); + + int data2Len = (numNegOnes * bitsPerIndex + 7) / 8; + byte[] data2 = Util.readFullLength(is, data2Len); + int[] negOnes = ArrayEncoder.decodeModQ(data2, numNegOnes, maxIndex); + + return new SparseTernaryPolynomial(N, ones, negOnes); + } + + /** + * Generates a random polynomial with <code>numOnes</code> coefficients equal to 1, + * <code>numNegOnes</code> coefficients equal to -1, and the rest equal to 0. + * + * @param N number of coefficients + * @param numOnes number of 1's + * @param numNegOnes number of -1's + */ + public static SparseTernaryPolynomial generateRandom(int N, int numOnes, int numNegOnes, SecureRandom random) + { + int[] coeffs = Util.generateRandomTernary(N, numOnes, numNegOnes, random); + return new SparseTernaryPolynomial(coeffs); + } + + public IntegerPolynomial mult(IntegerPolynomial poly2) + { + int[] b = poly2.coeffs; + if (b.length != N) + { + throw new IllegalArgumentException("Number of coefficients must be the same"); + } + + int[] c = new int[N]; + for (int idx = 0; idx != ones.length; idx++) + { + int i = ones[idx]; + int j = N - 1 - i; + for (int k = N - 1; k >= 0; k--) + { + c[k] += b[j]; + j--; + if (j < 0) + { + j = N - 1; + } + } + } + + for (int idx = 0; idx != negOnes.length; idx++) + { + int i = negOnes[idx]; + int j = N - 1 - i; + for (int k = N - 1; k >= 0; k--) + { + c[k] -= b[j]; + j--; + if (j < 0) + { + j = N - 1; + } + } + } + + return new IntegerPolynomial(c); + } + + public IntegerPolynomial mult(IntegerPolynomial poly2, int modulus) + { + IntegerPolynomial c = mult(poly2); + c.mod(modulus); + return c; + } + + public BigIntPolynomial mult(BigIntPolynomial poly2) + { + BigInteger[] b = poly2.coeffs; + if (b.length != N) + { + throw new IllegalArgumentException("Number of coefficients must be the same"); + } + + BigInteger[] c = new BigInteger[N]; + for (int i = 0; i < N; i++) + { + c[i] = BigInteger.ZERO; + } + + for (int idx = 0; idx != ones.length; idx++) + { + int i = ones[idx]; + int j = N - 1 - i; + for (int k = N - 1; k >= 0; k--) + { + c[k] = c[k].add(b[j]); + j--; + if (j < 0) + { + j = N - 1; + } + } + } + + for (int idx = 0; idx != negOnes.length; idx++) + { + int i = negOnes[idx]; + int j = N - 1 - i; + for (int k = N - 1; k >= 0; k--) + { + c[k] = c[k].subtract(b[j]); + j--; + if (j < 0) + { + j = N - 1; + } + } + } + + return new BigIntPolynomial(c); + } + + public int[] getOnes() + { + return ones; + } + + public int[] getNegOnes() + { + return negOnes; + } + + /** + * Encodes the polynomial to a byte array writing <code>BITS_PER_INDEX</code> bits for each coefficient. + * + * @return the encoded polynomial + */ + public byte[] toBinary() + { + int maxIndex = 1 << BITS_PER_INDEX; + byte[] bin1 = ArrayEncoder.encodeModQ(ones, maxIndex); + byte[] bin2 = ArrayEncoder.encodeModQ(negOnes, maxIndex); + + byte[] bin = Arrays.copyOf(bin1, bin1.length + bin2.length); + System.arraycopy(bin2, 0, bin, bin1.length, bin2.length); + return bin; + } + + public IntegerPolynomial toIntegerPolynomial() + { + int[] coeffs = new int[N]; + for (int idx = 0; idx != ones.length; idx++) + { + int i = ones[idx]; + coeffs[i] = 1; + } + for (int idx = 0; idx != negOnes.length; idx++) + { + int i = negOnes[idx]; + coeffs[i] = -1; + } + return new IntegerPolynomial(coeffs); + } + + public int size() + { + return N; + } + + public void clear() + { + for (int i = 0; i < ones.length; i++) + { + ones[i] = 0; + } + for (int i = 0; i < negOnes.length; i++) + { + negOnes[i] = 0; + } + } + + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + N; + result = prime * result + Arrays.hashCode(negOnes); + result = prime * result + Arrays.hashCode(ones); + return result; + } + + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + SparseTernaryPolynomial other = (SparseTernaryPolynomial)obj; + if (N != other.N) + { + return false; + } + if (!Arrays.areEqual(negOnes, other.negOnes)) + { + return false; + } + if (!Arrays.areEqual(ones, other.ones)) + { + return false; + } + return true; + } +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/TernaryPolynomial.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/TernaryPolynomial.java new file mode 100644 index 00000000..6b9530b3 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/polynomial/TernaryPolynomial.java @@ -0,0 +1,25 @@ +package org.spongycastle.pqc.math.ntru.polynomial; + +/** + * A polynomial whose coefficients are all equal to -1, 0, or 1 + */ +public interface TernaryPolynomial + extends Polynomial +{ + + /** + * Multiplies the polynomial by an <code>IntegerPolynomial</code>, taking the indices mod N + */ + IntegerPolynomial mult(IntegerPolynomial poly2); + + int[] getOnes(); + + int[] getNegOnes(); + + /** + * Returns the maximum number of coefficients the polynomial can have + */ + int size(); + + void clear(); +} diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/util/ArrayEncoder.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/util/ArrayEncoder.java new file mode 100644 index 00000000..c4888cfc --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/util/ArrayEncoder.java @@ -0,0 +1,292 @@ +package org.spongycastle.pqc.math.ntru.util; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; + +import org.spongycastle.util.Arrays; + +/** + * Converts a coefficient array to a compact byte array and vice versa. + */ +public class ArrayEncoder +{ + /** + * Bit string to coefficient conversion table from P1363.1. Also found at + * {@link http://stackoverflow.com/questions/1562548/how-to-make-a-message-into-a-polynomial} + * <p/> + * Convert each three-bit quantity to two ternary coefficients as follows, and concatenate the resulting + * ternary quantities to obtain [the output]. + * <p/> + * <code> + * {0, 0, 0} -> {0, 0}<br/> + * {0, 0, 1} -> {0, 1}<br/> + * {0, 1, 0} -> {0, -1}<br/> + * {0, 1, 1} -> {1, 0}<br/> + * {1, 0, 0} -> {1, 1}<br/> + * {1, 0, 1} -> {1, -1}<br/> + * {1, 1, 0} -> {-1, 0}<br/> + * {1, 1, 1} -> {-1, 1}<br/> + * </code> + */ + private static final int[] COEFF1_TABLE = {0, 0, 0, 1, 1, 1, -1, -1}; + private static final int[] COEFF2_TABLE = {0, 1, -1, 0, 1, -1, 0, 1}; + /** + * Coefficient to bit string conversion table from P1363.1. Also found at + * {@link http://stackoverflow.com/questions/1562548/how-to-make-a-message-into-a-polynomial} + * <p/> + * Convert each set of two ternary coefficients to three bits as follows, and concatenate the resulting bit + * quantities to obtain [the output]: + * <p/> + * <code> + * {-1, -1} -> set "fail" to 1 and set bit string to {1, 1, 1} + * {-1, 0} -> {1, 1, 0}<br/> + * {-1, 1} -> {1, 1, 1}<br/> + * {0, -1} -> {0, 1, 0}<br/> + * {0, 0} -> {0, 0, 0}<br/> + * {0, 1} -> {0, 0, 1}<br/> + * {1, -1} -> {1, 0, 1}<br/> + * {1, 0} -> {0, 1, 1}<br/> + * {1, 1} -> {1, 0, 0}<br/> + * </code> + */ + private static final int[] BIT1_TABLE = {1, 1, 1, 0, 0, 0, 1, 0, 1}; + private static final int[] BIT2_TABLE = {1, 1, 1, 1, 0, 0, 0, 1, 0}; + private static final int[] BIT3_TABLE = {1, 0, 1, 0, 0, 1, 1, 1, 0}; + + /** + * Encodes an int array whose elements are between 0 and <code>q</code>, + * to a byte array leaving no gaps between bits.<br> + * <code>q</code> must be a power of 2. + * + * @param a the input array + * @param q the modulus + * @return the encoded array + */ + public static byte[] encodeModQ(int[] a, int q) + { + int bitsPerCoeff = 31 - Integer.numberOfLeadingZeros(q); + int numBits = a.length * bitsPerCoeff; + int numBytes = (numBits + 7) / 8; + byte[] data = new byte[numBytes]; + int bitIndex = 0; + int byteIndex = 0; + for (int i = 0; i < a.length; i++) + { + for (int j = 0; j < bitsPerCoeff; j++) + { + int currentBit = (a[i] >> j) & 1; + data[byteIndex] |= currentBit << bitIndex; + if (bitIndex == 7) + { + bitIndex = 0; + byteIndex++; + } + else + { + bitIndex++; + } + } + } + return data; + } + + /** + * Decodes a <code>byte</code> array encoded with {@link #encodeModQ(int[], int)} back to an <code>int</code> array.<br> + * <code>N</code> is the number of coefficients. <code>q</code> must be a power of <code>2</code>.<br> + * Ignores any excess bytes. + * + * @param data an encoded ternary polynomial + * @param N number of coefficients + * @param q + * @return an array containing <code>N</code> coefficients between <code>0</code> and <code>q-1</code> + */ + public static int[] decodeModQ(byte[] data, int N, int q) + { + int[] coeffs = new int[N]; + int bitsPerCoeff = 31 - Integer.numberOfLeadingZeros(q); + int numBits = N * bitsPerCoeff; + int coeffIndex = 0; + for (int bitIndex = 0; bitIndex < numBits; bitIndex++) + { + if (bitIndex > 0 && bitIndex % bitsPerCoeff == 0) + { + coeffIndex++; + } + int bit = getBit(data, bitIndex); + coeffs[coeffIndex] += bit << (bitIndex % bitsPerCoeff); + } + return coeffs; + } + + /** + * Decodes data encoded with {@link #encodeModQ(int[], int)} back to an <code>int</code> array.<br> + * <code>N</code> is the number of coefficients. <code>q</code> must be a power of <code>2</code>.<br> + * Ignores any excess bytes. + * + * @param is an encoded ternary polynomial + * @param N number of coefficients + * @param q + * @return the decoded polynomial + */ + public static int[] decodeModQ(InputStream is, int N, int q) + throws IOException + { + int qBits = 31 - Integer.numberOfLeadingZeros(q); + int size = (N * qBits + 7) / 8; + byte[] arr = Util.readFullLength(is, size); + return decodeModQ(arr, N, q); + } + + /** + * Decodes a <code>byte</code> array encoded with {@link #encodeMod3Sves(int[])} back to an <code>int</code> array + * with <code>N</code> coefficients between <code>-1</code> and <code>1</code>.<br> + * Ignores any excess bytes.<br> + * See P1363.1 section 9.2.2. + * + * @param data an encoded ternary polynomial + * @param N number of coefficients + * @return the decoded coefficients + */ + public static int[] decodeMod3Sves(byte[] data, int N) + { + int[] coeffs = new int[N]; + int coeffIndex = 0; + for (int bitIndex = 0; bitIndex < data.length * 8; ) + { + int bit1 = getBit(data, bitIndex++); + int bit2 = getBit(data, bitIndex++); + int bit3 = getBit(data, bitIndex++); + int coeffTableIndex = bit1 * 4 + bit2 * 2 + bit3; + coeffs[coeffIndex++] = COEFF1_TABLE[coeffTableIndex]; + coeffs[coeffIndex++] = COEFF2_TABLE[coeffTableIndex]; + // ignore bytes that can't fit + if (coeffIndex > N - 2) + { + break; + } + } + return coeffs; + } + + /** + * Encodes an <code>int</code> array whose elements are between <code>-1</code> and <code>1</code>, to a byte array. + * <code>coeffs[2*i]</code> and <code>coeffs[2*i+1]</code> must not both equal -1 for any integer <code>i</code>, + * so this method is only safe to use with arrays produced by {@link #decodeMod3Sves(byte[], int)}.<br> + * See P1363.1 section 9.2.3. + * + * @param arr + * @return the encoded array + */ + public static byte[] encodeMod3Sves(int[] arr) + { + int numBits = (arr.length * 3 + 1) / 2; + int numBytes = (numBits + 7) / 8; + byte[] data = new byte[numBytes]; + int bitIndex = 0; + int byteIndex = 0; + for (int i = 0; i < arr.length / 2 * 2; ) + { // if length is an odd number, throw away the highest coeff + int coeff1 = arr[i++] + 1; + int coeff2 = arr[i++] + 1; + if (coeff1 == 0 && coeff2 == 0) + { + throw new IllegalStateException("Illegal encoding!"); + } + int bitTableIndex = coeff1 * 3 + coeff2; + int[] bits = new int[]{BIT1_TABLE[bitTableIndex], BIT2_TABLE[bitTableIndex], BIT3_TABLE[bitTableIndex]}; + for (int j = 0; j < 3; j++) + { + data[byteIndex] |= bits[j] << bitIndex; + if (bitIndex == 7) + { + bitIndex = 0; + byteIndex++; + } + else + { + bitIndex++; + } + } + } + return data; + } + + /** + * Encodes an <code>int</code> array whose elements are between <code>-1</code> and <code>1</code>, to a byte array. + * + * @return the encoded array + */ + public static byte[] encodeMod3Tight(int[] intArray) + { + BigInteger sum = BigInteger.ZERO; + for (int i = intArray.length - 1; i >= 0; i--) + { + sum = sum.multiply(BigInteger.valueOf(3)); + sum = sum.add(BigInteger.valueOf(intArray[i] + 1)); + } + + int size = (BigInteger.valueOf(3).pow(intArray.length).bitLength() + 7) / 8; + byte[] arr = sum.toByteArray(); + + if (arr.length < size) + { + // pad with leading zeros so arr.length==size + byte[] arr2 = new byte[size]; + System.arraycopy(arr, 0, arr2, size - arr.length, arr.length); + return arr2; + } + + if (arr.length > size) + // drop sign bit + { + arr = Arrays.copyOfRange(arr, 1, arr.length); + } + return arr; + } + + /** + * Converts a byte array produced by {@link #encodeMod3Tight(int[])} back to an <code>int</code> array. + * + * @param b a byte array + * @param N number of coefficients + * @return the decoded array + */ + public static int[] decodeMod3Tight(byte[] b, int N) + { + BigInteger sum = new BigInteger(1, b); + int[] coeffs = new int[N]; + for (int i = 0; i < N; i++) + { + coeffs[i] = sum.mod(BigInteger.valueOf(3)).intValue() - 1; + if (coeffs[i] > 1) + { + coeffs[i] -= 3; + } + sum = sum.divide(BigInteger.valueOf(3)); + } + return coeffs; + } + + /** + * Converts data produced by {@link #encodeMod3Tight(int[])} back to an <code>int</code> array. + * + * @param is an input stream containing the data to decode + * @param N number of coefficients + * @return the decoded array + */ + public static int[] decodeMod3Tight(InputStream is, int N) + throws IOException + { + int size = (int)Math.ceil(N * Math.log(3) / Math.log(2) / 8); + byte[] arr = Util.readFullLength(is, size); + return decodeMod3Tight(arr, N); + } + + private static int getBit(byte[] arr, int bitIndex) + { + int byteIndex = bitIndex / 8; + int arrElem = arr[byteIndex] & 0xFF; + return (arrElem >> (bitIndex % 8)) & 1; + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/pqc/math/ntru/util/Util.java b/core/src/main/java/org/spongycastle/pqc/math/ntru/util/Util.java new file mode 100644 index 00000000..b07e1cb8 --- /dev/null +++ b/core/src/main/java/org/spongycastle/pqc/math/ntru/util/Util.java @@ -0,0 +1,158 @@ +package org.spongycastle.pqc.math.ntru.util; + +import java.io.IOException; +import java.io.InputStream; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.spongycastle.pqc.math.ntru.euclid.IntEuclidean; +import org.spongycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial; +import org.spongycastle.pqc.math.ntru.polynomial.TernaryPolynomial; +import org.spongycastle.util.Integers; + +public class Util +{ + private static volatile boolean IS_64_BITNESS_KNOWN; + private static volatile boolean IS_64_BIT_JVM; + + /** + * Calculates the inverse of n mod modulus + */ + public static int invert(int n, int modulus) + { + n %= modulus; + if (n < 0) + { + n += modulus; + } + return IntEuclidean.calculate(n, modulus).x; + } + + /** + * Calculates a^b mod modulus + */ + public static int pow(int a, int b, int modulus) + { + int p = 1; + for (int i = 0; i < b; i++) + { + p = (p * a) % modulus; + } + return p; + } + + /** + * Calculates a^b mod modulus + */ + public static long pow(long a, int b, long modulus) + { + long p = 1; + for (int i = 0; i < b; i++) + { + p = (p * a) % modulus; + } + return p; + } + + /** + * Generates a "sparse" or "dense" polynomial containing numOnes ints equal to 1, + * numNegOnes int equal to -1, and the rest equal to 0. + * + * @param N + * @param numOnes + * @param numNegOnes + * @param sparse whether to create a {@link SparseTernaryPolynomial} or {@link DenseTernaryPolynomial} + * @return a ternary polynomial + */ + public static TernaryPolynomial generateRandomTernary(int N, int numOnes, int numNegOnes, boolean sparse, SecureRandom random) + { + if (sparse) + { + return SparseTernaryPolynomial.generateRandom(N, numOnes, numNegOnes, random); + } + else + { + return DenseTernaryPolynomial.generateRandom(N, numOnes, numNegOnes, random); + } + } + + /** + * Generates an array containing numOnes ints equal to 1, + * numNegOnes int equal to -1, and the rest equal to 0. + * + * @param N + * @param numOnes + * @param numNegOnes + * @return an array of integers + */ + public static int[] generateRandomTernary(int N, int numOnes, int numNegOnes, SecureRandom random) + { + Integer one = Integers.valueOf(1); + Integer minusOne = Integers.valueOf(-1); + Integer zero = Integers.valueOf(0); + + List list = new ArrayList(); + for (int i = 0; i < numOnes; i++) + { + list.add(one); + } + for (int i = 0; i < numNegOnes; i++) + { + list.add(minusOne); + } + while (list.size() < N) + { + list.add(zero); + } + + Collections.shuffle(list, random); + + int[] arr = new int[N]; + for (int i = 0; i < N; i++) + { + arr[i] = ((Integer)list.get(i)).intValue(); + } + return arr; + } + + /** + * Takes an educated guess as to whether 64 bits are supported by the JVM. + * + * @return <code>true</code> if 64-bit support detected, <code>false</code> otherwise + */ + public static boolean is64BitJVM() + { + if (!IS_64_BITNESS_KNOWN) + { + String arch = System.getProperty("os.arch"); + String sunModel = System.getProperty("sun.arch.data.model"); + IS_64_BIT_JVM = "amd64".equals(arch) || "x86_64".equals(arch) || "ppc64".equals(arch) || "64".equals(sunModel); + IS_64_BITNESS_KNOWN = true; + } + return IS_64_BIT_JVM; + } + + /** + * Reads a given number of bytes from an <code>InputStream</code>. + * If there are not enough bytes in the stream, an <code>IOException</code> + * is thrown. + * + * @param is + * @param length + * @return an array of length <code>length</code> + * @throws IOException + */ + public static byte[] readFullLength(InputStream is, int length) + throws IOException + { + byte[] arr = new byte[length]; + if (is.read(arr) != arr.length) + { + throw new IOException("Not enough bytes to read."); + } + return arr; + } +}
\ No newline at end of file |