From f81ca6cb7a2837ff8ba5d7643579cf0b2b4aa0f1 Mon Sep 17 00:00:00 2001 From: d0x471b <0x471@protonmail.com> Date: Thu, 29 Aug 2024 22:54:55 +0300 Subject: [PATCH] fix little endian conversion & add test --- little-endian.py | 17 ++++++++++++ package-lock.json | 4 +-- src/chacha20.test.ts | 64 +++++++++++++++++++++++++++++++------------- src/chacha20.ts | 45 ++++++++++++++++--------------- 4 files changed, 88 insertions(+), 42 deletions(-) create mode 100644 little-endian.py diff --git a/little-endian.py b/little-endian.py new file mode 100644 index 0000000..ccbfa44 --- /dev/null +++ b/little-endian.py @@ -0,0 +1,17 @@ +hex_array = [ + b'61707865', b'3320646e', b'79622d32', b'6b206574', # ChaCha constants + b'03020100', b'07060504', b'0b0a0908', b'0f0e0d0c', # Key + b'13121110', b'17161514', b'1b1a1918', b'1f1e1d1c', # Key + b'00000001', # Block count + b'09000000', b'4a000000', b'00000000' # Nonce +] + +def process_hex(hex_num): + big = bytearray.fromhex(hex_num.decode('utf-8')) + big.reverse() + little = ''.join(f"{n:02X}" for n in big) + return little + +for hex_num in hex_array: + result = process_hex(hex_num) + print("0x"+result) diff --git a/package-lock.json b/package-lock.json index 2bede1e..0503bcf 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "package-name", + "name": "chacha20-o1js", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "package-name", + "name": "chacha20-o1js", "version": "0.1.0", "license": "Apache-2.0", "devDependencies": { diff --git a/src/chacha20.test.ts b/src/chacha20.test.ts index b46ea88..c17001d 100644 --- a/src/chacha20.test.ts +++ b/src/chacha20.test.ts @@ -8,23 +8,24 @@ function toHexString(value: UInt32): string { const numberValue = value.toBigint(); return numberValue.toString(16).padStart(8, '0'); } -function octetsToUint32Array(octetString:string) { + +function octetsToUint32Array(octetString: string) { const octets = octetString.split(':'); - + if (octets.length !== 32 && octets.length !== 12 && octets.length !== 4) { throw new Error("Invalid octet string length."); } - + let words = []; - + // Convert octets to little-endian 32-bit words for (let i = 0; i < octets.length; i += 4) { if (i + 3 < octets.length) { // Combine 4 octets into a 32-bit word in little-endian order const word = (parseInt(octets[i], 16) | - (parseInt(octets[i+1], 16) << 8) | - (parseInt(octets[i+2], 16) << 16) | - (parseInt(octets[i+3], 16) << 24)); + (parseInt(octets[i + 1], 16) << 8) | + (parseInt(octets[i + 2], 16) << 16) | + (parseInt(octets[i + 3], 16) << 24)); words.push(word); } } @@ -62,28 +63,28 @@ describe('ChaCha', () => { it('should add two ChaChaState instances correctly', async () => { const state1 = new ChaChaState(new Uint32Array(8).fill(0), new Uint32Array(3).fill(0), 0); const state2 = new ChaChaState(new Uint32Array(8).fill(0), new Uint32Array(3).fill(0), 0); - + state1.state[0] = UInt32.fromValue(0x11111111n); state1.state[1] = UInt32.fromValue(0x01020304n); state1.state[2] = UInt32.fromValue(0x9b8d6f43n); state1.state[3] = UInt32.fromValue(0x01234567n); - + state2.state[0] = UInt32.fromValue(0x00000001n); state2.state[1] = UInt32.fromValue(0x00000002n); state2.state[2] = UInt32.fromValue(0x00000003n); state2.state[3] = UInt32.fromValue(0x00000004n); - + state1.add(state2); - + // Expected values after carryless addition (modulo 2^32) const expectedState = [ UInt32.fromValue((0x11111111n + 0x00000001n) & 0xFFFFFFFFn), UInt32.fromValue((0x01020304n + 0x00000002n) & 0xFFFFFFFFn), - UInt32.fromValue((0x9b8d6f43n + 0x00000003n) & 0xFFFFFFFFn), + UInt32.fromValue((0x9b8d6f43n + 0x00000003n) & 0xFFFFFFFFn), UInt32.fromValue((0x01234567n + 0x00000004n) & 0xFFFFFFFFn), ]; - + expect(toHexString(state1.state[0])).toBe(toHexString(expectedState[0])); expect(toHexString(state1.state[1])).toBe(toHexString(expectedState[1])); expect(toHexString(state1.state[2])).toBe(toHexString(expectedState[2])); @@ -93,11 +94,11 @@ describe('ChaCha', () => { let key = "00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:1e:1f"; let nonce = "00:00:00:09:00:00:00:4a:00:00:00:00"; const counter = 1; - + const keyArray = octetsToUint32Array(key); const nonceArray = octetsToUint32Array(nonce); const chachaState = new ChaChaState(keyArray, nonceArray, counter); - + const expectedState: UInt32[] = [ UInt32.fromValue(0x61707865n), UInt32.fromValue(0x3320646en), UInt32.fromValue(0x79622d32n), UInt32.fromValue(0x6b206574n), // ChaCha constants UInt32.fromValue(0x03020100n), UInt32.fromValue(0x07060504n), UInt32.fromValue(0x0b0a0908n), UInt32.fromValue(0x0f0e0d0cn), // Key @@ -105,13 +106,40 @@ describe('ChaCha', () => { UInt32.fromValue(0x00000001n), // Block count UInt32.fromValue(0x09000000n), UInt32.fromValue(0x4a000000n), UInt32.fromValue(0x00000000n) // Nonce ]; - - + + for (let i = 0; i < chachaState.state.length; i++) { const receivedHex = toHexString(chachaState.state[i]); const expectedHex = toHexString(expectedState[i]); expect(receivedHex).toBe(expectedHex); } + + // Assert toLe4Bytes conversion + const le4Bytes = chachaState.toLe4Bytes(); + + const expectedLe4Bytes: UInt32[] = [ + UInt32.fromValue(0x65787061n), // Little-endian of 0x61707865 + UInt32.fromValue(0x6E642033n), // Little-endian of 0x3320646e + UInt32.fromValue(0x322D6279n), // Little-endian of 0x79622d32 + UInt32.fromValue(0x7465206Bn), // Little-endian of 0x6b206574 + UInt32.fromValue(0x00010203n), // Little-endian of 0x03020100 + UInt32.fromValue(0x04050607n), // Little-endian of 0x07060504 + UInt32.fromValue(0x08090A0Bn), // Little-endian of 0x0b0a0908 + UInt32.fromValue(0x0C0D0E0Fn), // Little-endian of 0x0f0e0d0c + UInt32.fromValue(0x10111213n), // Little-endian of 0x13121110 + UInt32.fromValue(0x14151617n), // Little-endian of 0x17161514 + UInt32.fromValue(0x18191A1Bn), // Little-endian of 0x1b1a1918 + UInt32.fromValue(0x1C1D1E1Fn), // Little-endian of 0x1f1e1d1c + UInt32.fromValue(0x01000000n), // Little-endian of 0x00000001 + UInt32.fromValue(0x00000009n), // Little-endian of 0x09000000 + UInt32.fromValue(0x0000004An), // Little-endian of 0x4a000000 + UInt32.fromValue(0x00000000n) // Little-endian of 0x00000000 + ]; + + for (let i = 0; i < le4Bytes.length; i++) { + expect(le4Bytes[i]).toEqual(expectedLe4Bytes[i]); + } + }); - // TODO: add tests for block generation and tole4bytes + // TODO: add tests for block generation }); \ No newline at end of file diff --git a/src/chacha20.ts b/src/chacha20.ts index 1f080e7..ac38453 100644 --- a/src/chacha20.ts +++ b/src/chacha20.ts @@ -15,7 +15,7 @@ class ChaChaState { key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7], counter, nonce[0], nonce[1], nonce[2], ]; - + this.state = stateValues .filter(value => value !== undefined) .map(value => UInt32.fromValue(BigInt(value))); @@ -26,7 +26,7 @@ class ChaChaState { let b = state[bIndex]; let c = state[cIndex]; let d = state[dIndex]; - + a = UInt32.fromFields([Field.from((a.toBigint() + b.toBigint()) & 0xFFFFFFFFn)]); d = UInt32.from(d.toBigint() ^ a.toBigint()); d = UInt32.fromFields([Gadgets.rotate32(d.toFields()[0], 16, 'left')]); @@ -68,26 +68,27 @@ class ChaChaState { this.state[i] = UInt32.fromFields([Field.from((this.state[i].toBigint() + other.state[i].toBigint()) & 0xFFFFFFFFn)]); } } - // toLe4Bytes(): Uint8Array { - // const length = this.state.length; - // const buffer = new Uint8Array(length * 4); - - // for (let i = 0; i < length; i++) { - // // Explicitly specify the type of value as number - // const value: number = this.state[i] as number; - - // // Convert each number value to 4 bytes in little-endian order - // buffer[i * 4] = value & 0xFF; - // buffer[i * 4 + 1] = (value >> 8) & 0xFF; - // buffer[i * 4 + 2] = (value >> 16) & 0xFF; - // buffer[i * 4 + 3] = (value >> 24) & 0xFF; - // } - - // return buffer; - // } - - - + + toLe4Bytes(): UInt32[] { + const res: UInt32[] = []; + + for (let i = 0; i < 16; i++) { + const value = this.state[i].toBigint(); + + // Convert to little-endian 4 bytes + const byte0 = (value & 0xFFn); + const byte1 = (value >> 8n) & 0xFFn; + const byte2 = (value >> 16n) & 0xFFn; + const byte3 = (value >> 24n) & 0xFFn; + + const leValue = (byte0 << 24n) | (byte1 << 16n) | (byte2 << 8n) | byte3; + res.push(UInt32.fromValue(leValue)); + } + + return res; + } + + // chacha20Block(): UInt32[] { // // Convert each value in this.state to UInt32