UNPKG

@iden3/js-jwz

Version:

JS implementation of JWZ

156 lines (155 loc) 6.8 kB
"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;