diff --git a/src/Cryptography/ShamirsSecretSharing.cs b/src/Cryptography/ShamirsSecretSharing.cs index e00ff77..86bcc7e 100644 --- a/src/Cryptography/ShamirsSecretSharing.cs +++ b/src/Cryptography/ShamirsSecretSharing.cs @@ -5,6 +5,11 @@ namespace SecretSharingDotNet.Cryptography; /// public abstract class ShamirsSecretSharing { + /// + /// The minimum number of shares required to reconstruct the secret + /// + protected const int MinimumShareLimit = 2; + /// /// Saves the known security levels (Mersenne prime exponents) /// diff --git a/src/Cryptography/ShamirsSecretSharing`3.cs b/src/Cryptography/ShamirsSecretSharing`3.cs index 2f5531c..0ade8c4 100644 --- a/src/Cryptography/ShamirsSecretSharing`3.cs +++ b/src/Cryptography/ShamirsSecretSharing`3.cs @@ -70,7 +70,7 @@ public class ShamirsSecretSharing @@ -84,7 +84,7 @@ public int SecurityLevel set { - if (value < 13) + if (value < SecurityLevels[0]) { throw new ArgumentOutOfRangeException(nameof(value), value, ErrorMessages.MinimumSecurityLevelExceeded); } @@ -128,7 +128,7 @@ public Shares MakeShares(TNumber numberOfMinimumShares, TNumber numberO int min = ((Calculator)numberOfMinimumShares).ToInt32(); int max = ((Calculator)numberOfShares).ToInt32(); - if (min < 2) + if (min < MinimumShareLimit) { throw new ArgumentOutOfRangeException(nameof(numberOfMinimumShares), numberOfMinimumShares, ErrorMessages.MinNumberOfSharesLowerThanTwo); } @@ -187,7 +187,7 @@ public Shares MakeShares(TNumber numberOfMinimumShares, TNumber numberO { int min = ((Calculator)numberOfMinimumShares).ToInt32(); int max = ((Calculator)numberOfShares).ToInt32(); - if (min < 2) + if (min < MinimumShareLimit) { throw new ArgumentOutOfRangeException(nameof(numberOfMinimumShares), numberOfMinimumShares, ErrorMessages.MinNumberOfSharesLowerThanTwo); } @@ -215,20 +215,40 @@ public Shares MakeShares(TNumber numberOfMinimumShares, TNumber numberO /// /// Minimum number of shared secrets for reconstruction /// +#if NET6_0_OR_GREATER + private unsafe Calculator[] CreatePolynomial(int numberOfMinimumShares) +#else private Calculator[] CreatePolynomial(int numberOfMinimumShares) +#endif { var polynomial = new Calculator[numberOfMinimumShares]; polynomial[0] = Calculator.Zero; byte[] randomNumber = new byte[this.mersennePrime.ByteCount]; - using (var rng = RandomNumberGenerator.Create()) +#if NET6_0_OR_GREATER + fixed (byte* pointer = randomNumber) { + var span = new Span(pointer, this.mersennePrime.ByteCount); + using var rng = RandomNumberGenerator.Create(); for (int i = 1; i < numberOfMinimumShares; i++) { - rng.GetBytes(randomNumber); - polynomial[i] = (Calculator.Create(randomNumber, typeof(TNumber)) as Calculator)?.Abs() % this.mersennePrime; + rng.GetBytes(span); + polynomial[i] = (Calculator.Create(randomNumber, typeof(TNumber)) as Calculator)?.Abs() % + this.mersennePrime; } - } + span.Clear(); + } +#else + using var rng = RandomNumberGenerator.Create(); + for (int i = 1; i < numberOfMinimumShares; i++) + { + rng.GetBytes(randomNumber); + polynomial[i] = (Calculator.Create(randomNumber, typeof(TNumber)) as Calculator)?.Abs() % + this.mersennePrime; + } + + Array.Clear(randomNumber, 0, randomNumber.Length); +#endif return polynomial; } diff --git a/src/Cryptography/SharedSeparator.cs b/src/Cryptography/SharedSeparator.cs index e86345d..94588ec 100644 --- a/src/Cryptography/SharedSeparator.cs +++ b/src/Cryptography/SharedSeparator.cs @@ -41,5 +41,5 @@ internal static class SharedSeparator /// /// Separator array for method usage to avoid allocation of a new array. /// - internal static readonly char[] CoordinateSeparatorArray = { CoordinateSeparator }; -} \ No newline at end of file + internal static readonly char[] CoordinateSeparatorArray = [CoordinateSeparator]; +} diff --git a/src/Cryptography/Shares.cs b/src/Cryptography/Shares.cs index f774a59..8bf101f 100644 --- a/src/Cryptography/Shares.cs +++ b/src/Cryptography/Shares.cs @@ -118,7 +118,7 @@ internal Shares(Secret secret, IList> shares) public static implicit operator Shares(string s) { var points = s - .Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries) + .Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries) .Select(line => new FinitePoint(line)) .ToArray(); return new Shares(points); diff --git a/src/Math/Calculator.cs b/src/Math/Calculator.cs index 840fbeb..9dbf0e3 100644 --- a/src/Math/Calculator.cs +++ b/src/Math/Calculator.cs @@ -117,7 +117,7 @@ protected static Dictionary> GetDerivedCtors var parameterExpression = Expression.Parameter(paramType); foreach (var childType in ChildTypes) { - var ctorInfo = childType.Value.GetConstructor(new[] {paramType}); + var ctorInfo = childType.Value.GetConstructor([paramType]); if (ctorInfo == null) { continue; diff --git a/src/Math/ExtendedEuclideanAlgorithm.cs b/src/Math/ExtendedEuclideanAlgorithm.cs index 75be33b..d7df016 100644 --- a/src/Math/ExtendedEuclideanAlgorithm.cs +++ b/src/Math/ExtendedEuclideanAlgorithm.cs @@ -60,7 +60,11 @@ public ExtendedGcdResult Compute(Calculator a, Calculator Compute(Calculator a, Calculator(lastR, coefficients, quotients); + return new ExtendedGcdResult(lastR, [lastX, lastY], [x, y]); } -} \ No newline at end of file +} diff --git a/src/Math/SecureBigInteger.cs b/src/Math/SecureBigInteger.cs new file mode 100644 index 0000000..20be6da --- /dev/null +++ b/src/Math/SecureBigInteger.cs @@ -0,0 +1,559 @@ +// ---------------------------------------------------------------------------- +// +// Copyright (c) 2024 All Rights Reserved +// +// Sebastian Walther +// 04/01/2024 07:34:00 PM +// ---------------------------------------------------------------------------- + +#region License + +// ---------------------------------------------------------------------------- +// Copyright 2022 Sebastian Walther +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#endregion + +#if NET6_0_OR_GREATER +namespace SecretSharingDotNet.Math; + +using System; +using System.Buffers.Binary; +using System.Runtime.InteropServices; +using System.Text; + +/// +/// Represents a secure big integer. +/// +public sealed class SecureBigInteger : IDisposable +{ + private readonly long length; + private readonly unsafe uint* digitsPtr; + private GCHandle digitsHandle; + private bool disposed; + + /// + /// Initializes a new instance of the class. + /// + /// The pointer to the digits. + /// The handle to the digits. + /// The length of the digits. + /// + /// The handle must be pinned to prevent the garbage collector from moving the object in memory. + /// + private unsafe SecureBigInteger(uint* digits, GCHandle digitsHandle, long length) + { + this.digitsPtr = digits; + this.length = length; + this.digitsHandle = digitsHandle; + } + + /// + /// Initializes a new instance of the class. + /// + /// The byte representation of the big integer. + public unsafe SecureBigInteger(Span bytes) + { + uint[] digits = new uint[(bytes.Length + 3) / 4]; + this.digitsHandle = GCHandle.Alloc(digits, GCHandleType.Pinned); + this.digitsPtr = (uint*)this.digitsHandle.AddrOfPinnedObject(); + this.length = digits.LongLength; + //// for (int i = 0; i < bytes.Length; i++) + for (int i = 0; i < bytes.Length; i += 4) + { + //// digits[i / 4] |= (uint)bytes[i] << i % 4 * 8; + digits[i / 4] = BinaryPrimitives.ReadUInt32LittleEndian(bytes.Slice(i, 4)); + } + } + + /// + /// Finalizes an instance of the class. + /// + ~SecureBigInteger() + { + this.Dispose(false); + } + + /// + /// Gets the number of bytes of the big integer. + /// + public long ByteCount => this.length * 4; + + /// + /// Gets a value indicating whether the big integer is even. + /// + public unsafe bool IsEven => (this.digitsPtr[0] & 1) == 0; + + /// + /// Gets a value indicating whether the big integer is odd. + /// + public unsafe bool IsOdd => (this.digitsPtr[0] & 1) == 1; + + /// + /// Gets a value indicating whether the is zero. + /// + /// + /// The IsZero property returns true if all the digits of the are zero; otherwise, false. + /// + public unsafe bool IsZero + { + get + { + for (long i = 0; i < this.length; ++i) + { + if (this.digitsPtr[i] != 0) + { + return false; + } + } + + return true; + } + } + + /// + /// Gets a value indicating whether the is one. + /// + public unsafe bool IsOne + { + get + { + for (long i = 1; i < this.length; ++i) + { + if (this.digitsPtr[i] != 0) + { + return false; + } + } + + return this.digitsPtr[0] == 1; + } + } + + /// + /// Gets a value representing zero value of . + /// + public static unsafe SecureBigInteger Zero + { + get + { + uint[] zeroDigits = [0]; + var zeroHandle = GCHandle.Alloc(zeroDigits, GCHandleType.Pinned); + return new SecureBigInteger((uint*)zeroHandle.AddrOfPinnedObject(), zeroHandle, 1); + } + } + + /// + /// Gets a value representing one value of . + /// + public static unsafe SecureBigInteger One + { + get + { + uint[] oneDigits = [1]; + var oneHandle = GCHandle.Alloc(oneDigits, GCHandleType.Pinned); + return new SecureBigInteger((uint*)oneHandle.AddrOfPinnedObject(), oneHandle, 1); + } + } + + /// + /// Adds two together and returns a new SecureBigInteger containing the sum. + /// + /// The to be added. + /// A new that is the sum of the current and + /// the specified . + public unsafe SecureBigInteger Add(SecureBigInteger other) + { + long maxLen = Math.Max(this.length, other.length); + + uint[] result = new uint[maxLen + 1]; + var resultDigitsHandle = GCHandle.Alloc(result, GCHandleType.Pinned); + ulong carry = 0; + + for (long index = 0; index < maxLen || carry != 0; ++index) + { + ulong sum = carry; + if (index < this.length) + { + sum += this.digitsPtr[index]; + } + + if (index < other.length) + { + sum += other.digitsPtr[index]; + } + + carry = sum >> 32; + result[index] = (uint)sum; + } + + uint* resultPtr = (uint*)resultDigitsHandle.AddrOfPinnedObject(); + return new SecureBigInteger(resultPtr, resultDigitsHandle, result.LongLength); + } + + /// + /// Subtracts the value of another from this instance. + /// + /// The to subtract. + /// A new containing the result of the subtraction. + /// Thrown when attempting to subtract a larger number from a smaller one. + public unsafe SecureBigInteger Subtract(SecureBigInteger other) + { + long maxLen = Math.Max(this.length, other.length); + + uint[] result = new uint[maxLen]; + var resultDigitsHandle = GCHandle.Alloc(result, GCHandleType.Pinned); + ulong borrow = 0; + + for (long index = 0; index < maxLen; ++index) + { + ulong diff; + ulong val1 = index < this.length ? this.digitsPtr[index] : 0; + ulong val2 = index < other.length ? other.digitsPtr[index] : 0; + + if (val1 < val2 + borrow) + { + diff = uint.MaxValue - val2 - borrow + val1 + 1; + borrow = 1; + } + else + { + diff = val1 - val2 - borrow; + borrow = 0; + } + + result[index] = (uint)diff; + } + + if (borrow != 0) + { + throw new InvalidOperationException("Cannot subtract a larger number from a smaller one."); + } + + uint* resultPtr = (uint*)resultDigitsHandle.AddrOfPinnedObject(); + return new SecureBigInteger(resultPtr, resultDigitsHandle, result.LongLength); + } + + /// + /// Multiplies the current instance with another instance. + /// + /// The instance to multiply with. + /// A new instance representing the product of the multiplication. + public unsafe SecureBigInteger Multiply(SecureBigInteger other) + { + long maxLen = this.length + other.length; + + uint[] result = new uint[maxLen]; + var resultDigitsHandle = GCHandle.Alloc(result, GCHandleType.Pinned); + + for (long i = 0; i < this.length; ++i) + { + ulong carry = 0; + for (long j = 0; j < other.length; ++j) + { + ulong product = (ulong)(this.digitsPtr[i]) * (ulong)(other.digitsPtr[j]) + carry + result[i + j]; + result[i + j] = (uint)product; + carry = product >> 32; + } + + result[i + other.length] = (uint)carry; + } + + uint* resultPtr = (uint*)resultDigitsHandle.AddrOfPinnedObject(); + return new SecureBigInteger(resultPtr, resultDigitsHandle, result.LongLength); + } + + /// + /// Right-shifts the current by the specified number of bits. + /// + /// The number of bits to right-shift the current SecureBigInteger. + /// A new that is the result of right-shifting the current SecureBigInteger by the specified number of bits. + /// Thrown when the shift value is less than zero. + public unsafe SecureBigInteger RightShift(int shift) + { + if (shift < 0) + { + throw new ArgumentOutOfRangeException(nameof(shift), + "The shift value must be greater than or equal to zero."); + } + + int shift32 = shift / 32; + int shiftMod32 = shift % 32; + long newLength = this.length - shift32; + if (shiftMod32 != 0) + { + newLength++; + } + + uint[] result = new uint[newLength]; + var resultDigitsHandle = GCHandle.Alloc(result, GCHandleType.Pinned); + + for (long i = 0; i < newLength; i++) + { + result[i] = this.digitsPtr[i + shift32] >> shiftMod32; + if (i + 1 < newLength && shiftMod32 != 0) + { + result[i] |= this.digitsPtr[i + shift32 + 1] << (32 - shiftMod32); + } + } + + uint* resultPtr = (uint*)resultDigitsHandle.AddrOfPinnedObject(); + return new SecureBigInteger(resultPtr, resultDigitsHandle, newLength); + } + + /// + /// Shifts the bits of the to the left by the specified amount. + /// + /// The number of bits to shift. + /// A new that represents the result of the left shift operation. + /// Thrown when the shift value is less than zero. + public unsafe SecureBigInteger LeftShift(int shift) + { + if (shift < 0) + { + throw new ArgumentOutOfRangeException(nameof(shift), + "The shift value must be greater than or equal to zero."); + } + + if (shift == 0 || this.IsZero) + { + return this; + } + + int digitShift = shift / 32; + int bitShift = shift % 32; + + uint[] result = new uint[this.length + digitShift + 1]; + var resultDigitsHandle = GCHandle.Alloc(result, GCHandleType.Pinned); + ulong carry = 0; + + for (int i = 0; i < this.length; ++i) + { + ulong temp = ((ulong)this.digitsPtr[i] << bitShift) | carry; + result[i + digitShift] = (uint)temp; + carry = temp >> 32; + } + + if (carry != 0) + { + result[this.length + digitShift] = (uint)carry; + } + + uint* resultPtr = (uint*)resultDigitsHandle.AddrOfPinnedObject(); + return new SecureBigInteger(resultPtr, resultDigitsHandle, result.LongLength); + } + + /// Todo: Issue + /// + /// Divides the current by the specified and returns the quotient. + /// + /// The to divide by. + /// The quotient obtained by dividing the current SecureBigInteger by the specified . + /// Thrown when the divisor is zero. + public unsafe SecureBigInteger Divide(SecureBigInteger other) + { + } + + /// + /// Computes the hash code for the object. + /// + /// The computed hash code. + public override int GetHashCode() + { + unsafe + { + unchecked + { + int hash = 17; + for (long i = 0; i < this.length; i++) + { + hash = hash * 29 + this.digitsPtr[i].GetHashCode(); + } + + return hash; + } + } + } + + /// + /// Determines whether the current instance of is equal to another object. + /// + /// The object to compare with the current instance. + /// + /// Returns if the current instance is equal to the other object; otherwise, . + /// + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is SecureBigInteger otherBigInt && this.Equals(otherBigInt); + } + + /// + /// Determines whether the current instance of is equal to another . + /// + /// The to compare with the current instance. + /// + /// if the current instance is equal to the other ; otherwise, . + /// + public bool Equals(SecureBigInteger other) + { + if (other is null) + { + return false; + } + + unsafe + { + if (this.length != other.length) + { + return false; + } + + for (long i = 0; i < this.length; i++) + { + if (this.digitsPtr[i] != other.digitsPtr[i]) + { + return false; + } + } + } + + return true; + } + + public static SecureBigInteger operator +(SecureBigInteger left, SecureBigInteger right) => left.Add(right); + public static SecureBigInteger operator -(SecureBigInteger left, SecureBigInteger right) => left.Subtract(right); + public static SecureBigInteger operator *(SecureBigInteger left, SecureBigInteger right) => left.Multiply(right); + public static SecureBigInteger operator /(SecureBigInteger left, SecureBigInteger right) => left.Divide(right); + public static SecureBigInteger operator >>(SecureBigInteger left, int right) => left.RightShift(right); + public static SecureBigInteger operator <<(SecureBigInteger left, int right) => left.LeftShift(right); + public static bool operator ==(SecureBigInteger left, SecureBigInteger right) => Equals(left, right); + public static bool operator !=(SecureBigInteger left, SecureBigInteger right) => !Equals(left, right); + + #region String Conversion + /// + /// Todo: Implement a more efficient & secure conversion. Only for testing purposes. + /// + /// + public override unsafe string ToString() + { + var sb = new StringBuilder(); + for (long i = this.length - 1; i >= 0; i--) + { + string binary = Convert.ToString(this.digitsPtr[i], 2).PadLeft(32, '0'); + sb.Append(binary); + } + + string binaryString = sb.ToString(); + string decimalString = BinaryToDecimal(binaryString); + return decimalString; + } + + private static string BinaryToDecimal(string binaryString) + { + string total = "0"; + for (int i = binaryString.Length - 1; i >= 0; i--) + { + if (binaryString[i] == '1') + { + total = AddStrings(total, PowerOfTwo(binaryString.Length - 1 - i)); + } + } + + return total; + } + + private static string AddStrings(string num1, string num2) + { + StringBuilder sb = new StringBuilder(); + int carry = 0; + int i = num1.Length - 1; + int j = num2.Length - 1; + while (i >= 0 || j >= 0 || carry > 0) + { + int sum = carry; + if (i >= 0) + { + sum += num1[i--] - '0'; + } + + if (j >= 0) + { + sum += num2[j--] - '0'; + } + + sb.Insert(0, sum % 10); + carry = sum / 10; + } + + return sb.ToString(); + } + + private static string PowerOfTwo(int power) + { + string result = "1"; + for (int i = 0; i < power; i++) + { + result = AddStrings(result, result); + } + + return result; + } + + #endregion + + /// + /// Releases the resources used by the object. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases the unmanaged resources used by the and optionally releases the managed + /// resources. + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + private unsafe void Dispose(bool disposing) + { + if (this.disposed) + { + return; + } + + if (disposing) + { + // Dispose managed resources. + } + + for (long i = 0; i < this.length; i++) + { + this.digitsPtr[i] = 0; + } + + this.digitsHandle.Free(); + this.digitsHandle = default; + + this.disposed = true; + } +} +#endif \ No newline at end of file diff --git a/src/SecretSharingDotNet.csproj b/src/SecretSharingDotNet.csproj index a196c0b..db83f4c 100644 --- a/src/SecretSharingDotNet.csproj +++ b/src/SecretSharingDotNet.csproj @@ -33,6 +33,14 @@ true + + true + + + + true + + ResXFileCodeGenerator