Skip to content

Commit

Permalink
Merge pull request #22 from zksecurity/16-comparison-operators
Browse files Browse the repository at this point in the history
Add comparison
  • Loading branch information
martonmoro authored Oct 1, 2024
2 parents bf11553 + f4030b9 commit c912c9b
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 0 deletions.
71 changes: 71 additions & 0 deletions src/program-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import {
assert,
Bool,
Bytes,
UInt8,
UInt32,
UInt64,
Field,
PrivateKey,
Provable,
Expand Down Expand Up @@ -218,6 +221,8 @@ const Input = {
const Operation = {
property,
equals,
lessThan,
lessThanEq,
and,
};

Expand All @@ -240,6 +245,8 @@ type Node<Data = any> =
| { type: 'root'; input: Record<string, Input> }
| { type: 'property'; key: string; inner: Node }
| { type: 'equals'; left: Node; right: Node }
| { type: 'lessThan'; left: Node; right: Node }
| { type: 'lessThanEq'; left: Node; right: Node }
| { type: 'and'; left: Node<Bool>; right: Node<Bool> };

type OutputNode<Data = any> = {
Expand Down Expand Up @@ -273,6 +280,9 @@ function evalNode<Data>(root: object, node: Node<Data>): Data {
let bool = Provable.equal(ProvableType.fromValue(left), left, right);
return bool as Data;
}
case 'lessThan':
case 'lessThanEq':
return compareNodes(root, node, node.type === 'lessThanEq') as Data;
case 'and': {
let left = evalNode(root, node.left);
let right = evalNode(root, node.right);
Expand All @@ -281,6 +291,46 @@ function evalNode<Data>(root: object, node: Node<Data>): Data {
}
}

function compareNodes(
root: object,
node: { left: Node<any>; right: Node<any> },
allowEqual: boolean
): Bool {
const numericTypeOrder = [UInt8, UInt32, UInt64, Field];

let left = evalNode(root, node.left);
let right = evalNode(root, node.right);

const leftTypeIndex = numericTypeOrder.findIndex(
(type) => left instanceof type
);
const rightTypeIndex = numericTypeOrder.findIndex(
(type) => right instanceof type
);

const leftConverted =
leftTypeIndex < rightTypeIndex
? right instanceof Field
? left.toField()
: right instanceof UInt64
? left.toUInt64()
: left.toUInt32()
: left;

const rightConverted =
leftTypeIndex > rightTypeIndex
? left instanceof Field
? right.toField()
: left instanceof UInt64
? right.toUInt64()
: right.toUInt32()
: right;

return allowEqual
? leftConverted.lessThanOrEqual(rightConverted)
: leftConverted.lessThan(rightConverted);
}

function evalNodeType<Data>(
rootType: NestedProvable,
node: Node<Data>
Expand All @@ -307,6 +357,12 @@ function evalNodeType<Data>(
case 'equals': {
return Bool as any;
}
case 'lessThan': {
return Bool as any;
}
case 'lessThanEq': {
return Bool as any;
}
case 'and': {
return Bool as any;
}
Expand Down Expand Up @@ -353,6 +409,21 @@ function equals<Data>(left: Node<Data>, right: Node<Data>): Node<Bool> {
return { type: 'equals', left, right };
}

type NumericType = Field | UInt64 | UInt32 | UInt8;
function lessThan<Data extends NumericType>(
left: Node<Data>,
right: Node<Data>
): Node<Bool> {
return { type: 'lessThan', left, right };
}

function lessThanEq<Data extends NumericType>(
left: Node<Data>,
right: Node<Data>
): Node<Bool> {
return { type: 'lessThanEq', left, right };
}

function and(left: Node<Bool>, right: Node<Bool>): Node<Bool> {
return { type: 'and', left, right };
}
Expand Down
60 changes: 60 additions & 0 deletions tests/program-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,66 @@ test(' Spec and Node operations', async (t) => {
assert.deepStrictEqual(dataResult, Field(30));
});

await t.test('Spec with multiple assertions and lessThan', () => {
const InputData = { age: Field, name: Bytes32 };
const spec = Spec(
{
data: Input.private(InputData),
targetAge: Input.public(Field),
targetName: Input.public(Bytes32),
},
({ data, targetAge, targetName }) => ({
assert: Operation.and(
Operation.lessThan(targetAge, Operation.property(data, 'age')),
Operation.equals(Operation.property(data, 'name'), targetName)
),
data: Operation.property(data, 'age'),
})
);

const root = {
data: { age: Field(30), name: Bytes32.fromString('Alice') },
targetAge: Field(18),
targetName: Bytes32.fromString('Alice'),
};

const assertResult = Node.eval(root, spec.logic.assert);
const dataResult = Node.eval(root, spec.logic.data);

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: Input.private(InputData),
targetAge: Input.public(Field),
targetName: Input.public(Bytes32),
},
({ data, targetAge, targetName }) => ({
assert: Operation.and(
Operation.lessThanEq(Operation.property(data, 'age'), targetAge),
Operation.equals(Operation.property(data, 'name'), targetName)
),
data: Operation.property(data, 'age'),
})
);

const root = {
data: { age: Field(30), name: Bytes32.fromString('Alice') },
targetAge: Field(30),
targetName: Bytes32.fromString('Alice'),
};

const assertResult = Node.eval(root, spec.logic.assert);
const dataResult = Node.eval(root, spec.logic.data);

assert.strictEqual(assertResult.toBoolean(), true);
assert.deepStrictEqual(dataResult, Field(30));
});

await t.test('Spec with nested properties', () => {
const InputData = { age: Field, name: Bytes32 };
const NestedInputData = { person: InputData, points: Field };
Expand Down

0 comments on commit c912c9b

Please sign in to comment.