Skip to content

Commit

Permalink
Add tuple and list membership modules (#1660)
Browse files Browse the repository at this point in the history
This PR adds tuple and list membership modules to the `gpcircuits`
package. This includes additions to the proto POD GPC and modifications
to the tests. All tests pass.

Some relevant notes for context:

* Tuples are represented as Poseidon hashes of arrays of entry value
hashes of some fixed length (assumed to be between 2 and 4), and the
module outputs multiple tuples as dictated by one of its parameters.
Value hashes are chosen by index, and tuples may be fed into other
tuples by appropriate indexing. An index of -1 implies a reference to 0,
the value of choice for padding. One tuple module is instantiated in the
proto POD GPC.

* List membership is checked in the usual linear way. Lists are assumed
to be padded with the first element of the list. One list membership
module is instantiated in the proto POD GPC and the value whose
membership is to be checked is referenced by index, where an index may
refer to either an entry value hash or a computed tuple hash. Again, an
index equal to -1 implies a reference to 0, though this device isn't
strictly necessary in this case.

* Some helper functions were added. A variant of `InputSelector` is
included to allow for -1 index trick mentioned above while keeping an
out-of-bounds check in place otherwise.

* Some changes were made to the circuit family parameters.

Marking this as a draft for now as the tests need to be fleshed out a
bit to cover some edge cases (e.g. the case of an empty membership
list), and I'd like to give the code (and comments) a once-over. The
design is unlikely to change much, so feel free to peruse.

---------

Co-authored-by: Andrew Twyman <[email protected]>
  • Loading branch information
ax0 and artwyman authored May 10, 2024
1 parent 64ac01f commit cb48db6
Show file tree
Hide file tree
Showing 31 changed files with 45,750 additions and 90 deletions.
43,780 changes: 43,780 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

48 changes: 32 additions & 16 deletions packages/lib/gpc/src/gpcChecks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import {
import {
checkPODEntryIdentifier,
splitCircuitIdentifier,
splitPODEntryIdentifier
splitPODEntryIdentifier,
DEFAULT_MAX_LISTS,
DEFAULT_MAX_LIST_ELEMENTS,
DEFAULT_MAX_TUPLES,
DEFAULT_TUPLE_ARITY
} from "./gpcUtil";

// TODO(POD-P2): Split out the parts of this which should be public from
Expand Down Expand Up @@ -96,11 +100,15 @@ export function checkProofConfig(
);
}

return {
maxObjects: totalObjects,
maxEntries: totalEntries,
merkleMaxDepth: requiredMerkleDepth
};
return ProtoPODGPCCircuitParams(
totalObjects,
totalEntries,
requiredMerkleDepth,
DEFAULT_MAX_LISTS,
DEFAULT_MAX_LIST_ELEMENTS,
DEFAULT_MAX_TUPLES,
DEFAULT_TUPLE_ARITY
);
}

function checkProofObjConfig(
Expand Down Expand Up @@ -200,11 +208,15 @@ export function checkProofInputs(
checkPODValue("watermark", proofInputs.watermark);
}

return {
maxObjects: totalObjects,
maxEntries: 1,
merkleMaxDepth: requiredMerkleDepth
};
return ProtoPODGPCCircuitParams(
totalObjects,
1,
requiredMerkleDepth,
DEFAULT_MAX_LISTS,
DEFAULT_MAX_LIST_ELEMENTS,
DEFAULT_MAX_TUPLES,
DEFAULT_TUPLE_ARITY
);
}

/**
Expand Down Expand Up @@ -402,11 +414,15 @@ export function checkRevealedClaims(
checkPODValue("watermark", revealedClaims.watermark);
}

return {
maxObjects: totalObjects,
maxEntries: totalEntries,
merkleMaxDepth: requiredMerkleDepth
};
return ProtoPODGPCCircuitParams(
totalObjects,
totalEntries,
requiredMerkleDepth,
DEFAULT_MAX_LISTS,
DEFAULT_MAX_LIST_ELEMENTS,
DEFAULT_MAX_TUPLES,
DEFAULT_TUPLE_ARITY
);
}

function checkRevealedObjectClaims(
Expand Down
22 changes: 21 additions & 1 deletion packages/lib/gpc/src/gpcCompile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ import {
GPCRevealedOwnerClaims,
PODEntryIdentifier
} from "./gpcTypes";
import { makeWatermarkSignal } from "./gpcUtil";
import {
dummyListMembership,
dummyTuples,
makeWatermarkSignal
} from "./gpcUtil";

/**
* Per-object info extracted by {@link prepCompilerMaps}.
Expand Down Expand Up @@ -184,6 +188,12 @@ export function compileProofConfig(
entryConstraintMetadata.firstOwnerIndex
);

// Create subset of inputs for multituple module padded to max size.
const circuitMultiTupleInputs = dummyTuples(circuitDesc);

// Create subset of inputs for list membership module padded to max size.
const circuitListMembershipInputs = dummyListMembership(circuitDesc);

// Create other global inputs.
const circuitGlobalInputs = compileProofGlobal(proofInputs);

Expand All @@ -196,6 +206,8 @@ export function compileProofConfig(
...circuitEntryInputs,
...circuitEntryConstraintInputs,
...circuitOwnerInputs,
...circuitMultiTupleInputs,
...circuitListMembershipInputs,
...circuitGlobalInputs
};
}
Expand Down Expand Up @@ -484,6 +496,12 @@ export function compileVerifyConfig(
entryConstraintMetadata.firstOwnerIndex
);

// Create subset of inputs for multituple module padded to max size.
const circuitMultiTupleInputs = dummyTuples(circuitDesc);

// Create subset of inputs for list membership module padded to max size.
const circuitListMembershipInputs = dummyListMembership(circuitDesc);

// Create other global inputs. Logic shared with compileProofConfig,
// since all the signals involved are public.
const circuitGlobalInputs = compileProofGlobal(verifyRevealed);
Expand All @@ -498,6 +516,8 @@ export function compileVerifyConfig(
...circuitEntryInputs,
...circuitEntryConstraintInputs,
...circuitOwnerInputs,
...circuitMultiTupleInputs,
...circuitListMembershipInputs,
...circuitGlobalInputs
},
circuitOutputs: {
Expand Down
54 changes: 53 additions & 1 deletion packages/lib/gpc/src/gpcUtil.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { CircuitDesc } from "@pcd/gpcircuits";
import {
CircuitDesc,
CircuitSignal,
padArray,
ProtoPODGPCCircuitDesc
} from "@pcd/gpcircuits";
import {
PODName,
PODValue,
Expand Down Expand Up @@ -209,3 +214,50 @@ export function makeWatermarkSignal(podValue: PODValue | undefined): bigint {
}
return getPODValueForCircuit(podValue) ?? podValueHash(podValue);
}

// TODO(POD-P2): Get rid of everything below this line.

// Stopgap until membership list compilation is ready.
export const DEFAULT_MAX_LISTS = 1;
export const DEFAULT_MAX_LIST_ELEMENTS = 1;
export const DEFAULT_MAX_TUPLES = 1;
export const DEFAULT_TUPLE_ARITY = 2;

// Returns default values for the input to the (multi)tuple module, viz.
// an array of tuples of 0, which corresponds to choosing the 0th entry
// value hash. Since these are not constrained anywhere (i.e. not in the
// list membership check, cf. below), there is no effect on the underlying
// logic.
export function dummyTuples(circuitDesc: ProtoPODGPCCircuitDesc): {
tupleIndices: CircuitSignal[][];
} {
return {
tupleIndices: padArray(
[],
circuitDesc.maxTuples,
padArray([], circuitDesc.tupleArity, 0n)
)
};
}

// Returns default values for the inputs to the list membership module, viz.
// a comparison value index of -1, which is a reference to the value 0, and
// a list of valid values consisting of zeroes. This results in the list
// membership check being trivially satisfied.
export function dummyListMembership(circuitDesc: ProtoPODGPCCircuitDesc): {
listComparisonValueIndex: CircuitSignal[];
listValidValues: CircuitSignal[][];
} {
return {
listComparisonValueIndex: padArray(
[],
circuitDesc.maxLists,
BABY_JUB_NEGATIVE_ONE
),
listValidValues: padArray(
[],
circuitDesc.maxLists,
padArray([], circuitDesc.maxListElements, 0n)
)
};
}
2 changes: 1 addition & 1 deletion packages/lib/gpcircuits/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
extends: ["@pcd/eslint-config-custom"],
root: true,
root: true
};
2 changes: 1 addition & 1 deletion packages/lib/gpcircuits/circomkit.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"dirCircuits": "./circuits",
"dirInputs": "./inputs",
"dirBuild": "./build",
"optimization": 1,
"optimization": 2,
"inspect": true,
"include": [
"../../../node_modules",
Expand Down
62 changes: 56 additions & 6 deletions packages/lib/gpcircuits/circuits.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
{
"proto-pod-gpc_1o-1e-5md": {
"proto-pod-gpc_1o-1e-5md-1x10l-2x2t": {
"file": "proto-pod-gpc",
"template": "ProtoPODGPC",
"params": [
1,
1,
5
5,
1,
10,
2,
2
],
"pubs": [
"objectSignerPubkeyAx",
Expand All @@ -18,16 +22,52 @@
"ownerEntryIndex",
"ownerExternalNullifier",
"ownerIsNullfierHashRevealed",
"tupleIndices",
"listComparisonValueIndex",
"listValidValues",
"globalWatermark"
]
},
"proto-pod-gpc_1o-5e-8md": {
"proto-pod-gpc_1o-5e-6md-1x10l-2x2t": {
"file": "proto-pod-gpc",
"template": "ProtoPODGPC",
"params": [
1,
5,
8
6,
1,
10,
2,
2
],
"pubs": [
"objectSignerPubkeyAx",
"objectSignerPubkeyAy",
"entryObjectIndex",
"entryNameHash",
"entryIsValueEnabled",
"entryIsValueHashRevealed",
"entryEqualToOtherEntryByIndex",
"ownerEntryIndex",
"ownerExternalNullifier",
"ownerIsNullfierHashRevealed",
"tupleIndices",
"listComparisonValueIndex",
"listValidValues",
"globalWatermark"
]
},
"proto-pod-gpc_3o-10e-8md-1x10l-2x2t": {
"file": "proto-pod-gpc",
"template": "ProtoPODGPC",
"params": [
3,
10,
8,
1,
10,
2,
2
],
"pubs": [
"objectSignerPubkeyAx",
Expand All @@ -40,16 +80,23 @@
"ownerEntryIndex",
"ownerExternalNullifier",
"ownerIsNullfierHashRevealed",
"tupleIndices",
"listComparisonValueIndex",
"listValidValues",
"globalWatermark"
]
},
"proto-pod-gpc_3o-10e-8md": {
"proto-pod-gpc_3o-10e-8md-2x20l-1x4t": {
"file": "proto-pod-gpc",
"template": "ProtoPODGPC",
"params": [
3,
10,
8
8,
2,
20,
1,
4
],
"pubs": [
"objectSignerPubkeyAx",
Expand All @@ -62,6 +109,9 @@
"ownerEntryIndex",
"ownerExternalNullifier",
"ownerIsNullfierHashRevealed",
"tupleIndices",
"listComparisonValueIndex",
"listValidValues",
"globalWatermark"
]
}
Expand Down
35 changes: 35 additions & 0 deletions packages/lib/gpcircuits/circuits/gpc-util.circom
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,38 @@ template InputSelector (N_INPUTS) {
signal muxOut[1] <== Multiplexer(1, N_INPUTS)(muxIn, selectedIndex);
signal output out <== muxOut[0];
}

/**
* Array appender. Useful for feeding a combination of arrays
* into `InputSelector` or `MaybeInputSelector`.
*/
template Append (M, N) {
signal input in1[M];
signal input in2[N];
signal output out[M + N];

for (var i = 0; i < M; i++) {
out[i] <== in1[i];
}

for (var i = 0; i < N; i++) {
out[M + i] <== in2[i];
}
}

/**
* Input selector where `selectedIndex` is interpreted as an index iff it is
* not -1. If `selectedIndex` is -1, then this template outputs 0.
*/
template MaybeInputSelector (N) {
signal input inputs[N];
signal input selectedIndex;

// `delta` is the vector whose ith component is $delta_i^ind$ for i in [0,..., N-1].
signal (delta[N], success) <== Decoder(N)(selectedIndex);

signal output out <== EscalarProduct(N)(inputs, delta);

// `selectedIndex` must have been in [0,...,N-1] or -1.
(1 - success)*(selectedIndex + 1) === 0;
}
38 changes: 38 additions & 0 deletions packages/lib/gpcircuits/circuits/list-membership.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
pragma circom 2.1.8;

include "circomlib/circuits/comparators.circom";

/**
* Module for checking whether a value is a member of a given list.
* A value may be either a POD entry value hash or a combination
* of such values (e.g. a tuple encoded as a hash).
*/
template ListMembershipModule(
// Maximum number of valid values
MAX_LIST_ELEMENTS
) {
// Value to be checked.
signal input comparisonValue;

// List of admissible value hashes. Assumed to have repetitions if the actual list length is smaller.
signal input validValues[MAX_LIST_ELEMENTS];

// Boolean indicating whether `comparisonValue` lies in `validValues`.
signal output isMember;

signal partialProduct[MAX_LIST_ELEMENTS];

for (var i = 0; i < MAX_LIST_ELEMENTS; i++) {
if (i == 0) {
partialProduct[i] <== comparisonValue - validValues[i];
} else {
partialProduct[i] <== partialProduct[i-1] * (comparisonValue - validValues[i]);
}
}

if (MAX_LIST_ELEMENTS == 0) {
isMember <== 0;
} else {
isMember <== IsZero()(partialProduct[MAX_LIST_ELEMENTS - 1]);
}
}
Loading

0 comments on commit cb48db6

Please sign in to comment.