Skip to content

Commit

Permalink
Tweak sha256 when input=Field & Add test cases for o1jsSha256(field)…
Browse files Browse the repository at this point in the history
… against noble hash
  • Loading branch information
Shigoto-dev19 committed Jan 15, 2024
1 parent 9231030 commit cb46534
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 16 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"testw": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
"lint": "npx eslint src/* --fix",
"digest": "node build/src/benchmarks/command.js",
"benchmark": "npm run build && node build/src/benchmarks/benchmark.js"
"benchmark": "npm run build && node build/src/benchmarks/benchmark.js",
"witness-time": "npm run build && node build/src/benchmarks/sha256-witness.js"
},
"devDependencies": {
"@babel/preset-env": "^7.16.4",
Expand Down
4 changes: 2 additions & 2 deletions src/benchmarks/benchmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class Bytes32 extends Bytes(32) {}
function o1jsSha3_256(input: string) {
const parsedInput = Bytes32.fromString(input);
const sha3Digest = Hash.SHA3_256.hash(parsedInput);
return sha3Digest

return sha3Digest;
}

const { mark, compare, run } = bench;
Expand Down
7 changes: 1 addition & 6 deletions src/benchmarks/command.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
nodeHash,
o1jsHash,
nobleHash,
Timer,
} from '../test-utils.js';
import { nodeHash, o1jsHash, nobleHash, Timer } from '../test-utils.js';
import { o1jsHashCircom } from '../test-utils.js';

function benchmarkHash(hashFunction: typeof nobleHash, input: string) {
Expand Down
8 changes: 4 additions & 4 deletions src/benchmarks/sha256-witness.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Bytes, Provable, Field, Hash, Poseidon } from 'o1js';
import { sha256O1js } from '../sha256.js'
import { sha256O1js } from '../sha256.js';

let Bytes32 = Bytes(32);

Expand All @@ -19,7 +19,7 @@ console.timeEnd('sha256 witness');

console.time('poseidon witness');
Provable.runAndCheck(() => {
let input = Provable.witness(Field, () => Field.random());
Poseidon.hash([input]);
let input = Provable.witness(Field, () => Field.random());
Poseidon.hash([input]);
});
console.timeEnd('poseidon witness');
console.timeEnd('poseidon witness');
20 changes: 19 additions & 1 deletion src/preprocessing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@ function splitArrayIntoBlocks(inputArray: Bool[]): Field[] {
return blocks;
}

function calculateBytes(field: Field): number {
const bigIntValue = field.toBigInt();
if (bigIntValue < 0n) {
throw new Error('Input must be a non-negative BigInt.');
}

// Calculate the number of bytes
let bytes = 0;
let value = bigIntValue;

while (value > 0n) {
value >>= 8n; // Right shift by 8 bits (1 byte)
bytes++;
}

return Math.max(bytes, 1); // Ensure at least 1 byte for zero
}

/**
* Parses the input (string or Field) into an array of Fields for SHA-256 processing in zkapp.
*
Expand All @@ -86,7 +104,7 @@ function parseSha2Input(input: string | Field): Field[] {
let inputBinary: Bool[];

if (typeof input === 'string') inputBinary = toBoolArray(input);
else inputBinary = input.toBits();
else inputBinary = input.toBits(calculateBytes(input) * 8).reverse();

const parsedInput = splitArrayIntoBlocks(inputBinary);

Expand Down
30 changes: 29 additions & 1 deletion src/sha256.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { o1jsHash, nodeHash, generateRandomInput } from './test-utils';
import { Field } from 'o1js';
import {
o1jsHash,
nodeHash,
o1jsHashField,
generateRandomInput,
bigToUint8Array,
generateRandomBytes,
} from './test-utils';
import { bytesToHex } from '@noble/hashes/utils';
import { sha256 as nobleHashUint } from '@noble/hashes/sha256';

// NIST test vectors (https://www.di-mgt.com.au/sha_testvectors.html)
// Note: - Input message: one million (1,000,000) repetitions of the character "a" (0x61).
Expand Down Expand Up @@ -127,4 +137,22 @@ describe('Testing o1js SHA256 hash function against to node-js implementation',
for (let i = 0; testWindow768.length; i++)
testWindow768[i] = generateRandomInput(768);
});

test.only('should have compliant digest for input=random Field/Uint8Array', () => {
const input = generateRandomBytes(31);
const actualDigest = o1jsHashField(Field(input));
const expectedDigest = bytesToHex(nobleHashUint(bigToUint8Array(input)));

expect(actualDigest).toBe(expectedDigest);
});

test.only('should have compliant digest for input=random Field/Uint8Array - 100 iterations', () => {
for (let i = 0; i < 100; i++) {
let input = generateRandomBytes(31);
let actualDigest = o1jsHashField(Field(input));
let expectedDigest = bytesToHex(nobleHashUint(bigToUint8Array(input)));

expect(actualDigest).toBe(expectedDigest);
}
});
});
39 changes: 38 additions & 1 deletion src/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { binaryToHex, fieldToBinary } from './binary-utils.js';
import { sha256O1js } from './sha256.js';
import { sha256 as nobleSha256 } from '@noble/hashes/sha256';
import { bytesToHex } from '@noble/hashes/utils';
import { sha256 as sha256Circom} from './benchmarks/comparator/sha256-circom.js';
import { sha256 as sha256Circom } from './benchmarks/comparator/sha256-circom.js';
import * as crypto from 'crypto';
import { Field } from 'o1js';

const TWO32 = BigInt(2 ** 32);

Expand All @@ -14,9 +15,11 @@ export {
generateRandomString,
generateRandomInput,
generateRandomBytes,
bigToUint8Array,
nodeHash,
nobleHash,
o1jsHash,
o1jsHashField,
o1jsHashCircom,
Timer,
};
Expand Down Expand Up @@ -119,10 +122,44 @@ function generateRandomBytes(byteNumber = 4): bigint {
return BigInt('0x' + randomBytes.toString('hex'));
}

function bigToUint8Array(big: bigint) {
const big0 = BigInt(0);
const big1 = BigInt(1);
const big8 = BigInt(8);

if (big < big0) {
const bits: bigint = (BigInt(big.toString(2).length) / big8 + big1) * big8;
const prefix1: bigint = big1 << bits;
big += prefix1;
}
let hex = big.toString(16);
if (hex.length % 2) {
hex = '0' + hex;
}
const len = hex.length / 2;
const u8 = new Uint8Array(len);
let i = 0;
let j = 0;
while (i < len) {
u8[i] = parseInt(hex.slice(j, j + 2), 16);
i += 1;
j += 2;
}
return u8;
}

function nodeHash(input: string): string {
return crypto.createHash('sha256').update(input).digest('hex');
}

function o1jsHashField(input: Field): string {
const digest = sha256O1js(input);
const digestBinary = digest.map(fieldToBinary).join('');
const digestHex = binaryToHex(digestBinary);

return digestHex;
}

function nobleHash(input: string): string {
return bytesToHex(nobleSha256(input));
}
Expand Down

0 comments on commit cb46534

Please sign in to comment.