mirror of
https://github.com/logos-messaging/js-rln.git
synced 2026-01-02 13:43:06 +00:00
Merge pull request #24 from waku-org/message-helpers
This commit is contained in:
commit
b5f0cbb3dd
7
package-lock.json
generated
7
package-lock.json
generated
@ -36,7 +36,7 @@
|
||||
"eslint-plugin-functional": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"fast-check": "^2.14.0",
|
||||
"fast-check": "^2.25.0",
|
||||
"gh-pages": "^3.2.3",
|
||||
"husky": "^7.0.4",
|
||||
"ignore-loader": "^0.1.2",
|
||||
@ -5052,8 +5052,9 @@
|
||||
},
|
||||
"node_modules/fast-check": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-2.25.0.tgz",
|
||||
"integrity": "sha512-wRUT2KD2lAmT75WNIJIHECawoUUMHM0I5jrlLXGtGeqmPL8jl/EldUDjY1VCp6fDY8yflyfUeIOsOBrIbIiArg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pure-rand": "^5.0.1"
|
||||
},
|
||||
@ -14684,6 +14685,8 @@
|
||||
},
|
||||
"fast-check": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-2.25.0.tgz",
|
||||
"integrity": "sha512-wRUT2KD2lAmT75WNIJIHECawoUUMHM0I5jrlLXGtGeqmPL8jl/EldUDjY1VCp6fDY8yflyfUeIOsOBrIbIiArg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pure-rand": "^5.0.1"
|
||||
|
||||
@ -76,7 +76,7 @@
|
||||
"eslint-plugin-functional": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"fast-check": "^2.14.0",
|
||||
"fast-check": "^2.25.0",
|
||||
"gh-pages": "^3.2.3",
|
||||
"husky": "^7.0.4",
|
||||
"ignore-loader": "^0.1.2",
|
||||
|
||||
39
src/byte_utils.ts
Normal file
39
src/byte_utils.ts
Normal file
@ -0,0 +1,39 @@
|
||||
// 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);
|
||||
}
|
||||
|
||||
let mul = 1;
|
||||
let i = 0;
|
||||
buf[offset] = value & 0xff;
|
||||
while (++i < byteLength && (mul *= 0x100)) {
|
||||
buf[offset + i] = (value / mul) & 0xff;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
@ -1,11 +1,7 @@
|
||||
import { expect } from "chai";
|
||||
import {
|
||||
DecoderV0,
|
||||
EncoderV0,
|
||||
MessageV0,
|
||||
} from "js-waku/lib/waku_message/version_0";
|
||||
import { DecoderV0, EncoderV0 } from "js-waku/lib/waku_message/version_0";
|
||||
|
||||
import { RLNDecoder, RLNEncoder } from "./encoder.js";
|
||||
import { RLNDecoder, RLNEncoder } from "./codec.js";
|
||||
|
||||
import * as rln from "./index.js";
|
||||
|
||||
@ -26,7 +22,10 @@ describe("js-rln: encoder", () => {
|
||||
index,
|
||||
memKeys
|
||||
);
|
||||
const rlnDecoder = new RLNDecoder(new DecoderV0(TestContentTopic));
|
||||
const rlnDecoder = new RLNDecoder(
|
||||
rlnInstance,
|
||||
new DecoderV0(TestContentTopic)
|
||||
);
|
||||
|
||||
const bytes = await rlnEncoder.encode({ payload });
|
||||
const protoResult = await rlnDecoder.decodeProto(bytes!);
|
||||
@ -34,13 +33,11 @@ describe("js-rln: encoder", () => {
|
||||
const msg = (await rlnDecoder.decode(protoResult!))!;
|
||||
|
||||
// Validate proof
|
||||
const verifResult = rlnInstance.verifyProof(msg.rateLimitProof!);
|
||||
expect(verifResult).to.be.true;
|
||||
expect(msg.verify()).to.be.true;
|
||||
|
||||
const msgV0 = msg as MessageV0;
|
||||
expect(msgV0.contentTopic).to.eq(TestContentTopic);
|
||||
expect(msgV0.version).to.eq(0);
|
||||
expect(msgV0.payload).to.deep.eq(payload);
|
||||
expect(msgV0.timestamp).to.not.be.undefined;
|
||||
expect(msg.contentTopic).to.eq(TestContentTopic);
|
||||
expect(msg.msg.version).to.eq(0);
|
||||
expect(msg.payload).to.deep.eq(payload);
|
||||
expect(msg.timestamp).to.not.be.undefined;
|
||||
});
|
||||
});
|
||||
@ -7,6 +7,7 @@ import {
|
||||
ProtoMessage,
|
||||
} from "js-waku/lib/interfaces";
|
||||
|
||||
import { RlnMessage } from "./message.js";
|
||||
import { MembershipKey, RLNInstance } from "./rln.js";
|
||||
|
||||
const log = debug("waku:message:rln-encoder");
|
||||
@ -53,11 +54,11 @@ export class RLNEncoder implements Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
export class RLNDecoder implements Decoder<Message> {
|
||||
public contentTopic: string;
|
||||
export class RLNDecoder<T extends Message> implements Decoder<RlnMessage<T>> {
|
||||
constructor(private rlnInstance: RLNInstance, private decoder: Decoder<T>) {}
|
||||
|
||||
constructor(private decoder: Decoder<Message>) {
|
||||
this.contentTopic = decoder.contentTopic;
|
||||
get contentTopic(): string {
|
||||
return this.decoder.contentTopic;
|
||||
}
|
||||
|
||||
decodeProto(bytes: Uint8Array): Promise<ProtoMessage | undefined> {
|
||||
@ -66,12 +67,10 @@ export class RLNDecoder implements Decoder<Message> {
|
||||
return Promise.resolve(protoMessage);
|
||||
}
|
||||
|
||||
async decode(proto: ProtoMessage): Promise<Message | undefined> {
|
||||
const msg = await this.decoder.decode(proto);
|
||||
if (msg) {
|
||||
msg.rateLimitProof = proto.rateLimitProof;
|
||||
}
|
||||
return msg;
|
||||
async decode(proto: ProtoMessage): Promise<RlnMessage<T> | undefined> {
|
||||
const msg: T | undefined = await this.decoder.decode(proto);
|
||||
if (!msg) return;
|
||||
return new RlnMessage(this.rlnInstance, msg, proto.rateLimitProof);
|
||||
}
|
||||
}
|
||||
|
||||
17
src/epoch.spec.ts
Normal file
17
src/epoch.spec.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { expect } from "chai";
|
||||
import fc from "fast-check";
|
||||
|
||||
import { epochBytesToInt, epochIntToBytes } from "./epoch.js";
|
||||
|
||||
describe("epoch serialization", () => {
|
||||
it("Round trip", async function () {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(fc.integer({ min: 0 }), async (date) => {
|
||||
const bytes = epochIntToBytes(date);
|
||||
const _date = epochBytesToInt(bytes);
|
||||
|
||||
expect(_date.valueOf()).to.eq(date.valueOf());
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
21
src/epoch.ts
Normal file
21
src/epoch.ts
Normal file
@ -0,0 +1,21 @@
|
||||
const DefaultEpochUnitSeconds = 10; // the rln-relay epoch length in seconds
|
||||
|
||||
export function dateToEpoch(
|
||||
timestamp: Date,
|
||||
epochUnitSeconds: number = DefaultEpochUnitSeconds
|
||||
): number {
|
||||
const time = timestamp.getTime();
|
||||
return Math.floor(time / 1000 / epochUnitSeconds);
|
||||
}
|
||||
|
||||
export function epochIntToBytes(epoch: number): Uint8Array {
|
||||
const bytes = new Uint8Array(32);
|
||||
const db = new DataView(bytes.buffer);
|
||||
db.setUint32(0, epoch, true);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
export function epochBytesToInt(bytes: Uint8Array): number {
|
||||
const dv = new DataView(bytes.buffer);
|
||||
return dv.getUint32(0, true);
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { RLNDecoder, RLNEncoder } from "./encoder.js";
|
||||
import { RLNDecoder, RLNEncoder } from "./codec.js";
|
||||
import type { Proof, RLNInstance } from "./rln.js";
|
||||
import { MembershipKey } from "./rln.js";
|
||||
|
||||
|
||||
37
src/message.ts
Normal file
37
src/message.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { Message, RateLimitProof } from "js-waku/lib/interfaces";
|
||||
|
||||
import { epochBytesToInt } from "./epoch.js";
|
||||
import { RLNInstance } from "./rln.js";
|
||||
|
||||
export class RlnMessage<T extends Message> implements Message {
|
||||
constructor(
|
||||
public rlnInstance: RLNInstance,
|
||||
public msg: T,
|
||||
public rateLimitProof?: RateLimitProof
|
||||
) {}
|
||||
|
||||
public verify(): boolean | undefined {
|
||||
return this.rateLimitProof
|
||||
? this.rlnInstance.verifyProof(this.rateLimitProof)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
get payload(): Uint8Array | undefined {
|
||||
return this.msg.payload;
|
||||
}
|
||||
|
||||
get contentTopic(): string | undefined {
|
||||
return this.msg.contentTopic;
|
||||
}
|
||||
|
||||
get timestamp(): Date | undefined {
|
||||
return this.msg.timestamp;
|
||||
}
|
||||
|
||||
get epoch(): number | undefined {
|
||||
const bytes = this.msg.rateLimitProof?.epoch;
|
||||
if (!bytes) return;
|
||||
|
||||
return epochBytesToInt(bytes);
|
||||
}
|
||||
}
|
||||
56
src/rln.ts
56
src/rln.ts
@ -1,6 +1,8 @@
|
||||
import init, * as zerokitRLN from "@waku/zerokit-rln-wasm";
|
||||
import { RateLimitProof } from "js-waku/lib/interfaces";
|
||||
|
||||
import { writeUIntLE } from "./byte_utils.js";
|
||||
import { dateToEpoch, epochIntToBytes } from "./epoch.js";
|
||||
import verificationKey from "./resources/verification_key.js";
|
||||
import * as wc from "./witness_calculator.js";
|
||||
import { WitnessCalculator } from "./witness_calculator.js";
|
||||
@ -67,56 +69,6 @@ export class MembershipKey {
|
||||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
const writeUIntLE = 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);
|
||||
}
|
||||
|
||||
let mul = 1;
|
||||
let i = 0;
|
||||
buf[offset] = value & 0xff;
|
||||
while (++i < byteLength && (mul *= 0x100)) {
|
||||
buf[offset + i] = (value / mul) & 0xff;
|
||||
}
|
||||
|
||||
return buf;
|
||||
};
|
||||
|
||||
const DefaultEpochUnitSeconds = 10; // the rln-relay epoch length in seconds
|
||||
|
||||
export function toEpoch(
|
||||
timestamp: Date,
|
||||
epochUnitSeconds: number = DefaultEpochUnitSeconds
|
||||
): Uint8Array {
|
||||
const unix = Math.floor(timestamp.getTime() / 1000 / epochUnitSeconds);
|
||||
return writeUIntLE(new Uint8Array(32), unix, 0, 8);
|
||||
}
|
||||
|
||||
const proofOffset = 128;
|
||||
const rootOffset = proofOffset + 32;
|
||||
const epochOffset = rootOffset + 32;
|
||||
@ -200,9 +152,9 @@ export class RLNInstance {
|
||||
idKey: Uint8Array
|
||||
): Promise<RateLimitProof> {
|
||||
if (epoch == undefined) {
|
||||
epoch = toEpoch(new Date());
|
||||
epoch = epochIntToBytes(dateToEpoch(new Date()));
|
||||
} else if (epoch instanceof Date) {
|
||||
epoch = toEpoch(epoch);
|
||||
epoch = epochIntToBytes(dateToEpoch(epoch));
|
||||
}
|
||||
|
||||
if (epoch.length != 32) throw "invalid epoch";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user