Skip to content

Commit

Permalink
Added support for spans instead of array copying, added more tests, m…
Browse files Browse the repository at this point in the history
…ade low-level methods part of the public API
  • Loading branch information
bazzilic committed Sep 27, 2022
1 parent d72d308 commit cbec37c
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.1;net45</TargetFrameworks>
<TargetFramework>netstandard2.1</TargetFramework>
<Description>Extension for the .NET Framework cryptography subsystem, which introduces the Paillier public key cryptosystem with support for homomorphic addition.</Description>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Nullable>enable</Nullable>
<AssemblyName>Aprismatic.Paillier.Homomorphism</AssemblyName>
<RootNamespace>Aprismatic.Paillier.Homomorphism</RootNamespace>
<RepositoryUrl>https://github.com/aprismatic/paillier-homomorphism</RepositoryUrl>
</PropertyGroup>

<PropertyGroup>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
</Project>
102 changes: 62 additions & 40 deletions src/Aprismatic.Paillier.Homomorphism/PaillierHomomorphism.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,81 @@

namespace Aprismatic.Paillier.Homomorphism
{
/// <summary>
/// This class provides low-level methods for homomorphic operations on Paillier ciphertexts.
/// </summary>
public static class PaillierHomomorphism
{
public static byte[] Add(byte[] first, byte[] second, byte[] NSquare)
/// <summary>
/// Adds two encoded (i.e., supporting negatives through 2's complement) Paillier ciphertexts.
/// </summary>
/// <param name="firstActual">First ciphertext, the 'original sign' half</param>
/// <param name="firstNegative">First ciphertext, the 'negated sign' half</param>
/// <param name="secondActual">Second ciphertext, the 'original sign' half</param>
/// <param name="secondNegative">Second ciphertext, the 'negated sign' half</param>
/// <param name="NSquare">Part of the public key that is required for computation: N²</param>
/// <param name="resultActual">Span to write the 'original sign' part of the result</param>
/// <param name="resultNegative">Span to write the 'negated sign' part of the result</param>
public static void AddEncoded(
ReadOnlySpan<byte> firstActual, ReadOnlySpan<byte> firstNegative,
ReadOnlySpan<byte> secondActual, ReadOnlySpan<byte> secondNegative,
ReadOnlySpan<byte> NSquare,
Span<byte> resultActual, Span<byte> resultNegative)
{
var firstActual = new byte[first.Length / 2];
Array.Copy(first, firstActual, first.Length / 2);
var firstNegative = new byte[first.Length / 2];
Array.Copy(first, first.Length / 2, firstNegative, 0, first.Length / 2);
var secondActual = new byte[second.Length / 2];
Array.Copy(second, secondActual, second.Length / 2);
var secondNegative = new byte[second.Length / 2];
Array.Copy(second, second.Length / 2, secondNegative, 0, second.Length / 2);

var addActual = AddParts(firstActual, secondActual, NSquare);
var addNegative = AddParts(firstNegative, secondNegative, NSquare);

var add = new byte[first.Length];
Array.Copy(addActual, 0, add, 0, addActual.Length);
Array.Copy(addNegative, 0, add, add.Length / 2, addNegative.Length);

return add;
AddIntegers(firstActual, secondActual, NSquare, resultActual);
AddIntegers(firstNegative, secondNegative, NSquare, resultNegative);
}

public static byte[] Subtract(byte[] first, byte[] second, byte[] NSquare)
/// <summary>
/// Subtracts 2-complement encoded Paillier ciphertext `second` from `first`.
/// </summary>
/// <param name="firstActual">First ciphertext, the 'original sign' half</param>
/// <param name="firstNegative">First ciphertext, the 'negated sign' half</param>
/// <param name="secondActual">Second ciphertext, the 'original sign' half</param>
/// <param name="secondNegative">Second ciphertext, the 'negated sign' half</param>
/// <param name="NSquare">Part of the public key that is required for computation: N²</param>
/// <param name="resultActual">Span to write the 'original sign' part of the result</param>
/// <param name="resultNegative">Span to write the 'negated sign' part of the result</param>
public static void SubtractEncoded(
ReadOnlySpan<byte> firstActual, ReadOnlySpan<byte> firstNegative,
ReadOnlySpan<byte> secondActual, ReadOnlySpan<byte> secondNegative,
ReadOnlySpan<byte> NSquare,
Span<byte> resultActual, Span<byte> resultNegative)
{
var firstActual = new byte[first.Length / 2];
Array.Copy(first, firstActual, first.Length / 2);
var firstNegative = new byte[first.Length / 2];
Array.Copy(first, first.Length / 2, firstNegative, 0, first.Length / 2);
var secondActual = new byte[second.Length / 2];
Array.Copy(second, secondActual, second.Length / 2);
var secondNegative = new byte[second.Length / 2];
Array.Copy(second, second.Length / 2, secondNegative, 0, second.Length / 2);

var subActual = AddParts(firstActual, secondNegative, NSquare);
var subNegative = AddParts(firstNegative, secondActual, NSquare);

var sub = new byte[first.Length];
Array.Copy(subActual, 0, sub, 0, subActual.Length);
Array.Copy(subNegative, 0, sub, sub.Length / 2, subNegative.Length);

return sub;
AddIntegers(firstActual, secondNegative, NSquare, resultActual);
AddIntegers(firstNegative, secondActual, NSquare, resultNegative);
}

private static byte[] AddParts(byte[] first, byte[] second, byte[] NSquare)
/// <summary>
/// Low-level adds two Paillier-encrypted integers in byte-array form, not aware of the 2-complement or any other encoding.
/// </summary>
/// <param name="first">First ciphertext</param>
/// <param name="second">Second ciphertext</param>
/// <param name="NSquare">Public key N²</param>
/// <param name="result">Span to write the result to</param>
public static void AddIntegers(
ReadOnlySpan<byte> first, ReadOnlySpan<byte> second,
ReadOnlySpan<byte> NSquare,
Span<byte> result)
{
var A = new BigInteger(first);
var B = new BigInteger(second);
var NSquareBi = new BigInteger(NSquare);

var resBi = (A * B) % NSquareBi;
var res = resBi.ToByteArray();
return res;
var resBi = AddIntegers(A, B, NSquareBi);
resBi.TryWriteBytes(result, out _);
}

/// <summary>
/// Low-level adds two Paillier-encrypted integers in BigInteger form, not aware of the 2-complement or any other encoding.
/// </summary>
/// <param name="first">First ciphertext</param>
/// <param name="second">Second ciphertext</param>
/// <param name="NSquare">Public key N²</param>
/// <returns>BigInteger containing ciphertext of homomorphic sum of `first` and `second`</returns>
public static BigInteger AddIntegers(
BigInteger A, BigInteger B,
BigInteger NSquare) =>
(A * B) % NSquare;
}
}
57 changes: 46 additions & 11 deletions test/PaillierHomoTests/PaillierHomoTests.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,67 @@
using Xunit;
using System;
using System.Numerics;
using Xunit;
using Aprismatic.Paillier.Homomorphism;

namespace PaillierHomoTests
{
public class PaillierHomoTests
{
[Fact(DisplayName = "Addition")]
[Fact(DisplayName = "Addition - encoded")]
public void TestAddition()
{
byte[] first = { 1, 2 };
byte[] second = { 3, 4 };
byte[] firstPos = { 1 };
byte[] firstNeg = { 2 };
byte[] secondPos = { 3 };
byte[] secondNeg = { 4 };
byte[] NSquare = { 10 };
byte[] expected = { 3, 8 };
byte[] expected = {3, 8};

var res = PaillierHomomorphism.Add(first, second, NSquare);
var res = new byte[firstPos.Length * 2].AsSpan();
PaillierHomomorphism.AddEncoded(firstPos, firstNeg, secondPos, secondNeg, NSquare, res[..firstPos.Length], res[firstPos.Length..]);

Assert.Equal(expected, res);
Assert.Equal(expected, res.ToArray());
}

[Fact(DisplayName = "Subtraction")]
[Fact(DisplayName = "Subtraction - encoded")]
public void TestSubtraction()
{
byte[] first = { 1, 2 };
byte[] second = { 3, 4 };
byte[] firstPos = { 1 };
byte[] firstNeg = { 2 };
byte[] secondPos = { 3 };
byte[] secondNeg = { 4 };
byte[] NSquare = { 10 };
byte[] expected = { 4, 6 };

var res = PaillierHomomorphism.Subtract(first, second, NSquare);
var res = new byte[firstPos.Length * 2].AsSpan();
PaillierHomomorphism.SubtractEncoded(firstPos, firstNeg, secondPos, secondNeg, NSquare, res[..firstPos.Length], res[firstPos.Length..]);

Assert.Equal(expected, res.ToArray());
}

[Fact(DisplayName = "Addition - low-level, bytes")]
public void TestAdditionLowLevelBytes()
{
byte[] first = { 5 };
byte[] second = { 3 };
byte[] NSquare = { 10 };
byte[] expected = { 5 };

var res = new byte[first.Length].AsSpan();
PaillierHomomorphism.AddIntegers(first, second, NSquare, res);

Assert.Equal(expected, res.ToArray());
}

[Fact(DisplayName = "Addition - low-level, BI")]
public void TestAdditionLowLevelBI()
{
var first = new BigInteger(7);
var second = new BigInteger(4);
var NSquare = new BigInteger(10);
var expected = new BigInteger(8);

var res = PaillierHomomorphism.AddIntegers(first, second, NSquare);

Assert.Equal(expected, res);
}
Expand Down
2 changes: 1 addition & 1 deletion test/PaillierHomoTests/PaillierHomoTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
Expand Down

0 comments on commit cbec37c

Please sign in to comment.