diff --git a/lib/cryptography_utils.dart b/lib/cryptography_utils.dart index 4aa62ecd..405058bd 100644 --- a/lib/cryptography_utils.dart +++ b/lib/cryptography_utils.dart @@ -3,7 +3,7 @@ library cryptography_utils; export 'src/bip/bip.dart'; export 'src/cdsa/cdsa.dart'; export 'src/hash/hmac.dart'; -export 'src/hash/keccak.dart'; +export 'src/hash/keccak/keccak.dart'; export 'src/hash/pbkdf2.dart'; export 'src/hash/ripemd160.dart'; export 'src/signer/export.dart'; diff --git a/lib/src/bip/bip32/address_encoders/ethereum_address_encoder.dart b/lib/src/bip/bip32/address_encoders/ethereum_address_encoder.dart index 5536e9f3..9b25ce2d 100644 --- a/lib/src/bip/bip32/address_encoders/ethereum_address_encoder.dart +++ b/lib/src/bip/bip32/address_encoders/ethereum_address_encoder.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:codec_utils/codec_utils.dart'; import 'package:cryptography_utils/cryptography_utils.dart'; +import 'package:cryptography_utils/src/hash/keccak/keccak_bit_length.dart'; /// The [EthereumAddressEncoder] class is designed for encoding addresses in accordance with the Ethereum network. /// Ethereum uses the Keccak-256 hash function to generate addresses from public keys, resulting in 40 hexadecimal characters prefixed with "0x". @@ -24,7 +25,7 @@ class EthereumAddressEncoder extends ABlockchainAddressEncoder addressWithChecksum = address.split('').asMap().entries.map((MapEntry entry) { diff --git a/lib/src/hash/keccak.dart b/lib/src/hash/keccak.dart deleted file mode 100644 index 16facad5..00000000 --- a/lib/src/hash/keccak.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'dart:typed_data'; - -import 'package:pointycastle/digests/keccak.dart'; - -/// [Keccak] is an implementation of Keccak algorithm which is a secure hash -/// function that creates fixed-size outputs from variable-length inputs. -class Keccak { - final int digestSize; - - Keccak(this.digestSize); - - Uint8List process(Uint8List data) { - // TODO(dominik): Implement custom version of Keccak hashing. - return KeccakDigest(digestSize).process(data); - } -} diff --git a/lib/src/hash/keccak/keccak.dart b/lib/src/hash/keccak/keccak.dart new file mode 100644 index 00000000..2004ab64 --- /dev/null +++ b/lib/src/hash/keccak/keccak.dart @@ -0,0 +1,171 @@ +// This class was primarily influenced by: +// "pointycastle" - Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) +// +// 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. + +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:cryptography_utils/src/hash/keccak/keccak_bit_length.dart'; +import 'package:cryptography_utils/src/hash/keccak/keccakf1600.dart'; + +/// [Keccak256] is an implementation of Keccak algorithm which is a secure hash +/// function that creates fixed-size outputs from variable-length inputs. +class Keccak { + final Uint8List _hashStateUint8List = Uint8List(200); + final Uint8List _bufferUint8List = Uint8List(192); + final KeccakBitLength _inputKeccakBitLength; + + late int _blockSize; + late int _bufferBits; + late int _outputBitLength; + late bool _squeezeActiveBool; + + Keccak(this._inputKeccakBitLength) { + _initSponge(_inputKeccakBitLength.bitLength); + } + + int get _digestSize => _outputBitLength ~/ 8; + + Uint8List process(Uint8List inputUint8List) { + _absorbData(inputUint8List, 0, inputUint8List.length); + Uint8List outputUint8List = Uint8List(256); + int length = _complete(outputUint8List, 0); + return outputUint8List.sublist(0, length); + } + + void _absorbData(Uint8List inputUint8List, int outputOffset, int inputBitLength) { + int currentBytes = _bufferBits >> 3; + int rateBytes = _blockSize >> 3; + int availableBytes = rateBytes - currentBytes; + + if (inputBitLength < availableBytes) { + _bufferUint8List.setRange(currentBytes, currentBytes + inputBitLength, inputUint8List, outputOffset); + _bufferBits += inputBitLength << 3; + return; + } + + int processedBytes = 0; + if (currentBytes > 0) { + _bufferUint8List.setRange(currentBytes, currentBytes + availableBytes, inputUint8List.sublist(outputOffset)); + processedBytes += availableBytes; + _absorbBlock(_bufferUint8List, 0); + } + + int remainingBytes; + while ((remainingBytes = inputBitLength - processedBytes) >= rateBytes) { + _absorbBlock(inputUint8List, outputOffset + processedBytes); + processedBytes += rateBytes; + } + + _bufferUint8List.setRange(0, remainingBytes, inputUint8List, outputOffset + processedBytes); + _bufferBits = remainingBytes << 3; + } + + int _complete(Uint8List outputUint8List, int outputOffset) { + _squeeze(outputUint8List, outputOffset, _outputBitLength); + _resetState(); + return _digestSize; + } + + void _squeeze(Uint8List inputUint8List, int outputOffset, int inputBitLength) { + if (_squeezeActiveBool == false) { + _startSqueezingPhase(); + } + + int i = 0; + + while (i < inputBitLength) { + if (_bufferBits == 0) { + _extractBlock(); + } + + int activeBlock = min(_bufferBits, inputBitLength - i); + + inputUint8List.setRange( + outputOffset + (i ~/ 8), + outputOffset + (i ~/ 8) + (activeBlock ~/ 8), + _bufferUint8List.sublist((_blockSize - _bufferBits) ~/ 8), + ); + + _bufferBits -= activeBlock; + i += activeBlock; + } + } + + void _resetState() { + _initSponge(_outputBitLength); + } + + void _initSponge(int inputBitLength) { + int rate = 1600 - (inputBitLength << 1); + + _hashStateUint8List.fillRange(0, _hashStateUint8List.length, 0); + _bufferUint8List.fillRange(0, _bufferUint8List.length, 0); + _blockSize = rate; + _outputBitLength = (1600 - rate) ~/ 2; + _bufferBits = 0; + _squeezeActiveBool = false; + } + + void _startSqueezingPhase() { + _bufferUint8List[_bufferBits >> 3] |= 1 << (_bufferBits & 7); + if (++_bufferBits == _blockSize) { + _absorbBlock(_bufferUint8List, 0); + } else { + int fullBytes = _bufferBits >> 6, partialBits = _bufferBits & 63; + for (int i = 0; i < fullBytes * 8; ++i) { + _hashStateUint8List[i] ^= _bufferUint8List[i]; + } + if (partialBits > 0) { + for (int k = 0; k != 8; k++) { + if (partialBits >= 8) { + _hashStateUint8List[fullBytes * 8 + k] ^= _bufferUint8List[fullBytes * 8 + k]; + } else { + _hashStateUint8List[fullBytes * 8 + k] ^= _bufferUint8List[fullBytes * 8 + k] & ((1 << partialBits) - 1); + } + partialBits -= 8; + if (partialBits < 0) { + partialBits = 0; + } + } + } + } + _hashStateUint8List[((_blockSize - 1) >> 3)] ^= 1 << 7; + _bufferBits = 0; + _squeezeActiveBool = true; + } + + void _extractBlock() { + _applyPermutation(); + _bufferUint8List.setRange(0, _blockSize >> 3, _hashStateUint8List); + _bufferBits = _blockSize; + } + + void _absorbBlock(Uint8List inputUint8List, int outputOffset) { + int count = _blockSize >> 3; + for (int i = 0; i < count; ++i) { + _hashStateUint8List[i] ^= inputUint8List[outputOffset + i]; + } + _applyPermutation(); + } + + void _applyPermutation() { + KeccakF1600().applyPermutation(_hashStateUint8List); + } +} diff --git a/lib/src/hash/keccak/keccak_bit_length.dart b/lib/src/hash/keccak/keccak_bit_length.dart new file mode 100644 index 00000000..da7e1765 --- /dev/null +++ b/lib/src/hash/keccak/keccak_bit_length.dart @@ -0,0 +1,12 @@ +enum KeccakBitLength { + keccak128(128), + keccak224(224), + keccak256(256), + keccak288(288), + keccak384(384), + keccak512(512); + + final int bitLength; + + const KeccakBitLength(this.bitLength); +} diff --git a/lib/src/hash/keccak/keccakf1600.dart b/lib/src/hash/keccak/keccakf1600.dart new file mode 100644 index 00000000..e2ee3449 --- /dev/null +++ b/lib/src/hash/keccak/keccakf1600.dart @@ -0,0 +1,214 @@ +//This class was primarily influenced by: +// "pointycastle" - Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) +// +// 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. + +import 'dart:typed_data'; + +import 'package:cryptography_utils/src/hash/keccak/register64/register64.dart'; +import 'package:cryptography_utils/src/hash/keccak/register64/register64_list.dart'; + +///[KeccakF1600] This class is essential for processing input data through the sponge construction, ensuring proper diffusion and security +/// in hash computations. It operates on a 1600-bit state, repeatedly applying a series of transformations to mix the input data and produce +/// a secure cryptographic hash. +class KeccakF1600 { + static final Register64List _roundConstRegister64List = Register64List.from(const >[ + [0x00000000, 0x00000001], + [0x00000000, 0x00008082], + [0x80000000, 0x0000808a], + [0x80000000, 0x80008000], + [0x00000000, 0x0000808b], + [0x00000000, 0x80000001], + [0x80000000, 0x80008081], + [0x80000000, 0x00008009], + [0x00000000, 0x0000008a], + [0x00000000, 0x00000088], + [0x00000000, 0x80008009], + [0x00000000, 0x8000000a], + [0x00000000, 0x8000808b], + [0x80000000, 0x0000008b], + [0x80000000, 0x00008089], + [0x80000000, 0x00008003], + [0x80000000, 0x00008002], + [0x80000000, 0x00000080], + [0x00000000, 0x0000800a], + [0x80000000, 0x8000000a], + [0x80000000, 0x80008081], + [0x80000000, 0x00008080], + [0x00000000, 0x80000001], + [0x80000000, 0x80008008] + ]); + + static final List _rotationOffsetsList = [ + 0x00000000, + 0x00000001, + 0x0000003e, + 0x0000001c, + 0x0000001b, + 0x00000024, + 0x0000002c, + 0x00000006, + 0x00000037, + 0x00000014, + 0x00000003, + 0x0000000a, + 0x0000002b, + 0x00000019, + 0x00000027, + 0x00000029, + 0x0000002d, + 0x0000000f, + 0x00000015, + 0x00000008, + 0x00000012, + 0x00000002, + 0x0000003d, + 0x00000038, + 0x0000000e + ]; + + void applyPermutation(Uint8List inputUint8List) { + Register64List wordsRegister64List = Register64List(inputUint8List.length ~/ 8); + + _convertBytesToWords(wordsRegister64List, inputUint8List); + _performPermutation(wordsRegister64List); + _convertWordsToBytes(wordsRegister64List, inputUint8List); + } + + void _convertBytesToWords(Register64List wordsRegister64List, Uint8List inputUint8List) { + Register64 tmpRegister64 = Register64(); + + for (int i = 0; i < (1600 ~/ 64); i++) { + int byteIndex = i * (64 ~/ 8); + wordsRegister64List[i].setInt(0); + for (int j = 0; j < (64 ~/ 8); j++) { + tmpRegister64 + ..setInt(inputUint8List[byteIndex + j]) + ..shiftLeft(8 * j); + wordsRegister64List[i].performOr(tmpRegister64); + } + } + } + + void _performPermutation(Register64List wordsRegister64List) { + for (int numRounds = 0; numRounds < 24; numRounds++) { + _performTheta(wordsRegister64List); + _performRho(wordsRegister64List); + _performPi(wordsRegister64List); + _performChi(wordsRegister64List); + _performIota(wordsRegister64List, numRounds); + } + } + + void _convertWordsToBytes(Register64List wordsRegister64List, Uint8List inputUint8List) { + Register64 tmpRegister64 = Register64(); + + for (int i = 0; i < (1600 ~/ 64); i++) { + int byteIndex = i * (64 ~/ 8); + + for (int j = 0; j < (64 ~/ 8); j++) { + tmpRegister64 + ..setRegister64(wordsRegister64List[i]) + ..shiftRight(8 * j); + + inputUint8List[byteIndex + j] = tmpRegister64.lowerHalf; + } + } + } + + void _performTheta(Register64List wordsRegister64List) { + Register64List columnRegister64List = Register64List(5); + Register64 rotatedLeftRegister64 = Register64(); + Register64 rotatedRightRegister64 = Register64(); + + for (int x = 0; x < 5; x++) { + columnRegister64List[x].setInt(0); + for (int y = 0; y < 5; y++) { + columnRegister64List[x].performXor(wordsRegister64List[x + 5 * y]); + } + } + + for (int x = 0; x < 5; x++) { + rotatedLeftRegister64 + ..setRegister64(columnRegister64List[(x + 1) % 5]) + ..shiftLeft(1); + rotatedRightRegister64 + ..setRegister64(columnRegister64List[(x + 1) % 5]) + ..shiftRight(63); + rotatedLeftRegister64 + ..performXor(rotatedRightRegister64) + ..performXor(columnRegister64List[(x + 4) % 5]); + + for (int y = 0; y < 5; y++) { + wordsRegister64List[x + 5 * y].performXor(rotatedLeftRegister64); + } + } + } + + void _performRho(Register64List wordsRegister64List) { + Register64 tmpRegister64 = Register64(); + + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + int index = x + 5 * y; + + if (_rotationOffsetsList[index] != 0) { + tmpRegister64 + ..setRegister64(wordsRegister64List[index]) + ..shiftRight(64 - _rotationOffsetsList[index]); + wordsRegister64List[index] + ..shiftLeft(_rotationOffsetsList[index]) + ..performXor(tmpRegister64); + } + } + } + } + + void _performPi(Register64List wordsRegister64List) { + Register64List tmpRegister64List = Register64List(25); + + tmpRegister64List.setRange(0, tmpRegister64List.length, wordsRegister64List); + + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + wordsRegister64List[y + 5 * ((2 * x + 3 * y) % 5)].setRegister64(tmpRegister64List[x + 5 * y]); + } + } + } + + void _performChi(Register64List wordsRegister64List) { + Register64List tmpRegister64List = Register64List(5); + + for (int y = 0; y < 5; y++) { + for (int x = 0; x < 5; x++) { + tmpRegister64List[x].setRegister64(wordsRegister64List[((x + 1) % 5) + 5 * y]); + tmpRegister64List[x].performNot(); + tmpRegister64List[x].performAnd(wordsRegister64List[((x + 2) % 5) + 5 * y]); + tmpRegister64List[x].performXor(wordsRegister64List[x + 5 * y]); + } + + for (int x = 0; x < 5; x++) { + wordsRegister64List[x + 5 * y].setRegister64(tmpRegister64List[x]); + } + } + } + + void _performIota(Register64List wordsRegister64List, int numRounds) { + wordsRegister64List[0].performXor(_roundConstRegister64List[numRounds]); + } +} diff --git a/lib/src/hash/keccak/register64/register64.dart b/lib/src/hash/keccak/register64/register64.dart new file mode 100644 index 00000000..90f70631 --- /dev/null +++ b/lib/src/hash/keccak/register64/register64.dart @@ -0,0 +1,148 @@ +//This class was primarily influenced by: +// "pointycastle" - Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) +// +// 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. + +import 'package:equatable/equatable.dart'; + +///[Register64] serve as abstractions for managing the 64-bit lanes and the state matrix of the algorithm, ensuring clean and modular handling +/// of the algorithm’s 1600-bit internal state. Register64 is object represented by two 32-bit integers, together represent a 64-bit value. +class Register64 with EquatableMixin { + static const int _mask5Bits = 0x1F; + static const int _mask6Bits = 0x3F; + static const int _mask32Bits = 0xFFFFFFFF; + + static const List _mask32BitsList = [ + 0xFFFFFFFF, + 0x7FFFFFFF, + 0x3FFFFFFF, + 0x1FFFFFFF, + 0x0FFFFFFF, + 0x07FFFFFF, + 0x03FFFFFF, + 0x01FFFFFF, + 0x00FFFFFF, + 0x007FFFFF, + 0x003FFFFF, + 0x001FFFFF, + 0x000FFFFF, + 0x0007FFFF, + 0x0003FFFF, + 0x0001FFFF, + 0x0000FFFF, + 0x00007FFF, + 0x00003FFF, + 0x00001FFF, + 0x00000FFF, + 0x000007FF, + 0x000003FF, + 0x000001FF, + 0x000000FF, + 0x0000007F, + 0x0000003F, + 0x0000001F, + 0x0000000F, + 0x00000007, + 0x00000003, + 0x00000001, + 0x00000000 + ]; + + late int _lowerHalf; + late int _upperHalf; + + Register64([int initialValue = 0, int? lower32Bits]) { + setInt(initialValue, lower32Bits); + } + + int get lowerHalf => _lowerHalf; + + int get upperHalf => _upperHalf; + + void setInt(int initialValue, [int? lower32Bits]) { + if (lower32Bits != null) { + _upperHalf = initialValue; + _lowerHalf = lower32Bits; + } else { + _upperHalf = 0; + _lowerHalf = initialValue; + } + } + + void setRegister64(Register64 initialRegister64) { + _upperHalf = initialRegister64._upperHalf; + _lowerHalf = initialRegister64._lowerHalf; + } + + void performAnd(Register64 otherRegister64) { + _upperHalf &= otherRegister64._upperHalf; + _lowerHalf &= otherRegister64._lowerHalf; + } + + void performNot() { + _upperHalf = ~_upperHalf & _mask32Bits; + _lowerHalf = ~_lowerHalf & _mask32Bits; + } + + void performOr(Register64 otherRegister64) { + _upperHalf |= otherRegister64._upperHalf; + _lowerHalf |= otherRegister64._lowerHalf; + } + + void performXor(Register64 otherRegister64) { + _upperHalf ^= otherRegister64._upperHalf; + _lowerHalf ^= otherRegister64._lowerHalf; + } + + void shiftLeft(int shiftValue) { + int maskedN = shiftValue & _mask6Bits; + if (maskedN == 0) { + return; + } else if (maskedN >= 32) { + _upperHalf = _shiftLeft32Bits(_lowerHalf, maskedN - 32); + _lowerHalf = 0; + } else { + _upperHalf = _shiftLeft32Bits(_upperHalf, maskedN); + _upperHalf |= _lowerHalf >> (32 - maskedN); + _lowerHalf = _shiftLeft32Bits(_lowerHalf, maskedN); + } + } + + void shiftRight(int shiftValue) { + int maskedN = shiftValue & _mask6Bits; + if (maskedN == 0) { + return; + } else if (maskedN >= 32) { + _lowerHalf = _upperHalf >> (maskedN - 32); + _upperHalf = 0; + } else { + _lowerHalf = _lowerHalf >> maskedN; + _lowerHalf |= _shiftLeft32Bits(_upperHalf, 32 - maskedN); + _upperHalf = upperHalf >> maskedN; + } + } + + int _shiftLeft32Bits(int chunk32Bits, int shiftValue) { + int maskedN = shiftValue & _mask5Bits; + int maskedX = chunk32Bits & _mask32BitsList[maskedN]; + return (maskedX << maskedN) & _mask32Bits; + } + + @override + List get props => [_upperHalf, _lowerHalf]; +} diff --git a/lib/src/hash/keccak/register64/register64_list.dart b/lib/src/hash/keccak/register64/register64_list.dart new file mode 100644 index 00000000..9f96c2b4 --- /dev/null +++ b/lib/src/hash/keccak/register64/register64_list.dart @@ -0,0 +1,51 @@ +// This class was primarily influenced by: +// "pointycastle" - Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) +// +// 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. + +import 'dart:core'; + +import 'package:cryptography_utils/src/hash/keccak/register64/register64.dart'; +import 'package:equatable/equatable.dart'; + +/// [Register64List] manages a list of 64-bit registers for the algorithm's internal state. +class Register64List extends Equatable { + final List _register64List; + + Register64List(int listLength) : _register64List = List.generate(listLength, (_) => Register64(0, 0)); + + Register64List.from(List> valuesList) + : _register64List = List.generate( + valuesList.length, + (int i) => Register64(valuesList[i][0], valuesList[i][1]), + ); + + int get length => _register64List.length; + + Register64 operator [](int index) => _register64List[index]; + + void setRange(int start, int end, Register64List register64List, [int skipCount = 0]) { + int length = end - start; + for (int i = 0; i < length; i++) { + _register64List[start + i].setRegister64(register64List[skipCount + i]); + } + } + + @override + List> get props => >[_register64List]; +} diff --git a/lib/src/signer/ethereum/ethereum_signer.dart b/lib/src/signer/ethereum/ethereum_signer.dart index b4390679..dc5d479e 100644 --- a/lib/src/signer/ethereum/ethereum_signer.dart +++ b/lib/src/signer/ethereum/ethereum_signer.dart @@ -24,6 +24,7 @@ import 'dart:typed_data'; import 'package:crypto/crypto.dart'; import 'package:cryptography_utils/cryptography_utils.dart'; +import 'package:cryptography_utils/src/hash/keccak/keccak_bit_length.dart'; /// Provides functionality for creating Ethereum-compatible digital signatures using an ECDSA private key. /// This class supports signing arbitrary messages in a manner that is compliant with Ethereum's standard for @@ -54,7 +55,7 @@ class EthereumSigner { ECDSASigner ecdsaSigner = ECDSASigner(hashFunction: sha256, ecPrivateKey: _ecPrivateKey); EthereumVerifier ethereumVerifier = EthereumVerifier(_ecPrivateKey.ecPublicKey); - Uint8List hash = hashMessage ? Keccak(256).process(digest) : digest; + Uint8List hash = hashMessage ? Keccak(KeccakBitLength.keccak256).process(digest) : digest; if (hash.length != _ecPrivateKey.G.curve.baselen) { throw FormatException('Invalid digest: Digest length must be ${_ecPrivateKey.G.curve.baselen} got ${digest.length}'); } diff --git a/lib/src/signer/ethereum/ethereum_verifier.dart b/lib/src/signer/ethereum/ethereum_verifier.dart index 06d671f6..a8de8a62 100644 --- a/lib/src/signer/ethereum/ethereum_verifier.dart +++ b/lib/src/signer/ethereum/ethereum_verifier.dart @@ -23,6 +23,7 @@ import 'dart:typed_data'; import 'package:crypto/crypto.dart'; import 'package:cryptography_utils/cryptography_utils.dart'; import 'package:cryptography_utils/src/cdsa/ecdsa/signer/ecdsa_verifier.dart'; +import 'package:cryptography_utils/src/hash/keccak/keccak_bit_length.dart'; /// Provides functionality for verifying Ethereum-compatible digital signatures using an ECDSA public key. /// This class is used to confirm that a given signature corresponds to a specific message and was created using @@ -40,7 +41,7 @@ class EthereumVerifier { /// rather than a precomputed hash was signed. bool isSignatureValid(Uint8List digest, EthereumSignature ethereumSignature, {bool hashMessage = true}) { Uint8List signatureBytes = ethereumSignature.bytes.sublist(0, EthereumSignature.ethSignatureLength); - Uint8List hashDigest = hashMessage ? Keccak(256).process(digest) : digest; + Uint8List hashDigest = hashMessage ? Keccak(KeccakBitLength.keccak256).process(digest) : digest; ECDSAVerifier ecdsaVerifier = ECDSAVerifier(hashFunction: sha256, ecPublicKey: _ecPublicKey); ECSignature ecSignature = ECSignature.fromBytes(signatureBytes, ecCurve: _ecPublicKey.G.curve); diff --git a/lib/src/transactions/ethereum/abi/functions/abi_function_definition.dart b/lib/src/transactions/ethereum/abi/functions/abi_function_definition.dart index fded3a6c..e27e1c06 100644 --- a/lib/src/transactions/ethereum/abi/functions/abi_function_definition.dart +++ b/lib/src/transactions/ethereum/abi/functions/abi_function_definition.dart @@ -2,7 +2,8 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:codec_utils/codec_utils.dart'; -import 'package:cryptography_utils/cryptography_utils.dart'; +import 'package:cryptography_utils/src/hash/keccak/keccak.dart'; +import 'package:cryptography_utils/src/hash/keccak/keccak_bit_length.dart'; import 'package:cryptography_utils/src/transactions/ethereum/abi/functions/abi_param_definition.dart'; import 'package:cryptography_utils/src/transactions/ethereum/abi/solidity_types/solidity_int_type.dart'; import 'package:equatable/equatable.dart'; @@ -78,7 +79,7 @@ class AbiFunctionDefinition extends Equatable { /// The function selector, which is the first 4 bytes of the Keccak-256 hash of the function signature. /// This is used to identify the function in transaction data. String get functionSelector { - Uint8List hash = Keccak(256).process(utf8.encode(functionSignature)); + Uint8List hash = Keccak(KeccakBitLength.keccak256).process(utf8.encode(functionSignature)); return HexCodec.encode(hash).substring(0, 8); } diff --git a/pubspec.yaml b/pubspec.yaml index d4fdd4ba..843a3211 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: cryptography_utils description: "Dart package containing utility methods for common cryptographic and blockchain-specific operations" publish_to: none -version: 0.0.19 +version: 0.0.20 environment: sdk: ">=3.2.6" diff --git a/test/hash/keccak/keccak_test.dart b/test/hash/keccak/keccak_test.dart new file mode 100644 index 00000000..f8381fff --- /dev/null +++ b/test/hash/keccak/keccak_test.dart @@ -0,0 +1,23 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:cryptography_utils/src/hash/keccak/keccak.dart'; +import 'package:cryptography_utils/src/hash/keccak/keccak_bit_length.dart'; +import 'package:test/test.dart'; + +void main() { + group('Tests of Keccak256.process()', () { + test('Should [return Keccak256 HASH] constructed from given data', () { + //Arrange + Uint8List actualDataToHash = Uint8List.fromList('123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~'.codeUnits); + + // Act + Uint8List actualKeccakResult = Keccak(KeccakBitLength.keccak256).process(actualDataToHash); + + // Assert + Uint8List expectedKeccakResult = base64Decode('BCh2dEOFXrYvTet6a/a3PPmwZvQ7KQb60c8ib9WqhfE='); + + expect(actualKeccakResult, expectedKeccakResult); + }); + }); +} diff --git a/test/hash/keccak/keccakf1600_test.dart b/test/hash/keccak/keccakf1600_test.dart new file mode 100644 index 00000000..18185092 --- /dev/null +++ b/test/hash/keccak/keccakf1600_test.dart @@ -0,0 +1,28 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:cryptography_utils/src/hash/keccak/keccakf1600.dart'; +import 'package:test/expect.dart'; +import 'package:test/scaffolding.dart'; + +void main() { + group('Test for KeccakF1600.applyPermutation()', () { + test('Should [apply Keccak permutation] to the input state', () { + // Arrange + Uint8List actualOutput = base64Decode( + 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2Rl' + 'ZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsc='); + + // Act + KeccakF1600().applyPermutation(actualOutput); + + // Assert + Uint8List expectedOutput = base64Decode( + '+nzV2vWRKBIhKXbcp+X4uF63dQKMD6yPNUUxdJYD7kcslozLbajUF7A8RLUqp38OPigxa9G2r+wJUbwINJIDzDsC5R2U2mL4CJzE8m6dtpUGF86et6wjVRreePwkbgAksto' + 'ZsAY+Cym00S/rLkG441S2xyxBqq0x5LdES6m65SGdA1yVjoHceUNdMVG9xBzkwkD95PygPnzqYXg2DTXfDSrzLPOjC8qS3cx3xQJniaPeqbza5cLHb1lBD/ZWhKEPFq' + '4P49SBCAc='); + + expect(actualOutput, expectedOutput); + }); + }); +} diff --git a/test/hash/keccak/register64/register64_list_test.dart b/test/hash/keccak/register64/register64_list_test.dart new file mode 100644 index 00000000..451c12f7 --- /dev/null +++ b/test/hash/keccak/register64/register64_list_test.dart @@ -0,0 +1,65 @@ +import 'package:cryptography_utils/src/hash/keccak/register64/register64_list.dart'; +import 'package:test/expect.dart'; +import 'package:test/scaffolding.dart'; + +// ignore_for_file: cascade_invocations +void main() { + group('Tests for Register64List() constructor', () { + test('Should [return correct length] when Register64List is created with given length', () { + // Act + Register64List actualRegister64List = Register64List(5); + int actualLength = actualRegister64List.length; + + // Assert + int expectedLength = 5; + + expect(actualLength, expectedLength); + }); + }); + + group('Tests for Register64List.from() constructor', () { + test('Should [return correct length] when Register64List is created from a list of values', () { + // Arrange + List> actualValuesList = >[ + [1, 2], + [3, 4], + [5, 6], + ]; + + // Act + Register64List actualRegister64List = Register64List.from(actualValuesList); + int actualLength = actualRegister64List.length; + + // Assert + int expectedLength = 3; + + expect(actualLength, expectedLength); + }); + }); + + group('Tests for Register64List.setRange()', () { + test('Should [set range of values correctly] when setRange() is called', () { + // Arrange + Register64List actualSourceRegister64List = Register64List.from(const >[ + [1, 2], + [3, 4], + [5, 6], + [7, 8], + ]); + + // Act + Register64List actualRegister64List = Register64List(4); + actualRegister64List.setRange(1, 3, actualSourceRegister64List); + + // Assert + Register64List expectedRegister64List = Register64List.from(const >[ + [0, 0], + [1, 2], + [3, 4], + [0, 0], + ]); + + expect(actualRegister64List, expectedRegister64List); + }); + }); +} diff --git a/test/hash/keccak/register64/register64_test.dart b/test/hash/keccak/register64/register64_test.dart new file mode 100644 index 00000000..66693eb0 --- /dev/null +++ b/test/hash/keccak/register64/register64_test.dart @@ -0,0 +1,248 @@ +import 'package:cryptography_utils/src/hash/keccak/register64/register64.dart'; +import 'package:test/expect.dart'; +import 'package:test/scaffolding.dart'; + +// ignore_for_file: cascade_invocations +void main() { + group('Tests for Register64 constructor', () { + test('Should [return default values] when Register64 created WITHOUT parameters', () { + // Arrange + Register64 actualRegister64 = Register64(); + int actualUpperValue = actualRegister64.upperHalf; + int actualLowerValue = actualRegister64.lowerHalf; + + // Assert + int expectedUpperValue = 0; + int expectedLowerValue = 0; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + + test('Should [return values] when initialized WITH specific parameters', () { + // Arrange + Register64 actualRegister64 = Register64(0x12345678, 0x9ABCDEF0); + int actualUpperValue = actualRegister64.upperHalf; + int actualLowerValue = actualRegister64.lowerHalf; + + // Assert + int expectedUpperValue = 0x12345678; + int expectedLowerValue = 0x9ABCDEF0; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + }); + + group('Tests for Register64.setInt()', () { + test('Should [return values] when set is called WITH one parameter.', () { + // Arrange + Register64 actualRegister64 = Register64(); + + // Act + actualRegister64.setInt(0x2A); + int actualUpperValue = actualRegister64.upperHalf; + int actualLowerValue = actualRegister64.lowerHalf; + + // Assert + int expectedUpperValue = 0x0; + int expectedLowerValue = 0x2A; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + + test('Should [return values] when set is called WITH two parameters.', () { + // Arrange + Register64 actualRegister64 = Register64(); + + // Act + actualRegister64.setInt(0x12345678, 0x9ABCDEF0); + int actualUpperValue = actualRegister64.upperHalf; + int actualLowerValue = actualRegister64.lowerHalf; + + // Assert + int expectedUpperValue = 0x12345678; + int expectedLowerValue = 0x9ABCDEF0; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + }); + + group('Tests for Register64.setRegister64()', () { + test('Should [return values] when set is called.', () { + // Arrange + Register64 actualRegister64 = Register64(); + Register64 actualValueRegister64 = Register64(0xA, 0x14); + + // Act + actualRegister64.setRegister64(actualValueRegister64); + int actualUpperValue = actualRegister64.upperHalf; + int actualLowerValue = actualRegister64.lowerHalf; + + // Assert + int expectedUpperValue = 0xA; + int expectedLowerValue = 0x14; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + }); + + group('Tests for Register64.performAnd()', () { + test('Should [return result of logical AND operation] when performAnd() called', () { + // Arrange + Register64 actual1Register64 = Register64(0xFFFFFFFF, 0x00000000); + Register64 actual2Register64 = Register64(0x0F0F0F0F, 0xFFFFFFFF); + + // Act + actual1Register64.performAnd(actual2Register64); + + int actualUpperValue = actual1Register64.upperHalf; + int actualLowerValue = actual1Register64.lowerHalf; + + // Assert + int expectedUpperValue = 0x0F0F0F0F; + int expectedLowerValue = 0x00000000; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + }); + + group('Tests for Register64.performOr()', () { + test('Should [return result of logical OR operation] when performOr() called', () { + // Arrange + Register64 actual1Register64 = Register64(0x12345678, 0x00000000); + Register64 actual2Register64 = Register64(0x00000000, 0x9ABCDEF0); + + // Act + actual1Register64.performOr(actual2Register64); + + int actualUpperValue = actual1Register64.upperHalf; + int actualLowerValue = actual1Register64.lowerHalf; + + // Assert + int expectedUpperValue = 0x12345678; + int expectedLowerValue = 0x9ABCDEF0; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + }); + + group('Tests for Register64.performXor()', () { + test('Should [return result of logical XOR operation] when performXor() called', () { + // Arrange + Register64 actual1Register64 = Register64(0x12345678, 0x9ABCDEF0); + Register64 actual2Register64 = Register64(0xFFFFFFFF, 0xFFFFFFFF); + + // Act + actual1Register64.performXor(actual2Register64); + int actualUpperValue = actual1Register64.upperHalf; + int actualLowerValue = actual1Register64.lowerHalf; + + // Assert + int expectedUpperValue = 0xEDCBA987; + int expectedLowerValue = 0x6543210F; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + }); + + group('Tests for Register64.performNot()', () { + test('Should [return result of logical NOT operation] when performNot() called', () { + // Arrange + Register64 actualRegister64 = Register64(0x12345678, 0x9ABCDEF0); + + // Act + actualRegister64.performNot(); + + int actualUpperValue = actualRegister64.upperHalf; + int actualLowerValue = actualRegister64.lowerHalf; + + // Assert + int expectedUpperValue = 0xEDCBA987; + int expectedLowerValue = 0x6543210F; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + }); + + group('Tests for Register64.shiftLeft()', () { + test('Should [return default values] when shiftLeft() is called and shiftValue is equal to 0', () { + // Arrange + Register64 actualRegister64 = Register64(0x12345678, 0x9ABCDEF0); + + // Act + actualRegister64.shiftLeft(0); + + int actualUpperValue = actualRegister64.upperHalf; + int actualLowerValue = actualRegister64.lowerHalf; + + // Assert + int expectedUpperValue = 0x12345678; + int expectedLowerValue = 0x9ABCDEF0; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + test('Should [return result shifted] when shiftLeft() is called and shiftValue is different than zero', () { + // Arrange + Register64 actualRegister64 = Register64(0x00000000, 0x9ABCDEF0); + + // Act + actualRegister64.shiftLeft(32); + + int actualUpperValue = actualRegister64.upperHalf; + int actualLowerValue = actualRegister64.lowerHalf; + + // Assert + int expectedUpperValue = 0x9ABCDEF0; + int expectedLowerValue = 0x00000000; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + }); + + group('Test for Register64.shiftRight', () { + test('Should [return default values] when shiftRight() is called and shiftValue is equal to 0', () { + // Arrange + Register64 actualRegister64 = Register64(0x12345678, 0x9ABCDEF0); + + // Act + actualRegister64.shiftRight(0); + + int actualUpperValue = actualRegister64.upperHalf; + int actualLowerValue = actualRegister64.lowerHalf; + + // Assert + int expectedUpperValue = 0x12345678; + int expectedLowerValue = 0x9ABCDEF0; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + test('Should [return result shifted by the shiftedValue] when shiftRight() is called and shiftValue is different than zero', () { + // Arrange + Register64 actualRegister64 = Register64(0x80000000, 0x00000000); + + // Act + actualRegister64.shiftRight(31); + + int actualUpperValue = actualRegister64.upperHalf; + int actualLowerValue = actualRegister64.lowerHalf; + + // Assert + int expectedUpperValue = 0x00000001; + int expectedLowerValue = 0x00000000; + + expect(actualUpperValue, expectedUpperValue); + expect(actualLowerValue, expectedLowerValue); + }); + }); +} diff --git a/test/hash/keccak_test.dart b/test/hash/keccak_test.dart deleted file mode 100644 index 337c95a1..00000000 --- a/test/hash/keccak_test.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:cryptography_utils/cryptography_utils.dart'; -import 'package:test/test.dart'; - -void main() { - Uint8List actualDataToHash = Uint8List.fromList('123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~'.codeUnits); - - group('Tests of Keccak.process()', () { - test('Should [return Keccak128 HASH] constructed from given data', () { - // Act - Uint8List actualKeccakResult = Keccak(128).process(actualDataToHash); - - // Assert - Uint8List expectedKeccakResult = base64Decode('2Rbrmj3BGkVcYzxWf3zQhw=='); - - expect(actualKeccakResult, expectedKeccakResult); - }); - - test('Should [return Keccak224 HASH] constructed from given data', () { - // Act - Uint8List actualKeccakResult = Keccak(224).process(actualDataToHash); - - // Assert - Uint8List expectedKeccakResult = base64Decode('h3ZKJczNIEckiGE1zv38YNvm9Lig+oYr2fiGAg=='); - - expect(actualKeccakResult, expectedKeccakResult); - }); - - test('Should [return Keccak256 HASH] constructed from given data', () { - // Act - Uint8List actualKeccakResult = Keccak(256).process(actualDataToHash); - - // Assert - Uint8List expectedKeccakResult = base64Decode('BCh2dEOFXrYvTet6a/a3PPmwZvQ7KQb60c8ib9WqhfE='); - - expect(actualKeccakResult, expectedKeccakResult); - }); - - test('Should [return Keccak288 HASH] constructed from given data', () { - // Act - Uint8List actualKeccakResult = Keccak(288).process(actualDataToHash); - - // Assert - Uint8List expectedKeccakResult = base64Decode('KPDYH5BgWlRRteR9Hm+lL8Tmv7krLpj/pABDV53+vY3azshS'); - - expect(actualKeccakResult, expectedKeccakResult); - }); - - test('Should [return Keccak384 HASH] constructed from given data', () { - // Act - Uint8List actualKeccakResult = Keccak(384).process(actualDataToHash); - - // Assert - Uint8List expectedKeccakResult = base64Decode('hK/d5Vo4YuG2z90PuXxsXahV1Wk7OTB2Tv7IGkPNmScppcbc0z81cdCuG62GWZ9h'); - - expect(actualKeccakResult, expectedKeccakResult); - }); - - test('Should [return Keccak512 HASH] constructed from given data', () { - // Act - Uint8List actualKeccakResult = Keccak(512).process(actualDataToHash); - - // Assert - Uint8List expectedKeccakResult = base64Decode('jlBELCX09gYSS5Io1R2gRyLi55O9uFl5d2GyjffXA6WFbUhPlLQyZ/W4ifqpD/CntMJrcvL3VUFuO3Xck/A6Og=='); - - expect(actualKeccakResult, expectedKeccakResult); - }); - }); -}