Skip to main content

Private Guest List Contract

This Compact contract implements a party with a private guest list. It offers demonstration of the following features:

  • Hiding information on the public ledger
  • Verifying hidden ledger information
  • Access control to circuits
  • Operations on a Set
  • Introduction to witness functions
pragma language_version 0.22;
import CompactStandardLibrary;

export enum PartyState {
NOT_READY,
READY
}

export ledger organizers: Set<Bytes<32>>;// organizers are public
export ledger hashedPartyGoers: Set<Bytes<32>>;// participants are hashed until they arrive
export ledger checkedInParty: Set<Bytes<32>>;
export ledger partyState: PartyState;
export sealed ledger maxListSize: Uint<8>;

witness localStartParty(): PartyState;
witness localSk(): Bytes<32>;

constructor(){
const _sk = localSk();
const pubKey = publicKey(_sk);
organizers.insert(disclose(pubKey));

partyState = PartyState.NOT_READY;
maxListSize = 99;
}

export circuit addOrganizer(_organizerSk: Bytes<32>): [] {
const _sk = localSk();
const pubKey = publicKey(_sk);
assert(organizers.member(disclose(pubKey)), "You are not an organizer");

const newOrgPubKey = publicKey(_organizerSk);
assert(!organizers.member(disclose(newOrgPubKey)), "You are already in the organizer list");
assert(partyState == PartyState.NOT_READY, "The party has already started");

organizers.insert(disclose(newOrgPubKey));
}

export circuit addParticipant(_participantPk: Bytes<32>): [] {
// only organizers can add party goers
const _sk = localSk();
const pubKey = publicKey(_sk);
assert(organizers.member(disclose(pubKey)), "You are not an organizer");

assert(partyState == PartyState.NOT_READY, "The party has already started");
assert(hashedPartyGoers.size() < maxListSize, "The list is full");

const participant = commitWithSk(_participantPk, _sk);
assert(!hashedPartyGoers.member(disclose(participant)), "You are already in the list");
hashedPartyGoers.insert(disclose(participant));

if (hashedPartyGoers.size() == maxListSize) {
const localPartyState = localStartParty();
// don't trust, verify
assert(localPartyState == PartyState.READY, "Please start the party, the list is full");
partyState = PartyState.READY;
}
}

export circuit checkIn(participantPk: Bytes<32>): [] {
const _sk = localSk();
const pubKey = publicKey(_sk);
assert(organizers.member(disclose(pubKey)), "You are not an organizer");

assert(partyState == PartyState.READY, "The party has not started yet");
assert(checkedInParty.size() < hashedPartyGoers.size(), "All guests have already checked in");
assert(hashedPartyGoers.member(commitWithSk(participantPk, _sk)), "You are not on the list");

checkedInParty.insert(disclose(participantPk));
}

export circuit chainStartParty(): [] {
const _sk = localSk();
const pubKey = publicKey(_sk);
assert(organizers.member(disclose(pubKey)), "Only organizers can start the party");

assert(partyState == PartyState.NOT_READY, "The party has already started");

const localPartyState = disclose(localStartParty());
assert(localPartyState == PartyState.READY, "Please start the party locally");
partyState = localPartyState;
}

circuit commitWithSk(_participantPk: Bytes<32>, _sk: Bytes<32>): Bytes<32> {
const hash = persistentHash<Vector<2, Bytes<32>>>([_participantPk, _sk]);
return disclose(hash);
}

// hash a publicKey specific to this DApp so that users cannot be tracked
export circuit publicKey(_sk: Bytes<32>): Bytes<32> {
return persistentHash<Vector<2, Bytes<32>>>([pad(32, "guest-list:pk:"), _sk]);
}