mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-10 09:43:10 +00:00
fix: idCommitmentBigInt must always be less than the contract Q (#2394)
* chore: idCommitmentBigInt validates against contract Q * chore: fix linting * chore: add log * chore: rename Q and make sync * fix: test * chore: remove stubbed contract test * chore: hardcode default constant for Q * use non deprecated sha256 * chore: use full 32 bytes for bigint * chore: all storage in LE, but smart contract interactions in BE * chore: remove references to idCOmmitmentBigInt in Identity * chore: don't fetch Q from contract * chore: ByteUtils as a class * chore: store Identity in BE, convert during Keystore * chore: add IDCommitmentBigInt part of Identity * chore: minor improvements * chore: switch idTrapdoor to LE * chore: add logs * chore: rename `DEFAULT_Q` to `RLN_Q` * chore: rm spec test * chore: improve modulo logging * fix(tests): add IDCommitmentBigInt
This commit is contained in:
parent
5d8cfff7eb
commit
9b0c5e8311
@ -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;
|
||||
|
||||
@ -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<DecryptedCredentials | undefined> {
|
||||
@ -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))
|
||||
);
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -83,4 +83,4 @@ export const TEST_DATA = {
|
||||
),
|
||||
mockSignature:
|
||||
"0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c"
|
||||
} as const;
|
||||
};
|
||||
@ -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<IdentityCredential> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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>): 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);
|
||||
}
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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<var>|
|
||||
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<var> | 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()
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user