diff --git a/packages/rln/src/contract/constants.ts b/packages/rln/src/contract/constants.ts index 636e4e16bc..cbf71174f4 100644 --- a/packages/rln/src/contract/constants.ts +++ b/packages/rln/src/contract/constants.ts @@ -25,4 +25,13 @@ export const RATE_LIMIT_PARAMS = { EPOCH_LENGTH: 600 // Epoch length in seconds (10 minutes) } as const; +/** + * Default Q value for the RLN contract + * This is the upper bound for the ID commitment + * @see https://github.com/waku-org/specs/blob/master/standards/core/rln-contract.md#implementation-suggestions + */ +export const RLN_Q = BigInt( + "21888242871839275222246405745257275088548364400416034343698204186575808495617" +); + export const DEFAULT_RATE_LIMIT = RATE_LIMIT_PARAMS.MAX_RATE; diff --git a/packages/rln/src/contract/rln_base_contract.ts b/packages/rln/src/contract/rln_base_contract.ts index c75c66a4e3..d4b8306cad 100644 --- a/packages/rln/src/contract/rln_base_contract.ts +++ b/packages/rln/src/contract/rln_base_contract.ts @@ -3,6 +3,7 @@ import { ethers } from "ethers"; import { IdentityCredential } from "../identity.js"; import { DecryptedCredentials } from "../keystore/types.js"; +import { BytesUtils } from "../utils/bytes.js"; import { RLN_ABI } from "./abi.js"; import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./constants.js"; @@ -490,7 +491,6 @@ export class RLNBaseContract { log.error(`Error in withdraw: ${(error as Error).message}`); } } - public async registerWithIdentity( identity: IdentityCredential ): Promise { @@ -529,7 +529,9 @@ export class RLNBaseContract { identity.IDCommitmentBigInt, this.rateLimit, [], - { gasLimit } + { + gasLimit + } ); const txRegisterReceipt = await txRegisterResponse.wait(); @@ -626,7 +628,7 @@ export class RLNBaseContract { permit.v, permit.r, permit.s, - identity.IDCommitmentBigInt, + BytesUtils.buildBigIntFromUint8ArrayBE(identity.IDCommitment), this.rateLimit, idCommitmentsToErase.map((id) => ethers.BigNumber.from(id)) ); diff --git a/packages/rln/src/contract/rln_contract.spec.ts b/packages/rln/src/contract/rln_contract.spec.ts index 219b1268be..7603e7cc2e 100644 --- a/packages/rln/src/contract/rln_contract.spec.ts +++ b/packages/rln/src/contract/rln_contract.spec.ts @@ -4,13 +4,13 @@ import chaiAsPromised from "chai-as-promised"; import * as ethers from "ethers"; import sinon, { SinonSandbox } from "sinon"; -import { createTestRLNInstance, initializeRLNContract } from "./test-setup.js"; +import { createTestRLNInstance, initializeRLNContract } from "./test_setup.js"; import { createMockRegistryContract, createRegisterStub, mockRLNRegisteredEvent, verifyRegistration -} from "./test-utils.js"; +} from "./test_utils.js"; use(chaiAsPromised); diff --git a/packages/rln/src/contract/rln_contract.ts b/packages/rln/src/contract/rln_contract.ts index 12b733f6e1..0f894a59c1 100644 --- a/packages/rln/src/contract/rln_contract.ts +++ b/packages/rln/src/contract/rln_contract.ts @@ -4,7 +4,7 @@ import { ethers } from "ethers"; import type { RLNInstance } from "../rln.js"; import { MerkleRootTracker } from "../root_tracker.js"; -import { zeroPadLE } from "../utils/bytes.js"; +import { BytesUtils } from "../utils/bytes.js"; import { RLNBaseContract } from "./rln_base_contract.js"; import { RLNContractInitOptions } from "./types.js"; @@ -110,7 +110,10 @@ export class RLNContract extends RLNBaseContract { index = ethers.BigNumber.from(index); } - const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32); + const idCommitment = BytesUtils.zeroPadLE( + hexToBytes(_idCommitment), + 32 + ); rlnInstance.zerokit.insertMember(idCommitment); const numericIndex = index.toNumber(); diff --git a/packages/rln/src/contract/test-setup.ts b/packages/rln/src/contract/test_setup.ts similarity index 99% rename from packages/rln/src/contract/test-setup.ts rename to packages/rln/src/contract/test_setup.ts index dae1d26954..e097d780d0 100644 --- a/packages/rln/src/contract/test-setup.ts +++ b/packages/rln/src/contract/test_setup.ts @@ -83,4 +83,4 @@ export const TEST_DATA = { ), mockSignature: "0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c" -} as const; +}; diff --git a/packages/rln/src/contract/test-utils.ts b/packages/rln/src/contract/test_utils.ts similarity index 100% rename from packages/rln/src/contract/test-utils.ts rename to packages/rln/src/contract/test_utils.ts diff --git a/packages/rln/src/credentials_manager.ts b/packages/rln/src/credentials_manager.ts index 70544ed4a2..396f3a29ed 100644 --- a/packages/rln/src/credentials_manager.ts +++ b/packages/rln/src/credentials_manager.ts @@ -1,9 +1,9 @@ import { hmac } from "@noble/hashes/hmac"; -import { sha256 } from "@noble/hashes/sha256"; +import { sha256 } from "@noble/hashes/sha2"; import { Logger } from "@waku/utils"; import { ethers } from "ethers"; -import { LINEA_CONTRACT } from "./contract/constants.js"; +import { LINEA_CONTRACT, RLN_Q } from "./contract/constants.js"; import { RLNBaseContract } from "./contract/rln_base_contract.js"; import { IdentityCredential } from "./identity.js"; import { Keystore } from "./keystore/index.js"; @@ -13,10 +13,8 @@ import type { } from "./keystore/index.js"; import { KeystoreEntity, Password } from "./keystore/types.js"; import { RegisterMembershipOptions, StartRLNOptions } from "./types.js"; -import { - buildBigIntFromUint8Array, - extractMetaMaskSigner -} from "./utils/index.js"; +import { BytesUtils } from "./utils/bytes.js"; +import { extractMetaMaskSigner } from "./utils/index.js"; import { Zerokit } from "./zerokit.js"; const log = new Logger("waku:credentials"); @@ -116,7 +114,9 @@ export class RLNCredentialsManager { ); } else { log.info("Using local implementation to generate identity"); - identity = this.generateSeededIdentityCredential(options.signature); + identity = await this.generateSeededIdentityCredential( + options.signature + ); } } @@ -249,7 +249,9 @@ export class RLNCredentialsManager { * @param seed A string seed to generate the identity from * @returns IdentityCredential */ - private generateSeededIdentityCredential(seed: string): IdentityCredential { + private async generateSeededIdentityCredential( + seed: string + ): Promise { log.info("Generating seeded identity credential"); // Convert the seed to bytes const encoder = new TextEncoder(); @@ -257,26 +259,38 @@ export class RLNCredentialsManager { // Generate deterministic values using HMAC-SHA256 // We use different context strings for each component to ensure they're different - const idTrapdoor = hmac(sha256, seedBytes, encoder.encode("IDTrapdoor")); - const idNullifier = hmac(sha256, seedBytes, encoder.encode("IDNullifier")); + const idTrapdoorBE = hmac(sha256, seedBytes, encoder.encode("IDTrapdoor")); + const idNullifierBE = hmac( + sha256, + seedBytes, + encoder.encode("IDNullifier") + ); - // Generate IDSecretHash as a hash of IDTrapdoor and IDNullifier - const combinedBytes = new Uint8Array([...idTrapdoor, ...idNullifier]); - const idSecretHash = sha256(combinedBytes); + const combinedBytes = new Uint8Array([...idTrapdoorBE, ...idNullifierBE]); + const idSecretHashBE = sha256(combinedBytes); - // Generate IDCommitment as a hash of IDSecretHash - const idCommitment = sha256(idSecretHash); + const idCommitmentRawBE = sha256(idSecretHashBE); + const idCommitmentBE = this.reduceIdCommitment(idCommitmentRawBE); - // Convert IDCommitment to BigInt - const idCommitmentBigInt = buildBigIntFromUint8Array(idCommitment); - - log.info("Successfully generated identity credential"); + log.info( + "Successfully generated identity credential, storing in Big Endian format" + ); return new IdentityCredential( - idTrapdoor, - idNullifier, - idSecretHash, - idCommitment, - idCommitmentBigInt + idTrapdoorBE, + idNullifierBE, + idSecretHashBE, + idCommitmentBE ); } + + /** + * Helper: take 32-byte BE, reduce mod Q, return 32-byte BE + */ + private reduceIdCommitment( + bytesBE: Uint8Array, + limit: bigint = RLN_Q + ): Uint8Array { + const nBE = BytesUtils.buildBigIntFromUint8ArrayBE(bytesBE); + return BytesUtils.bigIntToUint8Array32BE(nBE % limit); + } } diff --git a/packages/rln/src/identity.ts b/packages/rln/src/identity.ts index aff76bcd41..87167165ba 100644 --- a/packages/rln/src/identity.ts +++ b/packages/rln/src/identity.ts @@ -1,13 +1,19 @@ -import { buildBigIntFromUint8Array } from "./utils/index.js"; +import { BytesUtils } from "./utils/bytes.js"; export class IdentityCredential { + public IDCommitmentBigInt: bigint; + /** + * All variables are in little-endian format + */ public constructor( public readonly IDTrapdoor: Uint8Array, public readonly IDNullifier: Uint8Array, public readonly IDSecretHash: Uint8Array, - public readonly IDCommitment: Uint8Array, - public readonly IDCommitmentBigInt: bigint - ) {} + public readonly IDCommitment: Uint8Array + ) { + this.IDCommitmentBigInt = + BytesUtils.buildBigIntFromUint8ArrayBE(IDCommitment); + } public static fromBytes(memKeys: Uint8Array): IdentityCredential { if (memKeys.length < 128) { @@ -18,14 +24,12 @@ export class IdentityCredential { const idNullifier = memKeys.subarray(32, 64); const idSecretHash = memKeys.subarray(64, 96); const idCommitment = memKeys.subarray(96, 128); - const idCommitmentBigInt = buildBigIntFromUint8Array(idCommitment, 32); return new IdentityCredential( idTrapdoor, idNullifier, idSecretHash, - idCommitment, - idCommitmentBigInt + idCommitment ); } } diff --git a/packages/rln/src/keystore/keystore.spec.ts b/packages/rln/src/keystore/keystore.spec.ts index 4936f80b1c..02f2f20bd3 100644 --- a/packages/rln/src/keystore/keystore.spec.ts +++ b/packages/rln/src/keystore/keystore.spec.ts @@ -8,7 +8,7 @@ use(deepEqualInAnyOrder); use(chaiAsPromised); import { IdentityCredential } from "../identity.js"; -import { buildBigIntFromUint8Array } from "../utils/bytes.js"; +import { BytesUtils } from "../utils/bytes.js"; import { Keystore } from "./keystore.js"; import type { KeystoreMembershipInfo } from "./types.js"; @@ -219,15 +219,12 @@ describe("Keystore", () => { IDCommitment: new Uint8Array([ 112, 216, 27, 89, 188, 135, 203, 19, 168, 211, 117, 13, 231, 135, 229, 58, 94, 20, 246, 8, 33, 65, 238, 37, 112, 97, 65, 241, 255, 93, 171, 15 - ]), - IDCommitmentBigInt: buildBigIntFromUint8Array( - new Uint8Array([ - 112, 216, 27, 89, 188, 135, 203, 19, 168, 211, 117, 13, 231, 135, 229, - 58, 94, 20, 246, 8, 33, 65, 238, 37, 112, 97, 65, 241, 255, 93, 171, - 15 - ]) - ) + ]) } as unknown as IdentityCredential; + // Add the missing property for test correctness + identity.IDCommitmentBigInt = BytesUtils.buildBigIntFromUint8ArrayBE( + identity.IDCommitment + ); const membership = { chainId: "0xAA36A7", treeIndex: 8, @@ -279,6 +276,9 @@ describe("Keystore", () => { 58, 94, 20, 246, 8, 33, 65, 238, 37, 112, 97, 65, 241, 255, 93, 171, 15 ] } as unknown as IdentityCredential; + identity.IDCommitmentBigInt = BytesUtils.buildBigIntFromUint8ArrayBE( + identity.IDCommitment + ); const membership = { chainId: "0xAA36A7", treeIndex: 8, diff --git a/packages/rln/src/keystore/keystore.ts b/packages/rln/src/keystore/keystore.ts index 98ba220b1c..cb86ff25a7 100644 --- a/packages/rln/src/keystore/keystore.ts +++ b/packages/rln/src/keystore/keystore.ts @@ -14,7 +14,7 @@ import { import _ from "lodash"; import { v4 as uuidV4 } from "uuid"; -import { buildBigIntFromUint8Array } from "../utils/bytes.js"; +import { BytesUtils } from "../utils/bytes.js"; import { decryptEipKeystore, keccak256Checksum } from "./cipher.js"; import { isCredentialValid, isKeystoreValid } from "./schema_validator.js"; @@ -250,26 +250,35 @@ export class Keystore { const str = bytesToUtf8(bytes); const obj = JSON.parse(str); - // TODO: add runtime validation of nwaku credentials + // Little Endian + const idCommitmentLE = Keystore.fromArraylikeToBytes( + _.get(obj, "identityCredential.idCommitment", []) + ); + const idTrapdoorLE = Keystore.fromArraylikeToBytes( + _.get(obj, "identityCredential.idTrapdoor", []) + ); + const idNullifierLE = Keystore.fromArraylikeToBytes( + _.get(obj, "identityCredential.idNullifier", []) + ); + const idSecretHashLE = Keystore.fromArraylikeToBytes( + _.get(obj, "identityCredential.idSecretHash", []) + ); + + // Big Endian + const idCommitmentBE = BytesUtils.switchEndianness(idCommitmentLE); + const idTrapdoorBE = BytesUtils.switchEndianness(idTrapdoorLE); + const idNullifierBE = BytesUtils.switchEndianness(idNullifierLE); + const idSecretHashBE = BytesUtils.switchEndianness(idSecretHashLE); + const idCommitmentBigInt = + BytesUtils.buildBigIntFromUint8ArrayBE(idCommitmentBE); + return { identity: { - IDCommitment: Keystore.fromArraylikeToBytes( - _.get(obj, "identityCredential.idCommitment", []) - ), - IDTrapdoor: Keystore.fromArraylikeToBytes( - _.get(obj, "identityCredential.idTrapdoor", []) - ), - IDNullifier: Keystore.fromArraylikeToBytes( - _.get(obj, "identityCredential.idNullifier", []) - ), - IDCommitmentBigInt: buildBigIntFromUint8Array( - Keystore.fromArraylikeToBytes( - _.get(obj, "identityCredential.idCommitment", []) - ) - ), - IDSecretHash: Keystore.fromArraylikeToBytes( - _.get(obj, "identityCredential.idSecretHash", []) - ) + IDCommitment: idCommitmentBE, + IDTrapdoor: idTrapdoorBE, + IDNullifier: idNullifierBE, + IDSecretHash: idSecretHashBE, + IDCommitmentBigInt: idCommitmentBigInt }, membership: { treeIndex: _.get(obj, "treeIndex"), @@ -320,15 +329,35 @@ export class Keystore { // follows nwaku implementation // https://github.com/waku-org/nwaku/blob/f05528d4be3d3c876a8b07f9bb7dfaae8aa8ec6e/waku/waku_keystore/protocol_types.nim#L98 + // IdentityCredential is stored in Big Endian format => switch to Little Endian private static fromIdentityToBytes(options: KeystoreEntity): Uint8Array { + const { IDCommitment, IDNullifier, IDSecretHash, IDTrapdoor } = + options.identity; + const idCommitmentLE = BytesUtils.switchEndianness(IDCommitment); + const idNullifierLE = BytesUtils.switchEndianness(IDNullifier); + const idSecretHashLE = BytesUtils.switchEndianness(IDSecretHash); + const idTrapdoorLE = BytesUtils.switchEndianness(IDTrapdoor); + + // eslint-disable-next-line no-console + console.log({ + idCommitmentBE: IDCommitment, + idCommitmentLE, + idNullifierBE: IDNullifier, + idNullifierLE, + idSecretHashBE: IDSecretHash, + idSecretHashLE, + idTrapdoorBE: IDTrapdoor, + idTrapdoorLE + }); + return utf8ToBytes( JSON.stringify({ treeIndex: options.membership.treeIndex, identityCredential: { - idCommitment: Array.from(options.identity.IDCommitment), - idNullifier: Array.from(options.identity.IDNullifier), - idSecretHash: Array.from(options.identity.IDSecretHash), - idTrapdoor: Array.from(options.identity.IDTrapdoor) + idCommitment: Array.from(idCommitmentLE), + idNullifier: Array.from(idNullifierLE), + idSecretHash: Array.from(idSecretHashLE), + idTrapdoor: Array.from(idTrapdoorLE) }, membershipContract: { chainId: options.membership.chainId, diff --git a/packages/rln/src/proof.ts b/packages/rln/src/proof.ts index 23a75acc37..ce3ad6f26d 100644 --- a/packages/rln/src/proof.ts +++ b/packages/rln/src/proof.ts @@ -1,6 +1,6 @@ import type { IRateLimitProof } from "@waku/interfaces"; -import { concatenate, poseidonHash } from "./utils/index.js"; +import { BytesUtils, poseidonHash } from "./utils/index.js"; const proofOffset = 128; const rootOffset = proofOffset + 32; @@ -57,7 +57,7 @@ export class Proof implements IRateLimitProof { } export function proofToBytes(p: IRateLimitProof): Uint8Array { - return concatenate( + return BytesUtils.concatenate( p.proof, p.merkleRoot, p.epoch, diff --git a/packages/rln/src/utils/bytes.ts b/packages/rln/src/utils/bytes.ts index 279a2b3822..d871e8f564 100644 --- a/packages/rln/src/utils/bytes.ts +++ b/packages/rln/src/utils/bytes.ts @@ -1,84 +1,130 @@ -/** - * Concatenate Uint8Arrays - * @param input - * @returns concatenation of all Uint8Array received as input - */ -export function concatenate(...input: Uint8Array[]): Uint8Array { - let totalLength = 0; - for (const arr of input) { - totalLength += arr.length; - } - const result = new Uint8Array(totalLength); - let offset = 0; - for (const arr of input) { - result.set(arr, offset); - offset += arr.length; - } - return result; -} - -// Adapted from https://github.com/feross/buffer -function checkInt( - buf: Uint8Array, - value: number, - offset: number, - ext: number, - max: number, - min: number -): void { - if (value > max || value < min) - throw new RangeError('"value" argument is out of bounds'); - if (offset + ext > buf.length) throw new RangeError("Index out of range"); -} - -export function writeUIntLE( - buf: Uint8Array, - value: number, - offset: number, - byteLength: number, - noAssert?: boolean -): Uint8Array { - value = +value; - offset = offset >>> 0; - byteLength = byteLength >>> 0; - if (!noAssert) { - const maxBytes = Math.pow(2, 8 * byteLength) - 1; - checkInt(buf, value, offset, byteLength, maxBytes, 0); +export class BytesUtils { + /** + * Switches endianness of a byte array + */ + public static switchEndianness(bytes: Uint8Array): Uint8Array { + return new Uint8Array([...bytes].reverse()); } - let mul = 1; - let i = 0; - buf[offset] = value & 0xff; - while (++i < byteLength && (mul *= 0x100)) { - buf[offset + i] = (value / mul) & 0xff; + /** + * Builds a BigInt from a big-endian Uint8Array + * @param bytes The big-endian bytes to convert + * @returns The resulting BigInt in big-endian format + */ + public static buildBigIntFromUint8ArrayBE(bytes: Uint8Array): bigint { + let result = 0n; + for (let i = 0; i < bytes.length; i++) { + result = (result << 8n) + BigInt(bytes[i]); + } + return result; } - return buf; -} + /** + * Switches endianness of a bigint value + * @param value The bigint value to switch endianness for + * @returns The bigint value with reversed endianness + */ + public static switchEndiannessBigInt(value: bigint): bigint { + // Convert bigint to byte array + const bytes = []; + let tempValue = value; + while (tempValue > 0n) { + bytes.push(Number(tempValue & 0xffn)); + tempValue >>= 8n; + } -/** - * Transforms Uint8Array into BigInt - * @param array: Uint8Array - * @returns BigInt - */ -export function buildBigIntFromUint8Array( - array: Uint8Array, - byteOffset: number = 0 -): bigint { - const dataView = new DataView(array.buffer); - return dataView.getBigUint64(byteOffset, true); -} - -/** - * Fills with zeros to set length - * @param array little endian Uint8Array - * @param length amount to pad - * @returns little endian Uint8Array padded with zeros to set length - */ -export function zeroPadLE(array: Uint8Array, length: number): Uint8Array { - const result = new Uint8Array(length); - for (let i = 0; i < length; i++) { - result[i] = array[i] || 0; + // Reverse bytes and convert back to bigint + return bytes + .reverse() + .reduce((acc, byte) => (acc << 8n) + BigInt(byte), 0n); + } + + /** + * Converts a big-endian bigint to a 32-byte big-endian Uint8Array + * @param value The big-endian bigint to convert + * @returns A 32-byte big-endian Uint8Array + */ + public static bigIntToUint8Array32BE(value: bigint): Uint8Array { + const bytes = new Uint8Array(32); + for (let i = 31; i >= 0; i--) { + bytes[i] = Number(value & 0xffn); + value >>= 8n; + } + return bytes; + } + + /** + * Writes an unsigned integer to a buffer in little-endian format + */ + public static writeUIntLE( + buf: Uint8Array, + value: number, + offset: number, + byteLength: number, + noAssert?: boolean + ): Uint8Array { + value = +value; + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) { + const maxBytes = Math.pow(2, 8 * byteLength) - 1; + BytesUtils.checkInt(buf, value, offset, byteLength, maxBytes, 0); + } + + let mul = 1; + let i = 0; + buf[offset] = value & 0xff; + while (++i < byteLength && (mul *= 0x100)) { + buf[offset + i] = (value / mul) & 0xff; + } + + return buf; + } + + /** + * Fills with zeros to set length + * @param array little endian Uint8Array + * @param length amount to pad + * @returns little endian Uint8Array padded with zeros to set length + */ + public static zeroPadLE(array: Uint8Array, length: number): Uint8Array { + const result = new Uint8Array(length); + for (let i = 0; i < length; i++) { + result[i] = array[i] || 0; + } + return result; + } + + // Adapted from https://github.com/feross/buffer + public static checkInt( + buf: Uint8Array, + value: number, + offset: number, + ext: number, + max: number, + min: number + ): void { + if (value > max || value < min) + throw new RangeError('"value" argument is out of bounds'); + if (offset + ext > buf.length) throw new RangeError("Index out of range"); + } + + /** + * Concatenate Uint8Arrays + * @param input + * @returns concatenation of all Uint8Array received as input + */ + public static concatenate(...input: Uint8Array[]): Uint8Array { + let totalLength = 0; + for (const arr of input) { + totalLength += arr.length; + } + const result = new Uint8Array(totalLength); + let offset = 0; + for (const arr of input) { + result.set(arr, offset); + offset += arr.length; + } + return result; } - return result; } diff --git a/packages/rln/src/utils/hash.ts b/packages/rln/src/utils/hash.ts index 78422e21e0..6aa8e29277 100644 --- a/packages/rln/src/utils/hash.ts +++ b/packages/rln/src/utils/hash.ts @@ -1,15 +1,25 @@ import * as zerokitRLN from "@waku/zerokit-rln-wasm"; -import { concatenate, writeUIntLE } from "./bytes.js"; +import { BytesUtils } from "./bytes.js"; export function poseidonHash(...input: Array): Uint8Array { - const inputLen = writeUIntLE(new Uint8Array(8), input.length, 0, 8); - const lenPrefixedData = concatenate(inputLen, ...input); + const inputLen = BytesUtils.writeUIntLE( + new Uint8Array(8), + input.length, + 0, + 8 + ); + const lenPrefixedData = BytesUtils.concatenate(inputLen, ...input); return zerokitRLN.poseidonHash(lenPrefixedData); } export function sha256(input: Uint8Array): Uint8Array { - const inputLen = writeUIntLE(new Uint8Array(8), input.length, 0, 8); - const lenPrefixedData = concatenate(inputLen, input); + const inputLen = BytesUtils.writeUIntLE( + new Uint8Array(8), + input.length, + 0, + 8 + ); + const lenPrefixedData = BytesUtils.concatenate(inputLen, input); return zerokitRLN.hash(lenPrefixedData); } diff --git a/packages/rln/src/utils/index.ts b/packages/rln/src/utils/index.ts index 74b5bb6016..dddc0b8285 100644 --- a/packages/rln/src/utils/index.ts +++ b/packages/rln/src/utils/index.ts @@ -1,9 +1,4 @@ export { extractMetaMaskSigner } from "./metamask.js"; -export { - concatenate, - writeUIntLE, - buildBigIntFromUint8Array, - zeroPadLE -} from "./bytes.js"; +export { BytesUtils } from "./bytes.js"; export { sha256, poseidonHash } from "./hash.js"; export { dateToEpoch, epochIntToBytes, epochBytesToInt } from "./epoch.js"; diff --git a/packages/rln/src/zerokit.ts b/packages/rln/src/zerokit.ts index 6fd9bd45f1..a7e7e628f4 100644 --- a/packages/rln/src/zerokit.ts +++ b/packages/rln/src/zerokit.ts @@ -5,12 +5,7 @@ import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./contract/constants.js"; import { IdentityCredential } from "./identity.js"; import { Proof, proofToBytes } from "./proof.js"; import { WitnessCalculator } from "./resources/witness_calculator"; -import { - concatenate, - dateToEpoch, - epochIntToBytes, - writeUIntLE -} from "./utils/index.js"; +import { BytesUtils, dateToEpoch, epochIntToBytes } from "./utils/index.js"; export class Zerokit { public constructor( @@ -57,13 +52,16 @@ export class Zerokit { ): void { // serializes a seq of IDCommitments to a byte seq // the order of serialization is |id_commitment_len<8>|id_commitment| - const idCommitmentLen = writeUIntLE( + const idCommitmentLen = BytesUtils.writeUIntLE( new Uint8Array(8), idCommitments.length, 0, 8 ); - const idCommitmentBytes = concatenate(idCommitmentLen, ...idCommitments); + const idCommitmentBytes = BytesUtils.concatenate( + idCommitmentLen, + ...idCommitments + ); zerokitRLN.setLeavesFrom(this.zkRLN, index, idCommitmentBytes); } @@ -83,9 +81,19 @@ export class Zerokit { rateLimit?: number ): Uint8Array { // calculate message length - const msgLen = writeUIntLE(new Uint8Array(8), uint8Msg.length, 0, 8); - const memIndexBytes = writeUIntLE(new Uint8Array(8), memIndex, 0, 8); - const rateLimitBytes = writeUIntLE( + const msgLen = BytesUtils.writeUIntLE( + new Uint8Array(8), + uint8Msg.length, + 0, + 8 + ); + const memIndexBytes = BytesUtils.writeUIntLE( + new Uint8Array(8), + memIndex, + 0, + 8 + ); + const rateLimitBytes = BytesUtils.writeUIntLE( new Uint8Array(8), rateLimit ?? this.rateLimit, 0, @@ -93,7 +101,7 @@ export class Zerokit { ); // [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal | rate_limit<8> ] - return concatenate( + return BytesUtils.concatenate( idKey, memIndexBytes, epoch, @@ -169,8 +177,8 @@ export class Zerokit { } // calculate message length - const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8); - const rateLimitBytes = writeUIntLE( + const msgLen = BytesUtils.writeUIntLE(new Uint8Array(8), msg.length, 0, 8); + const rateLimitBytes = BytesUtils.writeUIntLE( new Uint8Array(8), rateLimit ?? this.rateLimit, 0, @@ -179,7 +187,7 @@ export class Zerokit { return zerokitRLN.verifyRLNProof( this.zkRLN, - concatenate(pBytes, msgLen, msg, rateLimitBytes) + BytesUtils.concatenate(pBytes, msgLen, msg, rateLimitBytes) ); } @@ -196,19 +204,19 @@ export class Zerokit { pBytes = proofToBytes(proof); } // calculate message length - const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8); - const rateLimitBytes = writeUIntLE( + const msgLen = BytesUtils.writeUIntLE(new Uint8Array(8), msg.length, 0, 8); + const rateLimitBytes = BytesUtils.writeUIntLE( new Uint8Array(8), rateLimit ?? this.rateLimit, 0, 8 ); - const rootsBytes = concatenate(...roots); + const rootsBytes = BytesUtils.concatenate(...roots); return zerokitRLN.verifyWithRoots( this.zkRLN, - concatenate(pBytes, msgLen, msg, rateLimitBytes), + BytesUtils.concatenate(pBytes, msgLen, msg, rateLimitBytes), rootsBytes ); } @@ -226,8 +234,8 @@ export class Zerokit { } // calculate message length - const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8); - const rateLimitBytes = writeUIntLE( + const msgLen = BytesUtils.writeUIntLE(new Uint8Array(8), msg.length, 0, 8); + const rateLimitBytes = BytesUtils.writeUIntLE( new Uint8Array(8), rateLimit ?? this.rateLimit, 0, @@ -236,7 +244,7 @@ export class Zerokit { return zerokitRLN.verifyWithRoots( this.zkRLN, - concatenate(pBytes, msgLen, msg, rateLimitBytes), + BytesUtils.concatenate(pBytes, msgLen, msg, rateLimitBytes), new Uint8Array() ); }