Skip to content

Commit

Permalink
fix little endian conversion & add test
Browse files Browse the repository at this point in the history
  • Loading branch information
0x471 committed Aug 29, 2024
1 parent 2878999 commit f81ca6c
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 42 deletions.
17 changes: 17 additions & 0 deletions little-endian.py
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 46 additions & 18 deletions src/chacha20.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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]));
Expand All @@ -93,25 +94,52 @@ 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
UInt32.fromValue(0x13121110n), UInt32.fromValue(0x17161514n), UInt32.fromValue(0x1b1a1918n), UInt32.fromValue(0x1f1e1d1cn), // Key
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
});
45 changes: 23 additions & 22 deletions src/chacha20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand All @@ -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')]);
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit f81ca6c

Please sign in to comment.