@iden3/js-iden3-auth
Version:
iden3-auth implementation in JavaScript
313 lines (312 loc) • 14.4 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Verifier = exports.createAuthorizationRequestWithMessage = exports.createAuthorizationRequest = void 0;
const authV2_1 = require("../circuits/authV2");
const uuid_1 = require("uuid");
const registry_1 = require("../circuits/registry");
const js_jwz_1 = require("@iden3/js-jwz");
const js_sdk_1 = require("@0xpolygonid/js-sdk");
const path_1 = __importDefault(require("path"));
const js_iden3_core_1 = require("@iden3/js-iden3-core");
/**
* createAuthorizationRequest is a function to create protocol authorization request
* @param {string} reason - reason to request proof
* @param {string} sender - sender did
* @param {string} callbackUrl - callback that user should use to send response
* @returns `Promise<AuthorizationRequestMessage>`
*/
function createAuthorizationRequest(reason, sender, callbackUrl, opts) {
return createAuthorizationRequestWithMessage(reason, '', sender, callbackUrl, opts);
}
exports.createAuthorizationRequest = createAuthorizationRequest;
/**
* createAuthorizationRequestWithMessage is a function to create protocol authorization request with explicit message to sign
* @param {string} reason - reason to request proof
* @param {string} message - message to sign in the response
* @param {string} sender - sender did
* @param {string} callbackUrl - callback that user should use to send response
* @returns `Promise<AuthorizationRequestMessage>`
*/
function createAuthorizationRequestWithMessage(reason, message, sender, callbackUrl, opts) {
const uuid = (0, uuid_1.v4)();
const request = {
id: uuid,
thid: uuid,
from: sender,
typ: js_sdk_1.PROTOCOL_CONSTANTS.MediaType.PlainMessage,
type: js_sdk_1.PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE,
body: {
accept: opts?.accept,
reason: reason,
message: message,
callbackUrl: callbackUrl,
scope: opts?.scope || []
},
created_time: (0, js_iden3_core_1.getUnixTimestamp)(new Date()),
expires_time: opts?.expires_time ? (0, js_iden3_core_1.getUnixTimestamp)(opts.expires_time) : undefined
};
return request;
}
exports.createAuthorizationRequestWithMessage = createAuthorizationRequestWithMessage;
/**
*
* Verifier is responsible for verification of JWZ / JWS packed messages with zero-knowledge proofs inside.
*
* @public
* @class Verifier
*/
class Verifier {
/**
* Creates an instance of the Verifier.
* @private
* @param {Resolvers} resolvers - state resolvers instances
* @param {VerifierSuiteParams} params - suite for verification
*/
constructor(stateResolver, params) {
this.schemaLoader = params.documentLoader;
this.stateResolver = stateResolver;
this.packageManager = params.packageManager;
this.circuitStorage = params.circuitStorage;
this.prover = params.prover;
}
/**
* Creates an instance of the Verifier.
* @public
* @param {VerifierParams} params - params to init verifier
* @returns `Promise<Verifier>`
*/
static async newVerifier(params) {
if (!params.suite) {
const documentLoader = params.documentLoader ?? (0, js_sdk_1.cacheLoader)(params);
const dirname = params?.circuitsDir ?? path_1.default.join(process.cwd(), 'circuits');
const circuitStorage = new js_sdk_1.FSCircuitStorage({ dirname });
params.suite = {
documentLoader,
circuitStorage,
prover: new js_sdk_1.NativeProver(circuitStorage),
packageManager: new js_sdk_1.PackageManager()
};
const verifier = new Verifier(params.stateResolver, params.suite);
await verifier.initPackers(params.didDocumentResolver);
return verifier;
}
return new Verifier(params.stateResolver, params.suite);
}
// setPackageManager sets the package manager for the Verifier.
setPackageManager(manager) {
this.packageManager = manager;
}
// setPacker sets the custom packer manager for the Verifier.
setPacker(packer) {
return this.packageManager.registerPackers([packer]);
}
// setupAuthV2ZKPPacker sets the custom packer manager for the Verifier.
async setupAuthV2ZKPPacker(circuitStorage) {
if (!circuitStorage) {
throw new Error('circuit storage is not defined');
}
const authV2Set = await circuitStorage.loadCircuitData(js_sdk_1.CircuitId.AuthV2);
if (!authV2Set.verificationKey) {
throw new Error('verification key is not for authv2 circuit');
}
const mapKey = js_jwz_1.proving.provingMethodGroth16AuthV2Instance.methodAlg.toString();
const provingParamMap = new Map();
const stateVerificationFn = async (circuitId, pubSignals) => {
if (circuitId !== js_sdk_1.CircuitId.AuthV2) {
throw new Error(`CircuitId is not supported ${circuitId}`);
}
const verifier = new authV2_1.AuthPubSignalsV2(pubSignals);
await verifier.verifyStates(this.stateResolver);
return true;
};
const verificationFn = new js_sdk_1.VerificationHandlerFunc(stateVerificationFn);
const verificationParamMap = new Map();
verificationParamMap.set(mapKey, {
key: authV2Set.verificationKey,
verificationFn
});
const zkpPacker = new js_sdk_1.ZKPPacker(provingParamMap, verificationParamMap);
return this.setPacker(zkpPacker);
}
// setupJWSPacker sets the JWS packer for the Verifier.
setupJWSPacker(kms, documentResolver) {
const jwsPacker = new js_sdk_1.JWSPacker(kms, documentResolver);
return this.setPacker(jwsPacker);
}
verifyAuthRequest(request, opts) {
if (!opts?.allowExpiredMessages) {
(0, js_sdk_1.verifyExpiresTime)(request);
}
this.verifyProfile(request.type, request.body.accept);
const groupIdValidationMap = {};
const requestScope = request.body.scope;
for (const proofRequest of requestScope) {
const groupId = proofRequest.query.groupId;
if (groupId) {
const existingRequests = groupIdValidationMap[groupId] ?? [];
//validate that all requests in the group have the same schema, issuer and circuit
for (const existingRequest of existingRequests) {
if (existingRequest.query.type !== proofRequest.query.type) {
throw new Error(`all requests in the group should have the same type`);
}
if (existingRequest.query.context !== proofRequest.query.context) {
throw new Error(`all requests in the group should have the same context`);
}
const allowedIssuers = proofRequest.query.allowedIssuers;
const existingRequestAllowedIssuers = existingRequest.query.allowedIssuers;
if (!(allowedIssuers.includes('*') ||
allowedIssuers.every((issuer) => existingRequestAllowedIssuers.includes(issuer)))) {
throw new Error(`all requests in the group should have the same issuer`);
}
}
groupIdValidationMap[groupId] = [...(groupIdValidationMap[groupId] ?? []), proofRequest];
}
}
}
/**
* verifies zero knowledge proof response according to the proof request
* @public
* @param {AuthorizationResponseMessage} response - auth protocol response
* @param {AuthorizationRequestMessage} proofRequest - auth protocol request
* @param {VerifyOpts} opts - verification options
*
* @returns `Promise<void>`
*/
async verifyAuthResponse(response, request, opts) {
if (!opts?.allowExpiredMessages) {
(0, js_sdk_1.verifyExpiresTime)(request);
}
if ((request.body.message ?? '') !== (response.body.message ?? '')) {
throw new Error('message for signing from request is not presented in response');
}
if (request.from !== response.to) {
throw new Error(`sender of the request is not a target of response - expected ${request.from}, given ${response.to}`);
}
this.verifyAuthRequest(request);
const requestScope = request.body.scope;
const groupIdToLinkIdMap = new Map();
// group requests by query group id
for (const proofRequest of requestScope) {
const groupId = proofRequest.query.groupId;
const proofResp = response.body.scope.find((resp) => resp.id === proofRequest.id);
if (!proofResp) {
throw new Error(`proof is not given for requestId ${proofRequest.id}`);
}
const circuitId = proofResp.circuitId;
if (circuitId !== proofRequest.circuitId) {
throw new Error(`proof is not given for requested circuit expected: ${proofRequest.circuitId}, given ${circuitId}`);
}
const isValid = await this.prover.verify(proofResp, circuitId);
if (!isValid) {
throw new Error(`Proof with circuit id ${circuitId} and request id ${proofResp.id} is not valid`);
}
const CircuitVerifier = registry_1.Circuits.getCircuitPubSignals(circuitId);
if (!CircuitVerifier) {
throw new Error(`circuit ${circuitId} is not supported by the library`);
}
const params = proofRequest.params ?? {};
params.verifierDid = js_iden3_core_1.DID.parse(request.from);
// verify query
const verifier = new CircuitVerifier(proofResp.pub_signals);
const pubSignals = await verifier.verifyQuery(proofRequest.query, this.schemaLoader, proofResp.vp, opts, params);
// write linkId to the proof response
const pubSig = pubSignals;
if (pubSig.linkID && groupId) {
groupIdToLinkIdMap.set(groupId, [
...(groupIdToLinkIdMap.get(groupId) ?? []),
{ linkID: pubSig.linkID, requestId: proofResp.id }
]);
}
// verify states
await verifier.verifyStates(this.stateResolver, opts);
if (!response.from) {
throw new Error(`proof response doesn't contain from field`);
}
// verify id ownership
await verifier.verifyIdOwnership(response.from, BigInt(proofResp.id));
}
// verify grouping links
for (const [groupId, metas] of groupIdToLinkIdMap.entries()) {
// check that all linkIds are the same
if (metas.some((meta) => meta.linkID !== metas[0].linkID)) {
throw new Error(`Link id validation failed for group ${groupId}, request linkID to requestIds info: ${JSON.stringify(metas)}`);
}
}
}
/**
* verifies jwz token
* @public
* @param {string} tokenStr - token string
* @param {VerifyOpts} opts - verification options
*
* @returns `Promise<Token>`
*/
async verifyJWZ(tokenStr, opts) {
const token = await js_jwz_1.Token.parse(tokenStr);
const key = (await this.circuitStorage.loadCircuitData(token.circuitId))
.verificationKey;
if (!key) {
throw new Error(`verification key is not found for circuit ${token.circuitId}`);
}
const isValid = await token.verify(key);
if (!isValid) {
throw new Error(`zero-knowledge proof of jwz token is not valid`);
}
const CircuitVerifier = registry_1.Circuits.getCircuitPubSignals(token.circuitId);
if (!CircuitVerifier) {
throw new Error(`circuit ${token.circuitId} is not supported by the library`);
}
// outputs unmarshaller
const verifier = new CircuitVerifier(token.zkProof.pub_signals);
// state verification
await verifier.verifyStates(this.stateResolver, opts);
return token;
}
/**
* perform both verification of jwz / jws token and authorization request message
* @public
* @param {string} tokenStr - token string
* @param {AuthorizationRequestMessage} request - auth protocol request
* @param {VerifyOpts} opts - verification options
*
* @returns `Promise<AuthorizationResponseMessage>`
*/
async fullVerify(tokenStr, request, opts) {
const msg = await this.packageManager.unpack(js_sdk_1.byteEncoder.encode(tokenStr));
if (request.body.accept?.length) {
const acceptedMediaTypes = request.body.accept.map((accept) => (0, js_sdk_1.parseAcceptProfile)(accept).env);
if (!acceptedMediaTypes.includes(msg.unpackedMediaType)) {
throw new Error('response type is not in accept profiles of the request');
}
}
const response = msg.unpackedMessage;
await this.verifyAuthResponse(response, request, opts);
return response;
}
async initPackers(didResolver) {
await this.setupAuthV2ZKPPacker(this.circuitStorage);
// set default jws packer if packageManager is not present in options but did document resolver is.
if (didResolver) {
this.setupJWSPacker(new js_sdk_1.KMS(), didResolver);
}
}
verifyProfile(messageType, profile) {
if (!profile?.length) {
return;
}
const supportedMediaTypes = [];
for (const acceptProfile of profile) {
const { env } = (0, js_sdk_1.parseAcceptProfile)(acceptProfile);
if (this.packageManager.isProfileSupported(env, acceptProfile)) {
supportedMediaTypes.push(env);
}
}
if (!supportedMediaTypes.length) {
throw new Error('no packer with profile which meets `accept` header requirements');
}
}
}
exports.Verifier = Verifier;
;