UNPKG

mina-attestations

Version:
716 lines 31.2 kB
import { test } from 'node:test'; import assert from 'node:assert'; import { Spec, Claim, Constant } from "../src/program-spec.js"; import { Operation } from "../src/operation.js"; import { serializeNode, serializeInput, serializeSpec, } from "../src/serialize-spec.js"; import { Bool, Field, PublicKey, Signature, UInt32, UInt64, UInt8 } from 'o1js'; import { Credential } from "../src/credential-index.js"; import { serializeNestedProvable, serializeProvableType, } from "../src/serialize-provable.js"; import { ContextSchema, InputSchema, NodeSchema, } from "../src/validation.js"; import { serializeInputContext, deserializeHttpsContext, deserializeZkAppContext, } from "../src/context.js"; import { zkAppIdentity } from "./test-utils.js"; test('Serialize Inputs', async (t) => { await t.test('should serialize basic types correctly', () => { assert.deepStrictEqual(serializeProvableType(Field), { _type: 'Field' }); assert.deepStrictEqual(serializeProvableType(Bool), { _type: 'Bool' }); assert.deepStrictEqual(serializeProvableType(UInt8), { _type: 'UInt8' }); assert.deepStrictEqual(serializeProvableType(UInt32), { _type: 'UInt32' }); assert.deepStrictEqual(serializeProvableType(UInt64), { _type: 'UInt64' }); assert.deepStrictEqual(serializeProvableType(PublicKey), { _type: 'PublicKey', }); assert.deepStrictEqual(serializeProvableType(Signature), { _type: 'Signature', }); }); await t.test('should serialize structs correctly', () => { // TODO: implement }); await t.test('should serialize simple provable types (nested)', () => { assert.deepStrictEqual(serializeNestedProvable(Field), { _type: 'Field', }); assert.deepStrictEqual(serializeNestedProvable(Bool), { _type: 'Bool' }); assert.deepStrictEqual(serializeNestedProvable(UInt8), { _type: 'UInt8' }); assert.deepStrictEqual(serializeNestedProvable(UInt32), { _type: 'UInt32', }); assert.deepStrictEqual(serializeNestedProvable(UInt64), { _type: 'UInt64', }); assert.deepStrictEqual(serializeNestedProvable(PublicKey), { _type: 'PublicKey', }); assert.deepStrictEqual(serializeNestedProvable(Signature), { _type: 'Signature', }); }); await t.test('should serialize nested objects with provable types', () => { const nestedType = { field: Field, nested: { uint: UInt32, bool: Bool, }, }; assert.deepStrictEqual(serializeNestedProvable(nestedType), { field: { _type: 'Field' }, nested: { bool: { _type: 'Bool' }, uint: { _type: 'UInt32' }, }, }); }); await t.test('should serialize complex nested structures', () => { const complexType = { simpleField: Field, nestedObject: { publicKey: PublicKey, signature: Signature, deeplyNested: { bool: Bool, uint64: UInt64, }, }, }; assert.deepStrictEqual(serializeNestedProvable(complexType), { simpleField: { _type: 'Field' }, nestedObject: { publicKey: { _type: 'PublicKey' }, signature: { _type: 'Signature' }, deeplyNested: { bool: { _type: 'Bool' }, uint64: { _type: 'UInt64' }, }, }, }); }); await t.test('should throw an error for unsupported types', () => { assert.throws(() => serializeNestedProvable(123), { name: 'Error', message: 'Unsupported type in NestedProvable: 123', }); }); }); test('Serialize Nodes', async (t) => { await t.test('should serialize constant Node', () => { const constantNode = { type: 'constant', data: Field(123) }; const serialized = serializeNode(constantNode); const expected = { type: 'constant', data: { _type: 'Field', value: '123' }, }; assert.deepEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize root Node', () => { const rootNode = { type: 'root', input: { age: Credential.Unsigned(Field), isAdmin: Claim(Bool), }, }; const serialized = serializeNode(rootNode); const expected = { type: 'root' }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize property Node', () => { const propertyNode = { type: 'property', key: 'age', inner: { type: 'root', input: { age: Credential.Unsigned(Field), isAdmin: Claim(Bool), }, }, }; const serialized = serializeNode(propertyNode); const expected = { type: 'property', key: 'age', inner: { type: 'root' } }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize equals Node', () => { const equalsNode = Operation.equals({ type: 'constant', data: Field(10) }, { type: 'constant', data: Field(10) }); const serialized = serializeNode(equalsNode); const expected = { type: 'equals', left: { type: 'constant', data: { _type: 'Field', value: '10' } }, right: { type: 'constant', data: { _type: 'Field', value: '10' } }, }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize lessThan Node', () => { const lessThanNode = Operation.lessThan({ type: 'constant', data: UInt32.from(5) }, { type: 'constant', data: UInt32.from(10) }); const serialized = serializeNode(lessThanNode); const expected = { type: 'lessThan', left: { type: 'constant', data: { _type: 'UInt32', value: '5' } }, right: { type: 'constant', data: { _type: 'UInt32', value: '10' } }, }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize lessThanEq Node', () => { const lessThanEqNode = Operation.lessThanEq({ type: 'constant', data: UInt64.from(15) }, { type: 'constant', data: UInt64.from(15) }); const serialized = serializeNode(lessThanEqNode); const expected = { type: 'lessThanEq', left: { type: 'constant', data: { _type: 'UInt64', value: '15' } }, right: { type: 'constant', data: { _type: 'UInt64', value: '15' } }, }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize and Node', () => { const andNode = Operation.and({ type: 'constant', data: Bool(true) }, { type: 'constant', data: Bool(false) }); const serialized = serializeNode(andNode); const expected = { type: 'and', inputs: [ { type: 'constant', data: { _type: 'Bool', value: true } }, { type: 'constant', data: { _type: 'Bool', value: false } }, ], }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize or Node', () => { const orNode = Operation.or({ type: 'constant', data: Bool(true) }, { type: 'constant', data: Bool(false) }); const serialized = serializeNode(orNode); const expected = { type: 'or', left: { type: 'constant', data: { _type: 'Bool', value: true } }, right: { type: 'constant', data: { _type: 'Bool', value: false } }, }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize add Node', () => { const addNode = Operation.add({ type: 'constant', data: Field(5) }, { type: 'constant', data: Field(10) }); const serialized = serializeNode(addNode); const expected = { type: 'add', left: { type: 'constant', data: { _type: 'Field', value: '5' } }, right: { type: 'constant', data: { _type: 'Field', value: '10' } }, }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize sub Node', () => { const subNode = Operation.sub({ type: 'constant', data: Field(15) }, { type: 'constant', data: Field(7) }); const serialized = serializeNode(subNode); const expected = { type: 'sub', left: { type: 'constant', data: { _type: 'Field', value: '15' } }, right: { type: 'constant', data: { _type: 'Field', value: '7' } }, }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize mul Node', () => { const mulNode = Operation.mul({ type: 'constant', data: Field(3) }, { type: 'constant', data: Field(4) }); const serialized = serializeNode(mulNode); const expected = { type: 'mul', left: { type: 'constant', data: { _type: 'Field', value: '3' } }, right: { type: 'constant', data: { _type: 'Field', value: '4' } }, }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize div Node', () => { const divNode = Operation.div({ type: 'constant', data: Field(20) }, { type: 'constant', data: Field(5) }); const serialized = serializeNode(divNode); const expected = { type: 'div', left: { type: 'constant', data: { _type: 'Field', value: '20' } }, right: { type: 'constant', data: { _type: 'Field', value: '5' } }, }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize not Node', () => { const notNode = Operation.not({ type: 'constant', data: Bool(true), }); const serialized = serializeNode(notNode); const expected = { type: 'not', inner: { type: 'constant', data: { _type: 'Bool', value: true } }, }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize hash Node', () => { const hashNode = Operation.hash({ type: 'constant', data: Field(123), }); const serialized = serializeNode(hashNode); const expected = { type: 'hash', inputs: [{ type: 'constant', data: { _type: 'Field', value: '123' } }], prefix: null, }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize ifThenElse Node', () => { const ifThenElseNode = Operation.ifThenElse({ type: 'constant', data: Bool(true) }, { type: 'constant', data: Field(1) }, { type: 'constant', data: Field(0) }); const serialized = serializeNode(ifThenElseNode); const expected = { type: 'ifThenElse', condition: { type: 'constant', data: { _type: 'Bool', value: true } }, thenNode: { type: 'constant', data: { _type: 'Field', value: '1' } }, elseNode: { type: 'constant', data: { _type: 'Field', value: '0' } }, }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize record Node', () => { const recordNode = Operation.record({ field1: { type: 'constant', data: Field(123) }, field2: { type: 'constant', data: Bool(true) }, }); const serialized = serializeNode(recordNode); const expected = { type: 'record', data: { field1: { type: 'constant', data: { _type: 'Field', value: '123' } }, field2: { type: 'constant', data: { _type: 'Bool', value: true } }, }, }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize nested Nodes', () => { const nestedNode = Operation.and(Operation.lessThan({ type: 'constant', data: Field(5) }, { type: 'constant', data: Field(10) }), Operation.equals({ type: 'constant', data: Bool(true) }, { type: 'constant', data: Bool(true) })); const serialized = serializeNode(nestedNode); const expected = { type: 'and', inputs: [ { type: 'lessThan', left: { type: 'constant', data: { _type: 'Field', value: '5' } }, right: { type: 'constant', data: { _type: 'Field', value: '10' } }, }, { type: 'equals', left: { type: 'constant', data: { _type: 'Bool', value: true } }, right: { type: 'constant', data: { _type: 'Bool', value: true } }, }, ], }; assert.deepStrictEqual(serialized, expected); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize equalsOneOf Node with array options', () => { const options = [ { type: 'constant', data: Field(10) }, { type: 'constant', data: Field(20) }, { type: 'constant', data: Field(30) }, ]; const equalsOneOfNode = Operation.equalsOneOf({ type: 'constant', data: Field(20) }, options); const serialized = serializeNode(equalsOneOfNode); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid with array options: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize equalsOneOf Node with single node options', () => { const optionsNode = { type: 'constant', data: [Field(10), Field(20), Field(30)], }; const equalsOneOfNode = Operation.equalsOneOf({ type: 'constant', data: Field(20) }, optionsNode); const serialized = serializeNode(equalsOneOfNode); const result = NodeSchema.safeParse(serialized); assert(result.success, 'Node should be valid with single node options: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); }); test('serializeInput', async (t) => { await t.test('should serialize constant input', () => { const input = Constant(Field, Field(42)); const serialized = serializeInput(input); const expected = { type: 'constant', data: { _type: 'Field' }, value: '42', }; assert.deepStrictEqual(serialized, expected); const result = InputSchema.safeParse(serialized); assert(result.success, 'Constant input should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize public input', () => { const input = Claim(Field); const serialized = serializeInput(input); const expected = { type: 'claim', data: { _type: 'Field' }, }; assert.deepStrictEqual(serialized, expected); const result = InputSchema.safeParse(serialized); assert(result.success, 'Claim input should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize private input', () => { const input = Credential.Unsigned(Field); const serialized = serializeInput(input); const expected = { type: 'credential', credentialType: 'unsigned', witness: null, data: { _type: 'Field' }, }; assert.deepStrictEqual(serialized, expected); const result = InputSchema.safeParse(serialized); assert(result.success, 'Private input should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize credential input', () => { const InputData = { age: Field, isAdmin: Bool }; const input = Credential.Native(InputData); const serialized = serializeInput(input); const expected = { type: 'credential', credentialType: 'native', witness: null, data: { age: { _type: 'Field' }, isAdmin: { _type: 'Bool' }, }, }; assert.deepStrictEqual(serialized, expected); const result = InputSchema.safeParse(serialized); assert(result.success, 'Credential input should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize unsigned nested input', () => { const NestedInputData = { personal: { age: Field, id: UInt64, }, score: UInt32, }; const input = Credential.Unsigned(NestedInputData); const serialized = serializeInput(input); const expected = { type: 'credential', credentialType: 'unsigned', witness: null, data: { personal: { age: { _type: 'Field' }, id: { _type: 'UInt64' }, }, score: { _type: 'UInt32' }, }, }; assert.deepStrictEqual(serialized, expected); const result = InputSchema.safeParse(serialized); assert(result.success, 'Nested input should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should serialize native credential input with nested structure', () => { const input = Credential.Native({ personal: { age: Field, score: UInt64, }, verified: Bool, }); const serialized = serializeInput(input); const result = InputSchema.safeParse(serialized); assert(result.success, 'Nested structure input should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); }); await t.test('should throw error for unsupported input type', () => { const invalidInput = { type: 'invalid' }; assert.throws(() => serializeInput(invalidInput), { name: 'Error', message: 'Invalid input type', }); }); }); test('convertSpecToSerializable', async (t) => { await t.test('should serialize a simple Spec', () => { const spec = Spec({ age: Credential.Unsigned(Field), isAdmin: Claim(Bool), maxAge: Constant(Field, Field(100)), }, ({ age, isAdmin, maxAge }) => ({ assert: Operation.and(Operation.lessThan(age, maxAge), isAdmin), outputClaim: age, })); const serialized = serializeSpec(spec); const expected = { inputs: { age: { type: 'credential', credentialType: 'unsigned', witness: null, data: { _type: 'Field' }, }, isAdmin: { type: 'claim', data: { _type: 'Bool' } }, maxAge: { type: 'constant', data: { _type: 'Field' }, value: '100' }, }, assert: { type: 'and', inputs: [ { type: 'lessThan', left: { type: 'credential', credentialKey: 'age', credentialType: 'unsigned', }, right: { type: 'property', key: 'maxAge', inner: { type: 'root' }, }, }, { type: 'property', key: 'isAdmin', inner: { type: 'root' }, }, ], }, outputClaim: { type: 'credential', credentialKey: 'age', credentialType: 'unsigned', }, }; assert.deepStrictEqual(serialized, expected); }); await t.test('should serialize a Spec with an credential', () => { const spec = Spec({ signedData: Credential.Native({ field: Field }), zeroField: Constant(Field, Field(0)), }, ({ signedData, zeroField }) => ({ assert: Operation.equals(Operation.property(signedData, 'field'), zeroField), outputClaim: signedData, })); const serialized = serializeSpec(spec); const expected = { inputs: { signedData: { type: 'credential', credentialType: 'native', witness: null, data: { field: { _type: 'Field' }, }, }, zeroField: { type: 'constant', data: { _type: 'Field' }, value: '0' }, }, assert: { type: 'equals', left: { type: 'property', key: 'field', inner: { type: 'credential', credentialKey: 'signedData', credentialType: 'native', }, }, right: { type: 'property', key: 'zeroField', inner: { type: 'root' }, }, }, outputClaim: { type: 'credential', credentialKey: 'signedData', credentialType: 'native', }, }; assert.deepStrictEqual(serialized, expected); }); await t.test('should serialize a Spec with nested operations', () => { const spec = Spec({ field1: Credential.Unsigned(Field), field2: Credential.Unsigned(Field), zeroField: Constant(Field, Field(0)), }, ({ field1, field2, zeroField }) => ({ assert: Operation.and(Operation.lessThan(field1, field2), Operation.equals(field1, zeroField)), outputClaim: field2, })); const serialized = serializeSpec(spec); const expected = { inputs: { field1: { type: 'credential', credentialType: 'unsigned', witness: null, data: { _type: 'Field' }, }, field2: { type: 'credential', credentialType: 'unsigned', witness: null, data: { _type: 'Field' }, }, zeroField: { type: 'constant', data: { _type: 'Field' }, value: '0' }, }, assert: { type: 'and', inputs: [ { type: 'lessThan', left: { type: 'credential', credentialKey: 'field1', credentialType: 'unsigned', }, right: { type: 'credential', credentialKey: 'field2', credentialType: 'unsigned', }, }, { type: 'equals', left: { type: 'credential', credentialKey: 'field1', credentialType: 'unsigned', }, right: { type: 'property', key: 'zeroField', inner: { type: 'root' }, }, }, ], }, outputClaim: { type: 'credential', credentialKey: 'field2', credentialType: 'unsigned', }, }; assert.deepStrictEqual(serialized, expected); }); }); test('Serialize spec with owner and issuer nodes', async (t) => { const InputData = { age: Field }; const SignedData = Credential.Native(InputData); const spec = Spec({ signedData: SignedData, targetAge: Claim(Field), }, ({ signedData, targetAge }) => ({ assert: Operation.equals(Operation.property(signedData, 'age'), targetAge), outputClaim: Operation.record({ owner: Operation.owner, issuer: Operation.issuer(signedData), age: Operation.property(signedData, 'age'), }), })); const serialized = serializeSpec(spec); assert.deepStrictEqual(serialized.outputClaim.data.owner, { type: 'owner', }); assert.deepStrictEqual(serialized.outputClaim.data.issuer, { type: 'issuer', credentialKey: 'signedData', }); }); test('serializeInputContext', async (t) => { await t.test('should serialize zk-app context', () => { const context = { type: 'zk-app', action: 'myMethod', verifierIdentity: zkAppIdentity, serverNonce: Field(789), }; const serialized = serializeInputContext(context); assert.deepStrictEqual(serialized, { type: 'zk-app', action: 'myMethod', serverNonce: { _type: 'Field', value: '789' }, verifierIdentity: { network: 'devnet', publicKey: { _type: 'PublicKey', value: zkAppIdentity.publicKey.toBase58(), }, tokenId: { _type: 'Field', value: '1' }, }, }); const result = ContextSchema.safeParse(serialized); assert(result.success, 'Valid ZkApp context should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); const deserialized = deserializeZkAppContext(serialized); assert.deepStrictEqual(deserialized, context); const reserialized = serializeInputContext(deserialized); assert.deepStrictEqual(serialized, reserialized); }); await t.test('should serialize https context', () => { const context = { type: 'https', action: 'POST /api/verify', serverNonce: Field(789), }; const serialized = serializeInputContext(context); assert.deepStrictEqual(serialized, { type: 'https', action: 'POST /api/verify', serverNonce: { _type: 'Field', value: '789' }, }); const result = ContextSchema.safeParse(serialized); assert(result.success, 'Valid HTTPS context should be valid: ' + (result.success ? '' : JSON.stringify(result.error.issues, null, 2))); const deserialized = deserializeHttpsContext(serialized); assert.deepStrictEqual(deserialized, context); const reserialized = serializeInputContext(deserialized); assert.deepStrictEqual(serialized, reserialized); }); }); //# sourceMappingURL=serialize.test.js.map