mina-attestations
Version:
Private Attestations on Mina
580 lines • 26.5 kB
JavaScript
import { test } from 'node:test';
import assert from 'node:assert';
import { Bool, Bytes, Field, Signature, UInt32, UInt64, UInt8 } from 'o1js';
import { Spec, splitUserInputs, rootValue, Claim, Constant, } from "../src/program-spec.js";
import { issuerKey, owner } from "./test-utils.js";
import { unsafeMissingOwner, } from "../src/credential.js";
import { Credential } from "../src/credential-index.js";
import { hashDynamic, Operation } from "../src/index.js";
import { Node } from "../src/operation.js";
function cred(data) {
return { data, issuer: Field(0), witness: undefined };
}
test('Spec and Node operations', async (t) => {
const Bytes32 = Bytes(32);
await t.test('Basic Spec with equality check', () => {
const InputData = { age: Field };
const spec = Spec({
data: Credential.Unsigned(InputData),
targetAge: Claim(Field),
}, ({ data, targetAge }) => ({
assert: Operation.equals(Operation.property(data, 'age'), targetAge),
outputClaim: Operation.property(data, 'age'),
}));
const root = {
owner: unsafeMissingOwner(),
data: cred({ age: Field(25) }),
targetAge: Field(25),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Field(25));
});
const data = cred({
age: Field(30),
name: Bytes32.fromString('Alice'),
});
await t.test('Spec with multiple assertions - and', () => {
const InputData = { age: Field, name: Bytes32 };
const spec = Spec({
data: Credential.Unsigned(InputData),
targetAge: Claim(Field),
targetName: Claim(Bytes32),
}, ({ data, targetAge, targetName }) => ({
assert: Operation.and(Operation.equals(Operation.property(data, 'age'), targetAge), Operation.equals(Operation.property(data, 'name'), targetName)),
outputClaim: Operation.property(data, 'age'),
}));
const root = {
owner: unsafeMissingOwner(),
data,
targetAge: Field(30),
targetName: Bytes32.fromString('Alice'),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Field(30));
});
await t.test('Spec with multiple assertions - or - both are true', () => {
const InputData = { age: Field, name: Bytes32 };
const spec = Spec({
data: Credential.Unsigned(InputData),
targetAge: Claim(Field),
targetName: Claim(Bytes32),
}, ({ data, targetAge, targetName }) => ({
assert: Operation.or(Operation.equals(Operation.property(data, 'age'), targetAge), Operation.equals(Operation.property(data, 'name'), targetName)),
outputClaim: Operation.property(data, 'age'),
}));
const root = {
owner: unsafeMissingOwner(),
data,
targetAge: Field(30),
targetName: Bytes32.fromString('Alice'),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Field(30));
});
await t.test('Spec with multiple assertions - or - only left is true', () => {
const InputData = { age: Field, name: Bytes32 };
const spec = Spec({
data: Credential.Unsigned(InputData),
targetAge: Claim(Field),
targetName: Claim(Bytes32),
}, ({ data, targetAge, targetName }) => ({
assert: Operation.or(Operation.equals(Operation.property(data, 'age'), targetAge), Operation.equals(Operation.property(data, 'name'), targetName)),
outputClaim: Operation.property(data, 'age'),
}));
const root = {
owner: unsafeMissingOwner(),
data,
targetAge: Field(30),
targetName: Bytes32.fromString('Bob'),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Field(30));
});
await t.test('Spec with multiple assertions - or - only right is true', () => {
const InputData = { age: Field, name: Bytes32 };
const spec = Spec({
data: Credential.Unsigned(InputData),
targetAge: Claim(Field),
targetName: Claim(Bytes32),
}, ({ data, targetAge, targetName }) => ({
assert: Operation.or(Operation.equals(Operation.property(data, 'age'), targetAge), Operation.equals(Operation.property(data, 'name'), targetName)),
outputClaim: Operation.property(data, 'age'),
}));
const root = {
owner: unsafeMissingOwner(),
data: cred({ age: Field(11), name: Bytes32.fromString('Alice') }),
targetAge: Field(30),
targetName: Bytes32.fromString('Alice'),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Field(11));
});
await t.test('Spec with multiple assertions - not', () => {
const InputData = { age: Field, name: Bytes32 };
const spec = Spec({
data: Credential.Unsigned(InputData),
targetAge: Claim(Field),
targetName: Claim(Bytes32),
}, ({ data, targetAge, targetName }) => ({
assert: Operation.and(Operation.not(Operation.equals(Operation.property(data, 'age'), targetAge)), Operation.equals(Operation.property(data, 'name'), targetName)),
outputClaim: Operation.property(data, 'age'),
}));
const root = {
owner: unsafeMissingOwner(),
data: cred({ age: Field(11), name: Bytes32.fromString('Alice') }),
targetAge: Field(30),
targetName: Bytes32.fromString('Alice'),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Field(11));
});
await t.test('Spec with hash operation and equality check', () => {
const InputData = { value: Field };
const spec = Spec({
data: Credential.Unsigned(InputData),
expectedHash: Claim(Field),
}, ({ data, expectedHash }) => ({
assert: Operation.equals(Operation.hash(Operation.property(data, 'value')), expectedHash),
outputClaim: Operation.property(data, 'value'),
}));
const inputValue = Field(123456);
const expectedHashValue = hashDynamic(inputValue);
const root = {
owner: unsafeMissingOwner(),
data: cred({ value: inputValue }),
expectedHash: expectedHashValue,
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, inputValue);
});
await t.test('Spec with multiple assertions and lessThan', () => {
const InputData = { age: Field, name: Bytes32 };
const spec = Spec({
data: Credential.Unsigned(InputData),
targetAge: Claim(Field),
targetName: Claim(Bytes32),
}, ({ data, targetAge, targetName }) => ({
assert: Operation.and(Operation.lessThan(targetAge, Operation.property(data, 'age')), Operation.equals(Operation.property(data, 'name'), targetName)),
outputClaim: Operation.property(data, 'age'),
}));
const root = {
owner: unsafeMissingOwner(),
data: cred({ age: Field(30), name: Bytes32.fromString('Alice') }),
targetAge: Field(18),
targetName: Bytes32.fromString('Alice'),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Field(30));
});
await t.test('Spec with multiple assertions and lessThanEq', () => {
const InputData = { age: Field, name: Bytes32 };
const spec = Spec({
data: Credential.Unsigned(InputData),
targetAge: Claim(Field),
targetName: Claim(Bytes32),
}, ({ data, targetAge, targetName }) => ({
assert: Operation.and(Operation.lessThanEq(Operation.property(data, 'age'), targetAge), Operation.equals(Operation.property(data, 'name'), targetName)),
outputClaim: Operation.property(data, 'age'),
}));
const root = {
owner: unsafeMissingOwner(),
data: cred({ age: Field(30), name: Bytes32.fromString('Alice') }),
targetAge: Field(30),
targetName: Bytes32.fromString('Alice'),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Field(30));
});
await t.test('Spec with add UInt32 and UInt64', () => {
const spec = Spec({
value1: Credential.Unsigned(UInt32),
value2: Credential.Unsigned(UInt64),
sum: Claim(UInt64),
}, ({ value1, value2, sum }) => ({
assert: Operation.equals(Operation.add(value1, value2), sum),
outputClaim: Operation.add(value1, value2),
}));
const root = {
owner: unsafeMissingOwner(),
value1: cred(UInt32.from(1000000)),
value2: cred(UInt64.from(1000000)),
sum: UInt64.from(2000000),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, UInt64.from(2000000));
});
await t.test('Spec with sub UInt32 and UInt8', () => {
const spec = Spec({
value1: Credential.Unsigned(UInt32),
value2: Credential.Unsigned(UInt8),
difference: Claim(UInt32),
}, ({ value1, value2, difference }) => ({
assert: Operation.equals(Operation.sub(value1, value2), difference),
outputClaim: Operation.sub(value1, value2),
}));
const root = {
owner: unsafeMissingOwner(),
value1: cred(UInt32.from(1000)),
value2: cred(UInt8.from(200)),
difference: UInt32.from(800),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, UInt32.from(800));
});
await t.test('Spec with mul UInt32 and UInt64', () => {
const spec = Spec({
value1: Credential.Unsigned(UInt32),
value2: Credential.Unsigned(UInt64),
product: Claim(UInt64),
}, ({ value1, value2, product }) => ({
assert: Operation.equals(Operation.mul(value1, value2), product),
outputClaim: Operation.mul(value1, value2),
}));
const root = {
owner: unsafeMissingOwner(),
value1: cred(UInt32.from(1000)),
value2: cred(UInt64.from(2000)),
product: UInt64.from(2000000),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, UInt64.from(2000000));
});
await t.test('Spec with div UInt64 and UInt32', () => {
const spec = Spec({
value1: Credential.Unsigned(UInt64),
value2: Credential.Unsigned(UInt32),
quotient: Claim(UInt64),
}, ({ value1, value2, quotient }) => ({
assert: Operation.equals(Operation.div(value1, value2), quotient),
outputClaim: Operation.div(value1, value2),
}));
const root = {
owner: unsafeMissingOwner(),
value1: cred(UInt64.from(1000000)),
value2: cred(UInt32.from(1000)),
quotient: UInt64.from(1000),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, UInt64.from(1000));
});
await t.test('Spec with ifThenElse operation', () => {
const InputData = { value: Field };
const spec = Spec({
data: Credential.Unsigned(InputData),
threshold: Claim(Field),
zero: Constant(Field, Field(0)),
}, ({ data, threshold, zero }) => ({
assert: Operation.constant(Bool(true)),
outputClaim: Operation.ifThenElse(Operation.lessThan(Operation.property(data, 'value'), threshold), zero, Operation.property(data, 'value')),
}));
// value < threshold
const root1 = {
owner: unsafeMissingOwner(),
data: cred({ value: Field(5) }),
threshold: Field(10),
zero: Field(0),
};
const result1 = Node.eval(root1, spec.outputClaim);
assert.deepStrictEqual(result1, Field(0));
// value >= threshold
const root2 = {
owner: unsafeMissingOwner(),
data: cred({ value: Field(15) }),
threshold: Field(10),
zero: Field(0),
};
const result2 = Node.eval(root2, spec.outputClaim);
assert.deepStrictEqual(result2, Field(15));
});
await t.test('Spec with ifThenElse in assert', () => {
const InputData = { value: Field };
const spec = Spec({
data: Credential.Unsigned(InputData),
threshold: Claim(Field),
lowLimit: Constant(Field, Field(10)),
highLimit: Constant(Field, Field(20)),
}, ({ data, threshold, lowLimit, highLimit }) => ({
assert: Operation.ifThenElse(Operation.lessThan(Operation.property(data, 'value'), threshold), Operation.lessThan(Operation.property(data, 'value'), lowLimit), Operation.not(Operation.lessThan(Operation.property(data, 'value'), highLimit))),
outputClaim: Operation.property(data, 'value'),
}));
// value < threshold and < lowLimit (should pass)
const root1 = {
owner: unsafeMissingOwner(),
data: cred({ value: Field(5) }),
threshold: Field(15),
lowLimit: Field(10),
highLimit: Field(20),
};
const assertResult1 = Node.eval(root1, spec.assert);
assert.strictEqual(assertResult1.toBoolean(), true);
// value >= threshold and >= highLimit (should pass)
const root2 = {
owner: unsafeMissingOwner(),
data: cred({ value: Field(25) }),
threshold: Field(15),
lowLimit: Field(10),
highLimit: Field(20),
};
const assertResult2 = Node.eval(root2, spec.assert);
assert.strictEqual(assertResult2.toBoolean(), true);
// lowLimit <= value < threshold (should fail)
const root3 = {
owner: unsafeMissingOwner(),
data: cred({ value: Field(12) }),
threshold: Field(15),
lowLimit: Field(10),
highLimit: Field(20),
};
const assertResult3 = Node.eval(root3, spec.assert);
assert.strictEqual(assertResult3.toBoolean(), false);
// threshold <= value < highLimit (should fail)
const root4 = {
owner: unsafeMissingOwner(),
data: cred({ value: Field(18) }),
threshold: Field(15),
lowLimit: Field(10),
highLimit: Field(20),
};
const assertResult4 = Node.eval(root4, spec.assert);
assert.strictEqual(assertResult4.toBoolean(), false);
});
await t.test('Spec with ifThenElse as part of a complex assert', () => {
const InputData = { value: Field };
const spec = Spec({
data: Credential.Unsigned(InputData),
threshold: Claim(Field),
lowLimit: Constant(Field, Field(10)),
highLimit: Constant(Field, Field(20)),
}, ({ data, threshold, lowLimit, highLimit }) => ({
assert: Operation.and(Operation.lessThan(lowLimit, Operation.property(data, 'value')), Operation.ifThenElse(Operation.lessThan(Operation.property(data, 'value'), threshold), Operation.lessThan(Operation.property(data, 'value'), highLimit), Operation.equals(Operation.property(data, 'value'), highLimit))),
outputClaim: Operation.property(data, 'value'),
}));
// 10 < value < threshold < highLimit (should pass)
const root1 = {
owner: unsafeMissingOwner(),
data: cred({ value: Field(15) }),
threshold: Field(18),
lowLimit: Field(10),
highLimit: Field(20),
};
const assertResult1 = Node.eval(root1, spec.assert);
assert.strictEqual(assertResult1.toBoolean(), true);
// 10 < threshold <= value = highLimit (should pass)
const root2 = {
owner: unsafeMissingOwner(),
data: cred({ value: Field(20) }),
threshold: Field(18),
lowLimit: Field(10),
highLimit: Field(20),
};
const assertResult2 = Node.eval(root2, spec.assert);
assert.strictEqual(assertResult2.toBoolean(), true);
// value <= lowLimit (should fail)
const root3 = {
owner: unsafeMissingOwner(),
data: cred({ value: Field(10) }),
threshold: Field(18),
lowLimit: Field(10),
highLimit: Field(20),
};
const assertResult3 = Node.eval(root3, spec.assert);
assert.strictEqual(assertResult3.toBoolean(), false);
// 10 < threshold <= value < highLimit (should fail)
const root4 = {
owner: unsafeMissingOwner(),
data: cred({ value: Field(19) }),
threshold: Field(18),
lowLimit: Field(10),
highLimit: Field(20),
};
const assertResult4 = Node.eval(root4, spec.assert);
assert.strictEqual(assertResult4.toBoolean(), false);
});
await t.test('Spec with compute operations', async (t) => {
await t.test('compute with addition', () => {
const spec = Spec({
input: Credential.Unsigned({ x: Field, y: Field }),
}, ({ input }) => ({
outputClaim: Operation.compute([Operation.property(input, 'x'), Operation.property(input, 'y')], Field, (x, y) => x.add(y)),
}));
const root = {
owner: unsafeMissingOwner(),
input: cred({ x: Field(10), y: Field(5) }),
};
const result = Node.eval(root, spec.outputClaim);
assert.deepStrictEqual(result, Field(15));
});
await t.test('compute with boolean output', () => {
const spec = Spec({
value: Credential.Unsigned(Field),
threshold: Claim(Field),
}, ({ value, threshold }) => ({
outputClaim: Operation.compute([value, threshold], Bool, (v, t) => v.greaterThan(t)),
}));
const validRoot = {
owner: unsafeMissingOwner(),
value: cred(Field(20)),
threshold: Field(10),
};
const invalidRoot = {
owner: unsafeMissingOwner(),
value: cred(Field(5)),
threshold: Field(10),
};
const validResult = Node.eval(validRoot, spec.outputClaim);
const invalidResult = Node.eval(invalidRoot, spec.outputClaim);
assert.strictEqual(validResult.toBoolean(), true);
assert.strictEqual(invalidResult.toBoolean(), false);
});
await t.test('compute with nested operations', () => {
const spec = Spec({
position: Credential.Unsigned({
x: Field,
y: Field,
}),
center: Claim({
x: Field,
y: Field,
}),
maxDistance: Claim(Field),
}, ({ position, center, maxDistance }) => {
const distanceSquared = Operation.compute([
Operation.property(position, 'x'),
Operation.property(position, 'y'),
Operation.property(center, 'x'),
Operation.property(center, 'y'),
], Field, (px, py, cx, cy) => {
const dx = px.sub(cx);
const dy = py.sub(cy);
return dx.mul(dx).add(dy.mul(dy));
});
return {
assert: Operation.compute([distanceSquared, maxDistance], Bool, (d, max) => d.lessThanOrEqual(max.mul(max))),
outputClaim: distanceSquared,
};
});
const root = {
owner: unsafeMissingOwner(),
position: cred({
x: Field(3),
y: Field(4),
}),
center: {
x: Field(0),
y: Field(0),
},
maxDistance: Field(5),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Field(25));
});
});
await t.test('Spec with nested properties', () => {
const InputData = { age: Field, name: Bytes32 };
const NestedInputData = { person: InputData, points: Field };
const spec = Spec({
data: Credential.Unsigned(NestedInputData),
targetAge: Claim(Field),
targetPoints: Claim(Field),
}, ({ data, targetAge, targetPoints }) => ({
assert: Operation.and(Operation.equals(Operation.property(Operation.property(data, 'person'), 'age'), targetAge), Operation.equals(Operation.property(data, 'points'), targetPoints)),
outputClaim: Operation.property(Operation.property(data, 'person'), 'name'),
}));
const root = {
owner: unsafeMissingOwner(),
data: cred({
person: { age: Field(25), name: Bytes32.fromString('Bob') },
points: Field(100),
}),
targetAge: Field(25),
targetPoints: Field(100),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Bytes32.fromString('Bob'));
});
await t.test('Spec with constant input', () => {
const InputData = { age: Field, name: Bytes32 };
const spec = Spec({
data: Credential.Unsigned(InputData),
constAge: Constant(Field, Field(25)),
}, ({ data, constAge }) => ({
assert: Operation.equals(Operation.property(data, 'age'), constAge),
outputClaim: Operation.property(data, 'name'),
}));
const root = {
owner: unsafeMissingOwner(),
data: cred({ age: Field(25), name: Bytes32.fromString('Charlie') }),
constAge: Field(25),
};
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Bytes32.fromString('Charlie'));
});
await t.test('Spec with credential', () => {
const InputData = { age: Field, name: Bytes32 };
const spec = Spec({
signedData: Credential.Native(InputData),
targetAge: Claim(Field),
targetName: Claim(Bytes32),
}, ({ 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(30), name: Bytes32.fromString('David') };
const signedData = Credential.sign(issuerKey, { owner, data });
let userInputs = {
context: Field(0),
ownerSignature: Signature.empty(),
credentials: { signedData },
claims: { targetAge: Field(30), targetName: Bytes32.fromString('David') },
};
let { privateInput, publicInput } = splitUserInputs(userInputs);
let credentialOutputs = {
owner,
credentials: [{ data, issuer: Field(1234), witness: undefined }],
};
let root = rootValue(spec, publicInput, privateInput, credentialOutputs);
assert.deepStrictEqual(root, {
owner,
signedData: { data, issuer: Field(1234), witness: undefined },
targetAge: Field(30),
targetName: Bytes32.fromString('David'),
});
const assertResult = Node.eval(root, spec.assert);
const dataResult = Node.eval(root, spec.outputClaim);
assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Field(30));
});
});
//# sourceMappingURL=program-spec.test.js.map