@iden3/js-jwz
Version:
JS implementation of JWZ
156 lines (155 loc) • 6.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Token = exports.RawJSONWebZeroknowledge = exports.Header = void 0;
const hash_1 = require("./hash");
const proving_1 = require("./proving");
const rfc4648_1 = require("rfc4648");
const js_iden3_core_1 = require("@iden3/js-iden3-core");
var Header;
(function (Header) {
Header["Type"] = "typ";
Header["Alg"] = "alg";
Header["CircuitId"] = "circuitId";
Header["Critical"] = "crit";
})(Header = exports.Header || (exports.Header = {}));
class RawJSONWebZeroknowledge {
constructor(payload, protectedHeaders, header, zkp) {
this.payload = payload;
this.protectedHeaders = protectedHeaders;
this.header = header;
this.zkp = zkp;
}
async sanitized() {
if (!this.payload) {
throw new Error('iden3/js-jwz: missing payload in JWZ message');
}
const headers = JSON.parse(new TextDecoder().decode(this.protectedHeaders));
const criticalHeaders = headers[Header.Critical];
criticalHeaders.forEach((key) => {
if (!headers[key]) {
throw new Error(`iden3/js-jwz: header is listed in critical ${key}, but not presented`);
}
});
const alg = headers[Header.Alg];
const circuitId = headers[Header.CircuitId];
const method = await (0, proving_1.getProvingMethod)(new proving_1.ProvingMethodAlg(alg, circuitId));
const zkp = JSON.parse(new TextDecoder().decode(this.zkp));
const token = new Token(method, new TextDecoder().decode(this.payload));
token.alg = alg;
token.circuitId = circuitId;
token.zkProof = zkp;
for (const [key, value] of Object.entries(headers)) {
token.setHeader(key, value);
}
return token;
}
}
exports.RawJSONWebZeroknowledge = RawJSONWebZeroknowledge;
// Token represents a JWZ Token.
class Token {
constructor(method, payload, inputsPreparer) {
this.method = method;
this.inputsPreparer = inputsPreparer;
this.zkProof = {};
this.alg = this.method.alg;
this.circuitId = this.method.circuitId;
this.raw = {};
this.raw.header = this.getDefaultHeaders();
this.raw.payload = new TextEncoder().encode(payload);
}
setHeader(key, value) {
this.raw.header[key] = value;
}
getPayload() {
return new TextDecoder().decode(this.raw.payload);
}
getDefaultHeaders() {
return {
[Header.Alg]: this.alg,
[Header.Critical]: [Header.CircuitId],
[Header.CircuitId]: this.circuitId,
[Header.Type]: 'JWZ'
};
}
// Parse parses a jwz message in compact or full serialization format.
static parse(tokenStr) {
// Parse parses a jwz message in compact or full serialization format.
const token = tokenStr?.trim();
return token.startsWith('{') ? Token.parseFull(tokenStr) : Token.parseCompact(tokenStr);
}
// parseCompact parses a message in compact format.
static async parseCompact(tokenStr) {
const parts = tokenStr.split('.');
if (parts.length != 3) {
throw new Error('iden3/js-jwz: compact JWZ format must have three segments');
}
const rawProtected = rfc4648_1.base64url.parse(parts[0], { loose: true });
const rawPayload = rfc4648_1.base64url.parse(parts[1], { loose: true });
const proof = rfc4648_1.base64url.parse(parts[2], { loose: true });
const raw = new RawJSONWebZeroknowledge(rawPayload, rawProtected, {}, proof);
return await raw.sanitized();
}
// parseFull parses a message in full format.
static async parseFull(tokenStr) {
const raw = JSON.parse(tokenStr);
return await raw.sanitized();
}
// Prove creates and returns a complete, proved JWZ.
// The token is proven using the Proving Method specified in the token.
async prove(provingKey, wasm) {
// all headers must be protected
const headers = this.serializeHeaders();
this.raw.protectedHeaders = new TextEncoder().encode(headers);
const msgHash = await this.getMessageHash();
if (!this.inputsPreparer) {
throw new Error('iden3/jwz: prepare func must be defined');
}
const inputs = await (0, proving_1.prepare)(this.inputsPreparer, msgHash, this.circuitId);
const proof = await this.method.prove(inputs, provingKey, wasm);
const marshaledProof = JSON.stringify(proof);
this.zkProof = proof;
this.raw.zkp = new TextEncoder().encode(marshaledProof);
return this.compactSerialize();
}
// CompactSerialize returns token serialized in three parts: base64 encoded headers, payload and proof.
compactSerialize() {
if (!this.raw.header || !this.raw.protectedHeaders || !this.zkProof) {
throw new Error("iden3/jwz:can't serialize without one of components");
}
const serializedProtected = rfc4648_1.base64url.stringify(this.raw.protectedHeaders, {
pad: false
});
const serializedProof = rfc4648_1.base64url.stringify(this.raw.zkp, { pad: false });
const serializedPayload = rfc4648_1.base64url.stringify(this.raw.payload, {
pad: false
});
return `${serializedProtected}.${serializedPayload}.${serializedProof}`;
}
// fullSerialize returns marshaled presentation of raw token as json string.
fullSerialize() {
return JSON.stringify(this.raw);
}
async getMessageHash() {
const serializedHeadersJSON = this.serializeHeaders();
const serializedHeaders = new TextEncoder().encode(serializedHeadersJSON);
const protectedHeaders = rfc4648_1.base64url.stringify(serializedHeaders, {
pad: false
});
const payload = rfc4648_1.base64url.stringify(this.raw.payload, { pad: false });
// JWZ ZkProof input value is ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload)).
const messageToProof = new TextEncoder().encode(`${protectedHeaders}.${payload}`);
const hashInt = await (0, hash_1.hash)(messageToProof);
return (0, js_iden3_core_1.toBigEndian)(hashInt, 32);
}
// Verify perform zero knowledge verification.
async verify(verificationKey) {
// 1. prepare hash o payload message that had to be proven
const msgHash = await this.getMessageHash();
// 2. verify that zkp is valid
return this.method.verify(msgHash, this.zkProof, verificationKey);
}
serializeHeaders() {
return JSON.stringify(this.raw.header, Object.keys(this.raw.header).sort());
}
}
exports.Token = Token;