Skip to content

Commit

Permalink
Changed winning ticket to packed winning numbers
Browse files Browse the repository at this point in the history
  • Loading branch information
aii23 committed May 23, 2024
1 parent 1cd9e59 commit 28e61bc
Showing 1 changed file with 25 additions and 157 deletions.
182 changes: 25 additions & 157 deletions src/Lottery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,157 +20,27 @@ import {
SelfProof,
} from 'o1js';
import { PackedUInt32Factory } from 'o1js-pack';

const NUMBERS_IN_TICKET = 6;

const TICKET_PRICE = UInt64.from(10); // #TODO change to field in smartcontract
const BLOCK_PER_ROUND = 480; // Aproximate blocks per day
import { Ticket } from './Ticket';
import { BLOCK_PER_ROUND, NUMBERS_IN_TICKET, TICKET_PRICE } from './constants';
import { DistributionProof } from './DistributionProof';

export class NumberPacked extends PackedUInt32Factory() {}

// #TODO add user address to ticket
// technically we can remove round from ticket
class Ticket extends Struct({
numbers: Provable.Array(UInt8, NUMBERS_IN_TICKET),
round: UInt32,
}) {
static from(numbers: number[], round: number): Ticket {
if (numbers.length != NUMBERS_IN_TICKET) {
throw new Error(
`Wrong amount of numbers. Got: ${numbers.length}, expect: ${NUMBERS_IN_TICKET}`
);
}
return new Ticket({
numbers: numbers.map((number) => UInt8.from(number)),
round: UInt32.from(round),
});
}

static generateFromSeed(seed: Field, round: UInt32): Ticket {
const initMask = 0b1111;
const masks = [...Array(NUMBERS_IN_TICKET)].map(
(val, i) => initMask << (i * 4)
);

const numbers = masks
.map((mask, i) => {
const masked = Gadgets.and(seed, Field.from(mask), (i + 1) * 4);
return Gadgets.rightShift64(masked, i * 4);
})
.map((val) => UInt8.from(val));

return new Ticket({
numbers,
round,
});
}

check(): Bool {
return this.numbers.reduce(
(acc, val) => acc.and(val.lessThan(10)),
Bool(true)
);
}

hash(): Field {
return Poseidon.hash(
this.numbers.map((number) => number.value).concat(this.round.value)
);
}

getScore(winningCombination: Field[]): Field {
let result = Field.from(0);

for (let i = 0; i < NUMBERS_IN_TICKET; i++) {
result = result.add(
Provable.if(
winningCombination[i].equals(this.numbers[i].value),
Field.from(1),
Field.from(0)
)
);
}

const conditions = [...Array(NUMBERS_IN_TICKET)].map((val, index) =>
result.equals(index)
);
const generateNumbersSeed = (seed: Field): UInt32[] => {
const initMask = 0b1111;
const masks = [...Array(NUMBERS_IN_TICKET)].map(
(val, i) => initMask << (i * 4)
);

const values = [0, 10, 100, 1000, 10000, 100000].map((val) =>
Field.from(val)
);

return Provable.switch(conditions, Field, values);
}
}
const numbers = masks
.map((mask, i) => {
const masked = Gadgets.and(seed, Field.from(mask), (i + 1) * 4);
return Gadgets.rightShift64(masked, i * 4);
})
.map((val) => UInt32.fromFields([val])); // #TODO can we use fromFields here?

export class DistributionProofPublicInput extends Struct({
winingCombination: Provable.Array(Field, NUMBERS_IN_TICKET),
ticket: Ticket,
oldValue: Field,
valueWitness: MerkleMapWitness,
valueDiff: Field,
}) {}

export class DistributionProofPublicOutput extends Struct({
root: Field,
total: Field,
}) {}

const emptyMap = new MerkleMap();
const emptyMapRoot = emptyMap.getRoot();

const DistibutionProgram = ZkProgram({
name: 'distribution-program',
publicInput: DistributionProofPublicInput,
publicOutput: DistributionProofPublicOutput,
methods: {
init: {
privateInputs: [],
async method(): Promise<DistributionProofPublicOutput> {
return new DistributionProofPublicOutput({
root: emptyMapRoot,
total: Field.from(0),
});
},
},
addTicket: {
privateInputs: [SelfProof],
async method(
input: DistributionProofPublicInput,
prevProof: SelfProof<
DistributionProofPublicInput,
DistributionProofPublicOutput
>
) {
input.valueDiff.assertGreaterThan(
Field.from(0),
'valueDiff should be > 0'
);
prevProof.verify();

const [initialRoot, key] = input.valueWitness.computeRootAndKey(
input.oldValue
);
key.assertEquals(input.ticket.hash(), 'Wrong key for that ticket');
initialRoot.assertEquals(prevProof.publicOutput.root);

const newValue = input.oldValue.add(input.valueDiff);

const [newRoot] = input.valueWitness.computeRootAndKey(newValue);
const ticketScore = input.ticket
.getScore(input.winingCombination)
.mul(input.valueDiff);

return new DistributionProofPublicOutput({
root: newRoot,
total: prevProof.publicOutput.total.add(ticketScore),
});
},
},
},
});

export class DistributionProof extends ZkProgram.Proof(DistibutionProgram) {}
return numbers;
};

// #TODO constrain round to current

Expand Down Expand Up @@ -266,15 +136,14 @@ export class Lottery extends SmartContract {
);

// Generate new ticket using value from blockchain
let winningTicket = Ticket.generateFromSeed(
this.network.stakingEpochData.seed.getAndRequireEquals(), // #TODO check how often it is updated
UInt32.fromFields([round]) // #TODO check can we do like it
let winningNumbers = generateNumbersSeed(
this.network.stakingEpochData.seed.getAndRequireEquals() // Probably not secure as seed is not updating quite often
);

let newLeafValue = NumberPacked.pack(winningNumbers);

// Update result tree
const [newResultRoot] = resultWiness.computeRootAndKey(
winningTicket.hash()
);
const [newResultRoot] = resultWiness.computeRootAndKey(newLeafValue);

this.roundResultRoot.set(newResultRoot);
}
Expand All @@ -285,7 +154,7 @@ export class Lottery extends SmartContract {
roundWitness: MerkleMapWitness,
ticketWitness: MerkleMapWitness,
dp: DistributionProof,
winningTicket: Ticket, // We do not need ticket here, we can zipp numbers in field. But for simplicity we will use ticket for now
winningNumbers: Field,
resutWitness: MerkleMapWitness
) {
// Verify distibution proof
Expand Down Expand Up @@ -316,9 +185,8 @@ export class Lottery extends SmartContract {
);

// Check result root info
const [resultRoot, resultRound] = resutWitness.computeRootAndKey(
winningTicket.hash()
);
const [resultRoot, resultRound] =
resutWitness.computeRootAndKey(winningNumbers);
resultRound.assertEquals(
round,
'Winning ticket and your ticket is from different rounds'
Expand All @@ -329,7 +197,7 @@ export class Lottery extends SmartContract {

// Compute score using winnging ticket
const score = ticket.getScore(
winningTicket.numbers.map((number) => number.value)
NumberPacked.unpack(winningNumbers).map((number) => number.value)
);

// Pay user
Expand Down

0 comments on commit 28e61bc

Please sign in to comment.