minauth-simple-preimage-plugin
Version:
A very simple Minauth plugin that allows users to authenticate by providing a preimage to a given hash.
137 lines • 5.54 kB
JavaScript
import { Cache, verify } from 'o1js';
import { outputInvalid, outputValid } from 'minauth/dist/plugin/plugintype.js';
import ProvePreimageProgram from './hash-preimage-proof.js';
import { Router } from 'express';
import { z } from 'zod';
import * as fs from 'fs/promises';
import { wrapZodDec, combineEncDec, noOpEncoder } from 'minauth/dist/plugin/encodedecoder.js';
import { JsonProofSchema } from 'minauth/dist/common/proof.js';
/**
* The plugin configuration schema.
*/
export const rolesSchema = z.record(
// FIXME: the key should be a valid poseidon hash
/** Hash preimage of which is used to authorize operations */
z.string(),
/** An auxilliary name for the hash - for example
* a name of a role in the system */
z.string());
export const configurationSchema = z
.object({
roles: rolesSchema
})
.or(z.object({
/** Alternatively, the "roles" can be loaded from a file */
loadRolesFrom: z.string()
}));
export const InputSchema = z.object({
proof: JsonProofSchema
});
/**
* Somewhat trivial example of a plugin.
* The plugin keeps a fixed set of hashes.
* Each hash is associated with a role in the system.
* One can prove that they have the role by providing the secret
* preimage of the hash.
*
* NOTE. Although one can always generate valid zkproof its output must
* match the list kept by the server.
*/
export class SimplePreimagePlugin {
/**
* Verify a proof and return the role.
*/
async verifyAndGetOutput(inp) {
const proof = inp.proof;
const key = proof.publicOutput.toString();
const role = this.roles[key]; // Directly accessing the property in the record
if (role === undefined) {
throw new Error('unable to find role');
}
this.logger.debug('Proof verification...');
const valid = await verify(proof, this.verificationKey.data);
if (!valid) {
this.logger.info('Proof verification failed.');
throw new Error('Invalid proof!');
}
this.logger.info('Proof verification succeeded.');
return { provedHash: key, role };
}
/**
* Check if produced output is still valid. If the roles dictionary was edited
* it may become invalid. Notice that the proof and output consumer must not
* allow output forgery as this will accept forged outputs without verification.
* To prevent it the plugin could take the reponsibility by having a cache of outputs
* with unique identifiers.
*/
async checkOutputValidity(output) {
this.logger.debug('Checking validity of ', output);
if (!this.roles.hasOwnProperty(output.provedHash)) {
this.logger.debug('Proved hash no longer exists.');
return Promise.resolve(outputInvalid('Proved hash is no longer valid.'));
}
if (this.roles[output.provedHash] !== output.role) {
this.logger.debug('Proved hash no longer exists.');
return Promise.resolve(outputInvalid('The role assigned to the hash is no longer valid.'));
}
return Promise.resolve(outputValid);
}
/**
* This ctor is meant ot be called by the `initialize` function.
*/
constructor(verificationKey, roles, logger) {
/**
* This plugin uses an idiomatic Typescript interface
*/
this.__interface_tag = 'ts';
/**
* Trivial - no public inputs.
*/
this.publicInputArgsSchema = z.any();
/**
* Provide an endpoint returning a list of roles recognized by the plugin.
* Additionally, provide an endpoint to update the roles
* NOTE. the setRoles endpoint should not be used by the client
* but rather by the plugin admin and that it is not persisted.
*/
this.customRoutes = Router()
.post('/admin/roles', (req, res) => {
try {
// Assuming the new roles are sent in the request body
this.roles = rolesSchema.parse(req.body);
res.status(200).json({ message: 'Roles updated successfully' });
}
catch (error) {
// Handle errors, such as invalid input
res.status(400).json({ message: 'Error updating roles' });
}
})
.get('/admin/roles', (_, res) => res.status(200).json(this.roles));
this.verificationKey = verificationKey;
this.roles = roles;
this.logger = logger;
}
/**
* Initialize the plugin with a configuration.
*/
static async initialize(configuration, logger) {
const { verificationKey } = await ProvePreimageProgram.compile({
cache: Cache.None
});
const roles = 'roles' in configuration
? configuration.roles
: await fs
.readFile(configuration.loadRolesFrom, 'utf-8')
.then(JSON.parse);
return new SimplePreimagePlugin(verificationKey, roles, logger);
}
}
SimplePreimagePlugin.__interface_tag = 'ts';
SimplePreimagePlugin.configurationDec = wrapZodDec('ts', configurationSchema);
SimplePreimagePlugin.inputDecoder = wrapZodDec('ts', z.object({ proof: JsonProofSchema }));
/** output parsing and serialization */
SimplePreimagePlugin.outputEncDec = combineEncDec(noOpEncoder('ts'), wrapZodDec('ts', z.object({ provedHash: z.string(), role: z.string() })));
// sanity check
SimplePreimagePlugin;
export default SimplePreimagePlugin;
//# sourceMappingURL=plugin.js.map