import { type Confidential, Default, handler, lift, type MaxConfidentiality, NAME, pattern, Stream, UI, type VNode, Writable, } from "commonfabric"; import { exchangeRule, exchangeRules, type PolicyOf, THIS_POLICY, } from "commonfabric/cfc"; // The policy releases the tally only when everything the tally read was // written by this module's `commit` step, and everything `commit` read was // written by its `submit` step. The identity alone would release whatever // `tallyBallot` computed, over any input a caller chose; the `inputWitness` // names the code that must have written the tally's confidential inputs, so // a vote list written by any other code is refused, and the nested one does // the same for `commit`'s inputs, so a brief other code added is refused. // Each brief is an object in a list, which the runtime stores behind a // reference that carries the writer's stamp too. export const releaseTally = exchangeRule({ appliesTo: THIS_POLICY, pre: { integrity: [{ type: "https://commonfabric.org/cfc/atom/TransformedBy", identity: { kind: "verified", moduleIdentity: THIS_POLICY.moduleIdentity, symbol: "tallyBallot", }, inputWitness: { type: "https://commonfabric.org/cfc/atom/TransformedBy", identity: { kind: "verified", moduleIdentity: THIS_POLICY.moduleIdentity, symbol: "commit", }, inputWitness: { type: "https://commonfabric.org/cfc/atom/TransformedBy", identity: { kind: "verified", moduleIdentity: THIS_POLICY.moduleIdentity, symbol: "submit", }, }, }, }], }, post: { dropClause: true }, }); export const ballotRules = exchangeRules([releaseTally]); export interface Brief { vote: "approve" | "reject"; } export type Sealed = Confidential< T, readonly [PolicyOf] >; /** A store anyone in the room may read: it admits only public values. */ export type RoomText = MaxConfidentiality; export interface Committed { votes: string[]; } export const submit = handler[]> }>( (brief, { briefs }) => { briefs.push({ vote: brief.vote } as Sealed); }, ); /** The endorsed step whose output the tally must have read. */ export const commit = handler< void, { briefs: Sealed[]; committed: Writable> } >((_, { briefs, committed }) => { committed.set( { votes: (briefs ?? []).map((brief) => brief?.vote ?? "") } as Sealed< Committed >, ); }); /** Not the endorsed step: writes a vote list of its own choosing. */ export const forgeCommitted = handler< void, { briefs: Sealed[]; committed: Writable> } >((_, { briefs, committed }) => { committed.set( { votes: (briefs ?? []).map(() => "approve") } as Sealed, ); }); /** Not the entry point: adds a brief of its own choosing. */ export const plantBrief = handler< void, { briefs: Writable[]> } >((_, { briefs }) => { briefs.push({ vote: "approve" } as Sealed); }); /** Not the endorsed step: adds one vote beside the committed ones. */ export const appendVote = handler< void, { committed: Writable> } >((_, { committed }) => { committed.key("votes").push("approve"); }); /** Not the endorsed step: copies the committed votes into a list of its own. */ export const relayVotes = lift(( committed: Committed | undefined, ): Committed => ({ votes: [...(committed?.votes ?? [])], })); /** The released computation: how many votes approve. */ export const tallyBallot = lift((committed: Committed | undefined): string => String( (committed?.votes ?? []).filter((vote) => vote === "approve").length, ) ); interface WitnessedChainInput { briefs: Writable[], []>>; committed: Writable, { votes: [] }>>; } export interface WitnessedChainOutput { [NAME]: string; [UI]: VNode; tally: string; relayTally: string; submit: Stream; commit: Stream; forge: Stream; append: Stream; plant: Stream; } const WitnessedChain = pattern( ({ briefs, committed }) => { const tally = tallyBallot(committed); const relayTally = tallyBallot(relayVotes(committed)); return { [NAME]: "Witnessed CFC chain", [UI]: ( Tally
{tally}
), tally, relayTally, submit: submit({ briefs }), commit: commit({ briefs, committed }), forge: forgeCommitted({ briefs, committed }), append: appendVote({ committed }), plant: plantBrief({ briefs }), }; }, ); export default WitnessedChain;