Merge pull request #16 from waku-org/feat/encoder

This commit is contained in:
fryorcraken.eth 2022-09-27 13:45:45 +10:00 committed by GitHub
commit 5e8e997f21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 5172 additions and 22 deletions

5016
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -81,6 +81,7 @@
"husky": "^7.0.4",
"ignore-loader": "^0.1.2",
"isomorphic-fetch": "^3.0.0",
"js-waku": "^0.29.0-7714812",
"jsdom": "^19.0.0",
"jsdom-global": "^3.0.2",
"karma": "^6.3.12",

46
src/encoder.spec.ts Normal file
View File

@ -0,0 +1,46 @@
import { expect } from "chai";
import {
DecoderV0,
EncoderV0,
MessageV0,
} from "js-waku/lib/waku_message/version_0";
import { RLNDecoder, RLNEncoder } from "./encoder.js";
import * as rln from "./index.js";
const TestContentTopic = "/test/1/waku-message/utf8";
describe("js-rln: encoder", () => {
it("should attach a proof to a waku message", async function () {
const rlnInstance = await rln.create();
const memKeys = rlnInstance.generateMembershipKey();
const index = 0;
const payload = new Uint8Array([1, 2, 3, 4, 5]);
rlnInstance.insertMember(memKeys.IDCommitment);
const rlnEncoder = new RLNEncoder(
new EncoderV0(TestContentTopic),
rlnInstance,
index,
memKeys
);
const rlnDecoder = new RLNDecoder(new DecoderV0(TestContentTopic));
const bytes = await rlnEncoder.encode({ payload });
const protoResult = await rlnDecoder.decodeProto(bytes!);
const msg = (await rlnDecoder.decode(protoResult!))!;
// Validate proof
const verifResult = rlnInstance.verifyProof(msg.rateLimitProof!);
expect(verifResult).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;
});
});

81
src/encoder.ts Normal file
View File

@ -0,0 +1,81 @@
import debug from "debug";
import {proto_message, utils} from "js-waku";
import {
Decoder,
Encoder,
Message,
ProtoMessage,
} from "js-waku/lib/interfaces";
import { MembershipKey, RLNInstance } from "./rln.js";
const log = debug("waku:message:rln-encoder");
export class RLNEncoder implements Encoder {
public contentTopic: string;
private readonly idKey: Uint8Array;
constructor(
private encoder: Encoder,
private rlnInstance: RLNInstance,
private index: number,
membershipKey: MembershipKey
) {
if (index < 0) throw "invalid membership index";
this.idKey = membershipKey.IDKey;
this.contentTopic = encoder.contentTopic;
}
async encode(message: Message): Promise<Uint8Array | undefined> {
const protoMessage = await this.encodeProto(message);
if (!protoMessage) return;
return proto_message.WakuMessage.encode(protoMessage);
}
async encodeProto(message: Message): Promise<ProtoMessage | undefined> {
const protoMessage = await this.encoder.encodeProto(message);
if (!protoMessage) return;
const signal = toRLNSignal(message);
console.time("proof_gen_timer");
const proof = await this.rlnInstance.generateProof(
signal,
this.index,
message.timestamp,
this.idKey
);
console.timeEnd("proof_gen_timer");
protoMessage.rateLimitProof = proof;
return protoMessage;
}
}
export class RLNDecoder implements Decoder<Message> {
public contentTopic: string;
constructor(private decoder: Decoder<Message>) {
this.contentTopic = decoder.contentTopic;
}
decodeProto(bytes: Uint8Array): Promise<ProtoMessage | undefined> {
const protoMessage = proto_message.WakuMessage.decode(bytes);
log("Message decoded", protoMessage);
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;
}
}
function toRLNSignal(msg: Message): Uint8Array {
const contentTopicBytes = utils.utf8ToBytes(msg.contentTopic ?? "")
return new Uint8Array([...(msg.payload ?? []), ...contentTopicBytes]);
}

View File

@ -1,6 +1,6 @@
import { assert, expect } from "chai";
import * as rln from "./index";
import * as rln from "./index.js";
describe("js-rln", () => {
it("should verify a proof", async function () {
@ -50,11 +50,13 @@ describe("js-rln", () => {
try {
// Modifying the proof so it's invalid
const proofBytes = proof.toBytes();
proofBytes[7] = Math.floor(Math.random() * 256) % 255;
proof.proof[0] = 0;
proof.proof[1] = 1;
proof.proof[2] = 2;
proof.proof[3] = 3;
// verify the proof
const verifResult = rlnInstance.verifyProof(proofBytes);
const verifResult = rlnInstance.verifyProof(proof);
expect(verifResult).to.be.false;
} catch (err) {
//

View File

@ -1,4 +1,4 @@
import type { MembershipKey, RateLimitProof, RLNInstance } from "./rln.js";
import type { MembershipKey, Proof, RLNInstance } from "./rln.js";
// reexport the create function, dynamically imported from rln.ts
export async function create(): Promise<RLNInstance> {
@ -9,4 +9,4 @@ export async function create(): Promise<RLNInstance> {
return await rlnModule.create();
}
export { RLNInstance, MembershipKey, RateLimitProof };
export { RLNInstance, MembershipKey, Proof };

View File

@ -1,4 +1,5 @@
import init, * as zerokitRLN from "@waku/zerokit-rln-wasm";
import { RateLimitProof } from "js-waku/lib/interfaces";
import verificationKey from "./resources/verification_key.js";
import * as wc from "./witness_calculator.js";
@ -120,7 +121,7 @@ const shareYOffset = shareXOffset + 32;
const nullifierOffset = shareYOffset + 32;
const rlnIdentifierOffset = nullifierOffset + 32;
export class RateLimitProof {
export class Proof implements RateLimitProof {
readonly proof: Uint8Array;
readonly merkleRoot: Uint8Array;
readonly epoch: Uint8Array;
@ -143,18 +144,18 @@ export class RateLimitProof {
rlnIdentifierOffset
);
}
}
toBytes(): Uint8Array {
return concatenate(
this.proof,
this.merkleRoot,
this.epoch,
this.shareX,
this.shareY,
this.nullifier,
this.rlnIdentifier
);
}
function proofToBytes(p: RateLimitProof): Uint8Array {
return concatenate(
p.proof,
p.merkleRoot,
p.epoch,
p.shareX,
p.shareY,
p.nullifier,
p.rlnIdentifier
);
}
export class RLNInstance {
@ -218,13 +219,16 @@ export class RLNInstance {
rlnWitness
);
return new RateLimitProof(proofBytes);
return new Proof(proofBytes);
}
verifyProof(proof: RateLimitProof | Uint8Array): boolean {
if (proof instanceof RateLimitProof) {
proof = proof.toBytes();
let pBytes: Uint8Array;
if (proof instanceof Uint8Array) {
pBytes = proof;
} else {
pBytes = proofToBytes(proof);
}
return zerokitRLN.verifyProof(this.zkRLN, proof);
return zerokitRLN.verifyProof(this.zkRLN, pBytes);
}
}