l1-lottery-contracts
Version:
This repo contains smart contracts and related scripts for ZkNoid L1 Lottery proposed [here](https://forums.minaprotocol.com/t/zknoid-l1-lottery/6269)
171 lines • 7.29 kB
JavaScript
import { Field, MerkleList, Poseidon, Reducer, SelfProof, Struct, UInt64, ZkProgram, } from 'o1js';
import { Ticket } from '../Structs/Ticket.js';
import { MerkleMap20, MerkleMap20Witness } from '../Structs/CustomMerkleMap.js';
import { TICKET_PRICE } from '../constants.js';
import { NumberPacked } from '../util.js';
const emptyMap20 = new MerkleMap20();
// https://github.com/o1-labs/o1js-bindings/blob/71f2e698dadcdfc62c76a72248c0df71cfd39d4c/lib/binable.ts#L317
let encoder = new TextEncoder();
function stringToBytes(s) {
return [...encoder.encode(s)];
}
function prefixToField(
// Field: GenericSignableField<Field>,
Field, prefix) {
let fieldSize = Field.sizeInBytes;
if (prefix.length >= fieldSize)
throw Error('prefix too long');
let stringBytes = stringToBytes(prefix);
return Field.fromBytes(stringBytes.concat(Array(fieldSize - stringBytes.length).fill(0)));
}
// hashing helpers taken from https://github.com/o1-labs/o1js/blob/72a2779c6728e80e0c9d1462020347c954a0ffb5/src/lib/mina/events.ts#L28
function initialState() {
return [Field(0), Field(0), Field(0)];
}
function salt(prefix) {
return Poseidon.update(initialState(), [prefixToField(Field, prefix)]);
}
function hashWithPrefix(prefix, input) {
let init = salt(prefix);
return Poseidon.update(init, input)[0];
}
function emptyHashWithPrefix(prefix) {
return salt(prefix)[0];
}
export class LotteryAction extends Struct({
ticket: Ticket,
}) {
}
export const actionListAdd = (hash, action) => {
return Poseidon.hashWithPrefix('MinaZkappSeqEvents**', [
hash,
Poseidon.hashWithPrefix('MinaZkappEvent******', LotteryAction.toFields(action)),
]);
};
export class ActionList extends MerkleList.create(LotteryAction, actionListAdd, emptyHashWithPrefix('MinaZkappActionsEmpty')) {
}
export const merkleActionsAdd = (hash, actionsHash) => {
return Poseidon.hashWithPrefix('MinaZkappSeqEvents**', [hash, actionsHash]);
};
export class MerkleActions extends MerkleList.create(ActionList.provable, (hash, x) => merkleActionsAdd(hash, x.hash), emptyHashWithPrefix('MinaZkappActionStateEmptyElt')) {
}
export class TicketReduceProofPublicInput extends Struct({
action: LotteryAction,
ticketWitness: MerkleMap20Witness,
}) {
}
export class TicketReduceProofPublicOutput extends Struct({
finalState: Field,
newTicketRoot: Field,
newBank: Field,
totalScore: UInt64,
processedActionList: Field,
lastProcessedTicketId: Field,
winningNumbersPacked: Field,
}) {
}
export const init = async (input, winningNumbersPacked) => {
return new TicketReduceProofPublicOutput({
finalState: Reducer.initialActionState,
newTicketRoot: emptyMap20.getRoot(),
newBank: Field(0),
totalScore: UInt64.from(0),
processedActionList: ActionList.emptyHash,
lastProcessedTicketId: Field(-1),
winningNumbersPacked,
});
};
export const addTicket = async (input, prevProof) => {
prevProof.verify();
let [prevTicketRoot, ticketId] = input.ticketWitness.computeRootAndKeyV2(Field(0));
const expectedTicketId = prevProof.publicOutput.lastProcessedTicketId.add(1);
ticketId.assertEquals(expectedTicketId, 'Wrong id for ticket');
prevTicketRoot.assertEquals(prevProof.publicOutput.newTicketRoot, 'Wrong ticket root');
// Update root
let [newTicketRoot] = input.ticketWitness.computeRootAndKeyV2(input.action.ticket.hash());
let newBank = prevProof.publicOutput.newBank.add(TICKET_PRICE.mul(input.action.ticket.amount).value);
let processedActionList = actionListAdd(prevProof.publicOutput.processedActionList, input.action);
let ticketScore = input.action.ticket.getScore(NumberPacked.unpack(prevProof.publicOutput.winningNumbersPacked));
let newTotalScore = prevProof.publicOutput.totalScore.add(ticketScore);
return new TicketReduceProofPublicOutput({
finalState: prevProof.publicOutput.finalState,
newTicketRoot,
newBank,
totalScore: newTotalScore,
lastProcessedTicketId: expectedTicketId,
processedActionList,
winningNumbersPacked: prevProof.publicOutput.winningNumbersPacked,
});
};
export const cutActions = async (input, prevProof) => {
prevProof.verify();
let finalState = merkleActionsAdd(prevProof.publicOutput.finalState, prevProof.publicOutput.processedActionList);
let processedActionList = ActionList.emptyHash;
return new TicketReduceProofPublicOutput({
finalState,
newTicketRoot: prevProof.publicOutput.newTicketRoot,
newBank: prevProof.publicOutput.newBank,
totalScore: prevProof.publicOutput.totalScore,
processedActionList,
lastProcessedTicketId: prevProof.publicOutput.lastProcessedTicketId,
winningNumbersPacked: prevProof.publicOutput.winningNumbersPacked,
});
};
export const refund = async (input, prevProof) => {
prevProof.verify();
const ticket = input.action.ticket;
let [prevTicketRoot] = input.ticketWitness.computeRootAndKeyV2(Field(ticket.hash()));
prevTicketRoot.assertEquals(prevProof.publicOutput.newTicketRoot, 'Wrong ticket witness for refund');
let newBank = prevProof.publicOutput.newBank.sub(TICKET_PRICE.mul(ticket.amount).value);
let ticketScore = input.action.ticket.getScore(NumberPacked.unpack(prevProof.publicOutput.winningNumbersPacked));
let newTotalScore = prevProof.publicOutput.totalScore.sub(ticketScore);
let [newTicketRoot] = input.ticketWitness.computeRootAndKeyV2(Field(0));
return new TicketReduceProofPublicOutput({
finalState: prevProof.publicOutput.finalState,
newTicketRoot,
newBank,
totalScore: newTotalScore,
processedActionList: prevProof.publicOutput.processedActionList,
lastProcessedTicketId: prevProof.publicOutput.lastProcessedTicketId,
winningNumbersPacked: prevProof.publicOutput.winningNumbersPacked,
});
};
/*
init: simple initializer, create empty proof
addTicket: process next ticket, updates roots of merkle tries. Add actions to processedActionList merkleList
cutActions: updates finalState by adding processedActionList to finalState merkle list
*/
export const TicketReduceProgram = ZkProgram({
name: 'ticket-reduce-program',
publicInput: TicketReduceProofPublicInput,
publicOutput: TicketReduceProofPublicOutput,
methods: {
init: {
privateInputs: [Field],
async method(input, winningCombinationPacked) {
return init(input, winningCombinationPacked);
},
},
addTicket: {
privateInputs: [SelfProof],
async method(input, prevProof) {
return addTicket(input, prevProof);
},
},
cutActions: {
privateInputs: [SelfProof],
async method(input, prevProof) {
return cutActions(input, prevProof);
},
},
refund: {
privateInputs: [SelfProof],
async method(input, prevProof) {
return refund(input, prevProof);
},
},
},
});
export class TicketReduceProof extends ZkProgram.Proof(TicketReduceProgram) {
}
//# sourceMappingURL=TicketReduceProof.js.map