| // See file LICENSE for more information. |
| |
| import 'dart:math'; |
| import 'dart:typed_data'; |
| |
| import 'package:pointycastle/src/impl/base_digest.dart'; |
| |
| import '../ufixnum.dart'; |
| |
| // KeccakEngine |
| abstract class KeccakEngine extends BaseDigest { |
| static final _keccakRoundConstants = Register64List.from([ |
| [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 _keccakRhoOffsets = [ |
| 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 |
| ]; |
| |
| final _state = Uint8List(200); |
| final _dataQueue = Uint8List(192); |
| |
| late int _rate; |
| late int fixedOutputLength; |
| late int _bitsInQueue; |
| late bool _squeezing; |
| |
| /// dataQueue intended for use by subclasses only. |
| Uint8List get dataQueue => _dataQueue; |
| |
| /// squeezing intended for use by subclasses only. |
| bool get squeezing => _squeezing; |
| |
| @override |
| int get byteLength => _rate ~/ 8; |
| |
| @override |
| int get digestSize => fixedOutputLength ~/ 8; |
| |
| int get rate => _rate; |
| |
| @override |
| void reset() { |
| init(fixedOutputLength); |
| } |
| |
| @override |
| void updateByte(int inp) { |
| absorb(inp); |
| } |
| |
| @override |
| void update(Uint8List inp, int inpOff, int len) { |
| _doUpdate(inp, inpOff, len); |
| } |
| |
| void absorb(int data) { |
| if ((_bitsInQueue % 8) != 0) { |
| throw StateError('attempt to absorb with odd length queue'); |
| } |
| if (_squeezing) { |
| throw StateError('attempt to absorb while squeezing'); |
| } |
| |
| _dataQueue[_bitsInQueue >> 3] = data; |
| if ((_bitsInQueue += 8) == _rate) { |
| _keccakAbsorb(_dataQueue, 0); |
| _bitsInQueue = 0; |
| } |
| } |
| |
| void absorbBits(int data, int bits) { |
| if (bits < 1 || bits > 7) { |
| throw StateError('"bits" must be in the range 1 to 7'); |
| } |
| if ((_bitsInQueue % 8) != 0) { |
| throw StateError('attempt to absorb with odd length queue'); |
| } |
| if (_squeezing) { |
| throw StateError('attempt to absorb while squeezing'); |
| } |
| var mask = (1 << bits) - 1; |
| _dataQueue[_bitsInQueue >> 3] = data & mask; |
| _bitsInQueue += bits; |
| } |
| |
| void absorbRange(Uint8List data, int off, int len) { |
| if ((_bitsInQueue % 8) != 0) { |
| throw StateError('attempt to absorb with odd length queue'); |
| } |
| if (squeezing) { |
| throw StateError('attempt to absorb while squeezing'); |
| } |
| |
| var bytesInQueue = _bitsInQueue >> 3; |
| var rateBytes = _rate >> 3; |
| |
| var available = rateBytes - bytesInQueue; |
| if (len < available) { |
| _dataQueue.setRange(bytesInQueue, bytesInQueue + len, data, off); |
| _bitsInQueue += len << 3; |
| return; |
| } |
| |
| var count = 0; |
| if (bytesInQueue > 0) { |
| _dataQueue.setRange( |
| bytesInQueue, bytesInQueue + available, data.sublist(off)); |
| count += available; |
| _keccakAbsorb(_dataQueue, 0); |
| } |
| |
| int remaining; |
| while ((remaining = len - count) >= rateBytes) { |
| _keccakAbsorb(data, off + count); |
| count += rateBytes; |
| } |
| |
| _dataQueue.setRange(0, remaining, data, off + count); |
| _bitsInQueue = remaining << 3; |
| } |
| |
| void _doUpdate(Uint8List data, int off, int len) { |
| absorbRange(data, off, len); |
| } |
| |
| void init(int bitlen) { |
| _initSponge(1600 - (bitlen << 1)); |
| } |
| |
| void _initSponge(int theRate) { |
| if ((theRate <= 0) || (theRate >= 1600) || ((theRate % 64) != 0)) { |
| throw StateError('invalid rate value'); |
| } |
| |
| _rate = theRate; |
| _state.fillRange(0, _state.length, 0); |
| _dataQueue.fillRange(0, _dataQueue.length, 0); |
| _bitsInQueue = 0; |
| _squeezing = false; |
| fixedOutputLength = (1600 - theRate) ~/ 2; |
| } |
| |
| void _keccakAbsorb(Uint8List? data, int off) { |
| var count = _rate >> 3; |
| for (var i = 0; i < count; ++i) { |
| _state[i] ^= data![off + i]; |
| } |
| _keccakPermutation(); |
| } |
| |
| void _keccakExtract() { |
| _keccakPermutation(); |
| |
| _dataQueue.setRange(0, _rate >> 3, _state); |
| _bitsInQueue = _rate; |
| } |
| |
| void squeeze(Uint8List? output, int? offset, int outputLength) { |
| if (!squeezing) { |
| _padAndSwitchToSqueezingPhase(); |
| } |
| |
| if ((outputLength % 8) != 0) { |
| throw StateError('outputLength not a multiple of 8'); |
| } |
| |
| var i = 0; |
| while (i < outputLength) { |
| if (_bitsInQueue == 0) { |
| _keccakExtract(); |
| } |
| |
| var partialBlock = min(_bitsInQueue, outputLength - i); |
| |
| output!.setRange( |
| offset! + (i ~/ 8), |
| offset + (i ~/ 8) + (partialBlock ~/ 8), |
| dataQueue.sublist((_rate - _bitsInQueue) ~/ 8)); |
| |
| //System.arraycopy(dataQueue, (rate - bitsInQueue) / 8, output, offset + (int)(i / 8), partialBlock / 8); |
| _bitsInQueue -= partialBlock; |
| i += partialBlock; |
| } |
| } |
| |
| void _padAndSwitchToSqueezingPhase() { |
| _dataQueue[_bitsInQueue >> 3] |= 1 << (_bitsInQueue & 7); |
| if (++_bitsInQueue == _rate) { |
| _keccakAbsorb(_dataQueue, 0); |
| } else { |
| var full = _bitsInQueue >> 6, partial = _bitsInQueue & 63; |
| for (var i = 0; i < full * 8; ++i) { |
| _state[i] ^= _dataQueue[i]; |
| } |
| |
| if (partial > 0) { |
| for (var k = 0; k != 8; k++) { |
| if (partial >= 8) { |
| _state[full * 8 + k] ^= dataQueue[full * 8 + k]; |
| } else { |
| _state[full * 8 + k] ^= |
| dataQueue[full * 8 + k] & ((1 << partial) - 1); |
| } |
| partial -= 8; |
| if (partial < 0) { |
| partial = 0; |
| } |
| } |
| } |
| } |
| |
| _state[((_rate - 1) >> 3)] ^= 1 << 7; |
| _bitsInQueue = 0; |
| _squeezing = true; |
| } |
| |
| void _fromBytesToWords(Register64List stateAsWords, Uint8List state) { |
| final r = Register64(); |
| |
| for (var i = 0; i < (1600 ~/ 64); i++) { |
| final index = i * (64 ~/ 8); |
| |
| stateAsWords[i].set(0); |
| |
| for (var j = 0; j < (64 ~/ 8); j++) { |
| r.set(state[index + j]); |
| r.shiftl(8 * j); |
| stateAsWords[i].or(r); |
| } |
| } |
| } |
| |
| void _fromWordsToBytes(Uint8List state, Register64List stateAsWords) { |
| final r = Register64(); |
| |
| for (var i = 0; i < (1600 ~/ 64); i++) { |
| final index = i * (64 ~/ 8); |
| |
| for (var j = 0; j < (64 ~/ 8); j++) { |
| r.set(stateAsWords[i]); |
| r.shiftr(8 * j); |
| state[index + j] = r.lo32; |
| } |
| } |
| } |
| |
| void _keccakPermutation() { |
| final longState = Register64List(_state.length ~/ 8); |
| |
| _fromBytesToWords(longState, _state); |
| _keccakPermutationOnWords(longState); |
| _fromWordsToBytes(_state, longState); |
| } |
| |
| void _keccakPermutationOnWords(Register64List state) { |
| for (var i = 0; i < 24; i++) { |
| theta(state); |
| rho(state); |
| pi(state); |
| chi(state); |
| _iota(state, i); |
| } |
| } |
| |
| void theta(Register64List A) { |
| final C = Register64List(5); |
| final r0 = Register64(); |
| final r1 = Register64(); |
| |
| for (var x = 0; x < 5; x++) { |
| C[x].set(0); |
| |
| for (var y = 0; y < 5; y++) { |
| C[x].xor(A[x + 5 * y]); |
| } |
| } |
| |
| for (var x = 0; x < 5; x++) { |
| r0.set(C[(x + 1) % 5]); |
| r0.shiftl(1); |
| |
| r1.set(C[(x + 1) % 5]); |
| r1.shiftr(63); |
| |
| r0.xor(r1); |
| r0.xor(C[(x + 4) % 5]); |
| |
| for (var y = 0; y < 5; y++) { |
| A[x + 5 * y].xor(r0); |
| } |
| } |
| } |
| |
| void rho(Register64List A) { |
| final r = Register64(); |
| |
| for (var x = 0; x < 5; x++) { |
| for (var y = 0; y < 5; y++) { |
| final index = x + 5 * y; |
| |
| if (_keccakRhoOffsets[index] != 0) { |
| r.set(A[index]); |
| r.shiftr(64 - _keccakRhoOffsets[index]); |
| |
| A[index].shiftl(_keccakRhoOffsets[index]); |
| A[index].xor(r); |
| } |
| } |
| } |
| } |
| |
| void pi(Register64List A) { |
| final tempA = Register64List(25); |
| |
| tempA.setRange(0, tempA.length, A); |
| |
| for (var x = 0; x < 5; x++) { |
| for (var y = 0; y < 5; y++) { |
| A[y + 5 * ((2 * x + 3 * y) % 5)].set(tempA[x + 5 * y]); |
| } |
| } |
| } |
| |
| void chi(Register64List A) { |
| final chiC = Register64List(5); |
| |
| for (var y = 0; y < 5; y++) { |
| for (var x = 0; x < 5; x++) { |
| chiC[x].set(A[((x + 1) % 5) + (5 * y)]); |
| chiC[x].not(); |
| chiC[x].and(A[((x + 2) % 5) + (5 * y)]); |
| chiC[x].xor(A[x + 5 * y]); |
| } |
| for (var x = 0; x < 5; x++) { |
| A[x + 5 * y].set(chiC[x]); |
| } |
| } |
| } |
| |
| void _iota(Register64List A, int indexRound) { |
| A[(((0) % 5) + 5 * ((0) % 5))].xor(_keccakRoundConstants[indexRound]); |
| } |
| |
| @override |
| int doFinal(Uint8List out, int outOff) { |
| throw UnimplementedError('Subclasses must implement this.'); |
| } |
| } |