mina-attestations
Version:
Private Attestations on Mina
185 lines • 9.36 kB
JavaScript
import { test } from 'node:test';
import assert from 'node:assert';
import { Field, Bytes } from 'o1js';
import { Claim, Constant, Spec } from "../src/program-spec.js";
import { issuerKey, owner, ownerKey, zkAppIdentity, } from "./test-utils.js";
import { Credential } from "../src/credential-index.js";
import { Presentation, PresentationRequest } from "../src/presentation.js";
import { Operation } from "../src/operation.js";
import { PresentationRequestSchema } from "../src/validation.js";
const zkappContext = { ...zkAppIdentity, methodName: 'myMethod' };
test('program with simple spec and native credential', async (t) => {
const Bytes32 = Bytes(32);
const spec = Spec({
signedData: Credential.Native({ age: Field, name: Bytes32 }),
targetAge: Claim(Field),
targetName: Constant(Bytes32, Bytes32.fromString('Alice')),
}, ({ signedData, targetAge, targetName }) => ({
assert: Operation.and(Operation.equals(Operation.property(signedData, 'age'), targetAge), Operation.equals(Operation.property(signedData, 'name'), targetName)),
outputClaim: Operation.property(signedData, 'age'),
}));
// presentation request
let requestInitial = PresentationRequest.noContext(spec, {
targetAge: Field(18),
});
let json = PresentationRequest.toJSON(requestInitial);
let serialized = JSON.parse(json);
const result = PresentationRequestSchema.safeParse(serialized);
assert(result.success, 'No-context presentation request should be valid: ' +
(result.success ? '' : JSON.stringify(result.error.issues, null, 2)));
// wallet: deserialize and compile request
let deserialized = PresentationRequest.fromJSON('no-context', json);
let request = await Presentation.compile(deserialized);
await t.test('compile program', async () => {
assert(await request.program.compile(), 'Verification key should be generated for zk program');
});
await t.test('run program with valid input', async () => {
// issuance
let data = { age: Field(18), name: Bytes32.fromString('Alice') };
let signedData = Credential.sign(issuerKey, { owner, data });
// presentation
let presentation = await Presentation.create(ownerKey, {
request,
credentials: [signedData],
context: undefined,
});
// verifies
await Presentation.verify(request, presentation, undefined);
let { claims, outputClaim, proof } = presentation;
assert(proof, 'Proof should be generated');
assert.deepStrictEqual(claims.targetAge, Field(18), 'Public input should match');
assert.deepStrictEqual(outputClaim, Field(18), 'Public output should match the age');
});
await t.test('run program with invalid age input', async () => {
const data = { age: Field(20), name: Bytes32.fromString('Alice') };
let signedData = Credential.sign(issuerKey, { owner, data });
await assert.rejects(async () => await Presentation.create(ownerKey, {
request,
credentials: [signedData],
context: undefined,
}), /Program assertion failed/, 'Program should fail with invalid input');
});
await t.test('run program with invalid name input', async () => {
const data = { age: Field(18), name: Bytes32.fromString('Bob') };
let signedData = Credential.sign(issuerKey, { owner, data });
await assert.rejects(() => Presentation.create(ownerKey, {
request,
credentials: [signedData],
context: undefined,
}), /Program assertion failed/, 'Program should fail with invalid input');
});
});
test('program with owner and issuer operations', async (t) => {
const InputData = { dummy: Field };
const SignedData = Credential.Native(InputData);
const spec = Spec({
signedData: SignedData,
expectedDummy: Constant(Field, Field(123)),
}, ({ signedData, expectedDummy }) => ({
assert: Operation.equals(Operation.property(signedData, 'dummy'), expectedDummy),
outputClaim: Operation.record({
owner: Operation.owner,
issuer: Operation.issuer(signedData),
dummy: Operation.property(signedData, 'dummy'),
}),
}));
let requestInitial = PresentationRequest.noContext(spec, {});
let request = await Presentation.compile(requestInitial);
await t.test('compile program', async () => {
assert(await request.program.compile(), 'Program should compile');
});
await t.test('run program with valid input', async () => {
let dummyData = { dummy: Field(123) };
let signedData = Credential.sign(issuerKey, { owner, data: dummyData });
let presentation = await Presentation.create(ownerKey, {
request,
credentials: [signedData],
context: undefined,
});
await Presentation.verify(request, presentation, undefined);
let { outputClaim, proof } = presentation;
assert(proof, 'Proof should be generated');
assert.deepStrictEqual(outputClaim.owner, owner);
const expectedIssuerField = SignedData.issuer(signedData.witness);
assert.deepStrictEqual(outputClaim.issuer, expectedIssuerField);
assert.deepStrictEqual(outputClaim.dummy, Field(123));
});
});
test('presentation with context binding', async (t) => {
const Bytes32 = Bytes(32);
const InputData = { age: Field, name: Bytes32 };
const spec = Spec({
signedData: Credential.Native(InputData),
targetAge: Claim(Field),
targetName: Constant(Bytes32, Bytes32.fromString('Alice')),
}, ({ signedData, targetAge, targetName }) => ({
assert: Operation.and(Operation.equals(Operation.property(signedData, 'age'), targetAge), Operation.equals(Operation.property(signedData, 'name'), targetName)),
outputClaim: Operation.property(signedData, 'age'),
}));
const data = { age: Field(18), name: Bytes32.fromString('Alice') };
const signedData = Credential.sign(issuerKey, { owner, data });
await t.test('presentation with zk-app context', async (t) => {
let request = PresentationRequest.zkApp(spec, { targetAge: Field(18) }, zkappContext);
let presentation = await Presentation.create(ownerKey, {
request,
context: undefined,
credentials: [signedData],
});
// verifies
await Presentation.verify(request, presentation, undefined);
// doesn't verify against different context
await assert.rejects(() => Presentation.verify({
...request,
inputContext: {
...request.inputContext,
verifierIdentity: {
...zkAppIdentity,
network: 'mainnet',
},
},
}, presentation, undefined), /Invalid proof/, 'Should throw an error for invalid context');
// doesn't verify against request for different action
let request2 = PresentationRequest.zkApp(spec, { targetAge: Field(18) }, { ...zkappContext, methodName: 'otherMethod' });
await assert.rejects(() => Presentation.verify(request2, presentation, undefined), /Invalid proof/, 'Should throw an error for invalid context');
});
await t.test('presentation with https context', async () => {
let request = PresentationRequest.https(spec, { targetAge: Field(18) }, { action: 'POST /api/verify' });
let presentation = await Presentation.create(ownerKey, {
request,
credentials: [signedData],
context: { verifierIdentity: 'test.com' },
});
await Presentation.verify(request, presentation, {
verifierIdentity: 'test.com',
});
});
});
test('serialize presentation', async (t) => {
const Bytes32 = Bytes(32);
const InputData = { age: Field, name: Bytes32 };
const spec = Spec({
signedData: Credential.Native(InputData),
targetAge: Claim(Field),
targetName: Constant(Bytes32, Bytes32.fromString('Alice')),
}, ({ signedData, targetAge, targetName }) => ({
assert: Operation.and(Operation.equals(Operation.property(signedData, 'age'), targetAge), Operation.equals(Operation.property(signedData, 'name'), targetName)),
outputClaim: Operation.property(signedData, 'age'),
}));
const data = { age: Field(18), name: Bytes32.fromString('Alice') };
const signedData = Credential.sign(issuerKey, { owner, data });
await t.test('serialize presentation with zk-app context', async (t) => {
let request = PresentationRequest.zkApp(spec, { targetAge: Field(18) }, zkappContext);
let presentation = await Presentation.create(ownerKey, {
request,
context: undefined,
credentials: [signedData],
});
assert(presentation.proof, 'Proof should be generated');
let serialized = Presentation.toJSON(presentation);
let deserialized = Presentation.fromJSON(serialized);
let reserialized = Presentation.toJSON(deserialized);
assert.deepStrictEqual(deserialized, presentation);
assert.deepStrictEqual(reserialized, serialized);
});
});
//# sourceMappingURL=presentation.test.js.map