From 875696a73aaf29b74959d352e8a89497ed77d492 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Wed, 11 Mar 2026 08:54:36 +0100 Subject: [PATCH] added dex signing utils --- .../Authentication/Signing/CeAbiEncoder.cs | 147 +++++++ .../Signing/CeEip712TypedDataEncoder.cs | 312 ++++++++++++++ .../Authentication/Signing/CeSha3Keccack.cs | 385 ++++++++++++++++++ 3 files changed, 844 insertions(+) create mode 100644 CryptoExchange.Net/Authentication/Signing/CeAbiEncoder.cs create mode 100644 CryptoExchange.Net/Authentication/Signing/CeEip712TypedDataEncoder.cs create mode 100644 CryptoExchange.Net/Authentication/Signing/CeSha3Keccack.cs diff --git a/CryptoExchange.Net/Authentication/Signing/CeAbiEncoder.cs b/CryptoExchange.Net/Authentication/Signing/CeAbiEncoder.cs new file mode 100644 index 00000000..b7fea7bf --- /dev/null +++ b/CryptoExchange.Net/Authentication/Signing/CeAbiEncoder.cs @@ -0,0 +1,147 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Text; + +namespace CryptoExchange.Net.Authentication.Signing +{ + /// + /// ABI encoding + /// + public static class CeAbiEncoder + { + /// + /// ABI encode string as Sha3Keccack hashed byte value + /// + public static byte[] AbiValueEncodeString(string value) + { + var abiValueEncoded = CeSha3Keccack.CalculateHash(Encoding.UTF8.GetBytes(value)); + return abiValueEncoded; + } + + /// + /// ABI encode bool value as uint256 with 1 for true and 0 for false, as per ABI specification + /// + public static byte[] AbiValueEncodeBool(bool value) + => AbiValueEncodeInt((byte)(value ? 1 : 0)); + + /// + /// ABI encode byte value as uint256, as per ABI specification + /// + public static byte[] AbiValueEncodeInt(byte value) + => AbiValueEncodeBigInteger(false, new BigInteger(value)); + + /// + /// ABI encode short value as int256, as per ABI specification + /// + public static byte[] AbiValueEncodeInt(short value) + => AbiValueEncodeBigInteger(true, new BigInteger(value)); + + /// + /// ABI encode int value as int256, as per ABI specification + /// + /// + /// + public static byte[] AbiValueEncodeInt(int value) + => AbiValueEncodeBigInteger(true, new BigInteger(value)); + + /// + /// ABI encode long value as int256, as per ABI specification + /// + public static byte[] AbiValueEncodeInt(long value) + => AbiValueEncodeBigInteger(true, new BigInteger(value)); + + /// + /// ABI encode ushort value as uint256, as per ABI specification + /// + public static byte[] AbiValueEncodeInt(ushort value) + => AbiValueEncodeBigInteger(false, new BigInteger(value)); + + /// + /// ABI encode uint value as uint256, as per ABI specification + /// + /// + /// + public static byte[] AbiValueEncodeInt(uint value) + => AbiValueEncodeBigInteger(false, new BigInteger(value)); + + /// + /// ABI encode ulong value as uint256, as per ABI specification + /// + /// + /// + public static byte[] AbiValueEncodeInt(ulong value) + => AbiValueEncodeBigInteger(false, new BigInteger(value)); + + /// + /// ABI encode big integer value as int256 or uint256, as per ABI specification + /// + public static byte[] AbiValueEncodeBigInteger(bool signed, BigInteger value) + { + var result = new byte[32]; + if (signed && value < 0) + { + // Pad with FF + for (int i = 0; i < result.Length; i++) + { + result[i] = 0xFF; + } + } + + var t = value.ToByteArray(); + if (t.Length == 33) + { + // Strip last byte + var strip1 = new byte[32]; + Array.Copy(t, 0, strip1, 0, 32); + t = strip1; + } + + if (BitConverter.IsLittleEndian) + t = t.AsEnumerable().Reverse().ToArray(); + + t.CopyTo(result, result.Length - t.Length); + return result; + } + + /// + /// ABI encode address value as uint256, as per ABI specification + /// + public static byte[] AbiValueEncodeAddress(string value) + { + var result = new byte[32]; + var h = value.HexStringToBytes(); + h.CopyTo(result, result.Length - h.Length); + return result; + } + + /// + /// ABI encode hex string value as bytes32, as per ABI specification. The hex string is expected to be a 0x prefixed string, and the resulting bytes will be right aligned in the 32 bytes result, with leading zeros if the hex string is shorter than 32 bytes. If the hex string is longer than 32 bytes, an exception will be thrown. + /// + /// + /// + /// + public static byte[] AbiValueEncodeHexBytes(int length, string value) + => AbiValueEncodeBytes(value.Length, value.HexStringToBytes()); + + /// + /// ABI encode byte array value as bytes32, as per ABI specification. The resulting bytes will be right aligned in the 32 bytes result, with leading zeros if the byte array is shorter than 32 bytes. If the byte array is longer than 32 bytes, an exception will be thrown. + /// + /// + /// + /// + /// + public static byte[] AbiValueEncodeBytes(int length, byte[] value) + { + if (length != 32) + throw new Exception("Only 32 bytes size supported"); + + if (value.Length == 32) + return value; + + var result = new byte[32]; + value.CopyTo(result, result.Length - value.Length); + return result; + } + } +} diff --git a/CryptoExchange.Net/Authentication/Signing/CeEip712TypedDataEncoder.cs b/CryptoExchange.Net/Authentication/Signing/CeEip712TypedDataEncoder.cs new file mode 100644 index 00000000..c81b1e3e --- /dev/null +++ b/CryptoExchange.Net/Authentication/Signing/CeEip712TypedDataEncoder.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Text.RegularExpressions; + +namespace CryptoExchange.Net.Authentication.Signing +{ + /// + /// EIP712 Typed Data Encoder + /// + public static class CeEip712TypedDataEncoder + { + /// + /// Encode EIP712 typed data according to the specification, with the provided primary type, domain fields and message fields. + /// The resulting byte array is the 0x19 0x01 prefix followed by the hash of the domain and the hash of the message, which can be signed with ECDSA secp256k1 to produce a signature that can be verified on chain with EIP712. + /// Note that this implementation does not support all possible EIP712 types, but it should cover most common use cases + /// + public static byte[] EncodeEip721( + string primaryType, + IEnumerable<(string Name, string Type, object Value)> domainFields, + IEnumerable<(string Name, string Type, object Value)> messageFields) + { + var data = new CeTypedDataRaw() + { + PrimaryType = primaryType, + DomainRawValues = domainFields.Select(x => new CeMemberValue + { + TypeName = x.Type, + Value = x.Value, + }).ToArray(), + + Message = messageFields.Select(x => new CeMemberValue + { + TypeName = x.Type, + Value = x.Value, + }).ToArray(), + Types = new Dictionary + { + { + "EIP712Domain", + domainFields.Select(x => new CeMemberDescription + { + Name = x.Name, + Type = x.Type + }).ToArray() + }, + { + primaryType, + messageFields.Select(x => new CeMemberDescription + { + Name = x.Name, + Type = x.Type + }).ToArray() + } + } + }; + + return EncodeTypedDataRaw(data); + } + + /// + /// Encode EIP712 typed data according to the specification, with the provided primary type, domain fields and message fields. + /// The resulting byte array is the 0x19 0x01 prefix followed by the hash of the domain and the hash of the message, which can be signed with ECDSA secp256k1 to produce a signature that can be verified on chain with EIP712. + /// Note that this implementation does not support all possible EIP712 types, but it should cover most common use cases + /// + public static byte[] EncodeTypedDataRaw(CeTypedDataRaw typedData) + { + using var memoryStream = new MemoryStream(); + using var writer = new BinaryWriter(memoryStream); + + // Write 0x19 0x01 prefix + writer.Write((byte)0x19); + writer.Write((byte)0x01); + + // Write domain + writer.Write(HashStruct(typedData.Types, "EIP712Domain", typedData.DomainRawValues)); + + // Write message + writer.Write(HashStruct(typedData.Types, typedData.PrimaryType, typedData.Message)); + + writer.Flush(); + var result = memoryStream.ToArray(); + return result; + + } + + private static byte[] HashStruct(IDictionary types, string primaryType, IEnumerable message) + { + var memoryStream = new MemoryStream(); + var writer = new BinaryWriter(memoryStream); + + // Encode the type header + EncodeType(writer, types, primaryType); + + // Encode the data + EncodeData(writer, types, message); + + writer.Flush(); + return CeSha3Keccack.CalculateHash(memoryStream.ToArray()); + + } + + private static void EncodeData(BinaryWriter writer, IDictionary types, IEnumerable memberValues) + { + foreach (var memberValue in memberValues) + { + switch (memberValue.TypeName) + { + case var refType when IsReferenceType(refType): + writer.Write(HashStruct(types, memberValue.TypeName, (IEnumerable)memberValue.Value)); + break; + + case "string": + writer.Write(CeAbiEncoder.AbiValueEncodeString((string)memberValue.Value)); + break; + + case "bool": + writer.Write(CeAbiEncoder.AbiValueEncodeBool((bool)memberValue.Value)); + break; + + case "address": + writer.Write(CeAbiEncoder.AbiValueEncodeAddress((string)memberValue.Value)); + break; + + default: + if (memberValue.TypeName.Contains("[")) + { + var items = (IList)memberValue.Value; + var itemsMemberValues = new List(); + foreach (var item in items) + { + itemsMemberValues.Add(new CeMemberValue() + { + TypeName = memberValue.TypeName.Substring(0, memberValue.TypeName.LastIndexOf("[")), + Value = item + }); + } + + var memoryStream = new MemoryStream(); + var writerItem = new BinaryWriter(memoryStream); + + EncodeData(writerItem, types, itemsMemberValues); + writerItem.Flush(); + writer.Write(CeSha3Keccack.CalculateHash(memoryStream.ToArray())); + } + else if (memberValue.TypeName.StartsWith("int") || memberValue.TypeName.StartsWith("uint")) + { + if (memberValue.Value is string v) + { + if (!BigInteger.TryParse(v, out BigInteger parsedOutput)) + throw new Exception(""); + + writer.Write(CeAbiEncoder.AbiValueEncodeBigInteger(memberValue.TypeName[0] != 'u', parsedOutput)); + } + else if (memberValue.Value is byte b) + { + writer.Write(CeAbiEncoder.AbiValueEncodeInt(b)); + } + else if (memberValue.Value is short s) + { + writer.Write(CeAbiEncoder.AbiValueEncodeInt(s)); + } + else if (memberValue.Value is int i) + { + writer.Write(CeAbiEncoder.AbiValueEncodeInt(i)); + } + else if (memberValue.Value is long l) + { + writer.Write(CeAbiEncoder.AbiValueEncodeInt(l)); + } + else if (memberValue.Value is ushort us) + { + writer.Write(CeAbiEncoder.AbiValueEncodeInt(us)); + } + else if (memberValue.Value is uint ui) + { + writer.Write(CeAbiEncoder.AbiValueEncodeInt(ui)); + } + else if (memberValue.Value is ulong ul) + { + writer.Write(CeAbiEncoder.AbiValueEncodeInt(ul)); + } + else + { + throw new Exception(); + } + } + else if (memberValue.TypeName.StartsWith("bytes")) + { + // Applicable? + //if (memberValue.Value is string v) + // writer.Write(AbiEncoder.AbiValueEncodeHexBytes(v)); + //else if (memberValue.Value is byte[] b) + // writer.Write(AbiEncoder.AbiValueEncodeBytes(b)); + //else + // throw new Exception("Unknown byte value type"); + + var length = memberValue.TypeName.Length == 5 ? 32 : int.Parse(memberValue.TypeName.Substring(5)); + writer.Write(CeAbiEncoder.AbiValueEncodeBytes(length, (byte[])memberValue.Value)); + } + break; + + } + } + } + + private static void EncodeType(BinaryWriter writer, IDictionary types, string typeName) + { + var encodedTypes = EncodeTypes(types, typeName); + var encodedPrimaryType = encodedTypes.Single(x => x.Key == typeName); + var encodedReferenceTypes = encodedTypes.Where(x => x.Key != typeName).OrderBy(x => x.Key).Select(x => x.Value); + var fullyEncodedType = encodedPrimaryType.Value + string.Join(string.Empty, encodedReferenceTypes.ToArray()); + + writer.Write(CeSha3Keccack.CalculateHash(Encoding.UTF8.GetBytes(fullyEncodedType))); + } + + /// + /// Create a list of type => type(parameters), for example:
+ /// { IP712Domain, EIP712Domain(string name,string version,uint256 chainId,address verifyingContract) } + ///
+ private static IList> EncodeTypes(IDictionary types, string currentTypeName) + { + var currentTypeMembers = types[currentTypeName]; + var currentTypeMembersEncoded = currentTypeMembers.Select(x => x.Type + " " + x.Name); + var result = new List> + { + new KeyValuePair(currentTypeName, currentTypeName + "(" + string.Join(",", currentTypeMembersEncoded.ToArray()) + ")") + }; + + result.AddRange(currentTypeMembers.Select(x => x.Type.Contains("[") ? x.Type.Substring(0, x.Type.IndexOf("[")) : x.Type) + .Distinct() + .Where(IsReferenceType) + .SelectMany(x => EncodeTypes(types, x))); + return result; + } + + internal static bool IsReferenceType(string typeName) + { + switch (typeName) + { + case var bytes when new Regex("bytes\\d+").IsMatch(bytes): + case var @uint when new Regex("uint\\d+").IsMatch(@uint): + case var @int when new Regex("int\\d+").IsMatch(@int): + case "bytes": + case "string": + case "bool": + case "address": + case var array when array.Contains("["): + return false; + default: + return true; + } + } + } + + /// + /// Member description + /// + public class CeMemberDescription + { + /// + /// Name + /// + public string Name { get; set; } = string.Empty; + /// + /// Type + /// + public string Type { get; set; } = string.Empty; + } + + /// + /// Member value + /// + public class CeMemberValue + { + /// + /// Type name + /// + public string TypeName { get; set; } = string.Empty; + /// + /// Value + /// + public object Value { get; set; } = string.Empty; + } + + /// + /// Typed data raw, used for encoding EIP712 typed data with the provided primary type, domain fields and message fields. + /// + public class CeTypedDataRaw + { + /// + /// Type dictionary + /// + public IDictionary Types { get; set; } = new Dictionary(); + /// + /// Primary type + /// + public string PrimaryType { get; set; } = string.Empty; + /// + /// Message values + /// + public CeMemberValue[] Message { get; set; } = []; + /// + /// Domain values + /// + public CeMemberValue[] DomainRawValues { get; set; } = []; + } +} \ No newline at end of file diff --git a/CryptoExchange.Net/Authentication/Signing/CeSha3Keccack.cs b/CryptoExchange.Net/Authentication/Signing/CeSha3Keccack.cs new file mode 100644 index 00000000..b1038490 --- /dev/null +++ b/CryptoExchange.Net/Authentication/Signing/CeSha3Keccack.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace CryptoExchange.Net.Authentication.Signing +{ + /// + /// Sha3 Keccack hashing, as per Ethereum specification, with 256 bit output + /// + public class CeSha3Keccack + { + /// + /// Calculate the Keccack256 hash of the provided data, as per Ethereum specification + /// + public static byte[] CalculateHash(byte[] data) + { + var digest = new CeKeccakDigest256(); + var output = new byte[digest.GetDigestSize()]; + digest.BlockUpdate(data, data.Length); + digest.DoFinal(output, 0); + return output; + } + } + + internal class CeKeccakDigest256 + { + private static readonly ulong[] _keccakRoundConstants = KeccakInitializeRoundConstants(); + private static readonly int[] _keccakRhoOffsets = KeccakInitializeRhoOffsets(); + + private readonly int _rate; + private const int _stateLength = 1600 / 8; + private readonly ulong[] _state = new ulong[_stateLength / 8]; + private readonly byte[] _dataQueue = new byte[1536 / 8]; + private int _bitsInQueue; + private int _fixedOutputLength; + private bool _squeezing; + private int _bitsAvailableForSqueezing; + + public CeKeccakDigest256() + { + _rate = 1600 - (256 << 1); + _bitsInQueue = 0; + _squeezing = false; + _bitsAvailableForSqueezing = 0; + _fixedOutputLength = 1600 - _rate >> 1; + } + + internal void BlockUpdate(byte[] data, int length) + { + int bytesInQueue = _bitsInQueue >> 3; + int rateBytes = _rate >> 3; + + int count = 0; + while (count < length) + { + if (bytesInQueue == 0 && count <= length - rateBytes) + { + do + { + KeccakAbsorb(data, count); + count += rateBytes; + } while (count <= length - rateBytes); + } + else + { + int partialBlock = Math.Min(rateBytes - bytesInQueue, length - count); + Array.Copy(data, count, _dataQueue, bytesInQueue, partialBlock); + + bytesInQueue += partialBlock; + count += partialBlock; + + if (bytesInQueue == rateBytes) + { + KeccakAbsorb(_dataQueue, 0); + bytesInQueue = 0; + } + } + } + + _bitsInQueue = bytesInQueue << 3; + } + + internal void DoFinal(byte[] output, int outOff) + { + Squeeze(output, outOff, _fixedOutputLength >> 3); + } + + internal int GetDigestSize() => _fixedOutputLength >> 3; + + protected void Squeeze(byte[] output, int off, int len) + { + if (!_squeezing) + PadAndSwitchToSqueezingPhase(); + + long outputLength = (long)len << 3; + long i = 0; + while (i < outputLength) + { + if (_bitsAvailableForSqueezing == 0) + { + KeccakPermutation(); + KeccakExtract(); + _bitsAvailableForSqueezing = _rate; + } + + int partialBlock = (int)Math.Min(_bitsAvailableForSqueezing, outputLength - i); + Array.Copy(_dataQueue, _rate - _bitsAvailableForSqueezing >> 3, output, off + (int)(i >> 3), + partialBlock >> 3); + _bitsAvailableForSqueezing -= partialBlock; + i += partialBlock; + } + } + + private static ulong[] KeccakInitializeRoundConstants() + { + ulong[] keccakRoundConstants = new ulong[24]; + byte LFSRState = 0x01; + + for (int i = 0; i < 24; i++) + { + keccakRoundConstants[i] = 0; + for (int j = 0; j < 7; j++) + { + int bitPosition = (1 << j) - 1; + + // LFSR86540 + + bool loBit = (LFSRState & 0x01) != 0; + if (loBit) + keccakRoundConstants[i] ^= 1UL << bitPosition; + + bool hiBit = (LFSRState & 0x80) != 0; + LFSRState <<= 1; + if (hiBit) + LFSRState ^= 0x71; + } + } + + return keccakRoundConstants; + } + private static int[] KeccakInitializeRhoOffsets() + { + int[] keccakRhoOffsets = new int[25]; + int x, y, t, newX, newY; + + int rhoOffset = 0; + keccakRhoOffsets[0] = rhoOffset; + x = 1; + y = 0; + for (t = 1; t < 25; t++) + { + rhoOffset = rhoOffset + t & 63; + keccakRhoOffsets[x % 5 + 5 * (y % 5)] = rhoOffset; + newX = (0 * x + 1 * y) % 5; + newY = (2 * x + 3 * y) % 5; + x = newX; + y = newY; + } + + return keccakRhoOffsets; + } + + private void KeccakAbsorb(byte[] data, int off) + { + int count = _rate >> 6; + for (int i = 0; i < count; ++i) + { + _state[i] ^= Pack.LeToUInt64(data, off); + off += 8; + } + + KeccakPermutation(); + } + + private void KeccakPermutation() + { + for (int i = 0; i < 24; i++) + { + Theta(_state); + Rho(_state); + Pi(_state); + Chi(_state); + Iota(_state, i); + } + } + private static ulong LeftRotate(ulong v, int r) + { + return v << r | v >> -r; + } + + private static void Theta(ulong[] A) + { + ulong C0 = A[0 + 0] ^ A[0 + 5] ^ A[0 + 10] ^ A[0 + 15] ^ A[0 + 20]; + ulong C1 = A[1 + 0] ^ A[1 + 5] ^ A[1 + 10] ^ A[1 + 15] ^ A[1 + 20]; + ulong C2 = A[2 + 0] ^ A[2 + 5] ^ A[2 + 10] ^ A[2 + 15] ^ A[2 + 20]; + ulong C3 = A[3 + 0] ^ A[3 + 5] ^ A[3 + 10] ^ A[3 + 15] ^ A[3 + 20]; + ulong C4 = A[4 + 0] ^ A[4 + 5] ^ A[4 + 10] ^ A[4 + 15] ^ A[4 + 20]; + + ulong dX = LeftRotate(C1, 1) ^ C4; + + A[0] ^= dX; + A[5] ^= dX; + A[10] ^= dX; + A[15] ^= dX; + A[20] ^= dX; + + dX = LeftRotate(C2, 1) ^ C0; + + A[1] ^= dX; + A[6] ^= dX; + A[11] ^= dX; + A[16] ^= dX; + A[21] ^= dX; + + dX = LeftRotate(C3, 1) ^ C1; + + A[2] ^= dX; + A[7] ^= dX; + A[12] ^= dX; + A[17] ^= dX; + A[22] ^= dX; + + dX = LeftRotate(C4, 1) ^ C2; + + A[3] ^= dX; + A[8] ^= dX; + A[13] ^= dX; + A[18] ^= dX; + A[23] ^= dX; + + dX = LeftRotate(C0, 1) ^ C3; + + A[4] ^= dX; + A[9] ^= dX; + A[14] ^= dX; + A[19] ^= dX; + A[24] ^= dX; + } + + private static void Rho(ulong[] A) + { + // KeccakRhoOffsets[0] == 0 + for (int x = 1; x < 25; x++) + { + A[x] = LeftRotate(A[x], _keccakRhoOffsets[x]); + } + } + + private static void Pi(ulong[] A) + { + ulong a1 = A[1]; + A[1] = A[6]; + A[6] = A[9]; + A[9] = A[22]; + A[22] = A[14]; + A[14] = A[20]; + A[20] = A[2]; + A[2] = A[12]; + A[12] = A[13]; + A[13] = A[19]; + A[19] = A[23]; + A[23] = A[15]; + A[15] = A[4]; + A[4] = A[24]; + A[24] = A[21]; + A[21] = A[8]; + A[8] = A[16]; + A[16] = A[5]; + A[5] = A[3]; + A[3] = A[18]; + A[18] = A[17]; + A[17] = A[11]; + A[11] = A[7]; + A[7] = A[10]; + A[10] = a1; + } + + private static void Chi(ulong[] A) + { + ulong chiC0, chiC1, chiC2, chiC3, chiC4; + + for (int yBy5 = 0; yBy5 < 25; yBy5 += 5) + { + chiC0 = A[0 + yBy5] ^ ~A[(0 + 1) % 5 + yBy5] & A[(0 + 2) % 5 + yBy5]; + chiC1 = A[1 + yBy5] ^ ~A[(1 + 1) % 5 + yBy5] & A[(1 + 2) % 5 + yBy5]; + chiC2 = A[2 + yBy5] ^ ~A[(2 + 1) % 5 + yBy5] & A[(2 + 2) % 5 + yBy5]; + chiC3 = A[3 + yBy5] ^ ~A[(3 + 1) % 5 + yBy5] & A[(3 + 2) % 5 + yBy5]; + chiC4 = A[4 + yBy5] ^ ~A[(4 + 1) % 5 + yBy5] & A[(4 + 2) % 5 + yBy5]; + + A[0 + yBy5] = chiC0; + A[1 + yBy5] = chiC1; + A[2 + yBy5] = chiC2; + A[3 + yBy5] = chiC3; + A[4 + yBy5] = chiC4; + } + } + + private static void Iota(ulong[] A, int indexRound) + { + A[0] ^= _keccakRoundConstants[indexRound]; + } + + private void PadAndSwitchToSqueezingPhase() + { + Debug.Assert(_bitsInQueue < _rate); + + _dataQueue[_bitsInQueue >> 3] |= (byte)(1U << (_bitsInQueue & 7)); + + if (++_bitsInQueue == _rate) + { + KeccakAbsorb(_dataQueue, 0); + _bitsInQueue = 0; + } + + { + int full = _bitsInQueue >> 6, partial = _bitsInQueue & 63; + int off = 0; + for (int i = 0; i < full; ++i) + { + _state[i] ^= Pack.LeToUInt64(_dataQueue, off); + off += 8; + } + + if (partial > 0) + { + ulong mask = (1UL << partial) - 1UL; + _state[full] ^= Pack.LeToUInt64(_dataQueue, off) & mask; + } + + _state[_rate - 1 >> 6] ^= 1UL << 63; + } + + KeccakPermutation(); + KeccakExtract(); + _bitsAvailableForSqueezing = _rate; + + _bitsInQueue = 0; + _squeezing = true; + } + private void KeccakExtract() + { + Pack.UInt64ToLe(_state, 0, _rate >> 6, _dataQueue, 0); + } + + static class Pack + { + internal static ulong LeToUInt64(byte[] bs, int off) + { + uint lo = LeToUInt32(bs, off); + uint hi = LeToUInt32(bs, off + 4); + return (ulong)hi << 32 | lo; + } + internal static uint LeToUInt32(byte[] bs, int off) + { + return bs[off] + | (uint)bs[off + 1] << 8 + | (uint)bs[off + 2] << 16 + | (uint)bs[off + 3] << 24; + } + + internal static void UInt64ToLe(ulong[] ns, int nsOff, int nsLen, byte[] bs, int bsOff) + { + for (int i = 0; i < nsLen; ++i) + { + UInt64ToLe(ns[nsOff + i], bs, bsOff); + bsOff += 8; + } + } + internal static void UInt64ToLe(ulong n, byte[] bs, int off) + { + UInt32ToLe((uint)n, bs, off); + UInt32ToLe((uint)(n >> 32), bs, off + 4); + } + + internal static void UInt32ToLe(uint n, byte[] bs, int off) + { + bs[off] = (byte)n; + bs[off + 1] = (byte)(n >> 8); + bs[off + 2] = (byte)(n >> 16); + bs[off + 3] = (byte)(n >> 24); + } + } + } +}