From 2ccdc75b9e1b2d3a85fdd6f42fa0374dfa6bc3ea Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Thu, 11 May 2023 19:32:38 +0700 Subject: Refactoring in Picnic --- crypto/src/pqc/crypto/picnic/PicnicEngine.cs | 544 ++++++++++----------- crypto/src/pqc/crypto/picnic/PicnicSigner.cs | 14 +- crypto/src/pqc/crypto/picnic/PicnicUtilities.cs | 34 +- crypto/src/pqc/crypto/picnic/Signature.cs | 20 +- crypto/src/pqc/crypto/picnic/Tape.cs | 8 +- crypto/src/pqc/crypto/picnic/Tree.cs | 2 - crypto/src/pqc/crypto/picnic/View.cs | 10 +- .../test/src/pqc/crypto/test/PicnicVectorTest.cs | 33 +- 8 files changed, 350 insertions(+), 315 deletions(-) diff --git a/crypto/src/pqc/crypto/picnic/PicnicEngine.cs b/crypto/src/pqc/crypto/picnic/PicnicEngine.cs index 38b1fecc0..f25f0b812 100644 --- a/crypto/src/pqc/crypto/picnic/PicnicEngine.cs +++ b/crypto/src/pqc/crypto/picnic/PicnicEngine.cs @@ -86,10 +86,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic return CRYPTO_BYTES + messageLength; } - //todo dont do this internal int GetTrueSignatureSize() { - return signatureLength + 4; + return signatureLength; } internal PicnicEngine(int picnicParams, LowmcConstants lowmcConstants) @@ -319,19 +318,19 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic private int picnic_verify(byte[] pk, byte[] message, byte[] signature, uint sigLen) { - byte[] plaintext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; - byte[] ciphertext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; - picnic_read_public_key(ciphertext_bytes, plaintext_bytes, pk); uint[] ciphertext = new uint[stateSizeWords]; uint[] plaintext = new uint[stateSizeWords]; - - Pack.LE_To_UInt32(plaintext_bytes, 0, plaintext); - Pack.LE_To_UInt32(ciphertext_bytes, 0, ciphertext); + picnic_read_public_key(ciphertext, plaintext, pk); if (is_picnic3(parameters)) { Signature2 sig = new Signature2(this); - DeserializeSignature2(sig, signature, sigLen, message.Length + 4); + int ret = DeserializeSignature2(sig, signature, sigLen, message.Length + 4); + if (ret != 0) + { + Console.Error.Write("Error couldn't deserialize signature (2)!"); + return -1; + } return verify_picnic3(sig, ciphertext, plaintext, message); } @@ -342,6 +341,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic if (ret != 0) { Console.Error.Write("Error couldn't deserialize signature!"); + return -1; } return Verify(sig, ciphertext, plaintext, message); @@ -398,9 +398,13 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic view1s[i] = new View(this); view2s[i] = new View(this); - VerifyProof(proofs[i], view1s[i], view2s[i], + if (!VerifyProof(proofs[i], view1s[i], view2s[i], GetChallenge(received_challengebits, i), sig.salt, (uint)i, - tmp, plaintext, tape); + tmp, plaintext, tape)) + { + Console.Error.Write(("Invalid signature. Did not verify\n")); + return -1; + } // create ordered array of commitments with order computed based on the challenge // check commitments of the two opened views @@ -419,7 +423,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic viewOutputs[i][challenge] = view1s[i].outputShare; viewOutputs[i][(challenge + 1) % 3] = view2s[i].outputShare; uint[] view3Output = new uint[stateSizeWords]; /* pointer into the slab to the current 3rd view */ - xor_three(view3Output, view1s[i].outputShare, view2s[i].outputShare, pubKey, stateSizeBytes); + xor_three(view3Output, view1s[i].outputShare, view2s[i].outputShare, pubKey); viewOutputs[i][(challenge + 2) % 3] = view3Output; } @@ -436,7 +440,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic return status; } - private void VerifyProof(Signature.Proof proof, View view1, View view2, int challenge, byte[] salt, + private bool VerifyProof(Signature.Proof proof, View view1, View view2, int challenge, byte[] salt, uint roundNumber, byte[] tmp, uint[] plaintext, Tape tape) { Array.Copy(proof.communicatedBits, 0, view2.communicatedBits, 0, andSizeBytes); @@ -460,9 +464,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic 1, tmp, stateSizeBytes + andSizeBytes); if (!status) - { break; - } Pack.LE_To_UInt32(tmp, 0, view2.inputShare); //todo check Array.Copy(tmp, stateSizeBytes, tape.tapes[1], 0, andSizeBytes); @@ -481,12 +483,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic Array.Copy(tmp, stateSizeBytes, tape.tapes[0], 0, andSizeBytes); status = status && CreateRandomTape(proof.seed2, 0, salt, roundNumber, 2, tape.tapes[1], andSizeBytes); + if (!status) - { break; - } - Array.Copy(proof.inputShare, 0, view2.inputShare, 0, stateSizeBytes); + Array.Copy(proof.inputShare, 0, view2.inputShare, 0, stateSizeWords); break; } case 2: @@ -495,13 +496,12 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic // it is not computable from the seed. We just need to compute view2's input from // its seed status = CreateRandomTape(proof.seed1, 0, salt, roundNumber, 2, tape.tapes[0], andSizeBytes); - Array.Copy(proof.inputShare, 0, view1.inputShare, 0, stateSizeBytes); + Array.Copy(proof.inputShare, 0, view1.inputShare, 0, stateSizeWords); status = status && CreateRandomTape(proof.seed2, 0, salt, roundNumber, 0, tmp, (stateSizeBytes + andSizeBytes)); + if (!status) - { break; - } Pack.LE_To_UInt32(tmp, 0, view2.inputShare); //todo check Array.Copy(tmp, stateSizeBytes, tape.tapes[1], 0, andSizeBytes); @@ -518,27 +518,18 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic { Console.Error.Write( "Failed to generate random tapes, signature verification will fail (but signature may actually be valid)\n"); + return false; } - /* When input shares are read from the tapes, and the length is not a whole number of bytes, the trailing bits must be zero */ - byte[] view_bytes = new byte[stateSizeBytes * 4]; - Pack.UInt32_To_LE(view1.inputShare, view_bytes, 0); - Arrays.Fill(view_bytes, stateSizeBytes, view_bytes.Length, - (byte) 0); //todo have correct size: reduce view.inputshare by /4 - PicnicUtilities.ZeroTrailingBits(view_bytes, stateSizeBits); - Pack.LE_To_UInt32(view_bytes, 0, view1.inputShare); - - Pack.UInt32_To_LE(view2.inputShare, view_bytes, 0); - Arrays.Fill(view_bytes, stateSizeBytes, view_bytes.Length, (byte) 0); - PicnicUtilities.ZeroTrailingBits(view_bytes, stateSizeBits); - - Pack.LE_To_UInt32(view_bytes, 0, view2.inputShare); + PicnicUtilities.ZeroTrailingBits(view1.inputShare, stateSizeBits); + PicnicUtilities.ZeroTrailingBits(view2.inputShare, stateSizeBits); uint[] tmp_ints = Pack.LE_To_UInt32(tmp, 0, tmp.Length / 4); mpc_LowMC_verify(view1, view2, tape, tmp_ints, plaintext, challenge); + return true; } - private void mpc_LowMC_verify(View view1, View view2, Tape tapes, uint[] tmp, uint[] plaintext, int challenge) + private void mpc_LowMC_verify(View view1, View view2, Tape tapes, uint[] tmp, uint[] plaintext, int challenge) { PicnicUtilities.Fill(tmp, 0, tmp.Length, 0); @@ -552,7 +543,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic view2.inputShare, 0, current.GetData(), current.GetMatrixPointer()); - mpc_xor(tmp, tmp, stateSizeWords, 2); + mpc_xor(tmp, tmp, 2); for (int r = 1; r <= numRounds; ++r) { @@ -573,7 +564,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic current = _lowmcConstants.RConstant(this, r - 1); mpc_xor_constant_verify(tmp, current.GetData(), current.GetMatrixPointer(), stateSizeWords, challenge); - mpc_xor(tmp, tmp, stateSizeWords, 2); + mpc_xor(tmp, tmp, 2); } Array.Copy(tmp, 2 * stateSizeWords, view1.outputShare, 0, stateSizeWords); @@ -617,10 +608,14 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic private void mpc_AND_verify(uint[] in1, uint[] in2, uint[] output, Tape rand, View view1, View view2) { - uint[] r = {PicnicUtilities.GetBit(rand.tapes[0], rand.pos), PicnicUtilities.GetBit(rand.tapes[1], rand.pos)}; + uint r0 = PicnicUtilities.GetBit(rand.tapes[0], rand.pos); + uint r1 = PicnicUtilities.GetBit(rand.tapes[1], rand.pos); - output[0] = (in1[0] & in2[1]) ^ (in1[1] & in2[0]) ^ (in1[0] & in2[0]) ^ r[0] ^ r[1]; - PicnicUtilities.SetBit(view1.communicatedBits, rand.pos, (byte) (output[0] & 0xff)); + uint a0 = in1[0], a1 = in1[1]; + uint b0 = in2[0], b1 = in2[1]; + + output[0] = (a0 & b1) ^ (a1 & b0) ^ (a0 & b0) ^ r0 ^ r1; + PicnicUtilities.SetBit(view1.communicatedBits, rand.pos, (byte)output[0]); output[1] = PicnicUtilities.GetBit(view2.communicatedBits, rand.pos); rand.pos++; @@ -643,46 +638,45 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic return; } - for (int i = 0; i < length; i++) - { - state[i + offset] = state[i + offset] ^ input[i +inOffset]; - } - + Nat.XorTo(length, input, inOffset, state, offset); } private int DeserializeSignature(Signature sig, byte[] sigBytes, uint sigBytesLen, int sigBytesOffset) { Signature.Proof[] proofs = sig.proofs; byte[] challengeBits = sig.challengeBits; + int challengesLength = PicnicUtilities.NumBytes(2 * numMPCRounds); /* Validate input buffer is large enough */ - if (sigBytesLen < PicnicUtilities.NumBytes(2 * numMPCRounds)) + if (sigBytesLen < challengesLength) { /* ensure the input has at least the challenge */ return -1; } - int inputShareSize = ComputeInputShareSize(sigBytes, stateSizeBytes); - int bytesExpected = PicnicUtilities.NumBytes(2 * numMPCRounds) + saltSizeBytes + - numMPCRounds * (2 * seedSizeBytes + andSizeBytes + digestSizeBytes) + inputShareSize; + // NOTE: This also validates that there are no challenges > 2 + int numNonZeroChallenges = CountNonZeroChallenges(sigBytes, sigBytesOffset); + if (numNonZeroChallenges < 0) + return -1; + + int inputShareSize = numNonZeroChallenges * stateSizeBytes; + int bytesRequired = challengesLength + saltSizeBytes + + numMPCRounds * (2 * seedSizeBytes + andSizeBytes + digestSizeBytes) + inputShareSize; if (transform == TRANSFORM_UR) { - bytesExpected += UnruhGWithoutInputBytes * numMPCRounds; + bytesRequired += UnruhGWithInputBytes * (numMPCRounds - numNonZeroChallenges); + bytesRequired += UnruhGWithoutInputBytes * numNonZeroChallenges; } - if (sigBytesLen < bytesExpected) + if (sigBytesLen != bytesRequired) { + Console.Error.Write("sigBytesLen = %d, expected bytesRequired = %d\n", sigBytesLen, bytesRequired); return -1; } - Array.Copy(sigBytes, sigBytesOffset, challengeBits, 0, PicnicUtilities.NumBytes(2 * numMPCRounds)); - sigBytesOffset += PicnicUtilities.NumBytes(2 * numMPCRounds); - - if (!IsChallengeValid(challengeBits)) - { - return -1; - } + Array.Copy(sigBytes, sigBytesOffset, challengeBits, 0, challengesLength); + sigBytesOffset += challengesLength; Array.Copy(sigBytes, sigBytesOffset, sig.salt, 0, saltSizeBytes); sigBytesOffset += saltSizeBytes; @@ -716,60 +710,63 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic Pack.LE_To_UInt32(sigBytes, sigBytesOffset, proofs[i].inputShare, 0, stateSizeBytes / 4); if (stateSizeBits == 129) { - proofs[i].inputShare[stateSizeWords - 1] = (uint)sigBytes[sigBytesOffset + stateSizeBytes - 1] & 0xff; + proofs[i].inputShare[stateSizeWords - 1] = (uint)sigBytes[sigBytesOffset + stateSizeBytes - 1]; } sigBytesOffset += stateSizeBytes; - if (!ArePaddingBitsZero(Pack.UInt32_To_LE(proofs[i].inputShare), stateSizeBits)) - { + + if (!ArePaddingBitsZero(proofs[i].inputShare, stateSizeBits)) return -1; - } } - } return 0; } - private bool IsChallengeValid(byte[] challengeBits) - { - for (int i = 0; i < numMPCRounds; i++) - { - int challenge = GetChallenge(challengeBits, i); - if (challenge > 2) - { - return false; - } - } - - return true; - } - - private int ComputeInputShareSize(byte[] challengeBits, int stateSizeBytes) + private int CountNonZeroChallenges(byte[] challengeBits, int challengeBitsOffset) { /* When the FS transform is used, the input share is included in the proof - * only when the challenge is 1 or 2. When dersializing, to compute the + * only when the challenge is 1 or 2. When deserializing, to compute the * number of bytes expected, we must check how many challenge values are 1 - * or 2. The parameter stateSizeBytes is the size of an input share. */ - int inputShareSize = 0; + * or 2. We also check that no challenges have the invalid value 3. */ + int count = 0; + uint challenges3 = 0U; - for (int i = 0; i < numMPCRounds; i++) + int i = 0; + while (i + 16 <= numMPCRounds) { - int challenge = GetChallenge(challengeBits, i); - if (challenge == 1 || challenge == 2) - { - inputShareSize += stateSizeBytes; - } + uint challenges = Pack.LE_To_UInt32(challengeBits, challengeBitsOffset + (i >> 2)); + challenges3 |= challenges & (challenges >> 1); + count += Integers.PopCount((challenges ^ (challenges >> 1)) & 0x55555555U); + i += 16; + } + + int remainingBits = (numMPCRounds - i) * 2; + if (remainingBits > 0) + { + int remainingBytes = (remainingBits + 7) / 8; + uint challenges = Pack.LE_To_UInt32_Low(challengeBits, challengeBitsOffset + (i >> 2), remainingBytes); + challenges &= PicnicUtilities.GetTrailingBitsMask(remainingBits); + challenges3 |= challenges & (challenges >> 1); + count += Integers.PopCount((challenges ^ (challenges >> 1)) & 0x55555555U); } - return inputShareSize; + return (challenges3 & 0x55555555U) == 0U ? count : -1; } - private uint picnic_read_public_key(byte[] ciphertext, byte[] plaintext, byte[] pk) + private void picnic_read_public_key(uint[] ciphertext, uint[] plaintext, byte[] pk) { - Array.Copy(pk, 1, ciphertext, 0, stateSizeBytes); - Array.Copy(pk, 1 + stateSizeBytes, plaintext, 0, stateSizeBytes); - return 0; + int ciphertextPos = 1, plaintextPos = 1 + stateSizeBytes; + int fullWords = stateSizeBytes / 4; + Pack.LE_To_UInt32(pk, ciphertextPos, ciphertext, 0, fullWords); + Pack.LE_To_UInt32(pk, plaintextPos, plaintext, 0, fullWords); + + if (fullWords < stateSizeWords) + { + int fullWordBytes = fullWords * 4, partialWordBytes = stateSizeBytes - fullWordBytes; + ciphertext[fullWords] = Pack.LE_To_UInt32_Low(pk, ciphertextPos + fullWordBytes, partialWordBytes); + plaintext[fullWords] = Pack.LE_To_UInt32_Low(pk, plaintextPos + fullWordBytes, partialWordBytes); + } } private int verify_picnic3(Signature2 sig, uint[] pubKey, uint[] plaintext, byte[] message) @@ -939,15 +936,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic uint[] missingLeaves = GetMissingLeavesList(sig.challengeC); ret = treeCv.AddMerkleNodes(missingLeaves, (uint)missingLeavesSize, sig.cvInfo, (uint)sig.cvInfoLen); if (ret != 0) - { return -1; - } ret = treeCv.VerifyMerkleTree(Cv, sig.salt); if (ret != 0) - { return -1; - } /* Compute the challenge hash */ HCP(challengeHash, null, null, Ch, treeCv.nodes[0], sig.salt, pubKey, plaintext, message); @@ -968,9 +961,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic int bytesRequired = digestSizeBytes + saltSizeBytes; if (sigBytes.Length < bytesRequired) - { return -1; - } Array.Copy(sigBytes, sigBytesOffset, sig.challengeHash, 0, digestSizeBytes); sigBytesOffset += digestSizeBytes; @@ -1001,7 +992,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic { if (Contains(sig.challengeC, numOpenedRounds, t)) { - uint P_t = sig.challengeP[IndexOf(sig.challengeC, numOpenedRounds, t)]; + uint P_t = sig.challengeP[IndexOf(sig.challengeC, numOpenedRounds, t)]; if (P_t != (numMPCParties - 1)) { bytesRequired += andSizeBytes; @@ -1017,7 +1008,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic /* Fail if the signature does not have the exact number of bytes we expect */ if (sigLen != bytesRequired) { - Console.Error.Write("sigBytesLen = %d, expected bytesRequired = %d\n", sigBytes.Length, bytesRequired); + Console.Error.Write("sigLen = %d, expected bytesRequired = %d\n", sigLen, bytesRequired); return -1; } @@ -1033,7 +1024,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic /* Read the proofs */ for (uint t = 0; t < numMPCRounds; t++) { - if (Contains(sig.challengeC, numOpenedRounds, (uint)t)) + if (Contains(sig.challengeC, numOpenedRounds, t)) { sig.proofs[t] = new Signature2.Proof2(this); sig.proofs[t].seedInfoLen = seedInfoLen; @@ -1079,40 +1070,51 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic int byteLength = PicnicUtilities.NumBytes(bitLength); for (int i = bitLength; i < byteLength * 8; i++) { - uint bit_i = PicnicUtilities.GetBit(data, i); + uint bit_i = PicnicUtilities.GetBit(data, i); if (bit_i != 0) - { return false; - } } - return true; } + private bool ArePaddingBitsZero(uint[] data, int bitLength) + { + int partialWord = bitLength & 31; + if (partialWord == 0) + return true; + + uint mask = PicnicUtilities.GetTrailingBitsMask(bitLength); + return (data[bitLength >> 5] & ~mask) == 0U; + } + internal void crypto_sign(byte[] sm, byte[] m, byte[] sk) { - picnic_sign(sk, m, sm); + bool ret = picnic_sign(sk, m, sm); + if (!ret) + return; // throw error? + Array.Copy(m, 0, sm, 4, m.Length); - sm = Arrays.CopyOfRange(sm, 0, signatureLength); } - private void picnic_sign(byte[] sk, byte[] message, byte[] signature) + private bool picnic_sign(byte[] sk, byte[] message, byte[] signature) { - //todo unify conversion - - byte[] data_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; - Array.Copy(sk, 1, data_bytes, 0, stateSizeBytes); - byte[] ciphertext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; - Array.Copy(sk, 1 + stateSizeBytes, ciphertext_bytes, 0, stateSizeBytes); - byte[] plaintext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; - Array.Copy(sk, 1 + 2 * stateSizeBytes, plaintext_bytes, 0, stateSizeBytes); uint[] data = new uint[stateSizeWords]; uint[] ciphertext = new uint[stateSizeWords]; uint[] plaintext = new uint[stateSizeWords]; - Pack.LE_To_UInt32(data_bytes, 0, data); - Pack.LE_To_UInt32(plaintext_bytes, 0, plaintext); - Pack.LE_To_UInt32(ciphertext_bytes, 0, ciphertext); + int dataPos = 1, ciphertextPos = 1 + stateSizeBytes, plaintextPos = 1 + 2 * stateSizeBytes; + int fullWords = stateSizeBytes / 4; + Pack.LE_To_UInt32(sk, dataPos, data, 0, fullWords); + Pack.LE_To_UInt32(sk, ciphertextPos, ciphertext, 0, fullWords); + Pack.LE_To_UInt32(sk, plaintextPos, plaintext, 0, fullWords); + + if (fullWords < stateSizeWords) + { + int fullWordBytes = fullWords * 4, partialWordBytes = stateSizeBytes - fullWordBytes; + data[fullWords] = Pack.LE_To_UInt32_Low(sk, dataPos + fullWordBytes, partialWordBytes); + ciphertext[fullWords] = Pack.LE_To_UInt32_Low(sk, ciphertextPos + fullWordBytes, partialWordBytes); + plaintext[fullWords] = Pack.LE_To_UInt32_Low(sk, plaintextPos + fullWordBytes, partialWordBytes); + } if (!is_picnic3(parameters)) { @@ -1122,24 +1124,40 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic if (ret != 0) { Console.Error.Write("Failed to create signature\n"); + return false; } int len = SerializeSignature(sig, signature, message.Length + 4); - if (len == -1) + if (len < 0) { Console.Error.Write("Failed to serialize signature\n"); + return false; } signatureLength = len; Pack.UInt32_To_LE((uint)len, signature, 0); + return true; } else { Signature2 sig = new Signature2(this); - sign_picnic3(data, ciphertext, plaintext, message, sig); + bool ret = sign_picnic3(data, ciphertext, plaintext, message, sig); + if (!ret) + { + Console.Error.WriteLine("Failed to create signature"); + return false; + } + int len = SerializeSignature2(sig, signature, message.Length + 4); + if (len < 0) + { + Console.Error.WriteLine("Failed to serialize signature"); + return false; + } + signatureLength = len; Pack.UInt32_To_LE((uint)len, signature, 0); + return true; } } @@ -1160,9 +1178,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic } if (CRYPTO_BYTES < bytesRequired) - { return -1; - } int sigByteIndex = sigOffset; @@ -1170,7 +1186,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic sigByteIndex += PicnicUtilities.NumBytes(2 * numMPCRounds); Array.Copy(sig.salt, 0, sigBytes, sigByteIndex, saltSizeBytes); - sigByteIndex += saltSizeBytes; for (int i = 0; i < numMPCRounds; i++) @@ -1207,10 +1222,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic return sigByteIndex - sigOffset; } - private int GetChallenge(byte[] challenge, int round) - { - return (PicnicUtilities.GetBit(challenge, 2 * round + 1) << 1) | PicnicUtilities.GetBit(challenge, 2 * round); - } + private static int GetChallenge(byte[] challenge, int round) => + PicnicUtilities.GetCrumbAligned(challenge, round); private int SerializeSignature2(Signature2 sig, byte[] sigBytes, int sigOffset) { @@ -1239,9 +1252,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic } if (sigBytes.Length < bytesRequired) - { return -1; - } int sigByteIndex = sigOffset; Array.Copy(sig.challengeHash, 0, sigBytes, sigByteIndex, digestSizeBytes); @@ -1290,13 +1301,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic { bool status; - /* Allocate views and commitments for all parallel iterations */ - View[][] views = new View[numMPCRounds][]; // numMPCRounds, 3 - for (int i = 0; i < numMPCRounds; i++) - { - views[i] = new View[3]; - } - byte[][][] AS = new byte[numMPCRounds][][]; // numMPCRounds, numMPCParties, digestSizeBytes for (int i = 0; i < numMPCRounds; i++) { @@ -1328,13 +1332,14 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic Tape tape = new Tape(this); byte[] tmp = new byte[System.Math.Max(9 * stateSizeBytes, stateSizeBytes + andSizeBytes)]; - byte[] view_byte = new byte[stateSizeBytes * 4]; + + /* Allocate views and commitments for all parallel iterations */ + View[][] views = new View[numMPCRounds][]; // numMPCRounds, 3 for (int k = 0; k < numMPCRounds; k++) { - views[k][0] = new View(this); - views[k][1] = new View(this); - views[k][2] = new View(this); + var vk = views[k] = new View[3]{ new View(this), new View(this), new View(this) }; + // for first two players get all tape INCLUDING INPUT SHARE from seed for (int j = 0; j < 2; j++) { @@ -1346,9 +1351,10 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic return -1; } - Array.Copy(tmp, 0, view_byte, 0, stateSizeBytes); - PicnicUtilities.ZeroTrailingBits(view_byte, stateSizeBits); - Pack.LE_To_UInt32(view_byte, 0, views[k][j].inputShare); + uint[] inputShare = vk[j].inputShare; + Pack.LE_To_UInt32(tmp, 0, inputShare); + PicnicUtilities.ZeroTrailingBits(inputShare, stateSizeBits); + Array.Copy(tmp, stateSizeBytes, tape.tapes[j], 0, andSizeBytes); } @@ -1362,19 +1368,16 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic return -1; } - xor_three(views[k][2].inputShare, privateKey, views[k][0].inputShare, views[k][1].inputShare, - stateSizeBytes); + xor_three(vk[2].inputShare, privateKey, vk[0].inputShare, vk[1].inputShare); tape.pos = 0; uint[] tmp_int = Pack.LE_To_UInt32(tmp, 0, tmp.Length / 4); - mpc_LowMC(tape, views[k], plaintext, tmp_int); + mpc_LowMC(tape, vk, plaintext, tmp_int); Pack.UInt32_To_LE(tmp_int, tmp, 0); uint[] temp = new uint[LOWMC_MAX_WORDS]; - xor_three(temp, views[k][0].outputShare, views[k][1].outputShare, views[k][2].outputShare, - stateSizeBytes); - + xor_three(temp, vk[0].outputShare, vk[1].outputShare, vk[2].outputShare); if (!SubarrayEquals(temp, pubKey, stateSizeWords)) { @@ -1383,38 +1386,28 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic } //Committing - Commit(seeds, ((seedLen) * k) + 0 * seedSizeBytes, views[k][0], AS[k][0]); - Commit(seeds, ((seedLen) * k) + 1 * seedSizeBytes, views[k][1], AS[k][1]); - Commit(seeds, ((seedLen) * k) + 2 * seedSizeBytes, views[k][2], AS[k][2]); + Commit(seeds, ((seedLen) * k) + 0 * seedSizeBytes, vk[0], AS[k][0]); + Commit(seeds, ((seedLen) * k) + 1 * seedSizeBytes, vk[1], AS[k][1]); + Commit(seeds, ((seedLen) * k) + 2 * seedSizeBytes, vk[2], AS[k][2]); if (transform == TRANSFORM_UR) { - G(0, seeds, ((seedLen) * k) + 0 * seedSizeBytes, views[k][0], gs[k][0]); - G(1, seeds, ((seedLen) * k) + 1 * seedSizeBytes, views[k][1], gs[k][1]); - G(2, seeds, ((seedLen) * k) + 2 * seedSizeBytes, views[k][2], gs[k][2]); + G(0, seeds, ((seedLen) * k) + 0 * seedSizeBytes, vk[0], gs[k][0]); + G(1, seeds, ((seedLen) * k) + 1 * seedSizeBytes, vk[1], gs[k][1]); + G(2, seeds, ((seedLen) * k) + 2 * seedSizeBytes, vk[2], gs[k][2]); } } //Generating challenges - uint[][][] viewOutputs = new uint[numMPCRounds][][]; // [3][stateSizeBytes] - for (int i = 0; i < numMPCRounds; i++) - { - viewOutputs[i] = new uint[3][]; - for (int j = 0; j < 3; j++) - { - viewOutputs[i][j] = views[i][j].outputShare; - } - } - H3(pubKey, plaintext, viewOutputs, AS, sig.challengeBits, sig.salt, message, gs); + H3(pubKey, plaintext, views, AS, sig.challengeBits, sig.salt, message, gs); //Packing Z for (int i = 0; i < numMPCRounds; i++) { Signature.Proof proof = sig.proofs[i]; - Prove(proof, GetChallenge(sig.challengeBits, i), seeds, ((seedLen) * i), - views[i], AS[i], - (transform != TRANSFORM_UR) ? null : gs[i]); //todo check if + Prove(proof, GetChallenge(sig.challengeBits, i), seeds, seedLen * i, views[i], AS[i], + (transform != TRANSFORM_UR) ? null : gs[i]); //todo check if } return 0; @@ -1442,11 +1435,12 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic else { Console.Error.Write("Invalid challenge"); + throw new ArgumentException(nameof(challenge)); } if (challenge == 1 || challenge == 2) { - Array.Copy(views[2].inputShare, 0, proof.inputShare, 0, stateSizeBytes); + Array.Copy(views[2].inputShare, 0, proof.inputShare, 0, stateSizeWords); } Array.Copy(views[(challenge + 1) % 3].communicatedBits, 0, proof.communicatedBits, 0, andSizeBytes); @@ -1459,35 +1453,63 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic } } - private void H3(uint[] circuitOutput, uint[] plaintext, uint[][][] viewOutputs, + private void H3(uint[] circuitOutput, uint[] plaintext, View[][] views, byte[][][] AS, byte[] challengeBits, byte[] salt, byte[] message, byte[][][] gs) { - byte[] hash = new byte[digestSizeBytes]; + digest.Update((byte)1); - /* Depending on the number of rounds, we might not set part of the last - * byte, make sure it's always zero. */ - challengeBits[PicnicUtilities.NumBytes(numMPCRounds * 2) - 1] = 0; + byte[] tmp = new byte[stateSizeWords * 4]; + + /* Hash the output share from each view */ + for (int i = 0; i < numMPCRounds; i++) + { + for (int j = 0; j < 3; j++) + { + Pack.UInt32_To_LE(views[i][j].outputShare, tmp, 0); + digest.BlockUpdate(tmp, 0, stateSizeBytes); + } + } - digest.Update((byte) 1); + ImplH3(circuitOutput, plaintext, AS, challengeBits, salt, message, gs); + } + + private void H3(uint[] circuitOutput, uint[] plaintext, uint[][][] viewOutputs, + byte[][][] AS, byte[] challengeBits, byte[] salt, + byte[] message, byte[][][] gs) + { + digest.Update((byte)1); + + byte[] tmp = new byte[stateSizeWords * 4]; /* Hash the output share from each view */ for (int i = 0; i < numMPCRounds; i++) { for (int j = 0; j < 3; j++) { - digest.BlockUpdate(Pack.UInt32_To_LE(viewOutputs[i][j]), 0, stateSizeBytes); + Pack.UInt32_To_LE(viewOutputs[i][j], tmp, 0); + digest.BlockUpdate(tmp, 0, stateSizeBytes); } } + ImplH3(circuitOutput, plaintext, AS, challengeBits, salt, message, gs); + } + + private void ImplH3(uint[] circuitOutput, uint[] plaintext, byte[][][] AS, byte[] challengeBits, byte[] salt, + byte[] message, byte[][][] gs) + { + byte[] hash = new byte[digestSizeBytes]; + + /* Depending on the number of rounds, we might not set part of the last + * byte, make sure it's always zero. */ + challengeBits[PicnicUtilities.NumBytes(numMPCRounds * 2) - 1] = 0; + /* Hash all the commitments C */ for (int i = 0; i < numMPCRounds; i++) { for (int j = 0; j < 3; j++) { - digest.BlockUpdate( AS[i][j], - - 0, digestSizeBytes); + digest.BlockUpdate(AS[i][j], 0, digestSizeBytes); } } @@ -1538,15 +1560,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic } if (!isNotDone) - { break; - } } if (!isNotDone) - { break; - } /* We need more bits; hash set hash = H_1(hash) */ digest.Update((byte) 1); @@ -1601,7 +1619,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic current.GetData(), current.GetMatrixPointer()); } - mpc_xor(slab, slab, stateSizeWords, 3); + mpc_xor(slab, slab, 3); for (int r = 1; r <= numRounds; r++) { @@ -1624,7 +1642,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic mpc_xor_constant(slab, 3 * stateSizeWords, current.GetData(), current.GetMatrixPointer(), stateSizeWords); - mpc_xor(slab, slab, stateSizeWords, 3); + mpc_xor(slab, slab, 3); } for (int i = 0; i < 3; i++) @@ -1687,35 +1705,24 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic /*** Functions implementing Sign ***/ private void mpc_AND(uint[] in1, uint[] in2, uint[] output, Tape rand, View[] views) { - uint[] r = new uint[] - { - PicnicUtilities.GetBit(rand.tapes[0], rand.pos), - PicnicUtilities.GetBit(rand.tapes[1], rand.pos), - PicnicUtilities.GetBit(rand.tapes[2], rand.pos) - }; + uint r0 = PicnicUtilities.GetBit(rand.tapes[0], rand.pos); + uint r1 = PicnicUtilities.GetBit(rand.tapes[1], rand.pos); + uint r2 = PicnicUtilities.GetBit(rand.tapes[2], rand.pos); - for (int i = 0; i < 3; i++) - { - output[i] = - (in1[i] & in2[(i + 1) % 3]) ^ (in1[(i + 1) % 3] & in2[i]) - ^ (in1[i] & in2[i]) ^ r[i] ^ r[(i + 1) % 3]; + output[0] = (in1[0] & in2[1]) ^ (in1[1] & in2[0]) ^ (in1[0] & in2[0]) ^ r0 ^ r1; + output[1] = (in1[1] & in2[2]) ^ (in1[2] & in2[1]) ^ (in1[1] & in2[1]) ^ r1 ^ r2; + output[2] = (in1[2] & in2[0]) ^ (in1[0] & in2[2]) ^ (in1[2] & in2[2]) ^ r2 ^ r0; - PicnicUtilities.SetBit(views[i].communicatedBits, rand.pos, (byte) (output[i] & 0xff)); - } + PicnicUtilities.SetBit(views[0].communicatedBits, rand.pos, (byte)output[0]); + PicnicUtilities.SetBit(views[1].communicatedBits, rand.pos, (byte)output[1]); + PicnicUtilities.SetBit(views[2].communicatedBits, rand.pos, (byte)output[2]); rand.pos++; } - private void mpc_xor(uint[] state, uint[] input, int len, int players) + private void mpc_xor(uint[] state, uint[] input, int players) { - for (int player = 0; player < players; player++) - { - for (int i = 0; i < len; i++) - { - state[i + (players + player) * stateSizeWords] = - state[i + (players + player) * stateSizeWords] ^ input[i+player * stateSizeWords]; - } - } + Nat.XorTo(stateSizeWords * players, input, 0, state, players * stateSizeWords); } private void mpc_matrix_mul(uint[] output, int outputOffset, uint[] state, int stateOffset, @@ -1734,7 +1741,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic { for (int i = 0; i < len; i++) { - state[i + stateOffset] = state[i + stateOffset] ^ input[i+inOffset]; + state[i + stateOffset] ^= input[i + inOffset]; } } @@ -1742,9 +1749,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic byte[] tape, int tapeLen) { if (tapeLen < digestSizeBytes) - { return false; - } /* Hash the seed and a constant, store the result in tape. */ digest.Update((byte) 2); @@ -1766,11 +1771,12 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic private byte[] ComputeSeeds(uint[] privateKey, uint[] publicKey, uint[] plaintext, byte[] message) { byte[] allSeeds = new byte[seedSizeBytes * (numMPCParties * numMPCRounds) + saltSizeBytes]; + byte[] temp = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; - digest.BlockUpdate(Pack.UInt32_To_LE(privateKey), 0, stateSizeBytes); + UpdateDigest(privateKey, temp); digest.BlockUpdate(message, 0, message.Length); - digest.BlockUpdate(Pack.UInt32_To_LE(publicKey), 0, stateSizeBytes); - digest.BlockUpdate(Pack.UInt32_To_LE(plaintext), 0, stateSizeBytes); + UpdateDigest(publicKey, temp); + UpdateDigest(plaintext, temp); digest.BlockUpdate(Pack.UInt32_To_LE((uint)stateSizeBits), 0, 2); // Derive the N*T seeds + 1 salt @@ -1779,7 +1785,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic return allSeeds; } - private void sign_picnic3(uint[] privateKey, uint[] pubKey, uint[] plaintext, byte[] message, Signature2 sig) + private bool sign_picnic3(uint[] privateKey, uint[] pubKey, uint[] plaintext, byte[] message, Signature2 sig) { byte[] saltAndRoot = new byte[saltSizeBytes + seedSizeBytes]; ComputeSaltAndRootSeed(saltAndRoot, privateKey, pubKey, plaintext, message); @@ -1846,11 +1852,12 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic { msgs[t] = new Msg(this); uint[] maskedKey = Pack.LE_To_UInt32(inputs[t], 0, stateSizeWords); - xor_array(maskedKey, maskedKey, privateKey, 0, stateSizeWords); + Nat.XorTo(stateSizeWords, privateKey, maskedKey); int rv = SimulateOnline(maskedKey, tapes[t], tmp_shares, msgs[t], plaintext, pubKey); if (rv != 0) { Console.Error.Write("MPC simulation failed, aborting signature\n"); + return false; } Pack.UInt32_To_LE(maskedKey, inputs[t], 0); @@ -1925,6 +1932,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic Array.Copy(C[t][sig.challengeP[P_index]], 0, sig.proofs[t].C, 0, digestSizeBytes); } } + return true; } private static int IndexOf(uint[] list, int len, uint value) @@ -1942,8 +1950,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic { if (!Contains(challengeC, numOpenedRounds, (uint)i)) { - missingLeaves[pos] = (uint)i; - pos++; + missingLeaves[pos++] = (uint)i; } } @@ -1958,10 +1965,12 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic digest.BlockUpdate(Ch[t], 0, digestSizeBytes); } + byte[] temp = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; + digest.BlockUpdate(hCv, 0, digestSizeBytes); digest.BlockUpdate(salt, 0, saltSizeBytes); - digest.BlockUpdate(Pack.UInt32_To_LE(pubKey), 0, stateSizeBytes); - digest.BlockUpdate(Pack.UInt32_To_LE(plaintext), 0, stateSizeBytes); + UpdateDigest(pubKey, temp); + UpdateDigest(plaintext, temp); digest.BlockUpdate(message, 0, message.Length); digest.OutputFinal(challengeHash, 0, digestSizeBytes); @@ -1974,18 +1983,16 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic private static int BitsToChunks(int chunkLenBits, byte[] input, int inputLen, uint[] chunks) { if (chunkLenBits > inputLen * 8) - { return 0; - } - int chunkCount = ((inputLen * 8) / chunkLenBits); + int chunkCount = (inputLen * 8) / chunkLenBits; for (int i = 0; i < chunkCount; i++) { chunks[i] = 0; for (int j = 0; j < chunkLenBits; j++) { - chunks[i] += (uint) (PicnicUtilities.GetBit(input, i * chunkLenBits + j) << j); + chunks[i] += (uint)PicnicUtilities.GetBit(input, i * chunkLenBits + j) << j; } } @@ -2003,9 +2010,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic for (int i = 0; i < position; i++) { if (list[i] == value) - { return position; - } } list[position] = value; @@ -2034,9 +2039,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic } if (countC == numOpenedRounds) - { break; - } } digest.Update((byte) 1); @@ -2059,9 +2062,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic } if (countP == numOpenedRounds) - { break; - } } digest.Update((byte) 1); @@ -2102,7 +2103,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic KMatricesWithPointer current = _lowmcConstants.KMatrix(this, 0); matrix_mul(roundKey, maskedKey, current.GetData(), current.GetMatrixPointer()); // roundKey = maskedKey * KMatrix[0] - xor_array(state, roundKey, plaintext, 0, stateSizeWords); // state = plaintext + roundKey + xor_array(state, roundKey, plaintext, 0); // state = plaintext + roundKey for (int r = 1; r <= numRounds; r++) { @@ -2114,12 +2115,10 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic current.GetMatrixPointer()); // state = state * LMatrix (r-1) current = _lowmcConstants.RConstant(this, r - 1); - xor_array(state, state, current.GetData(), current.GetMatrixPointer(), - stateSizeWords); // state += RConstant - + Nat.XorTo(stateSizeWords, current.GetData(), current.GetMatrixPointer(), state, 0); // state += RConstant current = _lowmcConstants.KMatrix(this, r); matrix_mul(roundKey, maskedKey, current.GetData(), current.GetMatrixPointer()); - xor_array(state, roundKey, state, 0, stateSizeWords); // state += roundKey + xor_array(state, roundKey, state, 0); // state += roundKey } if (!SubarrayEquals(state, pubKey, stateSizeWords)) @@ -2137,8 +2136,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic { digest.BlockUpdate(seeds[i + seedsOffset], 0, seedSizeBytes); digest.BlockUpdate(salt, 0, saltSizeBytes); - digest.BlockUpdate(Pack.UInt32_To_LE(t), 0, 2); - digest.BlockUpdate(Pack.UInt32_To_LE(i), 0, 2); + digest.BlockUpdate(Pack.UInt32_To_LE((t & 0xFFFFU) | (i << 16)), 0, 4); digest.OutputFinal(tape.tapes[i], 0, tapeSizeBytes); } } @@ -2153,7 +2151,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic if (a[i] != b[i]) return false; } - return true; } @@ -2167,7 +2164,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic if (a[i] != b[i]) return false; } - return true; } @@ -2267,11 +2263,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic for (int i = 0; i < len; i++) { if (list[i] == value) - { return true; - } } - return false; } @@ -2309,49 +2302,44 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic } digest.BlockUpdate(salt, 0, saltSizeBytes); - digest.BlockUpdate(Pack.UInt32_To_LE(t), 0, 2); - digest.BlockUpdate(Pack.UInt32_To_LE(j), 0, 2); + digest.BlockUpdate(Pack.UInt32_To_LE((t & 0xFFFFU) | (j << 16)), 0, 4); digest.OutputFinal(digest_arr, 0, digestSizeBytes); } private void ComputeSaltAndRootSeed(byte[] saltAndRoot, uint[] privateKey, uint[] pubKey, uint[] plaintext, byte[] message) { - //todo unify conversion - //copy back to byte array - byte[] privatekey_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; - byte[] pubkey_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; - byte[] plaintext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; - Pack.UInt32_To_LE(privateKey, privatekey_bytes, 0); - Pack.UInt32_To_LE(pubKey, pubkey_bytes, 0); - Pack.UInt32_To_LE(plaintext, plaintext_bytes, 0); - privatekey_bytes = Arrays.CopyOfRange(privatekey_bytes, 0, stateSizeBytes); - pubkey_bytes = Arrays.CopyOfRange(pubkey_bytes, 0, stateSizeBytes); - plaintext_bytes = Arrays.CopyOfRange(plaintext_bytes, 0, stateSizeBytes); - // + byte[] temp = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; // init done in constructor - digest.BlockUpdate(privatekey_bytes, 0, stateSizeBytes); + UpdateDigest(privateKey, temp); digest.BlockUpdate(message, 0, message.Length); - digest.BlockUpdate(pubkey_bytes, 0, stateSizeBytes); - digest.BlockUpdate(plaintext_bytes, 0, stateSizeBytes); - digest.BlockUpdate(Pack.UInt16_To_LE((ushort) (stateSizeBits & 0xffff)), 0, 2); + UpdateDigest(pubKey, temp); + UpdateDigest(plaintext, temp); + Pack.UInt16_To_LE((ushort)stateSizeBits, temp); + digest.BlockUpdate(temp, 0, 2); digest.OutputFinal(saltAndRoot, 0, saltAndRoot.Length); } + private void UpdateDigest(uint[] block, byte[] temp) + { + Pack.UInt32_To_LE(block, temp, 0); + digest.BlockUpdate(temp, 0, stateSizeBytes); + } + private static bool is_picnic3(int parameters) { - return parameters == 7 /*Picnic3_L1*/ || - parameters == 8 /*Picnic3_L3*/ || - parameters == 9 /*Picnic3_L5*/ ; + return parameters == 7 /*Picnic3_L1*/ + || parameters == 8 /*Picnic3_L3*/ + || parameters == 9 /*Picnic3_L5*/; } //todo return int; internal void crypto_sign_keypair(byte[] pk, byte[] sk, SecureRandom random) { - // set array sizes - byte[] plaintext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; - byte[] ciphertext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; - byte[] data_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE]; + // set array sizes sufficient to be worked with as words + byte[] plaintext_bytes = new byte[stateSizeWords * 4]; + byte[] ciphertext_bytes = new byte[stateSizeWords * 4]; + byte[] data_bytes = new byte[stateSizeWords * 4]; picnic_keygen(plaintext_bytes, ciphertext_bytes, data_bytes, random); picnic_write_public_key(ciphertext_bytes, plaintext_bytes, pk); @@ -2400,13 +2388,13 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic // generate a private key random.NextBytes(data_bytes, 0, stateSizeBytes); - PicnicUtilities.ZeroTrailingBits(data_bytes, stateSizeBits); Pack.LE_To_UInt32(data_bytes, 0, data); + PicnicUtilities.ZeroTrailingBits(data, stateSizeBits); // generate a plaintext block random.NextBytes(plaintext_bytes, 0, stateSizeBytes); - PicnicUtilities.ZeroTrailingBits(plaintext_bytes, stateSizeBits); Pack.LE_To_UInt32(plaintext_bytes, 0, plaintext); + PicnicUtilities.ZeroTrailingBits(plaintext, stateSizeBits); // compute ciphertext LowMCEnc(plaintext, ciphertext, data); @@ -2430,7 +2418,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic KMatricesWithPointer current = _lowmcConstants.KMatrix(this, 0); matrix_mul(roundKey, key, current.GetData(), current.GetMatrixPointer()); - xor_array(output, output, roundKey, 0, stateSizeWords); + Nat.XorTo(stateSizeWords, roundKey, output); for (int r = 1; r <= numRounds; r++) { @@ -2443,8 +2431,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic matrix_mul(output, output, current.GetData(), current.GetMatrixPointer()); current = _lowmcConstants.RConstant(this, r - 1); - xor_array(output, output, current.GetData(), current.GetMatrixPointer(), stateSizeWords); - xor_array(output, output, roundKey, 0, stateSizeWords); + Nat.XorTo(stateSizeWords, current.GetData(), current.GetMatrixPointer(), output, 0); + Nat.XorTo(stateSizeWords, roundKey, output); } } @@ -2462,21 +2450,17 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic } } - private void xor_three(uint[] output, uint[] in1, uint[] in2, uint[] in3, int lenBytes) + private void xor_three(uint[] output, uint[] in1, uint[] in2, uint[] in3) { - int wholeWords = stateSizeWords; - for (int i = 0; i < wholeWords; i++) + for (int i = 0; i < stateSizeWords; i++) { output[i] = in1[i] ^ in2[i] ^ in3[i]; } } - internal void xor_array(uint[] output, uint[] in1, uint[] in2, int in2_offset, int length) + internal void xor_array(uint[] output, uint[] in1, uint[] in2, int in2_offset) { - for (int i = 0; i < length; i++) - { - output[i] = in1[i] ^ in2[i + in2_offset]; - } + Nat.Xor(stateSizeWords, in1, 0, in2, in2_offset, output, 0); } internal void matrix_mul(uint[] output, uint[] state, uint[] matrix, int matrixOffset) diff --git a/crypto/src/pqc/crypto/picnic/PicnicSigner.cs b/crypto/src/pqc/crypto/picnic/PicnicSigner.cs index b391b62bc..f76eb1af3 100644 --- a/crypto/src/pqc/crypto/picnic/PicnicSigner.cs +++ b/crypto/src/pqc/crypto/picnic/PicnicSigner.cs @@ -1,5 +1,7 @@ +using System; + using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Security; +using Org.BouncyCastle.Crypto.Utilities; using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Pqc.Crypto.Picnic @@ -33,18 +35,20 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic byte[] sig = new byte[engine.GetSignatureSize(message.Length)]; engine.crypto_sign(sig, message, privKey.GetEncoded()); - return Arrays.CopyOfRange(sig, message.Length + 4, engine.GetTrueSignatureSize() + message.Length); + byte[] signature = new byte[engine.GetTrueSignatureSize()]; + Array.Copy(sig, message.Length + 4, signature, 0, engine.GetTrueSignatureSize()); + return signature; } public bool VerifySignature(byte[] message, byte[] signature) { PicnicEngine engine = pubKey.Parameters.GetEngine(); byte[] verify_message = new byte[message.Length]; - bool verify = engine.crypto_sign_open(verify_message, signature, pubKey.GetEncoded()); + byte[] attached_signature = Arrays.ConcatenateAll(Pack.UInt32_To_LE((uint)signature.Length), message, signature); + + bool verify = engine.crypto_sign_open(verify_message, attached_signature, pubKey.GetEncoded()); if (!Arrays.AreEqual(message, verify_message)) - { return false; - } return verify; } diff --git a/crypto/src/pqc/crypto/picnic/PicnicUtilities.cs b/crypto/src/pqc/crypto/picnic/PicnicUtilities.cs index d77d7ce29..338729d02 100644 --- a/crypto/src/pqc/crypto/picnic/PicnicUtilities.cs +++ b/crypto/src/pqc/crypto/picnic/PicnicUtilities.cs @@ -1,7 +1,3 @@ -#if NETCOREAPP3_0_OR_GREATER -using System.Runtime.Intrinsics.X86; -#endif - using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Pqc.Crypto.Picnic @@ -66,6 +62,14 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic return (byte)((array[arrayPos] >> bitPos) & 1); } + /* Get a crumb (i.e. two bits) from a byte array. */ + internal static byte GetCrumbAligned(byte[] array, int crumbNumber) + { + int arrayPos = crumbNumber >> 2, bitPos = ((crumbNumber << 1) & 6) ^ 6; + uint b = (uint)array[arrayPos] >> bitPos; + return (byte)((b & 1) << 1 | (b & 2) >> 1); + } + internal static uint GetBit(uint word, int bitNumber) { int bitPos = bitNumber ^ 7; @@ -106,13 +110,27 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic array[arrayPos] = t; } - internal static void ZeroTrailingBits(byte[] data, int bitLength) + internal static void ZeroTrailingBits(uint[] data, int bitLength) { - int partial = bitLength & 7; - if (partial != 0) + int partialWord = bitLength & 31; + if (partialWord != 0) { - data[bitLength >> 3] &= (byte)(0xFF00 >> partial); + data[bitLength >> 5] &= GetTrailingBitsMask(bitLength); } } + + internal static uint GetTrailingBitsMask(int bitLength) + { + int partialShift = bitLength & ~7; + uint mask = ~(0xFFFFFFFFU << partialShift); + + int partialByte = bitLength & 7; + if (partialByte != 0) + { + mask ^= ((0xFF00U >> partialByte) & 0xFFU) << partialShift; + } + + return mask; + } } } diff --git a/crypto/src/pqc/crypto/picnic/Signature.cs b/crypto/src/pqc/crypto/picnic/Signature.cs index edb70b5a6..6605dc1ea 100644 --- a/crypto/src/pqc/crypto/picnic/Signature.cs +++ b/crypto/src/pqc/crypto/picnic/Signature.cs @@ -2,9 +2,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic { internal class Signature { - internal byte[] challengeBits; - internal byte[] salt; - internal Proof[] proofs; + internal readonly byte[] challengeBits; + internal readonly byte[] salt; + internal readonly Proof[] proofs; internal Signature(PicnicEngine engine) { @@ -19,20 +19,20 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic internal class Proof { - internal byte[] seed1; - internal byte[] seed2; + internal readonly byte[] seed1; + internal readonly byte[] seed2; - internal uint[] inputShare; // Input share of the party which does not derive it from the seed (not included if challenge is 0) + internal readonly uint[] inputShare; // Input share of the party which does not derive it from the seed (not included if challenge is 0) - internal byte[] communicatedBits; - internal byte[] view3Commitment; - internal byte[] view3UnruhG; // we include the max length, but we will only serialize the bytes we use + internal readonly byte[] communicatedBits; + internal readonly byte[] view3Commitment; + internal readonly byte[] view3UnruhG; // we include the max length, but we will only serialize the bytes we use internal Proof(PicnicEngine engine) { seed1 = new byte[engine.seedSizeBytes]; seed2 = new byte[engine.seedSizeBytes]; - inputShare = new uint[engine.stateSizeBytes]; + inputShare = new uint[engine.stateSizeWords]; communicatedBits = new byte[engine.andSizeBytes]; view3Commitment = new byte[engine.digestSizeBytes]; if (engine.UnruhGWithInputBytes > 0) diff --git a/crypto/src/pqc/crypto/picnic/Tape.cs b/crypto/src/pqc/crypto/picnic/Tape.cs index b1dc9ca1d..63c747399 100644 --- a/crypto/src/pqc/crypto/picnic/Tape.cs +++ b/crypto/src/pqc/crypto/picnic/Tape.cs @@ -1,5 +1,5 @@ using Org.BouncyCastle.Crypto.Utilities; -using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Math.Raw; namespace Org.BouncyCastle.Pqc.Crypto.Picnic { @@ -67,9 +67,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic // {System.out.printf("%08x ", key[i]);}System.out.Println(); - if(inputs != null) + if (inputs != null) { - Pack.UInt32_To_LE(Arrays.CopyOf(key, engine.stateSizeWords), inputs, 0); + Pack.UInt32_To_LE(key, 0, engine.stateSizeWords, inputs, 0); } @@ -78,7 +78,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic current = engine._lowmcConstants.KMatrix(engine, r); engine.matrix_mul(roundKey, key, current.GetData(), current.GetMatrixPointer()); // roundKey = key * KMatrix(r) - engine.xor_array(x, x, roundKey, 0, engine.stateSizeWords); + Nat.XorTo(engine.stateSizeWords, roundKey, x); current = engine._lowmcConstants.LMatrixInv(engine, r-1); engine.matrix_mul(y, x, current.GetData(), current.GetMatrixPointer()); diff --git a/crypto/src/pqc/crypto/picnic/Tree.cs b/crypto/src/pqc/crypto/picnic/Tree.cs index 52b36f1c8..beffdcb55 100644 --- a/crypto/src/pqc/crypto/picnic/Tree.cs +++ b/crypto/src/pqc/crypto/picnic/Tree.cs @@ -148,9 +148,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic { inLen -= (uint)engine.seedSizeBytes; if (inLen < 0) - { return -1; - } System.Array.Copy(input, i * engine.seedSizeBytes, this.nodes[revealed[i]], 0, engine.seedSizeBytes); this.haveNode[revealed[i]] = true; diff --git a/crypto/src/pqc/crypto/picnic/View.cs b/crypto/src/pqc/crypto/picnic/View.cs index cac47631a..f295f5707 100644 --- a/crypto/src/pqc/crypto/picnic/View.cs +++ b/crypto/src/pqc/crypto/picnic/View.cs @@ -2,15 +2,15 @@ namespace Org.BouncyCastle.Pqc.Crypto.Picnic { internal class View { - internal uint[] inputShare; - internal byte[] communicatedBits; - internal uint[] outputShare; + internal readonly uint[] inputShare; + internal readonly byte[] communicatedBits; + internal readonly uint[] outputShare; internal View(PicnicEngine engine) { - inputShare = new uint[engine.stateSizeBytes]; + inputShare = new uint[engine.stateSizeWords]; communicatedBits = new byte[engine.andSizeBytes]; - outputShare = new uint[engine.stateSizeBytes]; + outputShare = new uint[engine.stateSizeWords]; } } } diff --git a/crypto/test/src/pqc/crypto/test/PicnicVectorTest.cs b/crypto/test/src/pqc/crypto/test/PicnicVectorTest.cs index c3c00cba6..6ceb54697 100644 --- a/crypto/test/src/pqc/crypto/test/PicnicVectorTest.cs +++ b/crypto/test/src/pqc/crypto/test/PicnicVectorTest.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Pqc.Crypto.Picnic; using Org.BouncyCastle.Pqc.Crypto.Utilities; +using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Encoders; using Org.BouncyCastle.Utilities.Test; @@ -33,6 +34,36 @@ namespace Org.BouncyCastle.Pqc.Crypto.Tests private static readonly IEnumerable TestVectorFiles = Parameters.Keys; + [Test] + public void TestPicnicRandom() + { + byte[] msg = Strings.ToByteArray("Hello World!"); + PicnicKeyPairGenerator keyGen = new PicnicKeyPairGenerator(); + + SecureRandom random = new SecureRandom(); + + keyGen.Init(new PicnicKeyGenerationParameters(random, PicnicParameters.picnic3l1)); + + for (int i = 0; i != 100; i++) + { + AsymmetricCipherKeyPair keyPair = keyGen.GenerateKeyPair(); + + // sign + PicnicSigner signer = new PicnicSigner(); + PicnicPrivateKeyParameters skparam = (PicnicPrivateKeyParameters)keyPair.Private; + signer.Init(true, skparam); + + byte[] sigGenerated = signer.GenerateSignature(msg); + + // verify + PicnicSigner verifier = new PicnicSigner(); + PicnicPublicKeyParameters pkparam = (PicnicPublicKeyParameters)keyPair.Public; + verifier.Init(false, pkparam); + + Assert.True(verifier.VerifySignature(msg, sigGenerated), "count = " + i); + } + } + [TestCaseSource(nameof(TestVectorFiles))] [Parallelizable(ParallelScope.All)] public void TV(string testVectorFile) @@ -82,7 +113,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.Tests Assert.True(smlen == attachedSig.Length, name + " " + count + ": signature length"); signer.Init(false, pubParams); - Assert.True(signer.VerifySignature(msg, attachedSig), (name + " " + count + ": signature verify")); + Assert.True(signer.VerifySignature(msg, sigGenerated), (name + " " + count + ": signature verify")); Assert.True(Arrays.AreEqual(sigExpected, attachedSig), name + " " + count + ": signature gen match"); } -- cgit 1.4.1