-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
349 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { | ||
abimethod, | ||
Account, | ||
arc4, | ||
assert, | ||
BigUint, | ||
Bytes, | ||
ensureBudget, | ||
Global, | ||
itxn, | ||
LocalState, | ||
op, | ||
OpUpFeeSource, | ||
TemplateVar, | ||
Txn, | ||
uint64, | ||
} from '@algorandfoundation/algorand-typescript' | ||
|
||
const curveMod = 21888242871839275222246405745257275088548364400416034343698204186575808495617n | ||
const verifierBudget = 145000 | ||
|
||
export default class ZkWhitelistContract extends arc4.Contract { | ||
appName: arc4.Str | undefined | ||
whiteList = LocalState<boolean>() | ||
|
||
@abimethod({ onCreate: 'require' }) | ||
create(name: arc4.Str) { | ||
// Create the application | ||
this.appName = name | ||
} | ||
|
||
@abimethod({ allowActions: ['UpdateApplication', 'DeleteApplication'] }) | ||
update() { | ||
// Update the application if it is mutable (manager only) | ||
assert(Global.creatorAddress === Txn.sender) | ||
} | ||
|
||
@abimethod({ allowActions: ['OptIn', 'CloseOut'] }) | ||
optInOrOut() { | ||
// Opt in or out of the application | ||
return | ||
} | ||
|
||
@abimethod() | ||
addAddressToWhitelist(address: arc4.Address, proof: arc4.DynamicArray<arc4.Address>): arc4.Str { | ||
/* | ||
Add caller to the whitelist if the zk proof is valid. | ||
On success, will return an empty string. Otherwise, will return an error | ||
message. | ||
*/ | ||
ensureBudget(verifierBudget, OpUpFeeSource.GroupCredit) | ||
// The verifier expects public inputs to be in the curve field, but an | ||
// Algorand address might represent a number larger than the field | ||
// modulus, so to be safe we take the address modulo the field modulus | ||
const addressMod = arc4.interpretAsArc4<arc4.Address>(op.bzero(32).bitwiseOr(Bytes(BigUint(address.bytes) % curveMod))) | ||
// Verify the proof by calling the deposit verifier app | ||
const verified = this.verifyProof(TemplateVar<uint64>('VERIFIER_APP_ID'), proof, new arc4.DynamicArray(addressMod)) | ||
if (!verified) { | ||
return new arc4.Str('Proof verification failed') | ||
} | ||
// if successful, add the sender to the whitelist by setting local state | ||
const account = Account(address.bytes) | ||
if (Txn.sender !== account) { | ||
return new arc4.Str('Sender address does not match authorized address') | ||
} | ||
this.whiteList(account).value = true | ||
return new arc4.Str('') | ||
} | ||
|
||
@abimethod() | ||
isOnWhitelist(address: arc4.Address): arc4.Bool { | ||
// Check if an address is on the whitelist | ||
const account = address.native | ||
const optedIn = op.appOptedIn(account, Global.currentApplicationId) | ||
if (!optedIn) { | ||
return new arc4.Bool(false) | ||
} | ||
const whitelisted = this.whiteList(account).value | ||
return new arc4.Bool(whitelisted) | ||
} | ||
|
||
verifyProof(appId: uint64, proof: arc4.DynamicArray<arc4.Address>, publicInputs: arc4.DynamicArray<arc4.Address>): arc4.Bool { | ||
// Verify a proof using the verifier app. | ||
const verified = itxn | ||
.applicationCall({ | ||
appId: appId, | ||
fee: 0, | ||
appArgs: [arc4.methodSelector('verify(byte[32][],byte[32][])bool'), proof.copy(), publicInputs.copy()], | ||
onCompletion: arc4.OnCompleteAction.NoOp, | ||
}) | ||
.submit().lastLog | ||
return arc4.interpretAsArc4<arc4.Bool>(verified, 'log') | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { arc4, Bytes } from '@algorandfoundation/algorand-typescript' | ||
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing' | ||
import { afterEach, describe, expect, it } from 'vitest' | ||
import { ABI_RETURN_VALUE_LOG_PREFIX } from '../../src/constants' | ||
import ZkWhitelistContract from './contract.algo' | ||
|
||
describe('ZK Whitelist', () => { | ||
const ctx = new TestExecutionContext() | ||
|
||
afterEach(() => { | ||
ctx.reset() | ||
}) | ||
|
||
it('should be able to add address to whitelist', () => { | ||
// Arrange | ||
const contract = ctx.contract.create(ZkWhitelistContract) | ||
contract.create(ctx.any.arc4.str(10)) | ||
|
||
const address = new arc4.Address(ctx.defaultSender) | ||
const proof = new arc4.DynamicArray<arc4.Address>(new arc4.Address(Bytes(new Uint8Array(Array(32).fill(0))))) | ||
|
||
const dummyVerifierApp = ctx.any.application({ appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes.fromHex('80'))] }) | ||
ctx.setTemplateVar('VERIFIER_APP_ID', dummyVerifierApp.id) | ||
|
||
// Act | ||
const result = contract.addAddressToWhitelist(address, proof) | ||
|
||
// Assert | ||
expect(result.native).toEqual('') | ||
expect(contract.whiteList(ctx.defaultSender).value).toEqual(true) | ||
}) | ||
|
||
it('returns error message if proof verification fails', () => { | ||
// Arrange | ||
const contract = ctx.contract.create(ZkWhitelistContract) | ||
contract.create(ctx.any.arc4.str(10)) | ||
|
||
const address = ctx.any.arc4.address() | ||
const proof = new arc4.DynamicArray<arc4.Address>(new arc4.Address(Bytes(new Uint8Array(Array(32).fill(0))))) | ||
const dummyVerifierApp = ctx.any.application({ appLogs: [ABI_RETURN_VALUE_LOG_PREFIX.concat(Bytes(''))] }) | ||
ctx.setTemplateVar('VERIFIER_APP_ID', dummyVerifierApp.id) | ||
|
||
// Act | ||
const result = contract.addAddressToWhitelist(address, proof) | ||
|
||
// Assert | ||
expect(result.native).toEqual('Proof verification failed') | ||
Check failure on line 47 in examples/zk-whitelist/contract.spec.ts
|
||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { OpUpFeeSource, uint64 } from '@algorandfoundation/algorand-typescript' | ||
|
||
export function ensureBudgetImpl(_budget: uint64, _feeSource: OpUpFeeSource = OpUpFeeSource.GroupCredit) { | ||
// ensureBudget function is emulated to be a no-op | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { internal } from '@algorandfoundation/algorand-typescript' | ||
import { DEFAULT_TEMPLATE_VAR_PREFIX } from '../constants' | ||
import { lazyContext } from '../context-helpers/internal-context' | ||
|
||
export function TemplateVarImpl<T>(variableName: string, prefix = DEFAULT_TEMPLATE_VAR_PREFIX): T { | ||
const key = prefix + variableName | ||
if (!Object.hasOwn(lazyContext.value.templateVars, key)) { | ||
throw internal.errors.codeError(`Template variable ${key} not found in test context!`) | ||
} | ||
return lazyContext.value.templateVars[prefix + variableName] as T | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.