Skip to content

Commit c19f09f

Browse files
committed
Feature: Keccak256 algorithm
The purpose of this branch is to reimplement Keccak algorithm based on the pointycastle library. This addition enables secure hashing and is essential for supporting Ethereum-related operations, such as transaction validation and signing. List of changes: - created keccak.dart to simplify data absorption, finalization, and output generation using the Keccak sponge construction. This cryptographic algorithm absorbs an arbitrary-length input into an internal state and squeezes out a fixed-length output. Works by iteratively applying a permutation function through controlled input-output mixing. - created keccakf1600.dart to perform transformations on the 1600-bit state, achieving diffusion and mixing of bits across the state - created register64.dart and register64_list.dart to manage the 64-bit lanes and the state matrix of the algorithm
1 parent 6de4bc1 commit c19f09f

File tree

17 files changed

+972
-95
lines changed

17 files changed

+972
-95
lines changed

lib/cryptography_utils.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ library cryptography_utils;
33
export 'src/bip/bip.dart';
44
export 'src/cdsa/cdsa.dart';
55
export 'src/hash/hmac.dart';
6-
export 'src/hash/keccak.dart';
6+
export 'src/hash/keccak/keccak.dart';
77
export 'src/hash/pbkdf2.dart';
88
export 'src/hash/ripemd160.dart';
99
export 'src/signer/export.dart';

lib/src/bip/bip32/address_encoders/ethereum_address_encoder.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:typed_data';
33

44
import 'package:codec_utils/codec_utils.dart';
55
import 'package:cryptography_utils/cryptography_utils.dart';
6+
import 'package:cryptography_utils/src/hash/keccak/keccak_bit_length.dart';
67

78
/// The [EthereumAddressEncoder] class is designed for encoding addresses in accordance with the Ethereum network.
89
/// 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<Secp256k1PublicKe
2425

2526
@override
2627
String encodePublicKey(Secp256k1PublicKey publicKey) {
27-
Uint8List keccakHash = Keccak(256).process(publicKey.uncompressed.sublist(1));
28+
Uint8List keccakHash = Keccak(KeccakBitLength.keccak256).process(publicKey.uncompressed.sublist(1));
2829
String keccakHex = HexCodec.encode(keccakHash, lowercaseBool: true);
2930

3031
String address = keccakHex.substring(_startByte);
@@ -36,7 +37,7 @@ class EthereumAddressEncoder extends ABlockchainAddressEncoder<Secp256k1PublicKe
3637

3738
static String _wrapWithChecksum(String address) {
3839
Uint8List addressBytes = utf8.encode(address.toLowerCase());
39-
Uint8List checksumBytes = Keccak(256).process(addressBytes);
40+
Uint8List checksumBytes = Keccak(KeccakBitLength.keccak256).process(addressBytes);
4041
String checksumHex = HexCodec.encode(checksumBytes, lowercaseBool: true);
4142

4243
List<String> addressWithChecksum = address.split('').asMap().entries.map((MapEntry<int, String> entry) {

lib/src/hash/keccak.dart

Lines changed: 0 additions & 16 deletions
This file was deleted.

lib/src/hash/keccak/keccak.dart

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// This class was primarily influenced by:
2+
// "pointycastle" - Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
// this software and associated documentation files (the "Software"), to deal in
6+
// the Software without restriction, including without limitation the rights to
7+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8+
// of the Software, and to permit persons to whom the Software is furnished to do
9+
// so, subject to the following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included in all
12+
// copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16+
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18+
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
21+
import 'dart:math';
22+
import 'dart:typed_data';
23+
24+
import 'package:cryptography_utils/src/hash/keccak/keccak_bit_length.dart';
25+
import 'package:cryptography_utils/src/hash/keccak/keccakf1600.dart';
26+
27+
/// [Keccak256] is an implementation of Keccak algorithm which is a secure hash
28+
/// function that creates fixed-size outputs from variable-length inputs.
29+
class Keccak {
30+
final Uint8List _hashStateUint8List = Uint8List(200);
31+
final Uint8List _bufferUint8List = Uint8List(192);
32+
final KeccakBitLength _inputKeccakBitLength;
33+
34+
late int _blockSize;
35+
late int _bufferBits;
36+
late int _outputBitLength;
37+
late bool _squeezeActiveBool;
38+
39+
Keccak(this._inputKeccakBitLength) {
40+
_initSponge(_inputKeccakBitLength.bitLength);
41+
}
42+
43+
int get _digestSize => _outputBitLength ~/ 8;
44+
45+
Uint8List process(Uint8List inputUint8List) {
46+
_absorbData(inputUint8List, 0, inputUint8List.length);
47+
Uint8List outputUint8List = Uint8List(256);
48+
int length = _complete(outputUint8List, 0);
49+
return outputUint8List.sublist(0, length);
50+
}
51+
52+
void _absorbData(Uint8List inputUint8List, int outputOffset, int inputBitLength) {
53+
int currentBytes = _bufferBits >> 3;
54+
int rateBytes = _blockSize >> 3;
55+
int availableBytes = rateBytes - currentBytes;
56+
57+
if (inputBitLength < availableBytes) {
58+
_bufferUint8List.setRange(currentBytes, currentBytes + inputBitLength, inputUint8List, outputOffset);
59+
_bufferBits += inputBitLength << 3;
60+
return;
61+
}
62+
63+
int processedBytes = 0;
64+
if (currentBytes > 0) {
65+
_bufferUint8List.setRange(currentBytes, currentBytes + availableBytes, inputUint8List.sublist(outputOffset));
66+
processedBytes += availableBytes;
67+
_absorbBlock(_bufferUint8List, 0);
68+
}
69+
70+
int remainingBytes;
71+
while ((remainingBytes = inputBitLength - processedBytes) >= rateBytes) {
72+
_absorbBlock(inputUint8List, outputOffset + processedBytes);
73+
processedBytes += rateBytes;
74+
}
75+
76+
_bufferUint8List.setRange(0, remainingBytes, inputUint8List, outputOffset + processedBytes);
77+
_bufferBits = remainingBytes << 3;
78+
}
79+
80+
int _complete(Uint8List outputUint8List, int outputOffset) {
81+
_squeeze(outputUint8List, outputOffset, _outputBitLength);
82+
_resetState();
83+
return _digestSize;
84+
}
85+
86+
void _squeeze(Uint8List inputUint8List, int outputOffset, int inputBitLength) {
87+
if (_squeezeActiveBool == false) {
88+
_startSqueezingPhase();
89+
}
90+
91+
int i = 0;
92+
93+
while (i < inputBitLength) {
94+
if (_bufferBits == 0) {
95+
_extractBlock();
96+
}
97+
98+
int activeBlock = min(_bufferBits, inputBitLength - i);
99+
100+
inputUint8List.setRange(
101+
outputOffset + (i ~/ 8),
102+
outputOffset + (i ~/ 8) + (activeBlock ~/ 8),
103+
_bufferUint8List.sublist((_blockSize - _bufferBits) ~/ 8),
104+
);
105+
106+
_bufferBits -= activeBlock;
107+
i += activeBlock;
108+
}
109+
}
110+
111+
void _resetState() {
112+
_initSponge(_outputBitLength);
113+
}
114+
115+
void _initSponge(int inputBitLength) {
116+
int rate = 1600 - (inputBitLength << 1);
117+
118+
_hashStateUint8List.fillRange(0, _hashStateUint8List.length, 0);
119+
_bufferUint8List.fillRange(0, _bufferUint8List.length, 0);
120+
_blockSize = rate;
121+
_outputBitLength = (1600 - rate) ~/ 2;
122+
_bufferBits = 0;
123+
_squeezeActiveBool = false;
124+
}
125+
126+
void _startSqueezingPhase() {
127+
_bufferUint8List[_bufferBits >> 3] |= 1 << (_bufferBits & 7);
128+
if (++_bufferBits == _blockSize) {
129+
_absorbBlock(_bufferUint8List, 0);
130+
} else {
131+
int fullBytes = _bufferBits >> 6, partialBits = _bufferBits & 63;
132+
for (int i = 0; i < fullBytes * 8; ++i) {
133+
_hashStateUint8List[i] ^= _bufferUint8List[i];
134+
}
135+
if (partialBits > 0) {
136+
for (int k = 0; k != 8; k++) {
137+
if (partialBits >= 8) {
138+
_hashStateUint8List[fullBytes * 8 + k] ^= _bufferUint8List[fullBytes * 8 + k];
139+
} else {
140+
_hashStateUint8List[fullBytes * 8 + k] ^= _bufferUint8List[fullBytes * 8 + k] & ((1 << partialBits) - 1);
141+
}
142+
partialBits -= 8;
143+
if (partialBits < 0) {
144+
partialBits = 0;
145+
}
146+
}
147+
}
148+
}
149+
_hashStateUint8List[((_blockSize - 1) >> 3)] ^= 1 << 7;
150+
_bufferBits = 0;
151+
_squeezeActiveBool = true;
152+
}
153+
154+
void _extractBlock() {
155+
_applyPermutation();
156+
_bufferUint8List.setRange(0, _blockSize >> 3, _hashStateUint8List);
157+
_bufferBits = _blockSize;
158+
}
159+
160+
void _absorbBlock(Uint8List inputUint8List, int outputOffset) {
161+
int count = _blockSize >> 3;
162+
for (int i = 0; i < count; ++i) {
163+
_hashStateUint8List[i] ^= inputUint8List[outputOffset + i];
164+
}
165+
_applyPermutation();
166+
}
167+
168+
void _applyPermutation() {
169+
KeccakF1600().applyPermutation(_hashStateUint8List);
170+
}
171+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
enum KeccakBitLength {
2+
keccak128(128),
3+
keccak224(224),
4+
keccak256(256),
5+
keccak288(288),
6+
keccak384(384),
7+
keccak512(512);
8+
9+
final int bitLength;
10+
11+
const KeccakBitLength(this.bitLength);
12+
}

0 commit comments

Comments
 (0)