Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
469 changes: 468 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,17 @@
"deps:check": "madge --warning --circular --extensions ts ./"
},
"devDependencies": {
"@iden3/eslint-config": "https://github.com/iden3/eslint-config",
"@types/node": "^22.15.19",
"mock-local-storage": "^1.1.23",
"fake-indexeddb": "^6.0.1",
"@cspell/eslint-plugin": "^8.14.2",
"@iden3/eslint-config": "https://github.com/iden3/eslint-config",
"@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.2",
"@types/jest": "^29.5.14",
"@types/node": "^22.15.19",
"esbuild": "^0.25.4",
"fake-indexeddb": "^6.0.1",
"madge": "^8.0.0",
"mock-local-storage": "^1.1.23",
"rimraf": "^6.0.1",
"rollup": "^4.41.0",
"ts-node": "^10.9.2",
Expand All @@ -80,5 +80,9 @@
"peerDependencies": {
"@iden3/js-crypto": "1.3.2",
"idb-keyval": "6.2.2"
},
"dependencies": {
"@ethersproject/abi": "^5.8.0",
"@ethersproject/keccak256": "^5.8.0"
}
}
10 changes: 8 additions & 2 deletions src/lib/db/inMemory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@

import { Bytes, Node } from '../../types';
import { ITreeStorage } from '../../types/storage';
import { Hash, ZERO_HASH } from '../hash/hash';
import { Hash, ZERO_HASH, HashAlgorithm } from '../hash/hash';

export class InMemoryDB implements ITreeStorage {
prefix: Bytes;
_algo: HashAlgorithm;

private _kvMap: {
[k in string]: Node;
};
private _currentRoot: Hash;

constructor(_prefix: Bytes) {
constructor(_prefix: Bytes, algo?: HashAlgorithm) {
this.prefix = _prefix;
this._kvMap = {};
this._currentRoot = ZERO_HASH;
this._algo = algo ?? HashAlgorithm.Poseidon;
}

async get(k: Bytes): Promise<Node | undefined> {
Expand All @@ -35,4 +38,7 @@ export class InMemoryDB implements ITreeStorage {
async setRoot(r: Hash): Promise<void> {
this._currentRoot = r;
}
getHashAlgorithm(): HashAlgorithm {
return this._algo;
}
}
13 changes: 9 additions & 4 deletions src/lib/db/indexedDB.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Bytes, Node } from '../../types';
import { ITreeStorage } from '../../types/storage';
import { Hash, ZERO_HASH } from '../hash/hash';
import { Hash, ZERO_HASH, HashAlgorithm } from '../hash/hash';
import { bytes2Hex } from '../utils';
import { get, set, UseStore, createStore } from 'idb-keyval';
import { NODE_TYPE_EMPTY, NODE_TYPE_LEAF, NODE_TYPE_MIDDLE } from '../../constants';
Expand All @@ -13,14 +13,16 @@ export class IndexedDBStorage implements ITreeStorage {
private readonly _store: UseStore;

private _currentRoot: Hash;
private _algo: HashAlgorithm;

constructor(private readonly _prefix: Bytes, databaseName?: string) {
constructor(private readonly _prefix: Bytes, algo?: HashAlgorithm, databaseName?: string) {
this._currentRoot = ZERO_HASH;
this._prefixHash = bytes2Hex(_prefix);
this._store = createStore(
`${databaseName ?? IndexedDBStorage.storageName}-db`,
IndexedDBStorage.storageName
);
this._algo = algo ?? HashAlgorithm.Poseidon;
}

async get(k: Bytes): Promise<Node | undefined> {
Expand All @@ -36,13 +38,13 @@ export class IndexedDBStorage implements ITreeStorage {
if (obj.type === NODE_TYPE_MIDDLE) {
const cL = new Hash(Uint8Array.from(obj.childL.bytes));
const cR = new Hash(Uint8Array.from(obj.childR.bytes));
return new NodeMiddle(cL, cR);
return new NodeMiddle(cL, cR, this._algo);
}
if (obj.type === NODE_TYPE_LEAF) {
const k = new Hash(Uint8Array.from(obj.entry[0].bytes));
const v = new Hash(Uint8Array.from(obj.entry[1].bytes));

return new NodeLeaf(k, v);
return new NodeLeaf(k, v, this._algo);
}
throw new Error(`error: value found for key ${key} is not of type Node`);
}
Expand Down Expand Up @@ -71,4 +73,7 @@ export class IndexedDBStorage implements ITreeStorage {
await set(this._prefixHash, r, this._store);
this._currentRoot = r;
}
getHashAlgorithm(): HashAlgorithm {
return this._algo;
}
}
13 changes: 9 additions & 4 deletions src/lib/db/localStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

import { Bytes, Node } from '../../types';
import { ITreeStorage } from '../../types/storage';
import { Hash, ZERO_HASH } from '../hash/hash';
import { Hash, ZERO_HASH, HashAlgorithm } from '../hash/hash';
import { NODE_TYPE_EMPTY, NODE_TYPE_LEAF, NODE_TYPE_MIDDLE } from '../../constants';
import { NodeEmpty, NodeLeaf, NodeMiddle } from '../node/node';
import { bytes2Hex } from '../utils';

export class LocalStorageDB implements ITreeStorage {
private _currentRoot: Hash;
private _algo: HashAlgorithm;

constructor(private readonly _prefix: Bytes) {
constructor(private readonly _prefix: Bytes, algo?: HashAlgorithm) {
const rootStr = localStorage.getItem(bytes2Hex(_prefix));
if (rootStr) {
const bytes: number[] = JSON.parse(rootStr);
Expand All @@ -19,6 +20,7 @@ export class LocalStorageDB implements ITreeStorage {
} else {
this._currentRoot = ZERO_HASH;
}
this._algo = algo ?? HashAlgorithm.Poseidon;
}

async get(k: Bytes): Promise<Node | undefined> {
Expand All @@ -38,12 +40,12 @@ export class LocalStorageDB implements ITreeStorage {
const cL = new Hash(Uint8Array.from(obj.childL));
const cR = new Hash(Uint8Array.from(obj.childR));

return new NodeMiddle(cL, cR);
return new NodeMiddle(cL, cR, this._algo);
case NODE_TYPE_LEAF:
const k = new Hash(Uint8Array.from(obj.entry[0]));
const v = new Hash(Uint8Array.from(obj.entry[1]));

return new NodeLeaf(k, v);
return new NodeLeaf(k, v, this._algo);
}

throw `error: value found for key ${bytes2Hex(kBytes)} is not of type Node`;
Expand Down Expand Up @@ -73,4 +75,7 @@ export class LocalStorageDB implements ITreeStorage {
this._currentRoot = r;
localStorage.setItem(bytes2Hex(this._prefix), JSON.stringify(Array.from(r.bytes)));
}
getHashAlgorithm(): HashAlgorithm {
return this._algo;
}
}
18 changes: 12 additions & 6 deletions src/lib/entry/entry.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { Data } from '../entry/data';
import { Hash, ZERO_HASH, hashElems } from '../hash/hash';
import { Hash, ZERO_HASH, hashElems, HashAlgorithm } from '../hash/hash';
import { checkBigIntInField } from '../utils';

import { ElemBytes } from './elemBytes';

export class Entry {
private _algo: HashAlgorithm;
private _data: Data;
private _hIndex: Hash;
private _hValue: Hash;

constructor(_data?: Data) {
constructor(_data?: Data, algo?: HashAlgorithm) {
this._data = _data ? _data : new Data();
this._hIndex = ZERO_HASH;
this._hValue = ZERO_HASH;
this._algo = algo ?? HashAlgorithm.Poseidon;
}

get data(): Data {
Expand All @@ -27,16 +29,20 @@ export class Entry {
return this._data.value.slice(4, 8);
}

get algo(): HashAlgorithm {
return this._algo;
}

async hIndex(): Promise<Hash> {
if (this._hIndex === ZERO_HASH) {
return hashElems(elemBytesToBigInts(this.index));
return hashElems(elemBytesToBigInts(this.index), this._algo);
}
return this._hIndex;
}

async hValue(): Promise<Hash> {
if (this._hValue === ZERO_HASH) {
return hashElems(elemBytesToBigInts(this.value));
return hashElems(elemBytesToBigInts(this.value), this._algo);
}
return this._hValue;
}
Expand All @@ -58,7 +64,7 @@ export class Entry {
}

clone(): Entry {
return new Entry(this._data);
return new Entry(this._data, this._algo);
}
}

Expand All @@ -75,7 +81,7 @@ export const checkEntryInField = (e: Entry): boolean => {
let flag = true;

bigInts.forEach((b) => {
if (!checkBigIntInField(b)) {
if (!e.algo.checkEntryInField(b)) {
flag = false;
}
});
Expand Down
63 changes: 52 additions & 11 deletions src/lib/hash/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,32 @@ import {
} from '../utils';
import { Bytes, IHash, Siblings } from '../../types';
import { Hex, poseidon } from '@iden3/js-crypto';
import { keccak256 } from '@ethersproject/keccak256';
import { AbiCoder } from '@ethersproject/abi';

export class HashAlgorithm {
static Poseidon: HashAlgorithm = new HashAlgorithm(0);
static Keccak256: HashAlgorithm = new HashAlgorithm(1);

private _algo: number;
constructor(_algo: number) {
this._algo = _algo;
}

checkEntryInField(entry: bigint): boolean {
switch (this._algo) {
case 0:
// Poseidon
return checkBigIntInField(entry);
case 1:
// Keccak256
// no-op
return entry < BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF');
default:
throw new Error(`Unsupported hash algorithm ${this._algo}`);
}
}
}

export class Hash implements IHash {
// little endian
Expand Down Expand Up @@ -57,18 +83,15 @@ export class Hash implements IHash {

static fromString(s: string): Hash {
try {
return Hash.fromBigInt(BigInt(s));
const bigIntValue = BigInt(s);
return Hash.fromBigInt(bigIntValue);
} catch (e) {
const deserializedHash = JSON.parse(s);
const bytes = Uint8Array.from(Object.values(deserializedHash.bytes));
return new Hash(bytes);
}
}
static fromBigInt(i: bigint): Hash {
if (!checkBigIntInField(i)) {
throw new Error('NewBigIntFromHashBytes: Value not inside the Finite Field');
}

const bytes = bigIntToUINT8Array(i);

return new Hash(swapEndianness(bytes));
Expand Down Expand Up @@ -112,14 +135,32 @@ export const newHashFromString = (decimalString: string): Hash => {
return Hash.fromString(decimalString);
};

export const hashElems = (e: Array<bigint>): Hash => {
const hashBigInt = poseidon.hash(e);
return Hash.fromBigInt(hashBigInt);
export const hashElems = (e: Array<bigint>, algo: HashAlgorithm): Hash => {
switch (algo) {
case HashAlgorithm.Poseidon:
const hashBigInt = poseidon.hash(e);
return Hash.fromBigInt(hashBigInt);
case HashAlgorithm.Keccak256:
const hashString = keccak256(new AbiCoder().encode(e.map(p => 'uint256'), e));
return Hash.fromString(hashString);
default:
throw new Error(`Unsupported hash algorithm ${algo}`);
}
};

export const hashElemsKey = (k: bigint, e: Array<bigint>): Hash => {
const hashBigInt = poseidon.hash([...e, k]);
return Hash.fromBigInt(hashBigInt);
export const hashElemsKey = (k: bigint, e: Array<bigint>, algo: HashAlgorithm): Hash => {
switch (algo) {
case HashAlgorithm.Poseidon:
const hashBigInt = poseidon.hash([...e, k]);
return Hash.fromBigInt(hashBigInt);
case HashAlgorithm.Keccak256:
const params = [...e, k];
const encoded = new AbiCoder().encode(params.map(p => 'uint256'), [...e, k]);
const hashString = keccak256(encoded);
return Hash.fromString(hashString);
default:
throw new Error(`Unsupported hash algorithm ${algo}`);
}
};

export const circomSiblingsFromSiblings = (siblings: Siblings, levels: number): Siblings => {
Expand Down
Loading