UNPKG

mina-attestations

Version:
128 lines 7.18 kB
import { DynamicArray } from "./dynamic-array.js"; import { DynamicString } from "./dynamic-string.js"; import "./dynamic-record.js"; import { hashArray, hashDynamic, hashRecord, hashSafe, hashSafeWithPrefix, hashString, packToField, } from "./dynamic-hash.js"; import { test } from 'node:test'; import * as nodeAssert from 'node:assert'; import { Bytes, Field, MerkleList, Poseidon, Provable, UInt32, UInt8, } from 'o1js'; import { DynamicRecord } from "./dynamic-record.js"; // some hash collisions to be aware of, that are also quite natural hashDynamic(5n).assertEquals(hashDynamic(5), 'bigint ~ number'); hashDynamic(true).assertEquals(hashDynamic(1), 'bool ~ number'); hashDynamic({ a: 0n }).assertEquals(hashDynamic({ a: false }), 'record ~ record with equivalent fields'); hashDynamic(undefined).assertEquals(hashDynamic(null), 'undefined ~ null ~ any empty type'); hashDynamic('\x01\x02').assertEquals(hashDynamic([1, 2].map(UInt8.from)), 'strings ~ dynamic arrays of bytes'); hashDynamic([UInt32.from(0)]).assertEquals(hashDynamic([Field(0)]), 'dynamic arrays of zeros of same length, regardless of packing density of type (because padding is zeros)'); // this used to be an unexpected hash collision that was fixed hashRecord({ '\x00': 0 }).assertNotEquals(hashRecord({})); let shortString = 'hi'; let ShortString = DynamicString({ maxLength: 5 }); let shortHash = hashString(shortString); let longString = 'Poseidon (/pəˈsaɪdən, pɒ-, poʊ-/;[1] Greek: Ποσειδῶν) is one of the Twelve Olympians'; let LongString = DynamicString({ maxLength: 100 }); let longHash = hashString(longString); async function main() { await test('hash strings', () => { let shortStringVar = Provable.witness(ShortString, () => shortString); shortStringVar.hash().assertEquals(shortHash, 'short string'); let longStringVar = Provable.witness(LongString, () => longString); longStringVar.hash().assertEquals(longHash, 'long string'); // we can even convert the `ShortString` into a `LongString` LongString.from(shortStringVar) .hash() .assertEquals(shortHash, 'short -> long string'); // the other way round doesn't work because the string is too long nodeAssert.throws(() => { ShortString.from(LongString.from(longString)); }, /larger than target size/); // for strings, hashDynamic === packToField === hashString hashDynamic(shortString).assertEquals(shortHash, 'short string'); packToField(shortString).assertEquals(shortHash, 'short string'); hashDynamic(shortStringVar).assertEquals(shortHash, 'short string'); packToField(shortStringVar).assertEquals(shortHash, 'short string'); }); // arrays of strings let shortArray = [shortString, shortString]; let ShortArray = DynamicArray(ShortString, { maxLength: 5 }); let longArray = Array(8).fill(longString); let LongArray = DynamicArray(LongString, { maxLength: 10 }); let shortArrayHash = hashDynamic(shortArray); let longArrayHash = hashDynamic(longArray); await test('hash arrays of strings', () => { let shortArrayVar = Provable.witness(ShortArray, () => shortArray); shortArrayVar.hash().assertEquals(shortArrayHash, 'short array'); Provable.witness(LongArray, () => longArray) .hash() .assertEquals(longArrayHash, 'long array'); // for arrays, hashDynamic === packToField === hashArray hashArray(shortArray).assertEquals(shortArrayHash, 'short array'); packToField(shortArray).assertEquals(shortArrayHash, 'short array'); hashDynamic(shortArrayVar).assertEquals(shortArrayHash, 'short array'); packToField(shortArrayVar).assertEquals(shortArrayHash, 'short array'); }); // single-field values await test('plain values', () => { // stay the same when packing packToField(-1n).assertEquals(Field(-1n), 'pack bigint'); packToField(true).assertEquals(Field(1), 'pack boolean'); packToField(123).assertEquals(Field(123), 'pack number'); packToField(undefined).assertEquals(hashSafe([]), 'pack undefined'); // hash is plain poseidon hash hashDynamic(-1n).assertEquals(hashSafe([Field(-1n)]), 'hash bigint'); hashDynamic(true).assertEquals(hashSafe([Field(1)]), 'hash boolean'); hashDynamic(123).assertEquals(hashSafe([Field(123)]), 'hash number'); hashDynamic(undefined).assertEquals(hashSafe([]), 'pack undefined'); // hash of several plain values is poseidon hash hashDynamic(6n, 7, true).assertEquals(hashSafe([6, 7, 1].map(Field))); }); // records of plain values let record = { a: shortString, b: 1, c: true, d: -1n }; let recordHash = hashDynamic(record); let Record = DynamicRecord({}, { maxEntries: 5 }); await test('hash records', () => { let recordVar = Provable.witness(Record, () => record); recordVar.hash().assertEquals(recordHash, 'record'); packToField(recordVar).assertEquals(recordHash, 'record'); hashRecord(record).assertEquals(recordHash, 'record'); }); // arrays of records let array = [record, record, record]; let arrayHash = hashDynamic(array); let RecordArray = DynamicArray(Record, { maxLength: 5 }); await test('hash arrays of records', () => { let arrayVar = Provable.witness(RecordArray, () => array); arrayVar.hash().assertEquals(arrayHash, 'array'); hashDynamic(array).assertEquals(arrayHash, 'array'); }); } await test('outside circuit', () => main()); await test('inside circuit', () => Provable.runAndCheck(main)); // hashSafe await test('collisions', async () => { await test('Poseidon collisions', () => { Poseidon.hash([]).assertEquals(Poseidon.hash([Field(0)])); Poseidon.hash([]).assertEquals(Poseidon.hash([Field(0), Field(0)])); Poseidon.hash([1, 2, 3].map(Field)).assertEquals(Poseidon.hash([1, 2, 3, 0].map(Field))); }); await test('No hashSafe collisions', () => { hashSafe([]).assertNotEquals(hashSafe([0])); hashSafe([]).assertNotEquals(hashSafe([0, 0])); hashSafe([1, 2, 3]).assertNotEquals(hashSafe([1, 2, 3, 0])); }); await test('hashSafe with prefix', () => { hashSafeWithPrefix('blub', [1, 2, 3]).assertNotEquals(hashSafeWithPrefix('blob', [1, 2, 3])); }); }); // comparison of constraint efficiency of different approaches let cs = await Provable.constraintSystem(() => { Provable.witness(LongString, () => longString).hash(); }); console.log('constraints: string hash (100)', cs.rows); // merkle list of characters // list is represented as a single hash, so the equivalent of hashing is unpacking the entire list let CharList = MerkleList.create(UInt8, (hash, { value }) => Poseidon.hash([hash, value])); let cs2 = await Provable.constraintSystem(() => { Provable.witness(CharList, () => CharList.from(Bytes.fromString(longString).bytes)).forEach(100, (_item, _isDummy) => { }); }); console.log('constraints: merkle list of chars (100)', cs2.rows); //# sourceMappingURL=dynamic-hash.test.js.map