mirror of
https://github.com/logos-messaging/js-rln.git
synced 2026-08-01 20:23:10 +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-functional": "^4.0.2",
|
||||||
"eslint-plugin-import": "^2.25.3",
|
"eslint-plugin-import": "^2.25.3",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"fast-check": "^2.14.0",
|
"fast-check": "^2.25.0",
|
||||||
"gh-pages": "^3.2.3",
|
"gh-pages": "^3.2.3",
|
||||||
"husky": "^7.0.4",
|
"husky": "^7.0.4",
|
||||||
"ignore-loader": "^0.1.2",
|
"ignore-loader": "^0.1.2",
|
||||||
@ -5052,8 +5052,9 @@
|
|||||||
},
|
},
|
||||||
"node_modules/fast-check": {
|
"node_modules/fast-check": {
|
||||||
"version": "2.25.0",
|
"version": "2.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-2.25.0.tgz",
|
||||||
|
"integrity": "sha512-wRUT2KD2lAmT75WNIJIHECawoUUMHM0I5jrlLXGtGeqmPL8jl/EldUDjY1VCp6fDY8yflyfUeIOsOBrIbIiArg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pure-rand": "^5.0.1"
|
"pure-rand": "^5.0.1"
|
||||||
},
|
},
|
||||||
@ -14684,6 +14685,8 @@
|
|||||||
},
|
},
|
||||||
"fast-check": {
|
"fast-check": {
|
||||||
"version": "2.25.0",
|
"version": "2.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-2.25.0.tgz",
|
||||||
|
"integrity": "sha512-wRUT2KD2lAmT75WNIJIHECawoUUMHM0I5jrlLXGtGeqmPL8jl/EldUDjY1VCp6fDY8yflyfUeIOsOBrIbIiArg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"pure-rand": "^5.0.1"
|
"pure-rand": "^5.0.1"
|
||||||
|
|||||||
@ -76,7 +76,7 @@
|
|||||||
"eslint-plugin-functional": "^4.0.2",
|
"eslint-plugin-functional": "^4.0.2",
|
||||||
"eslint-plugin-import": "^2.25.3",
|
"eslint-plugin-import": "^2.25.3",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"fast-check": "^2.14.0",
|
"fast-check": "^2.25.0",
|
||||||
"gh-pages": "^3.2.3",
|
"gh-pages": "^3.2.3",
|
||||||
"husky": "^7.0.4",
|
"husky": "^7.0.4",
|
||||||
"ignore-loader": "^0.1.2",
|
"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 { expect } from "chai";
|
||||||
import {
|
import { DecoderV0, EncoderV0 } from "js-waku/lib/waku_message/version_0";
|
||||||
DecoderV0,
|
|
||||||
EncoderV0,
|
|
||||||
MessageV0,
|
|
||||||
} 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";
|
import * as rln from "./index.js";
|
||||||
|
|
||||||
@ -26,7 +22,10 @@ describe("js-rln: encoder", () => {
|
|||||||
index,
|
index,
|
||||||
memKeys
|
memKeys
|
||||||
);
|
);
|
||||||
const rlnDecoder = new RLNDecoder(new DecoderV0(TestContentTopic));
|
const rlnDecoder = new RLNDecoder(
|
||||||
|
rlnInstance,
|
||||||
|
new DecoderV0(TestContentTopic)
|
||||||
|
);
|
||||||
|
|
||||||
const bytes = await rlnEncoder.encode({ payload });
|
const bytes = await rlnEncoder.encode({ payload });
|
||||||
const protoResult = await rlnDecoder.decodeProto(bytes!);
|
const protoResult = await rlnDecoder.decodeProto(bytes!);
|
||||||
@ -34,13 +33,11 @@ describe("js-rln: encoder", () => {
|
|||||||
const msg = (await rlnDecoder.decode(protoResult!))!;
|
const msg = (await rlnDecoder.decode(protoResult!))!;
|
||||||
|
|
||||||
// Validate proof
|
// Validate proof
|
||||||
const verifResult = rlnInstance.verifyProof(msg.rateLimitProof!);
|
expect(msg.verify()).to.be.true;
|
||||||
expect(verifResult).to.be.true;
|
|
||||||
|
|
||||||
const msgV0 = msg as MessageV0;
|
expect(msg.contentTopic).to.eq(TestContentTopic);
|
||||||
expect(msgV0.contentTopic).to.eq(TestContentTopic);
|
expect(msg.msg.version).to.eq(0);
|
||||||
expect(msgV0.version).to.eq(0);
|
expect(msg.payload).to.deep.eq(payload);
|
||||||
expect(msgV0.payload).to.deep.eq(payload);
|
expect(msg.timestamp).to.not.be.undefined;
|
||||||
expect(msgV0.timestamp).to.not.be.undefined;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -7,6 +7,7 @@ import {
|
|||||||
ProtoMessage,
|
ProtoMessage,
|
||||||
} from "js-waku/lib/interfaces";
|
} from "js-waku/lib/interfaces";
|
||||||
|
|
||||||
|
import { RlnMessage } from "./message.js";
|
||||||
import { MembershipKey, RLNInstance } from "./rln.js";
|
import { MembershipKey, RLNInstance } from "./rln.js";
|
||||||
|
|
||||||
const log = debug("waku:message:rln-encoder");
|
const log = debug("waku:message:rln-encoder");
|
||||||
@ -53,11 +54,11 @@ export class RLNEncoder implements Encoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RLNDecoder implements Decoder<Message> {
|
export class RLNDecoder<T extends Message> implements Decoder<RlnMessage<T>> {
|
||||||
public contentTopic: string;
|
constructor(private rlnInstance: RLNInstance, private decoder: Decoder<T>) {}
|
||||||
|
|
||||||
constructor(private decoder: Decoder<Message>) {
|
get contentTopic(): string {
|
||||||
this.contentTopic = decoder.contentTopic;
|
return this.decoder.contentTopic;
|
||||||
}
|
}
|
||||||
|
|
||||||
decodeProto(bytes: Uint8Array): Promise<ProtoMessage | undefined> {
|
decodeProto(bytes: Uint8Array): Promise<ProtoMessage | undefined> {
|
||||||
@ -66,12 +67,10 @@ export class RLNDecoder implements Decoder<Message> {
|
|||||||
return Promise.resolve(protoMessage);
|
return Promise.resolve(protoMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
async decode(proto: ProtoMessage): Promise<Message | undefined> {
|
async decode(proto: ProtoMessage): Promise<RlnMessage<T> | undefined> {
|
||||||
const msg = await this.decoder.decode(proto);
|
const msg: T | undefined = await this.decoder.decode(proto);
|
||||||
if (msg) {
|
if (!msg) return;
|
||||||
msg.rateLimitProof = proto.rateLimitProof;
|
return new RlnMessage(this.rlnInstance, msg, proto.rateLimitProof);
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 type { Proof, RLNInstance } from "./rln.js";
|
||||||
import { MembershipKey } 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 init, * as zerokitRLN from "@waku/zerokit-rln-wasm";
|
||||||
import { RateLimitProof } from "js-waku/lib/interfaces";
|
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 verificationKey from "./resources/verification_key.js";
|
||||||
import * as wc from "./witness_calculator.js";
|
import * as wc from "./witness_calculator.js";
|
||||||
import { WitnessCalculator } 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 proofOffset = 128;
|
||||||
const rootOffset = proofOffset + 32;
|
const rootOffset = proofOffset + 32;
|
||||||
const epochOffset = rootOffset + 32;
|
const epochOffset = rootOffset + 32;
|
||||||
@ -200,9 +152,9 @@ export class RLNInstance {
|
|||||||
idKey: Uint8Array
|
idKey: Uint8Array
|
||||||
): Promise<RateLimitProof> {
|
): Promise<RateLimitProof> {
|
||||||
if (epoch == undefined) {
|
if (epoch == undefined) {
|
||||||
epoch = toEpoch(new Date());
|
epoch = epochIntToBytes(dateToEpoch(new Date()));
|
||||||
} else if (epoch instanceof Date) {
|
} else if (epoch instanceof Date) {
|
||||||
epoch = toEpoch(epoch);
|
epoch = epochIntToBytes(dateToEpoch(epoch));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (epoch.length != 32) throw "invalid epoch";
|
if (epoch.length != 32) throw "invalid epoch";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user