Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve validation + nullifier hash migration #379

Merged
merged 8 commits into from Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 10 additions & 4 deletions hasura/metadata/databases/default/tables/public_nullifier.yaml
Expand Up @@ -12,7 +12,7 @@ insert_permissions:
columns:
- action_id
- id
- merkle_root
- uses
- nullifier_hash
- credential_type
select_permissions:
Expand All @@ -30,7 +30,6 @@ select_permissions:
- action_id
- created_at
- id
- merkle_root
- nullifier_hash
- credential_type
filter:
Expand All @@ -44,7 +43,7 @@ select_permissions:
- action_id
- created_at
- id
- merkle_root
- uses
- nullifier_hash
- credential_type
filter: {}
Expand All @@ -54,7 +53,7 @@ select_permissions:
- action_id
- created_at
- id
- merkle_root
- uses
- nullifier_hash
- credential_type
filter:
Expand All @@ -64,3 +63,10 @@ select_permissions:
users:
id:
_eq: X-Hasura-User-Id
update_permissions:
- role: service
permission:
columns:
- uses
filter: {}
check: null
@@ -0,0 +1,28 @@
BEGIN;

ALTER TABLE "public"."nullifier"
DROP CONSTRAINT unique_nullifier_hash,
ADD COLUMN "merkle_root" TEXT NOT NULL;

DO $$
DECLARE
row_record RECORD;
BEGIN

FOR row_record IN (SELECT * FROM "public"."nullifier" WHERE uses > 1)
LOOP
-- Duplicate each row (uses - 1) times
FOR i IN 1..(row_record.uses - 1)
LOOP
INSERT INTO "public"."nullifier" (action_id, created_at, updated_at, nullifier_hash, merkle_root, credential_type)
VALUES (row_record.action_id, row_record.created_at, row_record.updated_at, row_record.nullifier_hash, row_record.merkle_root, row_record.credential_type);
END LOOP;
END LOOP;

UPDATE "public"."nullifier" SET uses = 1;

END $$;

ALTER TABLE "public"."nullifier" DROP COLUMN "uses";

COMMIT;
@@ -0,0 +1,36 @@
BEGIN;

ALTER TABLE "public"."nullifier"
DROP COLUMN "merkle_root",
ADD COLUMN uses INT DEFAULT 1;

CREATE TEMPORARY TABLE duplicates ON COMMIT DROP AS (
SELECT nullifier_hash, MIN(created_at) AS min_created_at, COUNT(*) AS cnt
FROM "public"."nullifier"
GROUP BY nullifier_hash
HAVING COUNT(*) > 1
);

-- get the earliest record for each nullifier_hash
CREATE TEMPORARY TABLE earliest ON COMMIT DROP AS (
SELECT id, nullifier_hash, created_at
FROM "public"."nullifier"
WHERE (nullifier_hash, created_at) IN (SELECT nullifier_hash, min_created_at FROM duplicates)
);

-- update the main nullifier_hash record with the appropriate number of uses
UPDATE "public"."nullifier"
SET uses = duplicates.cnt
FROM duplicates, earliest
WHERE "public"."nullifier".id = earliest.id
AND earliest.nullifier_hash = duplicates.nullifier_hash;

-- remove all but the earliest record for each nullifier_hash
DELETE FROM "public"."nullifier"
WHERE nullifier_hash IN (SELECT nullifier_hash FROM duplicates)
AND id NOT IN (SELECT id FROM earliest);

-- add the uniqueness constraint
ALTER TABLE "public"."nullifier" ADD CONSTRAINT unique_nullifier_hash UNIQUE(nullifier_hash);

COMMIT;
Expand Up @@ -14,7 +14,7 @@ export type FetchUserQuery = {
__typename?: "user";
id: string;
name: string;
email: string;
email?: string | null;
team: { __typename?: "team"; id: string; name?: string | null };
}>;
};
Expand Down
11 changes: 6 additions & 5 deletions web/src/backend/utils.ts
Expand Up @@ -130,14 +130,15 @@ export const protectConsumerBackendEndpoint = (
* Checks whether the person can be verified for a particular action based on the max number of verifications
*/
export const canVerifyForAction = (
nullifiers:
| Array<{
nullifier:
| {
uses: number;
nullifier_hash: string;
}>
}
| undefined,
max_verifications_per_person: number
): boolean => {
if (!nullifiers?.length) {
if (!nullifier) {
// Person has not verified before, can always verify for the first time
return true;
} else if (max_verifications_per_person <= 0) {
Expand All @@ -146,7 +147,7 @@ export const canVerifyForAction = (
}

// Else, can only verify if the max number of verifications has not been met
return (nullifiers?.length ?? 0) < max_verifications_per_person;
return nullifier.uses < max_verifications_per_person;
};

export const reportAPIEventToPostHog = async (
Expand Down
20 changes: 14 additions & 6 deletions web/src/backend/verify.ts
Expand Up @@ -24,15 +24,15 @@ const KNOWN_ERROR_CODES = [
},
];

interface IInputParams {
export interface IInputParams {
merkle_root: string;
signal: string;
nullifier_hash: string;
external_nullifier: string;
proof: string;
}

interface IVerifyParams {
export interface IVerifyParams {
is_staging: boolean;
credential_type: CredentialType;
}
Expand All @@ -48,6 +48,8 @@ interface IAppAction {
status: string;
external_nullifier: string;
nullifiers: {
uses: number;
created_at: string;
nullifier_hash: string;
}[];
max_verifications: number;
Expand Down Expand Up @@ -78,6 +80,8 @@ const queryFetchAppAction = gql`
external_nullifier
status
nullifiers(where: { nullifier_hash: { _eq: $nullifier_hash } }) {
uses
created_at
nullifier_hash
}
}
Expand Down Expand Up @@ -123,8 +127,7 @@ export const fetchActionForProof = async (
if (!result.data.app.length) {
return {
error: {
message:
"We couldn't find an app with this ID. App may be no longer active.",
message: "App not found. App may be no longer active.",
code: "not_found",
statusCode: 404,
},
Expand All @@ -136,7 +139,7 @@ export const fetchActionForProof = async (
if (!app.actions.length) {
return {
error: {
message: "We couldn't find the relevant action.",
message: "Action not found.",
code: "invalid_action",
statusCode: 400,
attribute: "action",
Expand All @@ -156,7 +159,12 @@ export const fetchActionForProof = async (
}

return {
app: { ...app, action: app.actions[0], actions: undefined },
app: {
...app,
actions: undefined,
action: app.actions[0],
nullifier: app.actions[0]?.nullifiers?.[0],
},
};
};

Expand Down
Expand Up @@ -13,7 +13,7 @@ export type FetchUserQuery = {
user: Array<{
__typename?: "user";
id: string;
email: string;
email?: string | null;
name: string;
team: { __typename?: "team"; id: string; name?: string | null };
}>;
Expand Down