mina-attestations
Version:
Private Attestations on Mina
117 lines (116 loc) • 5.72 kB
JavaScript
import { Bytes, Provable, UInt32, UInt64 } from 'o1js';
import { deepStrictEqual } from 'node:assert';
import { DynamicSHA2 } from "./dynamic-sha2.js";
import { stringLength, zip } from "../util.js";
import { DynamicBytes } from "./dynamic-bytes.js";
import test from 'node:test';
import { SHA2 } from "./sha2.js";
import { DynamicString } from "./dynamic-string.js";
const DynBytes = DynamicBytes({ maxLength: 430 });
const StaticBytes = Bytes(stringLength(longString()));
let bytes = DynBytes.fromString(longString());
let staticBytes = StaticBytes.fromString(longString());
await test('padding 256', () => {
let actualPadding = DynamicSHA2.padding256(bytes);
let expectedPadding = SHA2.padding256(staticBytes);
deepStrictEqual(actualPadding.toValue().map(blockToHexBytes), expectedPadding.map(blockToHexBytes));
});
await test('padding 512', () => {
let actualPadding = DynamicSHA2.padding512(bytes);
let expectedPadding = SHA2.padding512(staticBytes);
deepStrictEqual(actualPadding.toValue().map(blockToHexBytes64), expectedPadding.map(blockToHexBytes64));
});
const expectedHash256 = await sha2(256, longString());
const expectedHash384 = await sha2(384, longString());
const expectedHash512 = await sha2(512, longString());
await test('sha256 outside circuit', async () => {
deepStrictEqual(DynamicSHA2.hash(256, bytes).toBytes(), expectedHash256.toBytes());
deepStrictEqual(SHA2.hash(256, staticBytes).toBytes(), expectedHash256.toBytes());
// also works with DynamicString and DynamicBytes
let String = DynamicString({ maxLength: 20 });
let string = String.from('hello');
deepStrictEqual(string.hashToBytes('sha2-256').toHex(), (await sha2(256, 'hello')).toHex());
let Bytes = DynamicBytes({ maxLength: 20 });
let bytes_ = Bytes.fromString('hello again!');
deepStrictEqual(bytes_.hashToBytes('sha2-256').toHex(), (await sha2(256, 'hello again!')).toHex());
});
await test('sha384 outside circuit', () => {
deepStrictEqual(DynamicSHA2.hash(384, bytes).toBytes(), expectedHash384.toBytes());
deepStrictEqual(SHA2.hash(384, staticBytes).toBytes(), expectedHash384.toBytes());
});
await test('sha512 outside circuit', () => {
deepStrictEqual(DynamicSHA2.hash(512, bytes).toBytes(), expectedHash512.toBytes());
deepStrictEqual(SHA2.hash(512, staticBytes).toBytes(), expectedHash512.toBytes());
});
// in-circuit test
async function circuit(len) {
let bytesVar = Provable.witness(DynBytes, () => bytes);
let hash = DynamicSHA2.hash(len, bytesVar);
zip(hash.bytes, (await sha2(len, longString())).bytes).forEach(([a, b], i) => {
a.assertEquals(b, `hash[${i}]`);
});
}
async function circuitStatic(len) {
let bytesVar = Provable.witness(StaticBytes, () => staticBytes);
let hash = SHA2.hash(len, bytesVar);
zip(hash.bytes, (await sha2(len, longString())).bytes).forEach(([a, b], i) => {
a.assertEquals(b, `hash[${i}]`);
});
}
await test('sha256 inside circuit', async () => {
await Provable.runAndCheck(() => circuit(256));
await Provable.runAndCheck(() => circuitStatic(256));
});
await test('sha384 inside circuit', async () => {
await Provable.runAndCheck(() => circuit(384));
await Provable.runAndCheck(() => circuitStatic(384));
});
await test('sha512 inside circuit', async () => {
await Provable.runAndCheck(() => circuit(512));
await Provable.runAndCheck(() => circuitStatic(512));
});
// constraints
async function checkConstraints(len) {
let constraints = await Provable.constraintSystem(() => circuit(len));
console.log(`\nsha2 ${len} constraints`);
console.log('dynamic', constraints.rows);
let constraintStatic = await Provable.constraintSystem(() => circuitStatic(len));
console.log('static', constraintStatic.rows);
let staticPadding = len === 256 ? SHA2.padding256(staticBytes) : SHA2.padding512(staticBytes);
let dynamicPadding = len === 256 ? DynamicSHA2.padding256(bytes) : DynamicSHA2.padding512(bytes);
let ratio = constraints.rows / constraintStatic.rows;
console.log(`static # of bytes: ${staticBytes.length}`);
console.log(`max dynamic # of bytes: ${bytes.maxLength}`);
console.log(`static # of blocks: ${staticPadding.length}`);
console.log(`max dynamic # of blocks: ${dynamicPadding.maxLength}`);
console.log(`constraint overhead for dynamic: ${((ratio - 1) * 100).toFixed(2)}%`);
}
await test('constraints dynamic vs static (256)', () => checkConstraints(256));
await test('constraints dynamic vs static (512)', () => checkConstraints(512));
// reference implementation using the Web Crypto API
async function sha2(len, input) {
let buffer = await crypto.subtle.digest(`SHA-${len}`, new TextEncoder().encode(input));
return Bytes(len / 8).from(new Uint8Array(buffer));
}
// helpers
function toHexBytes(uint32) {
return UInt32.from(uint32).toBigint().toString(16).padStart(8, '0');
}
function blockToHexBytes(block) {
return block.map(toHexBytes);
}
function toHexBytes64(uint32) {
return UInt64.from(uint32).toBigInt().toString(16).padStart(16, '0');
}
function blockToHexBytes64(block) {
return block.map(toHexBytes64);
}
// ~400 bytes, needs 7 blocks, also contains unicode
function longString() {
return `SHA-2 (Secure Hash Algorithm 2) is a set of cryptographic hash functions designed by the
United States National Security Agency (NSA) and first published in 2001.[3][4]
They are built using the Merkle–Damgård construction, from a one-way compression function itself
built using the Davies–Meyer structure from a specialized block cipher.
SHA-2 includes significant changes from its predecessor, SHA-1.`;
}
//# sourceMappingURL=dynamic-sha2.test.js.map