diff --git a/examples/move/crypto/ecdsa_k1/Move.toml b/examples/move/crypto/ecdsa_k1/Move.toml new file mode 100644 index 0000000000000..8ebda25a8188a --- /dev/null +++ b/examples/move/crypto/ecdsa_k1/Move.toml @@ -0,0 +1,10 @@ +[package] +name = "ecdsa_k1" +version = "0.0.1" +edition = "2024.beta" + +[dependencies] +Sui = { local = "../../../../crates/sui-framework/packages/sui-framework" } + +[addresses] +ecdsa_k1 = "0x0" diff --git a/examples/move/crypto/ecdsa_k1/sources/example.move b/examples/move/crypto/ecdsa_k1/sources/example.move new file mode 100644 index 0000000000000..293012d61a9e0 --- /dev/null +++ b/examples/move/crypto/ecdsa_k1/sources/example.move @@ -0,0 +1,119 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// A basic ECDSA utility contract to do the following: +/// +/// 1) Hash a piece of data using Keccak256, output an object with hashed data. +/// 2) Recover a Secp256k1 signature to its public key, output an +/// object with the public key. +/// 3) Verify a Secp256k1 signature, produce an event for whether it is verified. +module ecdsa_k1::example { + use sui::ecdsa_k1; + use sui::event; + + // === Object Types === + + /// Object that holds the output data + public struct Output has key, store { + id: UID, + value: vector + } + + // == Event Types === + + /// Event on whether the signature is verified + public struct VerifiedEvent has copy, drop { + is_verified: bool, + } + + // === Public Functions === + + /// Hash the data using Keccak256, output an object with the hash to recipient. + public fun keccak256( + data: vector, + recipient: address, + ctx: &mut TxContext, + ) { + let hashed = Output { + id: object::new(ctx), + value: sui::hash::keccak256(&data), + }; + // Transfer an output data object holding the hashed data to the recipient. + transfer::public_transfer(hashed, recipient) + } + + /// Recover the public key using the signature and message, assuming the signature was produced + /// over the Keccak256 hash of the message. Output an object with the recovered pubkey to + /// recipient. + public fun ecrecover( + signature: vector, + msg: vector, + recipient: address, + ctx: &mut TxContext, + ) { + let pubkey = Output { + id: object::new(ctx), + value: ecdsa_k1::secp256k1_ecrecover(&signature, &msg, 0), + }; + // Transfer an output data object holding the pubkey to the recipient. + transfer::public_transfer(pubkey, recipient) + } + + /// Recover the Ethereum address using the signature and message, assuming the signature was + /// produced over the Keccak256 hash of the message. Output an object with the recovered address + /// to recipient. + public fun ecrecover_to_eth_address( + mut signature: vector, + msg: vector, + recipient: address, + ctx: &mut TxContext, + ) { + // Normalize the last byte of the signature to be 0 or 1. + let v = &mut signature[64]; + if (*v == 27) { + *v = 0; + } else if (*v == 28) { + *v = 1; + } else if (*v > 35) { + *v = (*v - 1) % 2; + }; + + // Ethereum signature is produced with Keccak256 hash of the message, so the last param is + // 0. + let pubkey = ecdsa_k1::secp256k1_ecrecover(&signature, &msg, 0); + let uncompressed = ecdsa_k1::decompress_pubkey(&pubkey); + + // Take the last 64 bytes of the uncompressed pubkey. + let mut uncompressed_64 = vector[]; + let mut i = 1; + while (i < 65) { + uncompressed_64.push_back(uncompressed[i]); + i = i + 1; + }; + + // Take the last 20 bytes of the hash of the 64-bytes uncompressed pubkey. + let hashed = sui::hash::keccak256(&uncompressed_64); + let mut addr = vector[]; + let mut i = 12; + while (i < 32) { + addr.push_back(hashed[i]); + i = i + 1; + }; + + let addr_object = Output { + id: object::new(ctx), + value: addr, + }; + + // Transfer an output data object holding the address to the recipient. + transfer::public_transfer(addr_object, recipient) + } + + /// Verified the secp256k1 signature using public key and message assuming Keccak was using when + /// signing. Emit an is_verified event of the verification result. + public fun secp256k1_verify(signature: vector, public_key: vector, msg: vector) { + event::emit(VerifiedEvent { + is_verified: ecdsa_k1::secp256k1_verify(&signature, &public_key, &msg, 0) + }); + } +} diff --git a/examples/move/crypto/groth16/Move.toml b/examples/move/crypto/groth16/Move.toml new file mode 100644 index 0000000000000..28c7c3286680b --- /dev/null +++ b/examples/move/crypto/groth16/Move.toml @@ -0,0 +1,10 @@ +[package] +name = "groth16" +version = "0.0.1" +edition = "2024.beta" + +[dependencies] +Sui = { local = "../../../../crates/sui-framework/packages/sui-framework" } + +[addresses] +groth16 = "0x0" diff --git a/examples/move/crypto/groth16/sources/example.move b/examples/move/crypto/groth16/sources/example.move new file mode 100644 index 0000000000000..86bb49d844e11 --- /dev/null +++ b/examples/move/crypto/groth16/sources/example.move @@ -0,0 +1,108 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// A verifier for the Groth16 zk-SNARK over the BLS12-381 construction. +/// See https://eprint.iacr.org/2016/260.pdf for details. +module groth16::example { + use sui::bls12381; + use sui::group_ops::Element; + + // === Types === + + /// A Groth16 proof. + public struct Proof has drop { + a: Element, + b: Element, + c: Element, + } + + /// A Groth16 verifying key used to verify a zero-knowledge proof. + public struct VerifyingKey has store, drop { + alpha: Element, + beta: Element, + gamma: Element, + gamma_abc: vector>, + delta: Element, + } + + /// A prepared verifying key. This makes verification faster than using the verifying key directly. + public struct PreparedVerifyingKey has store, drop { + alpha_beta: Element, + gamma_neg: Element, + gamma_abc: vector>, + delta_neg: Element, + } + + // === Errors === + + #[error] + const EInvalidNumberOfPublicInputs: vector = + b"There must be one more public input than gamma_abc entries in the verifying key."; + + // === Public Functions === + + /// Create a new `Proof`. + public fun create_proof( + a: Element, + b: Element, + c: Element, + ): Proof { + Proof { a, b, c } + } + + /// Create a new `VerifyingKey`. + public fun create_verifying_key( + alpha: Element, + beta: Element, + gamma: Element, + gamma_abc: vector>, + delta: Element, + ): VerifyingKey { + VerifyingKey { alpha, beta, gamma, gamma_abc, delta } + } + + /// Create a PreparedVerifyingKey from a VerifyingKey. This only have to be + /// done once. + public fun prepare(vk: VerifyingKey): PreparedVerifyingKey { + PreparedVerifyingKey { + alpha_beta: bls12381::pairing(&vk.alpha, &vk.beta), + gamma_neg: bls12381::g2_neg(&vk.gamma), + gamma_abc: vk.gamma_abc, + delta_neg: bls12381::g2_neg(&vk.delta), + } + } + + /// Verify a Groth16 proof with some public inputs and a verifying key. + public fun verify( + pvk: &PreparedVerifyingKey, + proof: &Proof, + public_inputs: &vector>, + ): bool { + let prepared_inputs = prepare_inputs(&pvk.gamma_abc, public_inputs); + let mut lhs = bls12381::pairing(&proof.a, &proof.b); + lhs = bls12381::gt_add(&lhs, &bls12381::pairing(&prepared_inputs, &pvk.gamma_neg)); + lhs = bls12381::gt_add(&lhs, &bls12381::pairing(&proof.c, &pvk.delta_neg)); + lhs == pvk.alpha_beta + } + + // === Helpers === + + fun prepare_inputs( + vk_gamma_abc: &vector>, + public_inputs: &vector>, + ): Element { + let length = public_inputs.length(); + assert!(length + 1 == vk_gamma_abc.length(), EInvalidNumberOfPublicInputs); + + let mut output = vk_gamma_abc[0]; + let mut i = 0; + while (i < length) { + output = bls12381::g1_add( + &output, + &bls12381::g1_mul(&public_inputs[i], &vk_gamma_abc[i + 1]), + ); + i = i + 1; + }; + output + } +} diff --git a/examples/move/crypto/groth16/tests/example_tests.move b/examples/move/crypto/groth16/tests/example_tests.move new file mode 100644 index 0000000000000..95e31adc898a2 --- /dev/null +++ b/examples/move/crypto/groth16/tests/example_tests.move @@ -0,0 +1,34 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module groth16::example_tests { + use sui::bls12381; + use groth16::example::{create_verifying_key, create_proof, verify}; + + #[test] + fun test_verification() { + let vk = create_verifying_key( + bls12381::g1_from_bytes(&x"b58cfc3b0f43d98e7dbe865af692577d52813cb62ef3c355215ec3be2a0355a1ae5da76dd3e626f8a60de1f4a8138dee"), + bls12381::g2_from_bytes(&x"9047b42915b32ef9dffe3acc0121a1450416e7f9791159f165ab0729d744da3ed82cd4822ca1d7fef35147cfd620b59b0ca09db7dff43aab6c71635ba8f86a83f058e9922e5cdacbe21d0e5e855cf1e776a61b272c12272fe526f5ba3b48d579"), + bls12381::g2_from_bytes(&x"ad7c5a6cefcae53a3fbae72662c7c04a2f8e1892cb83615a02b32c31247172b7f317489b84e72f14acaf4f3e9ed18141157c6c1464bf15d957227f75a3c550d6d27f295b41a753340c6eec47b471b2cb8664c84f3e9b725325d3fb8afc6b56d0"), + vector[ + bls12381::g1_from_bytes(&x"b2c9c61ccc28e913284a47c34e60d487869ff423dd574db080d35844f9eddd2b2967141b588a35fa82a278ce39ae6b1a"), + bls12381::g1_from_bytes(&x"9026ae12d58d203b4fc5dfad4968cbf51e43632ed1a05afdcb2e380ee552b036fbefc7780afe9675bcb60201a2421b2c") + ], + bls12381::g2_from_bytes(&x"b1294927d02f8e86ac57c3b832f4ecf5e03230445a9a785ac8d25cf968f48cca8881d0c439c7e8870b66567cf611da0c1734316632f39d3125c8cecca76a8661db91cbfae217547ea1fc078a24a1a31555a46765011411094ec649d42914e2f5"), + ); + + let public_inputs = vector[ + bls12381::scalar_from_bytes(&x"46722abc81a82d01ac89c138aa01a8223cb239ceb1f02cdaad7e1815eb997ca6") + ]; + + let proof = create_proof( + bls12381::g1_from_bytes(&x"9913bdcabdff2cf1e7dea1673c5edba5ed6435df2f2a58d6d9e624609922bfa3976a98d991db333812bf6290a590afaa"), + bls12381::g2_from_bytes(&x"b0265b35af5069593ee88626cf3ba9a0f07699510a25aec3a27048792ab83b3467d6b814d1c09c412c4dcd7656582e6607b72915081c82794ccedf643c27abace5b23a442079d8dcbd0d68dd697b8e0b699a1925a5f2c77f5237efbbbeda3bd0"), + bls12381::g1_from_bytes(&x"b1237cf48ca7aa98507e826aac336b9e24f14133de1923fffac602a1203b795b3037c4c94e7246bacee7b2757ae912e5"), + ); + + assert!(vk.prepare().verify(&proof, &public_inputs)); + } +} diff --git a/examples/move/vdf/Move.toml b/examples/move/vdf/Move.toml new file mode 100644 index 0000000000000..3f835c6add095 --- /dev/null +++ b/examples/move/vdf/Move.toml @@ -0,0 +1,10 @@ +[package] +name = "VDF" +version = "0.0.1" +edition = "2024.beta" + +[dependencies] +Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } + +[addresses] +vdf = "0x0" diff --git a/examples/move/vdf/sources/lottery.move b/examples/move/vdf/sources/lottery.move new file mode 100644 index 0000000000000..f61525f6b8159 --- /dev/null +++ b/examples/move/vdf/sources/lottery.move @@ -0,0 +1,231 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// A basic lottery game that depends on user-provided randomness which is processed by a verifiable +/// delay function (VDF) to make sure that it is unbiasable. +/// +/// During the submission phase, players can buy tickets. When buying a ticket, a user must provide +/// some randomness `r`. This randomness is added to the combined randomness of the lottery, `h`, as +/// `h = Sha2_256(h, r)`. +/// +/// After the submission phase has ended, the combined randomness is used to generate an input to +/// the VDF. Anyone may now compute the output and submit it along with a proof of correctness to +/// the `complete` function. If the output and proof verifies, the game ends, and the hash of the +/// output is used to pick a winner. +/// +/// The outcome is guaranteed to be fair if: +/// +/// 1) At least one player contributes true randomness, +/// +/// 2) The number of iterations is defined such that it takes at least `submission_phase_length` to +/// compute the VDF. +module vdf::lottery { + use sui::clock::Clock; + use std::hash::sha2_256; + use sui::vdf::{hash_to_input, vdf_verify}; + + // === Receiver Functions === + + public use fun delete_ticket as Ticket.delete; + public use fun delete_game_winner as GameWinner.delete; + public use fun ticket_game_id as Ticket.game_id; + public use fun game_winner_game_id as GameWinner.game_id; + + // === Object Types === + + /// Game represents a set of parameters of a single game. + /// This game can be extended to require ticket purchase, reward winners, etc. + public struct Game has key { + id: UID, + iterations: u64, + status: u8, + timestamp_start: u64, + submission_phase_length: u64, + participants: u64, + vdf_input_seed: vector, + winner: Option, + } + + /// Ticket represents a participant in a single game. + /// Can be deconstructed only by the owner. + public struct Ticket has key, store { + id: UID, + game_id: ID, + participant_index: u64, + } + + /// GameWinner represents a participant that won in a specific game. + /// Can be deconstructed only by the owner. + public struct GameWinner has key, store { + id: UID, + game_id: ID, + } + + // === Error Codes === + + #[error] + const EGameNotInProgress: vector = + b"Lottery not in progress, cannot participate."; + + #[error] + const EGameAlreadyCompleted: vector = + b"Lottery winner has already been selected"; + + #[error] + const EInvalidTicket: vector = + b"Ticket does not match lottery"; + + #[error] + const ENotWinner: vector = + b"Not the winning ticket"; + + #[error] + const ESubmissionPhaseInProgress: vector = + b"Cannot call winner or redeem funds until submission phase has completed."; + + #[error] + const EInvalidVdfProof: vector = + b"Invalid VDF Proof"; + + #[error] + const ESubmissionPhaseFinished: vector = + b"Cannot participate in a finished lottery."; + + #[error] + const EInvalidRandomness: vector = + b"Randomness length is not correct"; + + // === Constants === + + // Game status + const IN_PROGRESS: u8 = 0; + const COMPLETED: u8 = 1; + + const RANDOMNESS_LENGTH: u64 = 16; + + // === Public Functions === + + /// Create a shared-object Game. + public fun create(iterations: u64, submission_phase_length: u64, clock: &Clock, ctx: &mut TxContext) { + transfer::share_object(Game { + id: object::new(ctx), + iterations, + status: IN_PROGRESS, + timestamp_start: clock.timestamp_ms(), + submission_phase_length, + vdf_input_seed: vector::empty(), + participants: 0, + winner: option::none(), + }); + } + + /// Anyone can participate in the game and receive a ticket. + public fun participate( + self: &mut Game, + my_randomness: vector, + clock: &Clock, + ctx: &mut TxContext, + ): Ticket { + assert!(self.status == IN_PROGRESS, EGameNotInProgress); + assert!( + clock.timestamp_ms() - self.timestamp_start < self.submission_phase_length, + ESubmissionPhaseFinished, + ); + + // Update combined randomness by concatenating the provided randomness and hashing it + assert!(my_randomness.length() == RANDOMNESS_LENGTH, EInvalidRandomness); + self.vdf_input_seed.append(my_randomness); + self.vdf_input_seed = sha2_256(self.vdf_input_seed); + + // Assign index to this participant + let participant_index = self.participants; + self.participants = self.participants + 1; + + Ticket { + id: object::new(ctx), + game_id: object::id(self), + participant_index, + } + } + + /// Complete this lottery by sending VDF output and proof for the seed created from the + /// contributed randomness. Anyone can call this. + public fun complete( + self: &mut Game, + vdf_output: vector, + vdf_proof: vector, + clock: &Clock, + ) { + assert!(self.status != COMPLETED, EGameAlreadyCompleted); + assert!( + clock.timestamp_ms() - self.timestamp_start >= self.submission_phase_length, + ESubmissionPhaseInProgress, + ); + + // Hash combined randomness to vdf input + let vdf_input = hash_to_input(&self.vdf_input_seed); + + // Verify output and proof + assert!(vdf_verify(&vdf_input, &vdf_output, &vdf_proof, self.iterations), EInvalidVdfProof); + + // The randomness is derived from the VDF output by passing it through a hash function with + // uniformly distributed output to make it uniform. Any hash function with uniformly + // distributed output can be used. + let randomness = sha2_256(vdf_output); + + // Set winner and mark lottery completed + self.winner = option::some(safe_selection(self.participants, &randomness)); + self.status = COMPLETED; + } + + /// The winner can redeem its ticket. + public fun redeem(self: &Game, ticket: &Ticket, ctx: &mut TxContext): GameWinner { + assert!(self.status == COMPLETED, ESubmissionPhaseInProgress); + assert!(object::id(self) == ticket.game_id, EInvalidTicket); + assert!(self.winner.contains(&ticket.participant_index), ENotWinner); + + GameWinner { + id: object::new(ctx), + game_id: ticket.game_id, + } + } + + // Note that a ticket can be deleted before the game was completed. + public fun delete_ticket(ticket: Ticket) { + let Ticket { id, game_id: _, participant_index: _} = ticket; + object::delete(id); + } + + public fun delete_game_winner(ticket: GameWinner) { + let GameWinner { id, game_id: _} = ticket; + object::delete(id); + } + + public fun ticket_game_id(ticket: &Ticket): &ID { + &ticket.game_id + } + + public fun game_winner_game_id(ticket: &GameWinner): &ID { + &ticket.game_id + } + + // === Private Helpers === + + // Converts the first 16 bytes of rnd to a u128 number and outputs its modulo with input n. + // Since n is u64, the output is at most 2^{-64} biased assuming rnd is uniformly random. + fun safe_selection(n: u64, rnd: &vector): u64 { + assert!(rnd.length() >= 16, EInvalidRandomness); + let mut m: u128 = 0; + let mut i = 0; + while (i < 16) { + m = m << 8; + let curr_byte = rnd[i]; + m = m + (curr_byte as u128); + i = i + 1; + }; + let n_128 = (n as u128); + let module_128 = m % n_128; + let res = (module_128 as u64); + res + } +} diff --git a/examples/move/vdf/tests/lottery_tests.move b/examples/move/vdf/tests/lottery_tests.move new file mode 100644 index 0000000000000..850af7e49091e --- /dev/null +++ b/examples/move/vdf/tests/lottery_tests.move @@ -0,0 +1,155 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module vdf::lottery_tests { + use sui::test_scenario as ts; + use sui::clock; + use vdf::lottery::{Self, Game, GameWinner}; + + const OUTPUT: vector = + x"c0014d00b5e624fe10d1cc1e593c0ffb8c3084e49bb70efc4337640a73990bb29dfb430b55710475bcc7524c77627d8067415fffa63e0e84b1204225520fea384999719c66dbdc6e91863d99c64674af971631b56e22b7cc780765bf12d53edea1dadf566f80a62769e287a1e195596d4894b2e1360e451cbf06864762275b4d5063871d45627dea2e42ab93d5345bf172b9724216627abbf295b35a8e64e13e585bca54848a90212c9f7a3adffc25c3b87eefa7d4ab1660b523bf6410b9a9ea0e00c001327d73bebc768d150beb2a1b0de9e80c69ed594ae7787d548af44eb1e0a03616100133146c9c1202ea3a35c331864f3bfe59ffa3c88d6acb8af7a4b1b5ea842c4c4c88d415539389e614876d738d80217a3ad16d001f2de60f62b04a9d8de7ccb4716c3368f0d42e3e719dbb07bdb4355f0e5569714fbcc130f30ac7b49a5b207a444c7e00a0c27edae10c28b05f87545f337283f90c4e4ed69639683154d6a89e6589db4d18702d3a29705b434dc32e10fcbd3c62d1da20b45dba511bcecdf7c101009db7911c39f3937785dd8bd0e9db56c94777bdd867897493f82e0931e0e5facb730c3aa400f815f3d2f61de94373209dcbf9210c5e1f179b675adce2be7159238cb5f89c568004ccfc75d1b3de99a060255bcd6c9dd9a674844251ec27c48119b870f4ce63cac2081541c4269bbfa60148f9dbf2c60c76099a6538a33e88c24ce092e6aad9bdfff330470ffb87b01b666dd98e44843b41896f2be688361fe062692b441b4dd8f8ecfa96948d13daf486259bb8934cab7e9d9788da0eac7edf56"; + + const PROOF: vector = + x"c0010f1ea16b16f3fc46eb0b00a516092a6ab389e259a3013eee5e39e130702de85954b8aac435e724ad0bfd210ab7789fb91b54ac4352154f3251e0a87ccfd2c9a57d26468a384f527e129fc82b33c04b3ebbec3a99967798a95b39820c35ea015fdf4c81e143004b34b99e63462cf350689b2abdd6c3903adcbe55781d3a89c89dc571c312f9a80911a9d64884747319574b3a4ded25478e6d64b9cfb25d9c67366bc25d9ac99bcdba16665158da50a2ba179893292c4b7e76502ecaba1337d693c001fb3867669e0d4e45aa43d959dbe33c3d35b00e8414d1cf1bb9552726bb95bafa0a2c12a014a3b8fb0bd5ab9a40430ff59364b19d58d80665fee0bfee272a38c45413a3688832bf9bcacf7b5723436c120878f85ce084e72b13246ecfec7cd6a5d79e13296bbb51af785c10afe6c4f07f43a5bc711dc398271185d700b1695310d8e428ad3bc6b81a4faac2f5009b723460dbd260c940dfac06e34854d204dc779f94ab3f67847a7b90855dadc3962871c022e172e96b39a08648e045e14dad87c10102f976797f14be801a441f19771a4835640a74cf7c6ad216f18d9cdaf461bb56a897b804e053cd6cc68d659bd9f0ed985f094932d306c1bd76450bd349db3a81008d7591bc826a36583c3c361add7a8f245d18007d79704d79ae27eb08b52a44af17e2f23b441919049f061d69bac3a09c3e15074e4d75cf82f42dbff1c62ddc94fe6167ccb7265e7eab0def7d30d97be441ad763705dd30d4040815996e34643bf6d7a4f06c22aa5d6d5dd30253ea8aa59607724bb71f5425a5e7fee03b7e9fe8"; + + const BAD_PROOF: vector = + x"0101010180032cf35709e1301d02b40a0dbe3dadfe6ec1eeba8fb8060a1decd0c7a126ea3f27fadcad81435601b0e0abca5c89173ef639e5a88043aa29801e6799e430b509e479b57af981f9ddd48d3a8d5919f99258081557a08270bb441233c78030a01e03ec199b5e3eef5ccc9b1a3d4841cbe4ff529c22a8cd1b1b0075338d864e3890942df6b007d2c3e3a8ef1ce7490c6bbec5372adfcbf8704a1ffc9a69db8d9cdc54762f019036e450e457325eef74b794f3f16ff327d68079a5b9de49163d7323937374f8a785a8f9afe84d6a71b336e4de00f239ee3af1d7604a3985e610e1603bd0e1a4998e19fa0c8920ffd8d61b0a87eeee50ac7c03ff7c4708a34f3bc92fd0103758c954ee34032cee2c78ad8cdc79a35dbc810196b7bf6833e1c45c83b09c0d1b78bc6f8753e10770e7045b08d50b4aa16a75b27a096d5ec1331f1fd0a44e95a8737c20240c90307b5497d3470393c2a00da0649e86d13e820591296c644fc1eef9e7c6ca4967c5e19df3153cd7fbd598c271e11c10397349ddc8cc8452ec"; + + #[test] + #[expected_failure(abort_code = vdf::lottery::ESubmissionPhaseInProgress)] + fun test_complete_too_early() { + let user1 = @0x0; + + let mut ts = ts::begin(user1); + let mut clock = clock::create_for_testing(ts.ctx()); + + lottery::create(1000, 1000, &clock, ts.ctx()); + ts.next_tx(user1); + let mut game: Game = ts.take_shared(); + + // User 1 buys a ticket. + ts.next_tx(user1); + let _t1 = game.participate(b"user1 randomness", &clock, ts.ctx()); + + // Increment time but still in submission phase + clock.increment_for_testing(500); + + // User1 tries to complete the lottery too early. + ts.next_tx(user1); + game.complete(OUTPUT, PROOF, &clock); + abort 0 + } + + #[test] + fun test_play_vdf_lottery() { + let user1 = @0x0; + let user2 = @0x1; + let user3 = @0x2; + let user4 = @0x3; + + let mut ts = ts::begin(user1); + let mut clock = clock::create_for_testing(ts.ctx()); + + lottery::create(1000, 1000, &clock, ts.ctx()); + ts.next_tx(user1); + let mut game: Game = ts.take_shared(); + + // User 1 buys a ticket. + ts.next_tx(user1); + let t1 = game.participate(b"user1 randomness", &clock, ts.ctx()); + + // User 2 buys a ticket. + ts.next_tx(user2); + let t2 = game.participate(b"user2 randomness", &clock, ts.ctx()); + + // User 3 buys a ticket + ts.next_tx(user3); + let t3 = game.participate(b"user3 randomness", &clock, ts.ctx()); + + // User 4 buys a ticket + ts.next_tx(user4); + let t4 = game.participate(b"user4 randomness", &clock, ts.ctx()); + + // Increment time to after submission phase has ended + clock.increment_for_testing(1000); + + // User 3 completes by submitting output and proof of the VDF + ts.next_tx(user3); + game.complete(OUTPUT, PROOF, &clock); + + // User 1 is the winner since the mod of the hash results in 0. + ts.next_tx(user1); + assert!(!ts::has_most_recent_for_address(user1), 1); + let winner = game.redeem(&t1, ts.ctx()); + + // Make sure User1 now has a winner ticket for the right game id. + ts.next_tx(user1); + assert!(winner.game_id() == t1.game_id(), 1); + + t1.delete(); + t2.delete(); + t3.delete(); + t4.delete(); + winner.delete(); + + clock.destroy_for_testing(); + ts::return_shared(game); + ts.end(); + } + + #[test] + #[expected_failure(abort_code = vdf::lottery::EInvalidVdfProof)] + fun test_invalid_vdf_output() { + let user1 = @0x0; + let user2 = @0x1; + let user3 = @0x2; + let user4 = @0x3; + + let mut ts = ts::begin(user1); + let mut clock = clock::create_for_testing(ts.ctx()); + + lottery::create(1000, 1000, &clock, ts.ctx()); + ts.next_tx(user1); + let mut game: Game = ts.take_shared(); + + // User1 buys a ticket. + ts.next_tx(user1); + let _t1 = game.participate(b"user1 randomness", &clock, ts.ctx()); + // User2 buys a ticket. + ts.next_tx(user2); + let _t2 = game.participate(b"user2 randomness", &clock, ts.ctx()); + // User3 buys a ticket + ts.next_tx(user3); + let _t3 = game.participate(b"user3 randomness", &clock, ts.ctx()); + // User4 buys a ticket + ts.next_tx(user4); + let _t4 = game.participate(b"user4 randomness", &clock, ts.ctx()); + + // Increment time to after submission phase has ended + clock.increment_for_testing(1000); + + // User3 completes by submitting output and proof of the VDF + ts.next_tx(user3); + game.complete(OUTPUT, BAD_PROOF, &clock); + abort 0 + } + + #[test] + #[expected_failure(abort_code = vdf::lottery::EInvalidRandomness)] + fun test_empty_randomness() { + let user1 = @0x0; + + let mut ts = ts::begin(user1); + let clock = clock::create_for_testing(ts.ctx()); + + lottery::create(1000, 1000, &clock, ts.ctx()); + ts.next_tx(user1); + let mut game: Game = ts.take_shared(); + + // User1 buys a ticket, but with wrong randomness length. + ts.next_tx(user1); + let _t = game.participate(b"abcd", &clock, ts.ctx()); + abort 0 + } +}