From 1a2ff3764d32eed2bd6e6825ebfb193e2d4fa181 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:09:49 -0500 Subject: [PATCH 01/34] create README --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..0da9c8f --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# [WIP] Coin Toss Using Onchain Randomness + +> :warning: This repo is still a work in progress + +## Overview + +The contracts contained in this repo demonstrate how to use Flow's onchain randomness safely - safe randomness here meaning non-revertible randomness. + +Random sources are committed to the [`RandomBeaconHistory` contract](./contracts/RandomBeaconHistory.cdc) by the service account at the end of every block. These random sources are catalogued chronologically, extending historically for every associated block height to the initial commitment height. + +Used on their own, these random sources are not safe. In other words, using the random source in your contract without the framing of a commit-reveal mechanism would enable callers to condition their interactions with your contract on the random result. In the context of a random coin toss, I could revert my transaction if I didn't win - not a very fair game. + +To achieve non-revertible randomness, the contract should be structured to resolve in two phases: + +1. Commit - Caller commits to the resolution of their bet with some yet unknown source of randomness (i.e. in the future) +2. Reveal - Caller can then reveal the result of their bet + +Though a caller could still condition the revealing transaction on the coin flip result, they've already incurred the cost of their bet and would gain nothing by doing so. \ No newline at end of file From ef58175183ba0bcdfeb3734f3f4f3f62e4ccdd71 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 3 Oct 2023 18:59:13 -0500 Subject: [PATCH 02/34] first pass as PRG implementation --- contracts/PseudoRandomGenerator.cdc | 56 +++++++++++++++++-- .../next_uint64_from_address.cdc | 10 ++++ .../next_uint64_new_prg.cdc | 12 ++++ .../pseudo-random-generator/next_uint64.cdc | 15 +++++ .../pseudo-random-generator/setup_prg.cdc | 22 ++++++++ .../random-beacon-history/heartbeat.cdc | 22 ++++++++ 6 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 scripts/pseudo-random-generator/next_uint64_from_address.cdc create mode 100644 scripts/pseudo-random-generator/next_uint64_new_prg.cdc create mode 100644 transactions/pseudo-random-generator/next_uint64.cdc create mode 100644 transactions/pseudo-random-generator/setup_prg.cdc create mode 100644 transactions/random-beacon-history/heartbeat.cdc diff --git a/contracts/PseudoRandomGenerator.cdc b/contracts/PseudoRandomGenerator.cdc index 78e4ae1..3c1cdbf 100644 --- a/contracts/PseudoRandomGenerator.cdc +++ b/contracts/PseudoRandomGenerator.cdc @@ -3,19 +3,65 @@ access(all) contract PseudoRandomGenerator { access(all) let StoragePath: StoragePath access(all) let PublicPath: PublicPath + access(all) event NextUInt64(id: UInt64, value: UInt64) + + /// While not limited to 128 bits of state, this PRG is largely informed by XORShift128+ + /// access(all) resource PRG { access(all) let sourceOfRandomness: [UInt8] - access(all) let salt: UInt64 + access(all) let salt: Word64 + + /// The states below are of type Word64 to prevent overflow/underflow + // + access(all) var state0: Word64 + access(all) var state1: Word64 init(sourceOfRandomness: [UInt8], salt: UInt64) { + pre { + sourceOfRandomness.length == 32: "Expecting 32 bytes" + } self.sourceOfRandomness = sourceOfRandomness - self.salt = salt + self.salt = Word64(salt) + + // Convert the seed bytes to two Word64 values for state initialization + let segment0 = PseudoRandomGenerator.bytesToWord64(bytes: sourceOfRandomness, start: 0) + let segment1 = PseudoRandomGenerator.bytesToWord64(bytes: sourceOfRandomness, start: 8) + let segment2 = PseudoRandomGenerator.bytesToWord64(bytes: sourceOfRandomness, start: 16) + let segment3 = PseudoRandomGenerator.bytesToWord64(bytes: sourceOfRandomness, start: 24) + + self.state0 = segment0 ^ segment1 + self.state1 = segment2 ^ segment3 } - // TODO: replace unsafeRandom with PRG implementation access(all) fun nextUInt64(): UInt64 { - return unsafeRandom() + var a: Word64 = self.state0 + let b: Word64 = self.state1 + + self.state0 = b + a = a ^ (a << 23) // a + a = a ^ (a >> 17) // b + a = b ^ (b >> 5) // c + self.state1 = a + + let randUInt64: UInt64 = UInt64((a + b) ^ self.salt) + emit NextUInt64(id: self.uuid, value: randUInt64) + return randUInt64 + } + } + + /// Helper function to convert an array of bytes to Word64 + access(contract) fun bytesToWord64(bytes: [UInt8], start: Int): Word64 { + pre { + start % 8 == 0: "Expecting start to be a multiple of 8" + bytes.length % 8 == 0: "byte array length to be a multiple of 8" + } + var value: UInt64 = 0 + var i = 0 + while i < 8 { + value = value << 8 | UInt64(bytes[start + i]) + i = i + 1 } + return Word64(value) } access(all) fun createPRG(sourceOfRandomness: [UInt8], salt: UInt64): @PRG { @@ -26,4 +72,4 @@ access(all) contract PseudoRandomGenerator { self.StoragePath = StoragePath(identifier: "PseudoRandomGenerator_".concat(self.account.address.toString()))! self.PublicPath = PublicPath(identifier: "PseudoRandomGenerator_".concat(self.account.address.toString()))! } -} \ No newline at end of file +} diff --git a/scripts/pseudo-random-generator/next_uint64_from_address.cdc b/scripts/pseudo-random-generator/next_uint64_from_address.cdc new file mode 100644 index 0000000..4f01901 --- /dev/null +++ b/scripts/pseudo-random-generator/next_uint64_from_address.cdc @@ -0,0 +1,10 @@ +import "PseudoRandomGenerator" + +pub fun main(prgAddress: Address): UInt64 { + + return getAccount(prgAddress).getCapability<&PseudoRandomGenerator.PRG>( + PseudoRandomGenerator.PublicPath + ).borrow() + ?.nextUInt64() + ?? panic("Could not find PRG at address") +} diff --git a/scripts/pseudo-random-generator/next_uint64_new_prg.cdc b/scripts/pseudo-random-generator/next_uint64_new_prg.cdc new file mode 100644 index 0000000..f6a6065 --- /dev/null +++ b/scripts/pseudo-random-generator/next_uint64_new_prg.cdc @@ -0,0 +1,12 @@ +import "PseudoRandomGenerator" + +pub fun main(seed: [UInt8], salt: UInt64): UInt64 { + + let prg <- PseudoRandomGenerator.createPRG(sourceOfRandomness: seed, salt: salt) + + let randUInt64 = prg.nextUInt64() + + destroy prg + + return randUInt64 +} diff --git a/transactions/pseudo-random-generator/next_uint64.cdc b/transactions/pseudo-random-generator/next_uint64.cdc new file mode 100644 index 0000000..6fa0824 --- /dev/null +++ b/transactions/pseudo-random-generator/next_uint64.cdc @@ -0,0 +1,15 @@ +import "PseudoRandomGenerator" + +/// Saves and links a .PRG resource in the signer's storage and public namespace +/// +transaction(generationLength: Int) { + prepare(signer: AuthAccount) { + if let prg = signer.borrow<&PseudoRandomGenerator.PRG>(from: PseudoRandomGenerator.StoragePath) { + var i = 0 + while i < generationLength { + prg.nextUInt64() + i = i + 1 + } + } + } +} \ No newline at end of file diff --git a/transactions/pseudo-random-generator/setup_prg.cdc b/transactions/pseudo-random-generator/setup_prg.cdc new file mode 100644 index 0000000..1ee4895 --- /dev/null +++ b/transactions/pseudo-random-generator/setup_prg.cdc @@ -0,0 +1,22 @@ +import "PseudoRandomGenerator" + +/// Saves and links a .PRG resource in the signer's storage and public namespace +/// +transaction(seed: [UInt8], salt: UInt64) { + prepare(signer: AuthAccount) { + if signer.type(at: PseudoRandomGenerator.StoragePath) != nil { + return + } + signer.save( + <- PseudoRandomGenerator.createPRG( + sourceOfRandomness: seed, + salt: salt + ), + to: PseudoRandomGenerator.StoragePath + ) + signer.link<&PseudoRandomGenerator.PRG>( + PseudoRandomGenerator.PublicPath, + target: PseudoRandomGenerator.StoragePath + ) + } +} \ No newline at end of file diff --git a/transactions/random-beacon-history/heartbeat.cdc b/transactions/random-beacon-history/heartbeat.cdc new file mode 100644 index 0000000..ec1f4c3 --- /dev/null +++ b/transactions/random-beacon-history/heartbeat.cdc @@ -0,0 +1,22 @@ +import "RandomBeaconHistory" + +/// Commits the source of randomness for the requested block height from the RandomBeaconHistory Heartbeat +/// +/// Note: commits to RandomBeaconHistory.Heartbeat will be completed in the system chunk transaction by the service +/// account. This transaction is included for initial testing purposes until randomSourceHistory() is available in +/// an emulator build +/// +transaction(sor: [UInt8]) { + prepare(serviceAccount: AuthAccount) { + // Borrow the RandomBeaconHistory.Heartbeat Resource from the signing service account + let randomBeaconHistoryHeartbeat = serviceAccount.borrow<&RandomBeaconHistory.Heartbeat>( + from: RandomBeaconHistory.HeartbeatStoragePath + ) ?? panic("Couldn't borrow RandomBeaconHistory.Heartbeat Resource") + + // TODO + // let sor: [UInt8] = randomSourceHistory() + + // Commit the source of randomness at the current blockheight + randomBeaconHistoryHeartbeat.heartbeat(randomSourceHistory: sor) + } +} From 6347a403ac905a72ececb14bb1522aeb08313ad6 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:08:12 -0500 Subject: [PATCH 03/34] add references --- README.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0da9c8f..95df152 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,32 @@ -# [WIP] Coin Toss Using Onchain Randomness +# [WIP] Random Coin Toss -> :warning: This repo is still a work in progress +> :warning: This repo is still a work in progress - the underlying RandomBeaconHistory is also still a work in progress ## Overview -The contracts contained in this repo demonstrate how to use Flow's onchain randomness safely - safe randomness here meaning non-revertible randomness. +The contracts contained in this repo demonstrate how to use Flow's onchain randomness safely - safe randomness here +meaning non-revertible randomness. -Random sources are committed to the [`RandomBeaconHistory` contract](./contracts/RandomBeaconHistory.cdc) by the service account at the end of every block. These random sources are catalogued chronologically, extending historically for every associated block height to the initial commitment height. +Random sources are committed to the [`RandomBeaconHistory` contract](./contracts/RandomBeaconHistory.cdc) by the service +account at the end of every block. These random sources are catalogued chronologically, extending historically for every +associated block height to the initial commitment height. -Used on their own, these random sources are not safe. In other words, using the random source in your contract without the framing of a commit-reveal mechanism would enable callers to condition their interactions with your contract on the random result. In the context of a random coin toss, I could revert my transaction if I didn't win - not a very fair game. +Used on their own, these random sources are not safe. In other words, using the random source in your contract without +the framing of a commit-reveal mechanism would enable callers to condition their interactions with your contract on the +random result. In the context of a random coin toss, I could revert my transaction if I didn't win - not a very fair +game. To achieve non-revertible randomness, the contract should be structured to resolve in two phases: -1. Commit - Caller commits to the resolution of their bet with some yet unknown source of randomness (i.e. in the future) +1. Commit - Caller commits to the resolution of their bet with some yet unknown source of randomness (i.e. in the + future) 2. Reveal - Caller can then reveal the result of their bet -Though a caller could still condition the revealing transaction on the coin flip result, they've already incurred the cost of their bet and would gain nothing by doing so. \ No newline at end of file +Though a caller could still condition the revealing transaction on the coin flip result, they've already incurred the +cost of their bet and would gain nothing by doing so. + +## References + +- [Secure Random Number Generator Forum Post](https://forum.onflow.org/t/secure-random-number-generator-for-flow-s-smart-contracts/5110) +- [RandomBeaconHistory PR - flow-core-contracts](https://github.com/onflow/flow-core-contracts/pull/375) +- [FLIP: On-Chain randomness history for commit-reveal schemes](https://github.com/onflow/flips/pull/123) \ No newline at end of file From 411763d052867ff04578ea85a624a907f9f85455 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:08:47 -0500 Subject: [PATCH 04/34] update flow.json --- flow.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/flow.json b/flow.json index 672838c..038238c 100644 --- a/flow.json +++ b/flow.json @@ -1,21 +1,16 @@ { - "networks": { - "emulator": "127.0.0.1:3569", - "mainnet": "access.mainnet.nodes.onflow.org:9000", - "testnet": "access.devnet.nodes.onflow.org:9000" - }, "contracts": { "CoinToss": "./contracts/CoinToss.cdc", "FlowToken": { "source": "./contracts/utility/FlowToken.cdc", "aliases": { - "emulator": "0x0ae53cb6e3f42a79" + "emulator": "0ae53cb6e3f42a79" } }, "FungibleToken": { "source": "./contracts/utility/FungibleToken.cdc", "aliases": { - "emulator": "0xee82856bf20e2aa6" + "emulator": "ee82856bf20e2aa6" } }, "MetadataViews": { @@ -33,12 +28,17 @@ "PseudoRandomGenerator": "./contracts/PseudoRandomGenerator.cdc", "RandomBeaconHistory": "./contracts/RandomBeaconHistory.cdc", "ViewResolver": { + "source": "./contracts/utility/ViewResolver.cdc", "aliases": { "emulator": "f8d6e0586b0a20c7" - }, - "source": "./contracts/utility/ViewResolver.cdc" + } } }, + "networks": { + "emulator": "127.0.0.1:3569", + "mainnet": "access.mainnet.nodes.onflow.org:9000", + "testnet": "access.devnet.nodes.onflow.org:9000" + }, "accounts": { "emulator-account": { "address": "f8d6e0586b0a20c7", @@ -57,4 +57,4 @@ ] } } -} +} \ No newline at end of file From 47d84a3f8765ba59a12acc1d2ec6e4a39fbc6e61 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:17:38 -0500 Subject: [PATCH 05/34] add open source docs --- CODE_OF_CONDUCT.md | 76 ++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 91 ++++++++++++++++++++++++++++++++++++++++++++++ LICENSE.md | 24 ++++++++++++ SECURITY.md | 11 ++++++ 4 files changed, 202 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.md create mode 100644 SECURITY.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..9b988b1 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at os@dapperlabs.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..05157f5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,91 @@ +# Contributing to the Non-Fungible Token Standard + +The following is a set of guidelines for contributing to the Flow NFT standard. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. + +#### Table Of Contents + +[How Can I Contribute?](#how-can-i-contribute) + +- [Reporting Bugs](#reporting-bugs) +- [Suggesting Enhancements](#suggesting-enhancements) +- [Pull Requests](#pull-requests) + +[Styleguides](#styleguides) + +- [Git Commit Messages](#git-commit-messages) + +[Additional Notes](#additional-notes) + + +## How Can I Contribute? + +You are free to contribute however you want! You can submit a bug report in an issue, suggest an enhancment, or even just make a PR for us to review. We just ask that you are clear in your communication and documentation of all your work so we can understand how you are trying to help. + +### Reporting Bugs + +#### Before Submitting A Bug Report + +- **Search existing issues** to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. + +#### How Do I Submit A (Good) Bug Report? + +Explain the problem and include additional details to help maintainers reproduce the problem: + +- **Use a clear and descriptive title** for the issue to identify the problem. +- **Describe the exact steps which reproduce the problem** in as many details as possible. When listing steps, **don't just say what you did, but explain how you did it**. +- **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). +- **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. +- **Explain which behavior you expected to see instead and why.** +- **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. + +Provide more context by answering these questions: + +- **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. + +Include details about your configuration and environment: + +- **What's the name and version of the OS you're using**? +- **What's the name and version of the flow-cli that you are using**? + +### Suggesting Enhancements + +#### Before Submitting An Enhancement Suggestion + +- **Perform a cursory search** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. + +#### How Do I Submit A (Good) Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue and provide the following information: + +- **Use a clear and descriptive title** for the issue to identify the suggestion. +- **Provide a step-by-step description of the suggested enhancement** in as many details as possible. +- **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). +- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. +- **Include screenshots and animated GIFs**. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. +- **Explain why this enhancement would be useful** to be included in the standard. + +### Pull Requests + +The process described here has several goals: + +- Maintain code quality +- Fix problems that are important to users + +Please follow the [styleguides](#styleguides) to have your contribution considered by the maintainers. +Reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted. + +## Styleguides + +Before contributing, make sure to examine the project to get familiar with the patterns and style already being used. + +### Git Commit Messages + +- Use the present tense ("Add feature" not "Added feature") +- Use the imperative mood ("Move cursor to..." not "Moves cursor to...") +- Limit the first line to 72 characters or less +- Reference issues and pull requests liberally after the first line + + +### Additional Notes + +Thank you for your interest in contributing to the Flow Token Standards! \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3c577b0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..03eb45c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +# Responsible Disclosure Policy + +Flow was built from the ground up with security in mind. Our code, infrastructure, and development methodology helps us keep our users safe. + +We really appreciate the community's help. Responsible disclosure of vulnerabilities helps to maintain the security and privacy of everyone. + +If you care about making a difference, please follow the guidelines below. + +# **Guidelines For Responsible Disclosure** + +We ask that all researchers adhere to these guidelines [here](https://docs.onflow.org/bounties/responsible-disclosure/) \ No newline at end of file From dd7b29533d6ee7fd742f2d6892695537a59260a3 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:52:37 -0500 Subject: [PATCH 06/34] fix CoinToss contract bugs + add supporting txns --- contracts/CoinToss.cdc | 39 +++++++++++++------ transactions/coin-toss/0_commit_coin_toss.cdc | 23 +++++++++++ transactions/coin-toss/1_reveal_coin_toss.cdc | 23 +++++++++++ 3 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 transactions/coin-toss/0_commit_coin_toss.cdc create mode 100644 transactions/coin-toss/1_reveal_coin_toss.cdc diff --git a/contracts/CoinToss.cdc b/contracts/CoinToss.cdc index 0442e82..d902a70 100644 --- a/contracts/CoinToss.cdc +++ b/contracts/CoinToss.cdc @@ -1,4 +1,6 @@ +import "FungibleToken" import "FlowToken" + import "RandomBeaconHistory" import "PseudoRandomGenerator" @@ -6,7 +8,10 @@ access(all) contract CoinToss { access(self) let reserve: @FlowToken.Vault - access(self) let ReceiptStoragePath: StoragePath + access(all) let ReceiptStoragePath: StoragePath + + access(all) event CoinTossBet(betAmount: UFix64, commitBlock: UInt64, receiptID: UInt64) + access(all) event CoinTossReveal(betAmount: UFix64, winningAmount: UFix64, commitBlock: UInt64, receiptID: UInt64) access(all) resource Receipt { access(all) let betAmount: UFix64 @@ -21,32 +26,42 @@ access(all) contract CoinToss { // PRG implementation is not provided by the FLIP, we assume this contract // imports a suitable PRG implementation - access(all) fun commitCointoss(bet: @FlowToken.Vault): @Receipt { + access(all) fun commitCoinToss(bet: @FungibleToken.Vault): @Receipt { let receipt <- create Receipt( betAmount: bet.balance ) // commit the bet - // `self.reserve` is a `@FlowToken.Vault` field defined on the app contract + // `self.reserve` is a `@FungibleToken.Vault` field defined on the app contract // and represents a pool of funds self.reserve.deposit(from: <-bet) + + emit CoinTossBet(betAmount: receipt.betAmount, commitBlock: receipt.commitBlock, receiptID: receipt.uuid) + return <- receipt } - access(all) fun revealCointoss(receipt: @Receipt): @FlowToken.Vault { - let currentBlock = getCurrentBlock().height - if receipt.commitBlock >= currentBlock { - panic("cannot reveal yet") + access(all) fun revealCoinToss(receipt: @Receipt): @FungibleToken.Vault { + pre { + receipt.commitBlock <= getCurrentBlock().height: "Cannot reveal before commit block" } - let winnings = receipt.betAmount * 2.0 + let betAmount = receipt.betAmount + let commitBlock = receipt.commitBlock + let receiptID = receipt.uuid let coin = self.randomCoin(atBlockHeight: receipt.commitBlock, salt: receipt.uuid) + destroy receipt if coin == 1 { - return <- (FlowToken.createEmptyVault() as! @FlowToken.Vault) + emit CoinTossReveal(betAmount: betAmount, winningAmount: 0.0, commitBlock: commitBlock, receiptID: receiptID) + return <- FlowToken.createEmptyVault() } - - return <- (self.reserve.withdraw(amount: winnings) as! @FlowToken.Vault) + + let reward <- self.reserve.withdraw(amount: betAmount * 2.0) + + emit CoinTossReveal(betAmount: betAmount, winningAmount: reward.balance, commitBlock: commitBlock, receiptID: receiptID) + + return <- reward } access(all) fun randomCoin(atBlockHeight: UInt64, salt: UInt64): UInt8 { @@ -73,7 +88,7 @@ access(all) contract CoinToss { self.reserve <- (FlowToken.createEmptyVault() as! @FlowToken.Vault) let seedVault = self.account.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)! self.reserve.deposit( - from: <-seedVault.withdraw(amount: 100.0) + from: <-seedVault.withdraw(amount: 1000.0) ) self.ReceiptStoragePath = /storage/CoinTossReceipt diff --git a/transactions/coin-toss/0_commit_coin_toss.cdc b/transactions/coin-toss/0_commit_coin_toss.cdc new file mode 100644 index 0000000..7d58ce1 --- /dev/null +++ b/transactions/coin-toss/0_commit_coin_toss.cdc @@ -0,0 +1,23 @@ +import "FlowToken" + +import "CoinToss" + +transaction(betAmount: UFix64) { + + prepare(signer: AuthAccount) { + // Withdraw my bet amount from my FlowToken vault + let flowVault = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)! + let bet <- flowVault.withdraw(amount: betAmount) + + // Commit my bet and get a receipt + let receipt <- CoinToss.commitCoinToss(bet: <-bet) + + // Check that I don't already have a receipt stored + if signer.type(at: CoinToss.ReceiptStoragePath) != nil { + panic("You already have a receipt stored!") + } + + // Save that receipt to my storage + signer.save(<-receipt, to: CoinToss.ReceiptStoragePath) + } +} \ No newline at end of file diff --git a/transactions/coin-toss/1_reveal_coin_toss.cdc b/transactions/coin-toss/1_reveal_coin_toss.cdc new file mode 100644 index 0000000..de8e819 --- /dev/null +++ b/transactions/coin-toss/1_reveal_coin_toss.cdc @@ -0,0 +1,23 @@ +import "FlowToken" + +import "CoinToss" + +transaction { + + prepare(signer: AuthAccount) { + // load my receipt from storage + let receipt <- signer.load<@CoinToss.Receipt>(from: CoinToss.ReceiptStoragePath) + ?? panic("No Receipt found!") + + // Reveal by redeeming my receipt + let winnings <- CoinToss.revealCoinToss(receipt: <-receipt) + + if winnings.balance > 0.0 { + let flowVault = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)! + // deposit winnings into my FlowToken Vault + flowVault.deposit(from:<-winnings) + } else { + destroy winnings + } + } +} \ No newline at end of file From e4fcf22c9406550d3f7d08dd9b91f1b19f359a59 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:52:54 -0500 Subject: [PATCH 07/34] add FlowToken transfer txn --- transactions/flow-token/transfer_flow.cdc | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 transactions/flow-token/transfer_flow.cdc diff --git a/transactions/flow-token/transfer_flow.cdc b/transactions/flow-token/transfer_flow.cdc new file mode 100644 index 0000000..2c911bb --- /dev/null +++ b/transactions/flow-token/transfer_flow.cdc @@ -0,0 +1,23 @@ +import "FungibleToken" +import "FlowToken" + +transaction(recipient: Address, amount: UFix64) { + + let providerVault: &FlowToken.Vault + let receiver: &{FungibleToken.Receiver} + + prepare(signer: AuthAccount) { + self.providerVault = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)! + self.receiver = getAccount(recipient).getCapability<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + .borrow() + ?? panic("Could not borrow receiver reference") + } + + execute { + self.receiver.deposit( + from: <-self.providerVault.withdraw( + amount: amount + ) + ) + } +} From 17a1416dcbe664af5bcb6755d4fe7914dbf6a5f0 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:14:07 -0600 Subject: [PATCH 08/34] Apply suggestions from code review Co-authored-by: Tarak Ben Youssef <50252200+tarakby@users.noreply.github.com> --- README.md | 2 +- contracts/PseudoRandomGenerator.cdc | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 95df152..b3bb3d3 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ account at the end of every block. These random sources are catalogued chronolog associated block height to the initial commitment height. Used on their own, these random sources are not safe. In other words, using the random source in your contract without -the framing of a commit-reveal mechanism would enable callers to condition their interactions with your contract on the +the framing of a commit-reveal mechanism would enable non-trusted callers to condition their interactions with your contract on the random result. In the context of a random coin toss, I could revert my transaction if I didn't win - not a very fair game. diff --git a/contracts/PseudoRandomGenerator.cdc b/contracts/PseudoRandomGenerator.cdc index 3c1cdbf..103ecb2 100644 --- a/contracts/PseudoRandomGenerator.cdc +++ b/contracts/PseudoRandomGenerator.cdc @@ -11,7 +11,7 @@ access(all) contract PseudoRandomGenerator { access(all) let sourceOfRandomness: [UInt8] access(all) let salt: Word64 - /// The states below are of type Word64 to prevent overflow/underflow + /// The states below are of type Word64 (instead of UInt64) to prevent overflow/underflow // access(all) var state0: Word64 access(all) var state1: Word64 @@ -40,17 +40,17 @@ access(all) contract PseudoRandomGenerator { self.state0 = b a = a ^ (a << 23) // a a = a ^ (a >> 17) // b - a = b ^ (b >> 5) // c + a = b ^ (b >> 26) // c self.state1 = a - let randUInt64: UInt64 = UInt64((a + b) ^ self.salt) + let randUInt64: UInt64 = UInt64(a + b) emit NextUInt64(id: self.uuid, value: randUInt64) return randUInt64 } } - /// Helper function to convert an array of bytes to Word64 - access(contract) fun bytesToWord64(bytes: [UInt8], start: Int): Word64 { + /// Helper function to convert an array of big endian bytes to Word64 + access(contract) fun bigEndianBytesToWord64(bytes: [UInt8], start: Int): Word64 { pre { start % 8 == 0: "Expecting start to be a multiple of 8" bytes.length % 8 == 0: "byte array length to be a multiple of 8" From 30bbdc4b4a25f6d04155ad04c4d0e58c1491479c Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:27:16 -0600 Subject: [PATCH 09/34] update bigEndianBytesToWord64() pre-condition & message --- contracts/PseudoRandomGenerator.cdc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/PseudoRandomGenerator.cdc b/contracts/PseudoRandomGenerator.cdc index 103ecb2..6308a95 100644 --- a/contracts/PseudoRandomGenerator.cdc +++ b/contracts/PseudoRandomGenerator.cdc @@ -52,11 +52,10 @@ access(all) contract PseudoRandomGenerator { /// Helper function to convert an array of big endian bytes to Word64 access(contract) fun bigEndianBytesToWord64(bytes: [UInt8], start: Int): Word64 { pre { - start % 8 == 0: "Expecting start to be a multiple of 8" - bytes.length % 8 == 0: "byte array length to be a multiple of 8" + start + 8 < bytes.length: "At least 8 bytes from the start are required for conversion" } var value: UInt64 = 0 - var i = 0 + var i: Int = 0 while i < 8 { value = value << 8 | UInt64(bytes[start + i]) i = i + 1 From 19c1190f2f3b2c2a0c62d07f2bdbf925d99cd187 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:28:36 -0600 Subject: [PATCH 10/34] update instances of PRG.bytesToWord64() to .bigEndianBytesToWord64() --- contracts/PseudoRandomGenerator.cdc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/PseudoRandomGenerator.cdc b/contracts/PseudoRandomGenerator.cdc index 6308a95..ea0942f 100644 --- a/contracts/PseudoRandomGenerator.cdc +++ b/contracts/PseudoRandomGenerator.cdc @@ -24,10 +24,10 @@ access(all) contract PseudoRandomGenerator { self.salt = Word64(salt) // Convert the seed bytes to two Word64 values for state initialization - let segment0 = PseudoRandomGenerator.bytesToWord64(bytes: sourceOfRandomness, start: 0) - let segment1 = PseudoRandomGenerator.bytesToWord64(bytes: sourceOfRandomness, start: 8) - let segment2 = PseudoRandomGenerator.bytesToWord64(bytes: sourceOfRandomness, start: 16) - let segment3 = PseudoRandomGenerator.bytesToWord64(bytes: sourceOfRandomness, start: 24) + let segment0 = PseudoRandomGenerator.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 0) + let segment1 = PseudoRandomGenerator.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 8) + let segment2 = PseudoRandomGenerator.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 16) + let segment3 = PseudoRandomGenerator.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 24) self.state0 = segment0 ^ segment1 self.state1 = segment2 ^ segment3 From f1d006ffd44d9067b56ba96af29c6ab55f2240e0 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:30:57 -0600 Subject: [PATCH 11/34] remove PRG.sourceOfRandomness field - state storage not necessary --- contracts/PseudoRandomGenerator.cdc | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/PseudoRandomGenerator.cdc b/contracts/PseudoRandomGenerator.cdc index ea0942f..665f8d0 100644 --- a/contracts/PseudoRandomGenerator.cdc +++ b/contracts/PseudoRandomGenerator.cdc @@ -8,7 +8,6 @@ access(all) contract PseudoRandomGenerator { /// While not limited to 128 bits of state, this PRG is largely informed by XORShift128+ /// access(all) resource PRG { - access(all) let sourceOfRandomness: [UInt8] access(all) let salt: Word64 /// The states below are of type Word64 (instead of UInt64) to prevent overflow/underflow @@ -20,7 +19,6 @@ access(all) contract PseudoRandomGenerator { pre { sourceOfRandomness.length == 32: "Expecting 32 bytes" } - self.sourceOfRandomness = sourceOfRandomness self.salt = Word64(salt) // Convert the seed bytes to two Word64 values for state initialization From 3645694d7ea5db09fc13e3b61bdc84109ae579ac Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:33:10 -0600 Subject: [PATCH 12/34] update PRG.nextUInt64() final xor shift --- contracts/PseudoRandomGenerator.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/PseudoRandomGenerator.cdc b/contracts/PseudoRandomGenerator.cdc index 665f8d0..87c7a0a 100644 --- a/contracts/PseudoRandomGenerator.cdc +++ b/contracts/PseudoRandomGenerator.cdc @@ -38,7 +38,7 @@ access(all) contract PseudoRandomGenerator { self.state0 = b a = a ^ (a << 23) // a a = a ^ (a >> 17) // b - a = b ^ (b >> 26) // c + a = a ^ b ^ (b >> 26) // c self.state1 = a let randUInt64: UInt64 = UInt64(a + b) From 90ca8fe58540e8a6144e855c74158ce8bd166121 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Mon, 6 Nov 2023 16:03:16 -0600 Subject: [PATCH 13/34] rename PseudoRandomGenerator to XorShift128Plus & rename instances --- contracts/CoinToss.cdc | 8 +-- contracts/PseudoRandomGenerator.cdc | 63 +++++++++++++------ flow.json | 4 +- .../next_uint64_from_address.cdc | 6 +- .../next_uint64_new_prg.cdc | 4 +- .../pseudo-random-generator/next_uint64.cdc | 4 +- .../pseudo-random-generator/setup_prg.cdc | 14 ++--- 7 files changed, 63 insertions(+), 40 deletions(-) diff --git a/contracts/CoinToss.cdc b/contracts/CoinToss.cdc index d902a70..1a06646 100644 --- a/contracts/CoinToss.cdc +++ b/contracts/CoinToss.cdc @@ -2,7 +2,7 @@ import "FungibleToken" import "FlowToken" import "RandomBeaconHistory" -import "PseudoRandomGenerator" +import "XorShift128Plus" access(all) contract CoinToss { @@ -70,9 +70,9 @@ access(all) contract CoinToss { let sourceOfRandomness = RandomBeaconHistory.sourceOfRandomness(atBlockHeight: atBlockHeight) assert(sourceOfRandomness.blockHeight == atBlockHeight, message: "RandomSource block height mismatch") - // instantiate a PRG object using external `createPRG` that takes a `seed` - // and `salt` and returns a pseudo-random-generator object. - let prg <- PseudoRandomGenerator.createPRG( + // instantiate a PRG object using external `createPRG()` that takes a `seed` + // and `salt` and returns a pseudo-random generator object. + let prg <- XorShift128Plus.createPRG( sourceOfRandomness: sourceOfRandomness.value, salt: salt ) diff --git a/contracts/PseudoRandomGenerator.cdc b/contracts/PseudoRandomGenerator.cdc index 87c7a0a..b5f442b 100644 --- a/contracts/PseudoRandomGenerator.cdc +++ b/contracts/PseudoRandomGenerator.cdc @@ -1,36 +1,42 @@ -access(all) contract PseudoRandomGenerator { +import Crypto + +/// Defines a xorsift128+ pseudo random generator as a resource +/// +access(all) contract XorShift128Plus { access(all) let StoragePath: StoragePath access(all) let PublicPath: PublicPath - access(all) event NextUInt64(id: UInt64, value: UInt64) - /// While not limited to 128 bits of state, this PRG is largely informed by XORShift128+ /// access(all) resource PRG { - access(all) let salt: Word64 - - /// The states below are of type Word64 (instead of UInt64) to prevent overflow/underflow + + // The states below are of type Word64 (instead of UInt64) to prevent overflow/underflow as state evolves // access(all) var state0: Word64 access(all) var state1: Word64 - - init(sourceOfRandomness: [UInt8], salt: UInt64) { + + init(sourceOfRandomness: [UInt8]) { pre { - sourceOfRandomness.length == 32: "Expecting 32 bytes" + sourceOfRandomness.length == 32: "Expecting 32 bytes as sourceOfRandomness" } - self.salt = Word64(salt) - // Convert the seed bytes to two Word64 values for state initialization - let segment0 = PseudoRandomGenerator.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 0) - let segment1 = PseudoRandomGenerator.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 8) - let segment2 = PseudoRandomGenerator.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 16) - let segment3 = PseudoRandomGenerator.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 24) + let segment0: Word64 = XorShift128Plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 0) + let segment1: Word64 = XorShift128Plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 8) + let segment2: Word64 = XorShift128Plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 16) + let segment3: Word64 = XorShift128Plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 24) self.state0 = segment0 ^ segment1 self.state1 = segment2 ^ segment3 } + /// Advances the PRG state and generates the next UInt64 value + /// See https://arxiv.org/pdf/1404.0390.pdf for implementation details and reasoning for triplet selection. + /// Note that state only advances when this function is called from a transaction. Calls from within a script + /// will not advance state and will return the same value. + /// + /// @return The next UInt64 value + /// access(all) fun nextUInt64(): UInt64 { var a: Word64 = self.state0 let b: Word64 = self.state1 @@ -42,12 +48,17 @@ access(all) contract PseudoRandomGenerator { self.state1 = a let randUInt64: UInt64 = UInt64(a + b) - emit NextUInt64(id: self.uuid, value: randUInt64) return randUInt64 } } /// Helper function to convert an array of big endian bytes to Word64 + /// + /// @param bytes: The bytes to convert + /// @param start: The index of the first byte to convert + /// + /// @return The Word64 value + /// access(contract) fun bigEndianBytesToWord64(bytes: [UInt8], start: Int): Word64 { pre { start + 8 < bytes.length: "At least 8 bytes from the start are required for conversion" @@ -61,12 +72,24 @@ access(all) contract PseudoRandomGenerator { return Word64(value) } - access(all) fun createPRG(sourceOfRandomness: [UInt8], salt: UInt64): @PRG { - return <- create PRG(sourceOfRandomness: sourceOfRandomness, salt: salt) + /// Creates a new XORSift128+ PRG with the given source of randomness and salt + /// + /// @param sourceOfRandomness: The 32 byte source of randomness used to seed the PRG + /// @param salt: The bytes used to salt the source of randomness + /// + /// @return A new PRG resource + access(all) fun createPRG(sourceOfRandomness: [UInt8], salt: [UInt8]): @PRG { + let tmp: [UInt8] = sourceOfRandomness.concat(salt) + // Hash is 32 bytes + let hash: [UInt8] = Crypto.hash(tmp, algorithm: HashAlgorithm.SHA3_256) + // Reduce the seed to 16 bytes + let seed: [UInt8] = hash.slice(from: 0, upTo: 16) + + return <- create PRG(sourceOfRandomness: seed) } init() { - self.StoragePath = StoragePath(identifier: "PseudoRandomGenerator_".concat(self.account.address.toString()))! - self.PublicPath = PublicPath(identifier: "PseudoRandomGenerator_".concat(self.account.address.toString()))! + self.StoragePath = StoragePath(identifier: "XorShift128PlusPRG_".concat(self.account.address.toString()))! + self.PublicPath = PublicPath(identifier: "XorShift128PlusPRG_".concat(self.account.address.toString()))! } } diff --git a/flow.json b/flow.json index 038238c..8917f78 100644 --- a/flow.json +++ b/flow.json @@ -25,7 +25,7 @@ "emulator": "f8d6e0586b0a20c7" } }, - "PseudoRandomGenerator": "./contracts/PseudoRandomGenerator.cdc", + "XorShift128Plus": "./contracts/XorShift128Plus.cdc", "RandomBeaconHistory": "./contracts/RandomBeaconHistory.cdc", "ViewResolver": { "source": "./contracts/utility/ViewResolver.cdc", @@ -52,7 +52,7 @@ "MetadataViews", "ViewResolver", "RandomBeaconHistory", - "PseudoRandomGenerator", + "XorShift128Plus", "CoinToss" ] } diff --git a/scripts/pseudo-random-generator/next_uint64_from_address.cdc b/scripts/pseudo-random-generator/next_uint64_from_address.cdc index 4f01901..cafea69 100644 --- a/scripts/pseudo-random-generator/next_uint64_from_address.cdc +++ b/scripts/pseudo-random-generator/next_uint64_from_address.cdc @@ -1,9 +1,9 @@ -import "PseudoRandomGenerator" +import "XorShift128Plus" pub fun main(prgAddress: Address): UInt64 { - return getAccount(prgAddress).getCapability<&PseudoRandomGenerator.PRG>( - PseudoRandomGenerator.PublicPath + return getAccount(prgAddress).getCapability<&XorShift128Plus.PRG>( + XorShift128Plus.PublicPath ).borrow() ?.nextUInt64() ?? panic("Could not find PRG at address") diff --git a/scripts/pseudo-random-generator/next_uint64_new_prg.cdc b/scripts/pseudo-random-generator/next_uint64_new_prg.cdc index f6a6065..f5dbf07 100644 --- a/scripts/pseudo-random-generator/next_uint64_new_prg.cdc +++ b/scripts/pseudo-random-generator/next_uint64_new_prg.cdc @@ -1,8 +1,8 @@ -import "PseudoRandomGenerator" +import "XorShift128Plus" pub fun main(seed: [UInt8], salt: UInt64): UInt64 { - let prg <- PseudoRandomGenerator.createPRG(sourceOfRandomness: seed, salt: salt) + let prg <- XorShift128Plus.createPRG(sourceOfRandomness: seed, salt: salt) let randUInt64 = prg.nextUInt64() diff --git a/transactions/pseudo-random-generator/next_uint64.cdc b/transactions/pseudo-random-generator/next_uint64.cdc index 6fa0824..0ddf946 100644 --- a/transactions/pseudo-random-generator/next_uint64.cdc +++ b/transactions/pseudo-random-generator/next_uint64.cdc @@ -1,10 +1,10 @@ -import "PseudoRandomGenerator" +import "XorShift128Plus" /// Saves and links a .PRG resource in the signer's storage and public namespace /// transaction(generationLength: Int) { prepare(signer: AuthAccount) { - if let prg = signer.borrow<&PseudoRandomGenerator.PRG>(from: PseudoRandomGenerator.StoragePath) { + if let prg = signer.borrow<&XorShift128Plus.PRG>(from: XorShift128Plus.StoragePath) { var i = 0 while i < generationLength { prg.nextUInt64() diff --git a/transactions/pseudo-random-generator/setup_prg.cdc b/transactions/pseudo-random-generator/setup_prg.cdc index 1ee4895..ba43ef4 100644 --- a/transactions/pseudo-random-generator/setup_prg.cdc +++ b/transactions/pseudo-random-generator/setup_prg.cdc @@ -1,22 +1,22 @@ -import "PseudoRandomGenerator" +import "XorShift128Plus" /// Saves and links a .PRG resource in the signer's storage and public namespace /// transaction(seed: [UInt8], salt: UInt64) { prepare(signer: AuthAccount) { - if signer.type(at: PseudoRandomGenerator.StoragePath) != nil { + if signer.type(at: XorShift128Plus.StoragePath) != nil { return } signer.save( - <- PseudoRandomGenerator.createPRG( + <- XorShift128Plus.createPRG( sourceOfRandomness: seed, salt: salt ), - to: PseudoRandomGenerator.StoragePath + to: XorShift128Plus.StoragePath ) - signer.link<&PseudoRandomGenerator.PRG>( - PseudoRandomGenerator.PublicPath, - target: PseudoRandomGenerator.StoragePath + signer.link<&XorShift128Plus.PRG>( + XorShift128Plus.PublicPath, + target: XorShift128Plus.StoragePath ) } } \ No newline at end of file From 8e7f7569f803426624eed2faaa60454f2a97f9ae Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Mon, 6 Nov 2023 16:33:27 -0600 Subject: [PATCH 14/34] update prg.next_uint64() calling transactions distinguishing single v. iterative --- .../iterative_next_uint64.cdc | 21 +++++++++++++++++++ .../pseudo-random-generator/next_uint64.cdc | 18 +++++++--------- 2 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 transactions/pseudo-random-generator/iterative_next_uint64.cdc diff --git a/transactions/pseudo-random-generator/iterative_next_uint64.cdc b/transactions/pseudo-random-generator/iterative_next_uint64.cdc new file mode 100644 index 0000000..c571a2a --- /dev/null +++ b/transactions/pseudo-random-generator/iterative_next_uint64.cdc @@ -0,0 +1,21 @@ +import "XorShift128Plus" + +/// Generates an arbitrary number of random numbers using the XorShift128Plus.PRG saved in signer's storage. +/// While the values generated in this transaction are not used or stored, this transaction demonstrates how one would +/// go about generating any number of random numbers using the XorShift128Plus.PRG. If desired, the generated numbers +/// could be stored in some resource or +/// +transaction(generationLength: Int) { + + prepare(signer: AuthAccount) { + // Get the XorShift128Plus.PRG from signer's storage + if let prg = signer.borrow<&XorShift128Plus.PRG>(from: XorShift128Plus.StoragePath) { + var i = 0 + // Generate the desired number of random numbers + while i < generationLength { + prg.nextUInt64() + i = i + 1 + } + } + } +} diff --git a/transactions/pseudo-random-generator/next_uint64.cdc b/transactions/pseudo-random-generator/next_uint64.cdc index 0ddf946..753c6b5 100644 --- a/transactions/pseudo-random-generator/next_uint64.cdc +++ b/transactions/pseudo-random-generator/next_uint64.cdc @@ -1,15 +1,13 @@ import "XorShift128Plus" -/// Saves and links a .PRG resource in the signer's storage and public namespace +/// Advances the XorShift128Plus.PRG state and generates a random number. Since values cannot be returned from +/// transactions, a caller would need to have queried the PRG with prg.getNextUInt64() before running this transaction +/// to retrieve the number that will be generated. /// -transaction(generationLength: Int) { +transaction { prepare(signer: AuthAccount) { - if let prg = signer.borrow<&XorShift128Plus.PRG>(from: XorShift128Plus.StoragePath) { - var i = 0 - while i < generationLength { - prg.nextUInt64() - i = i + 1 - } - } + signer.borrow<&XorShift128Plus.PRG>(from: XorShift128Plus.StoragePath) + ?.nextUInt64() + ?? panic("No PRG found in signer's storage") } -} \ No newline at end of file +} From d1dd0fcf4248addf527c01304c21c981646475bd Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:24:11 -0600 Subject: [PATCH 15/34] rename PseudoRandomGenerator.cdc to XorShift128Plus.cdc --- contracts/{PseudoRandomGenerator.cdc => XorShift128Plus.cdc} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contracts/{PseudoRandomGenerator.cdc => XorShift128Plus.cdc} (100%) diff --git a/contracts/PseudoRandomGenerator.cdc b/contracts/XorShift128Plus.cdc similarity index 100% rename from contracts/PseudoRandomGenerator.cdc rename to contracts/XorShift128Plus.cdc From 7e44ddaea7ba78dee5d5617c9957f288a1fcebdf Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:32:08 -0600 Subject: [PATCH 16/34] remove RandomBeaconHistory transactions --- .../random-beacon-history/heartbeat.cdc | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 transactions/random-beacon-history/heartbeat.cdc diff --git a/transactions/random-beacon-history/heartbeat.cdc b/transactions/random-beacon-history/heartbeat.cdc deleted file mode 100644 index ec1f4c3..0000000 --- a/transactions/random-beacon-history/heartbeat.cdc +++ /dev/null @@ -1,22 +0,0 @@ -import "RandomBeaconHistory" - -/// Commits the source of randomness for the requested block height from the RandomBeaconHistory Heartbeat -/// -/// Note: commits to RandomBeaconHistory.Heartbeat will be completed in the system chunk transaction by the service -/// account. This transaction is included for initial testing purposes until randomSourceHistory() is available in -/// an emulator build -/// -transaction(sor: [UInt8]) { - prepare(serviceAccount: AuthAccount) { - // Borrow the RandomBeaconHistory.Heartbeat Resource from the signing service account - let randomBeaconHistoryHeartbeat = serviceAccount.borrow<&RandomBeaconHistory.Heartbeat>( - from: RandomBeaconHistory.HeartbeatStoragePath - ) ?? panic("Couldn't borrow RandomBeaconHistory.Heartbeat Resource") - - // TODO - // let sor: [UInt8] = randomSourceHistory() - - // Commit the source of randomness at the current blockheight - randomBeaconHistoryHeartbeat.heartbeat(randomSourceHistory: sor) - } -} From 9e5e9c476b40730faee9f3f8f6c18c7c519d6400 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:47:56 -0600 Subject: [PATCH 17/34] rename XorShift128Plus to Xorshift128plus & update instances --- contracts/CoinToss.cdc | 4 ++-- contracts/XorShift128Plus.cdc | 14 +++++++------- flow.json | 4 ++-- .../next_uint64_from_address.cdc | 6 +++--- .../next_uint64_new_prg.cdc | 4 ++-- .../iterative_next_uint64.cdc | 10 +++++----- .../pseudo-random-generator/next_uint64.cdc | 6 +++--- transactions/pseudo-random-generator/setup_prg.cdc | 14 +++++++------- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/contracts/CoinToss.cdc b/contracts/CoinToss.cdc index 1a06646..4feafa8 100644 --- a/contracts/CoinToss.cdc +++ b/contracts/CoinToss.cdc @@ -2,7 +2,7 @@ import "FungibleToken" import "FlowToken" import "RandomBeaconHistory" -import "XorShift128Plus" +import "Xorshift128plus" access(all) contract CoinToss { @@ -72,7 +72,7 @@ access(all) contract CoinToss { // instantiate a PRG object using external `createPRG()` that takes a `seed` // and `salt` and returns a pseudo-random generator object. - let prg <- XorShift128Plus.createPRG( + let prg <- Xorshift128plus.createPRG( sourceOfRandomness: sourceOfRandomness.value, salt: salt ) diff --git a/contracts/XorShift128Plus.cdc b/contracts/XorShift128Plus.cdc index b5f442b..392fdb4 100644 --- a/contracts/XorShift128Plus.cdc +++ b/contracts/XorShift128Plus.cdc @@ -2,7 +2,7 @@ import Crypto /// Defines a xorsift128+ pseudo random generator as a resource /// -access(all) contract XorShift128Plus { +access(all) contract Xorshift128plus { access(all) let StoragePath: StoragePath access(all) let PublicPath: PublicPath @@ -21,10 +21,10 @@ access(all) contract XorShift128Plus { sourceOfRandomness.length == 32: "Expecting 32 bytes as sourceOfRandomness" } // Convert the seed bytes to two Word64 values for state initialization - let segment0: Word64 = XorShift128Plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 0) - let segment1: Word64 = XorShift128Plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 8) - let segment2: Word64 = XorShift128Plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 16) - let segment3: Word64 = XorShift128Plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 24) + let segment0: Word64 = Xorshift128plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 0) + let segment1: Word64 = Xorshift128plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 8) + let segment2: Word64 = Xorshift128plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 16) + let segment3: Word64 = Xorshift128plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 24) self.state0 = segment0 ^ segment1 self.state1 = segment2 ^ segment3 @@ -89,7 +89,7 @@ access(all) contract XorShift128Plus { } init() { - self.StoragePath = StoragePath(identifier: "XorShift128PlusPRG_".concat(self.account.address.toString()))! - self.PublicPath = PublicPath(identifier: "XorShift128PlusPRG_".concat(self.account.address.toString()))! + self.StoragePath = StoragePath(identifier: "Xorshift128plusPRG_".concat(self.account.address.toString()))! + self.PublicPath = PublicPath(identifier: "Xorshift128plusPRG_".concat(self.account.address.toString()))! } } diff --git a/flow.json b/flow.json index 8917f78..0a7db18 100644 --- a/flow.json +++ b/flow.json @@ -25,7 +25,7 @@ "emulator": "f8d6e0586b0a20c7" } }, - "XorShift128Plus": "./contracts/XorShift128Plus.cdc", + "Xorshift128plus": "./contracts/Xorshift128plus.cdc", "RandomBeaconHistory": "./contracts/RandomBeaconHistory.cdc", "ViewResolver": { "source": "./contracts/utility/ViewResolver.cdc", @@ -52,7 +52,7 @@ "MetadataViews", "ViewResolver", "RandomBeaconHistory", - "XorShift128Plus", + "Xorshift128plus", "CoinToss" ] } diff --git a/scripts/pseudo-random-generator/next_uint64_from_address.cdc b/scripts/pseudo-random-generator/next_uint64_from_address.cdc index cafea69..77a7ba0 100644 --- a/scripts/pseudo-random-generator/next_uint64_from_address.cdc +++ b/scripts/pseudo-random-generator/next_uint64_from_address.cdc @@ -1,9 +1,9 @@ -import "XorShift128Plus" +import "Xorshift128plus" pub fun main(prgAddress: Address): UInt64 { - return getAccount(prgAddress).getCapability<&XorShift128Plus.PRG>( - XorShift128Plus.PublicPath + return getAccount(prgAddress).getCapability<&Xorshift128plus.PRG>( + Xorshift128plus.PublicPath ).borrow() ?.nextUInt64() ?? panic("Could not find PRG at address") diff --git a/scripts/pseudo-random-generator/next_uint64_new_prg.cdc b/scripts/pseudo-random-generator/next_uint64_new_prg.cdc index f5dbf07..ed0e18a 100644 --- a/scripts/pseudo-random-generator/next_uint64_new_prg.cdc +++ b/scripts/pseudo-random-generator/next_uint64_new_prg.cdc @@ -1,8 +1,8 @@ -import "XorShift128Plus" +import "Xorshift128plus" pub fun main(seed: [UInt8], salt: UInt64): UInt64 { - let prg <- XorShift128Plus.createPRG(sourceOfRandomness: seed, salt: salt) + let prg <- Xorshift128plus.createPRG(sourceOfRandomness: seed, salt: salt) let randUInt64 = prg.nextUInt64() diff --git a/transactions/pseudo-random-generator/iterative_next_uint64.cdc b/transactions/pseudo-random-generator/iterative_next_uint64.cdc index c571a2a..1fe1ce7 100644 --- a/transactions/pseudo-random-generator/iterative_next_uint64.cdc +++ b/transactions/pseudo-random-generator/iterative_next_uint64.cdc @@ -1,15 +1,15 @@ -import "XorShift128Plus" +import "Xorshift128plus" -/// Generates an arbitrary number of random numbers using the XorShift128Plus.PRG saved in signer's storage. +/// Generates an arbitrary number of random numbers using the Xorshift128plus.PRG saved in signer's storage. /// While the values generated in this transaction are not used or stored, this transaction demonstrates how one would -/// go about generating any number of random numbers using the XorShift128Plus.PRG. If desired, the generated numbers +/// go about generating any number of random numbers using the Xorshift128plus.PRG. If desired, the generated numbers /// could be stored in some resource or /// transaction(generationLength: Int) { prepare(signer: AuthAccount) { - // Get the XorShift128Plus.PRG from signer's storage - if let prg = signer.borrow<&XorShift128Plus.PRG>(from: XorShift128Plus.StoragePath) { + // Get the Xorshift128plus.PRG from signer's storage + if let prg = signer.borrow<&Xorshift128plus.PRG>(from: Xorshift128plus.StoragePath) { var i = 0 // Generate the desired number of random numbers while i < generationLength { diff --git a/transactions/pseudo-random-generator/next_uint64.cdc b/transactions/pseudo-random-generator/next_uint64.cdc index 753c6b5..ec1595e 100644 --- a/transactions/pseudo-random-generator/next_uint64.cdc +++ b/transactions/pseudo-random-generator/next_uint64.cdc @@ -1,12 +1,12 @@ -import "XorShift128Plus" +import "Xorshift128plus" -/// Advances the XorShift128Plus.PRG state and generates a random number. Since values cannot be returned from +/// Advances the Xorshift128plus.PRG state and generates a random number. Since values cannot be returned from /// transactions, a caller would need to have queried the PRG with prg.getNextUInt64() before running this transaction /// to retrieve the number that will be generated. /// transaction { prepare(signer: AuthAccount) { - signer.borrow<&XorShift128Plus.PRG>(from: XorShift128Plus.StoragePath) + signer.borrow<&Xorshift128plus.PRG>(from: Xorshift128plus.StoragePath) ?.nextUInt64() ?? panic("No PRG found in signer's storage") } diff --git a/transactions/pseudo-random-generator/setup_prg.cdc b/transactions/pseudo-random-generator/setup_prg.cdc index ba43ef4..c047eaa 100644 --- a/transactions/pseudo-random-generator/setup_prg.cdc +++ b/transactions/pseudo-random-generator/setup_prg.cdc @@ -1,22 +1,22 @@ -import "XorShift128Plus" +import "Xorshift128plus" /// Saves and links a .PRG resource in the signer's storage and public namespace /// transaction(seed: [UInt8], salt: UInt64) { prepare(signer: AuthAccount) { - if signer.type(at: XorShift128Plus.StoragePath) != nil { + if signer.type(at: Xorshift128plus.StoragePath) != nil { return } signer.save( - <- XorShift128Plus.createPRG( + <- Xorshift128plus.createPRG( sourceOfRandomness: seed, salt: salt ), - to: XorShift128Plus.StoragePath + to: Xorshift128plus.StoragePath ) - signer.link<&XorShift128Plus.PRG>( - XorShift128Plus.PublicPath, - target: XorShift128Plus.StoragePath + signer.link<&Xorshift128plus.PRG>( + Xorshift128plus.PublicPath, + target: Xorshift128plus.StoragePath ) } } \ No newline at end of file From ce51fcd2865470ac696a9e6188d362d29c59eb7d Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:00:11 -0600 Subject: [PATCH 18/34] refactor Xorshift128plus.PRG from resource to struct & update its instances --- contracts/CoinToss.cdc | 10 ++--- contracts/XorShift128Plus.cdc | 39 +++++++------------ ...ext_uint64_new_prg.cdc => next_uint64.cdc} | 4 +- .../next_uint64_from_address.cdc | 10 ----- .../iterative_next_uint64.cdc | 26 ++++++++----- .../pseudo-random-generator/next_uint64.cdc | 13 ------- .../pseudo-random-generator/setup_prg.cdc | 22 ----------- 7 files changed, 33 insertions(+), 91 deletions(-) rename scripts/pseudo-random-generator/{next_uint64_new_prg.cdc => next_uint64.cdc} (60%) delete mode 100644 scripts/pseudo-random-generator/next_uint64_from_address.cdc delete mode 100644 transactions/pseudo-random-generator/next_uint64.cdc delete mode 100644 transactions/pseudo-random-generator/setup_prg.cdc diff --git a/contracts/CoinToss.cdc b/contracts/CoinToss.cdc index 4feafa8..2e7a9da 100644 --- a/contracts/CoinToss.cdc +++ b/contracts/CoinToss.cdc @@ -23,9 +23,6 @@ access(all) contract CoinToss { } } - // PRG implementation is not provided by the FLIP, we assume this contract - // imports a suitable PRG implementation - access(all) fun commitCoinToss(bet: @FungibleToken.Vault): @Receipt { let receipt <- create Receipt( betAmount: bet.balance @@ -70,16 +67,15 @@ access(all) contract CoinToss { let sourceOfRandomness = RandomBeaconHistory.sourceOfRandomness(atBlockHeight: atBlockHeight) assert(sourceOfRandomness.blockHeight == atBlockHeight, message: "RandomSource block height mismatch") - // instantiate a PRG object using external `createPRG()` that takes a `seed` - // and `salt` and returns a pseudo-random generator object. - let prg <- Xorshift128plus.createPRG( + // instantiate a PRG object, seeding a source of randomness with and `salt` and returns a pseudo-random + // generator object. + let prg = Xorshift128plus.PRG( sourceOfRandomness: sourceOfRandomness.value, salt: salt ) // derive a 64-bit random using the object `prg` let rand = prg.nextUInt64() - destroy prg return UInt8(rand & 1) } diff --git a/contracts/XorShift128Plus.cdc b/contracts/XorShift128Plus.cdc index 392fdb4..25d7154 100644 --- a/contracts/XorShift128Plus.cdc +++ b/contracts/XorShift128Plus.cdc @@ -4,22 +4,30 @@ import Crypto /// access(all) contract Xorshift128plus { - access(all) let StoragePath: StoragePath - access(all) let PublicPath: PublicPath - /// While not limited to 128 bits of state, this PRG is largely informed by XORShift128+ /// - access(all) resource PRG { + access(all) struct PRG { // The states below are of type Word64 (instead of UInt64) to prevent overflow/underflow as state evolves // access(all) var state0: Word64 access(all) var state1: Word64 - init(sourceOfRandomness: [UInt8]) { + /// Initializer for PRG struct + /// + /// @param sourceOfRandomness: The 32 byte source of randomness used to seed the PRG + /// @param salt: The bytes used to salt the source of randomness + init(sourceOfRandomness: [UInt8], salt: [UInt8]) { pre { sourceOfRandomness.length == 32: "Expecting 32 bytes as sourceOfRandomness" } + + let tmp: [UInt8] = sourceOfRandomness.concat(salt) + // Hash is 32 bytes + let hash: [UInt8] = Crypto.hash(tmp, algorithm: HashAlgorithm.SHA3_256) + // Reduce the seed to 16 bytes + let seed: [UInt8] = hash.slice(from: 0, upTo: 16) + // Convert the seed bytes to two Word64 values for state initialization let segment0: Word64 = Xorshift128plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 0) let segment1: Word64 = Xorshift128plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 8) @@ -71,25 +79,4 @@ access(all) contract Xorshift128plus { } return Word64(value) } - - /// Creates a new XORSift128+ PRG with the given source of randomness and salt - /// - /// @param sourceOfRandomness: The 32 byte source of randomness used to seed the PRG - /// @param salt: The bytes used to salt the source of randomness - /// - /// @return A new PRG resource - access(all) fun createPRG(sourceOfRandomness: [UInt8], salt: [UInt8]): @PRG { - let tmp: [UInt8] = sourceOfRandomness.concat(salt) - // Hash is 32 bytes - let hash: [UInt8] = Crypto.hash(tmp, algorithm: HashAlgorithm.SHA3_256) - // Reduce the seed to 16 bytes - let seed: [UInt8] = hash.slice(from: 0, upTo: 16) - - return <- create PRG(sourceOfRandomness: seed) - } - - init() { - self.StoragePath = StoragePath(identifier: "Xorshift128plusPRG_".concat(self.account.address.toString()))! - self.PublicPath = PublicPath(identifier: "Xorshift128plusPRG_".concat(self.account.address.toString()))! - } } diff --git a/scripts/pseudo-random-generator/next_uint64_new_prg.cdc b/scripts/pseudo-random-generator/next_uint64.cdc similarity index 60% rename from scripts/pseudo-random-generator/next_uint64_new_prg.cdc rename to scripts/pseudo-random-generator/next_uint64.cdc index ed0e18a..f3a7b68 100644 --- a/scripts/pseudo-random-generator/next_uint64_new_prg.cdc +++ b/scripts/pseudo-random-generator/next_uint64.cdc @@ -2,11 +2,9 @@ import "Xorshift128plus" pub fun main(seed: [UInt8], salt: UInt64): UInt64 { - let prg <- Xorshift128plus.createPRG(sourceOfRandomness: seed, salt: salt) + let prg = Xorshift128plus.PRG(sourceOfRandomness: seed, salt: salt) let randUInt64 = prg.nextUInt64() - destroy prg - return randUInt64 } diff --git a/scripts/pseudo-random-generator/next_uint64_from_address.cdc b/scripts/pseudo-random-generator/next_uint64_from_address.cdc deleted file mode 100644 index 77a7ba0..0000000 --- a/scripts/pseudo-random-generator/next_uint64_from_address.cdc +++ /dev/null @@ -1,10 +0,0 @@ -import "Xorshift128plus" - -pub fun main(prgAddress: Address): UInt64 { - - return getAccount(prgAddress).getCapability<&Xorshift128plus.PRG>( - Xorshift128plus.PublicPath - ).borrow() - ?.nextUInt64() - ?? panic("Could not find PRG at address") -} diff --git a/transactions/pseudo-random-generator/iterative_next_uint64.cdc b/transactions/pseudo-random-generator/iterative_next_uint64.cdc index 1fe1ce7..1a25cd3 100644 --- a/transactions/pseudo-random-generator/iterative_next_uint64.cdc +++ b/transactions/pseudo-random-generator/iterative_next_uint64.cdc @@ -1,21 +1,27 @@ import "Xorshift128plus" -/// Generates an arbitrary number of random numbers using the Xorshift128plus.PRG saved in signer's storage. +/// Generates an arbitrary number of random numbers using the Xorshift128plus.PRG struct. /// While the values generated in this transaction are not used or stored, this transaction demonstrates how one would /// go about generating any number of random numbers using the Xorshift128plus.PRG. If desired, the generated numbers /// could be stored in some resource or /// -transaction(generationLength: Int) { +transaction(sourceOfRandomness: [UInt8], salt: [UInt8], generationLength: Int) { prepare(signer: AuthAccount) { - // Get the Xorshift128plus.PRG from signer's storage - if let prg = signer.borrow<&Xorshift128plus.PRG>(from: Xorshift128plus.StoragePath) { - var i = 0 - // Generate the desired number of random numbers - while i < generationLength { - prg.nextUInt64() - i = i + 1 - } + let prg = Xorshift128plus.PRG( + sourceOfRandomness: sourceOfRandomness.value, + salt: salt + ) + + var i:Int = 0 + let randomNumbers: [UInt64] = [] + // Generate the desired number of random numbers + while i < generationLength { + prg.nextUInt64() + i = i + 1 + randomNumbers.append(prg.nextUInt64()) } + + // Can then continue using array of random numbers for your use case... } } diff --git a/transactions/pseudo-random-generator/next_uint64.cdc b/transactions/pseudo-random-generator/next_uint64.cdc deleted file mode 100644 index ec1595e..0000000 --- a/transactions/pseudo-random-generator/next_uint64.cdc +++ /dev/null @@ -1,13 +0,0 @@ -import "Xorshift128plus" - -/// Advances the Xorshift128plus.PRG state and generates a random number. Since values cannot be returned from -/// transactions, a caller would need to have queried the PRG with prg.getNextUInt64() before running this transaction -/// to retrieve the number that will be generated. -/// -transaction { - prepare(signer: AuthAccount) { - signer.borrow<&Xorshift128plus.PRG>(from: Xorshift128plus.StoragePath) - ?.nextUInt64() - ?? panic("No PRG found in signer's storage") - } -} diff --git a/transactions/pseudo-random-generator/setup_prg.cdc b/transactions/pseudo-random-generator/setup_prg.cdc deleted file mode 100644 index c047eaa..0000000 --- a/transactions/pseudo-random-generator/setup_prg.cdc +++ /dev/null @@ -1,22 +0,0 @@ -import "Xorshift128plus" - -/// Saves and links a .PRG resource in the signer's storage and public namespace -/// -transaction(seed: [UInt8], salt: UInt64) { - prepare(signer: AuthAccount) { - if signer.type(at: Xorshift128plus.StoragePath) != nil { - return - } - signer.save( - <- Xorshift128plus.createPRG( - sourceOfRandomness: seed, - salt: salt - ), - to: Xorshift128plus.StoragePath - ) - signer.link<&Xorshift128plus.PRG>( - Xorshift128plus.PublicPath, - target: Xorshift128plus.StoragePath - ) - } -} \ No newline at end of file From d980baea2717d417a180717f144cc62e4a292a9b Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:00:52 -0600 Subject: [PATCH 19/34] add getter script for array of random UInt64 from prg --- .../get_array_next_uint64.cdc | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 scripts/pseudo-random-generator/get_array_next_uint64.cdc diff --git a/scripts/pseudo-random-generator/get_array_next_uint64.cdc b/scripts/pseudo-random-generator/get_array_next_uint64.cdc new file mode 100644 index 0000000..f90faab --- /dev/null +++ b/scripts/pseudo-random-generator/get_array_next_uint64.cdc @@ -0,0 +1,24 @@ +import "Xorshift128plus" + +/// Generates an arbitrary number of random numbers using the Xorshift128plus.PRG struct. +/// While the values generated in this transaction are not used or stored, this transaction demonstrates how one would +/// go about generating any number of random numbers using the Xorshift128plus.PRG. If desired, the generated numbers +/// could be stored in some resource or +/// +access(all) fun main(sourceOfRandomness: [UInt8], salt: [UInt8], generationLength: Int): [UInt64] { + let prg = Xorshift128plus.PRG( + sourceOfRandomness: sourceOfRandomness.value, + salt: salt + ) + + var i: Int = 0 + let randomNumbers: [UInt64] = [] + // Generate the desired number of random numbers + while i < generationLength { + prg.nextUInt64() + i = i + 1 + randomNumbers.append(prg.nextUInt64()) + } + + return randomNumbers +} From 8a392d071dd40d0f0f6b387865b1a81fb10acfc7 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:02:37 -0600 Subject: [PATCH 20/34] simplify next_uint64 script & add comment --- scripts/pseudo-random-generator/next_uint64.cdc | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/scripts/pseudo-random-generator/next_uint64.cdc b/scripts/pseudo-random-generator/next_uint64.cdc index f3a7b68..27d9c91 100644 --- a/scripts/pseudo-random-generator/next_uint64.cdc +++ b/scripts/pseudo-random-generator/next_uint64.cdc @@ -1,10 +1,7 @@ import "Xorshift128plus" +/// Returns a random number from a Xorshift128plus.PRG struct constructed from the given seed and salt. +/// pub fun main(seed: [UInt8], salt: UInt64): UInt64 { - - let prg = Xorshift128plus.PRG(sourceOfRandomness: seed, salt: salt) - - let randUInt64 = prg.nextUInt64() - - return randUInt64 + return Xorshift128plus.PRG(sourceOfRandomness: seed, salt: salt).nextUInt64() } From e05641a8ae197498986c2d62d55cb8f8ae82b118 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:05:39 -0600 Subject: [PATCH 21/34] clarify script comments --- scripts/pseudo-random-generator/get_array_next_uint64.cdc | 7 ++++--- scripts/pseudo-random-generator/next_uint64.cdc | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/pseudo-random-generator/get_array_next_uint64.cdc b/scripts/pseudo-random-generator/get_array_next_uint64.cdc index f90faab..bc63d8e 100644 --- a/scripts/pseudo-random-generator/get_array_next_uint64.cdc +++ b/scripts/pseudo-random-generator/get_array_next_uint64.cdc @@ -1,9 +1,10 @@ import "Xorshift128plus" /// Generates an arbitrary number of random numbers using the Xorshift128plus.PRG struct. -/// While the values generated in this transaction are not used or stored, this transaction demonstrates how one would -/// go about generating any number of random numbers using the Xorshift128plus.PRG. If desired, the generated numbers -/// could be stored in some resource or +/// This script demonstrates how one would go about generating any number of random numbers using the +/// example PRG implemented in Xorshift128plus. +/// +/// Note that if the PRG were stored onchain, this script would not advance the state of the PRG /// access(all) fun main(sourceOfRandomness: [UInt8], salt: [UInt8], generationLength: Int): [UInt64] { let prg = Xorshift128plus.PRG( diff --git a/scripts/pseudo-random-generator/next_uint64.cdc b/scripts/pseudo-random-generator/next_uint64.cdc index 27d9c91..091a1dc 100644 --- a/scripts/pseudo-random-generator/next_uint64.cdc +++ b/scripts/pseudo-random-generator/next_uint64.cdc @@ -2,6 +2,8 @@ import "Xorshift128plus" /// Returns a random number from a Xorshift128plus.PRG struct constructed from the given seed and salt. /// +/// Note that if the PRG were stored onchain, this script would not advance the state of the PRG +/// pub fun main(seed: [UInt8], salt: UInt64): UInt64 { return Xorshift128plus.PRG(sourceOfRandomness: seed, salt: salt).nextUInt64() } From 850cca7aed952a962956c8c518b113096a4eae61 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:08:00 -0600 Subject: [PATCH 22/34] fix iterative_next_uint64 txn and clarify comments --- .../pseudo-random-generator/iterative_next_uint64.cdc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/transactions/pseudo-random-generator/iterative_next_uint64.cdc b/transactions/pseudo-random-generator/iterative_next_uint64.cdc index 1a25cd3..bf7fd65 100644 --- a/transactions/pseudo-random-generator/iterative_next_uint64.cdc +++ b/transactions/pseudo-random-generator/iterative_next_uint64.cdc @@ -3,7 +3,9 @@ import "Xorshift128plus" /// Generates an arbitrary number of random numbers using the Xorshift128plus.PRG struct. /// While the values generated in this transaction are not used or stored, this transaction demonstrates how one would /// go about generating any number of random numbers using the Xorshift128plus.PRG. If desired, the generated numbers -/// could be stored in some resource or +/// could be stored in some resource or used in a transaction. +/// +/// Note that since transactions are mutating, calls to `nextUInt64` will advance the PRG state. /// transaction(sourceOfRandomness: [UInt8], salt: [UInt8], generationLength: Int) { From d9eecda97e7e647530a64d2955a87287bf01c175 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:09:59 -0600 Subject: [PATCH 23/34] fix PRG instantiation in CoinToss to match new interface --- contracts/CoinToss.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/CoinToss.cdc b/contracts/CoinToss.cdc index 2e7a9da..72dc32f 100644 --- a/contracts/CoinToss.cdc +++ b/contracts/CoinToss.cdc @@ -71,7 +71,7 @@ access(all) contract CoinToss { // generator object. let prg = Xorshift128plus.PRG( sourceOfRandomness: sourceOfRandomness.value, - salt: salt + salt: salt.toBigEndianBytes() ) // derive a 64-bit random using the object `prg` From 6e963b3731749aa1506d50e351442caf95d8725a Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:10:45 -0600 Subject: [PATCH 24/34] fix iterative_next_uint64 PRG instantiation --- .../pseudo-random-generator/iterative_next_uint64.cdc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transactions/pseudo-random-generator/iterative_next_uint64.cdc b/transactions/pseudo-random-generator/iterative_next_uint64.cdc index bf7fd65..2c6b657 100644 --- a/transactions/pseudo-random-generator/iterative_next_uint64.cdc +++ b/transactions/pseudo-random-generator/iterative_next_uint64.cdc @@ -11,11 +11,11 @@ transaction(sourceOfRandomness: [UInt8], salt: [UInt8], generationLength: Int) { prepare(signer: AuthAccount) { let prg = Xorshift128plus.PRG( - sourceOfRandomness: sourceOfRandomness.value, + sourceOfRandomness: sourceOfRandomness, salt: salt ) - var i:Int = 0 + var i: Int = 0 let randomNumbers: [UInt64] = [] // Generate the desired number of random numbers while i < generationLength { From 5f5ea131070e3a770c06a8d630e126ab993daec5 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:14:25 -0600 Subject: [PATCH 25/34] add clarifying comments to Xorshift128plus contract --- contracts/XorShift128Plus.cdc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/XorShift128Plus.cdc b/contracts/XorShift128Plus.cdc index 25d7154..1fb384e 100644 --- a/contracts/XorShift128Plus.cdc +++ b/contracts/XorShift128Plus.cdc @@ -1,6 +1,10 @@ import Crypto -/// Defines a xorsift128+ pseudo random generator as a resource +/// Defines a xorsift128+ pseudo random generator (PRG) struct used to generate random numbers given some +/// sourceOfRandomness and salt. +/// +/// See FLIP 123 for more details: https://github.com/onflow/flips/blob/main/protocol/20230728-commit-reveal.md +/// And the onflow/random-coin-toss repo for implementation context: https://github.com/onflow/random-coin-toss /// access(all) contract Xorshift128plus { From c439f1cc6e55998c3b74c1ef908b9e4be6039a34 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:15:57 -0600 Subject: [PATCH 26/34] fix PRG instantiation in script to match new interface --- scripts/pseudo-random-generator/get_array_next_uint64.cdc | 2 +- scripts/pseudo-random-generator/next_uint64.cdc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/pseudo-random-generator/get_array_next_uint64.cdc b/scripts/pseudo-random-generator/get_array_next_uint64.cdc index bc63d8e..a1adae7 100644 --- a/scripts/pseudo-random-generator/get_array_next_uint64.cdc +++ b/scripts/pseudo-random-generator/get_array_next_uint64.cdc @@ -8,7 +8,7 @@ import "Xorshift128plus" /// access(all) fun main(sourceOfRandomness: [UInt8], salt: [UInt8], generationLength: Int): [UInt64] { let prg = Xorshift128plus.PRG( - sourceOfRandomness: sourceOfRandomness.value, + sourceOfRandomness: sourceOfRandomness, salt: salt ) diff --git a/scripts/pseudo-random-generator/next_uint64.cdc b/scripts/pseudo-random-generator/next_uint64.cdc index 091a1dc..b29417c 100644 --- a/scripts/pseudo-random-generator/next_uint64.cdc +++ b/scripts/pseudo-random-generator/next_uint64.cdc @@ -4,6 +4,6 @@ import "Xorshift128plus" /// /// Note that if the PRG were stored onchain, this script would not advance the state of the PRG /// -pub fun main(seed: [UInt8], salt: UInt64): UInt64 { +pub fun main(seed: [UInt8], salt: [UInt8]): UInt64 { return Xorshift128plus.PRG(sourceOfRandomness: seed, salt: salt).nextUInt64() } From 1f72ce7960b208581874efbe142ed0a80b49707b Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:22:40 -0600 Subject: [PATCH 27/34] update CONTRIBUTING to name random-coin-toss repo --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 05157f5..0c55333 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to the Non-Fungible Token Standard +# Contributing to the random-coin-toss repo -The following is a set of guidelines for contributing to the Flow NFT standard. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. +The following is a set of guidelines for contributing to this Flow repository. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. #### Table Of Contents @@ -19,7 +19,7 @@ The following is a set of guidelines for contributing to the Flow NFT standard. ## How Can I Contribute? -You are free to contribute however you want! You can submit a bug report in an issue, suggest an enhancment, or even just make a PR for us to review. We just ask that you are clear in your communication and documentation of all your work so we can understand how you are trying to help. +You are free to contribute however you want! You can submit a bug report in an issue, suggest an enhancement, or even just make a PR for us to review. We just ask that you are clear in your communication and documentation of all your work so we can understand how you are trying to help. ### Reporting Bugs From cbf5d3e0760f28419aca5443a4839d1da3cd2269 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:27:51 -0600 Subject: [PATCH 28/34] Apply suggestions from code review Co-authored-by: Tarak Ben Youssef <50252200+tarakby@users.noreply.github.com> --- contracts/XorShift128Plus.cdc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/XorShift128Plus.cdc b/contracts/XorShift128Plus.cdc index 1fb384e..2b9e159 100644 --- a/contracts/XorShift128Plus.cdc +++ b/contracts/XorShift128Plus.cdc @@ -23,7 +23,7 @@ access(all) contract Xorshift128plus { /// @param salt: The bytes used to salt the source of randomness init(sourceOfRandomness: [UInt8], salt: [UInt8]) { pre { - sourceOfRandomness.length == 32: "Expecting 32 bytes as sourceOfRandomness" + sourceOfRandomness.length == 16: "Expecting 16 bytes as input seed" } let tmp: [UInt8] = sourceOfRandomness.concat(salt) @@ -38,8 +38,8 @@ access(all) contract Xorshift128plus { let segment2: Word64 = Xorshift128plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 16) let segment3: Word64 = Xorshift128plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 24) - self.state0 = segment0 ^ segment1 - self.state1 = segment2 ^ segment3 + self.state0 = segment0 + self.state1 = segment1 } /// Advances the PRG state and generates the next UInt64 value @@ -59,7 +59,7 @@ access(all) contract Xorshift128plus { a = a ^ b ^ (b >> 26) // c self.state1 = a - let randUInt64: UInt64 = UInt64(a + b) + let randUInt64: UInt64 = UInt64(Word64(a) + Word64(b)) return randUInt64 } } From e9157d5f642f36ce50b890a3362bb6a77735bfac Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:37:07 -0600 Subject: [PATCH 29/34] update Xorshift128plus contract with PR feedback --- contracts/XorShift128Plus.cdc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/XorShift128Plus.cdc b/contracts/XorShift128Plus.cdc index 2b9e159..7fc8ebf 100644 --- a/contracts/XorShift128Plus.cdc +++ b/contracts/XorShift128Plus.cdc @@ -19,11 +19,13 @@ access(all) contract Xorshift128plus { /// Initializer for PRG struct /// - /// @param sourceOfRandomness: The 32 byte source of randomness used to seed the PRG + /// @param sourceOfRandomness: The entropy bytes used to seed the PRG. It is recommended to use at least 16 + /// bytes of entropy. /// @param salt: The bytes used to salt the source of randomness + /// init(sourceOfRandomness: [UInt8], salt: [UInt8]) { pre { - sourceOfRandomness.length == 16: "Expecting 16 bytes as input seed" + sourceOfRandomness.length >= 16: "Expecting 16 bytes as input seed" } let tmp: [UInt8] = sourceOfRandomness.concat(salt) @@ -35,8 +37,6 @@ access(all) contract Xorshift128plus { // Convert the seed bytes to two Word64 values for state initialization let segment0: Word64 = Xorshift128plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 0) let segment1: Word64 = Xorshift128plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 8) - let segment2: Word64 = Xorshift128plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 16) - let segment3: Word64 = Xorshift128plus.bigEndianBytesToWord64(bytes: sourceOfRandomness, start: 24) self.state0 = segment0 self.state1 = segment1 From 61cc8079f441d4826ee08e51d72ea03833184bca Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:38:52 -0600 Subject: [PATCH 30/34] Rename XorShift128Plus.cdc to Xorshift128plus.cdc --- contracts/{XorShift128Plus.cdc => Xorshift128plus.cdc} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contracts/{XorShift128Plus.cdc => Xorshift128plus.cdc} (100%) diff --git a/contracts/XorShift128Plus.cdc b/contracts/Xorshift128plus.cdc similarity index 100% rename from contracts/XorShift128Plus.cdc rename to contracts/Xorshift128plus.cdc From ccf6d0e169b28489688aa972f8bc6f4ca1adb782 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:41:41 -0600 Subject: [PATCH 31/34] update Xorshift128plus comments & error messages --- contracts/Xorshift128plus.cdc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/Xorshift128plus.cdc b/contracts/Xorshift128plus.cdc index 7fc8ebf..8bdede6 100644 --- a/contracts/Xorshift128plus.cdc +++ b/contracts/Xorshift128plus.cdc @@ -8,7 +8,7 @@ import Crypto /// access(all) contract Xorshift128plus { - /// While not limited to 128 bits of state, this PRG is largely informed by XORShift128+ + /// While not limited to 128 bits of state, this PRG is largely informed by xorshift128+ /// access(all) struct PRG { @@ -25,7 +25,7 @@ access(all) contract Xorshift128plus { /// init(sourceOfRandomness: [UInt8], salt: [UInt8]) { pre { - sourceOfRandomness.length >= 16: "Expecting 16 bytes as input seed" + sourceOfRandomness.length >= 16: "At least 16 bytes of entropy should be used" } let tmp: [UInt8] = sourceOfRandomness.concat(salt) From 2f578d68b44f31d47a46ef3db1ba6303fdce0e10 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 18:20:16 -0600 Subject: [PATCH 32/34] add comments to CoinToss contract --- contracts/CoinToss.cdc | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/contracts/CoinToss.cdc b/contracts/CoinToss.cdc index 72dc32f..f2c7c10 100644 --- a/contracts/CoinToss.cdc +++ b/contracts/CoinToss.cdc @@ -4,15 +4,26 @@ import "FlowToken" import "RandomBeaconHistory" import "Xorshift128plus" +/// CoinToss is a simple game contract showcasing the safe use of onchain randomness by way of a commit-reveal sheme. +/// +/// See FLIP 123 for more details: https://github.com/onflow/flips/blob/main/protocol/20230728-commit-reveal.md +/// And the onflow/random-coin-toss repo for implementation context: https://github.com/onflow/random-coin-toss +/// access(all) contract CoinToss { + /// The Vault used by the contract to store funds. access(self) let reserve: @FlowToken.Vault + /// The canonical path for common Receipt storage access(all) let ReceiptStoragePath: StoragePath + /* --- Events --- */ + // access(all) event CoinTossBet(betAmount: UFix64, commitBlock: UInt64, receiptID: UInt64) access(all) event CoinTossReveal(betAmount: UFix64, winningAmount: UFix64, commitBlock: UInt64, receiptID: UInt64) + /// The Receipt resource is used to store the bet amount and block height at which the bet was committed. + /// access(all) resource Receipt { access(all) let betAmount: UFix64 access(all) let commitBlock: UInt64 @@ -23,13 +34,15 @@ access(all) contract CoinToss { } } + /* --- Commit --- */ + // + /// In this method, the caller commits a bet. The contract takes note of the block height and bet amount, returning a + /// Receipt resource which is used by the better to reveal the coin toss result and determine their winnings. + /// access(all) fun commitCoinToss(bet: @FungibleToken.Vault): @Receipt { let receipt <- create Receipt( betAmount: bet.balance ) - // commit the bet - // `self.reserve` is a `@FungibleToken.Vault` field defined on the app contract - // and represents a pool of funds self.reserve.deposit(from: <-bet) emit CoinTossBet(betAmount: receipt.betAmount, commitBlock: receipt.commitBlock, receiptID: receipt.uuid) @@ -37,6 +50,14 @@ access(all) contract CoinToss { return <- receipt } + /* --- Reveal --- */ + // + /// Here the caller provides the Receipt given to them at commitment. The contract then "flips a coin" with + /// randomCoin(), providing the committed block height and salting with the Receipts unique identifier. + /// If result is 1, user loses, if it's 0 the user doubles their bet. Note that the caller could condition the + /// revealing transaction, but they've already provided their bet amount so there's no loss for the contract if + /// they do. + /// access(all) fun revealCoinToss(receipt: @Receipt): @FungibleToken.Vault { pre { receipt.commitBlock <= getCurrentBlock().height: "Cannot reveal before commit block" @@ -45,6 +66,9 @@ access(all) contract CoinToss { let betAmount = receipt.betAmount let commitBlock = receipt.commitBlock let receiptID = receipt.uuid + + // self.randomCoin() errors if commitBlock <= current block height in call to + // RandomBeaconHistory.sourceOfRandomness() let coin = self.randomCoin(atBlockHeight: receipt.commitBlock, salt: receipt.uuid) destroy receipt @@ -61,9 +85,12 @@ access(all) contract CoinToss { return <- reward } + /// Helper method using RandomBeaconHistory to retrieve a source of randomness for a specific block height and the + /// given salt to instantiate a PRG object. A randomly generated UInt64 is then reduced by bitwise operation to + /// UInt8 value of 1 or 0 and returned. + /// access(all) fun randomCoin(atBlockHeight: UInt64, salt: UInt64): UInt8 { - // query the Random Beacon history core-contract. - // if `blockHeight` is the current block height, `sourceOfRandomness` errors. + // query the Random Beacon history core-contract - if `blockHeight` <= current block height, panic & revert let sourceOfRandomness = RandomBeaconHistory.sourceOfRandomness(atBlockHeight: atBlockHeight) assert(sourceOfRandomness.blockHeight == atBlockHeight, message: "RandomSource block height mismatch") @@ -74,7 +101,7 @@ access(all) contract CoinToss { salt: salt.toBigEndianBytes() ) - // derive a 64-bit random using the object `prg` + // derive a 64-bit random using the PRG object and reduce to a UInt8 value of 1 or 0 let rand = prg.nextUInt64() return UInt8(rand & 1) @@ -87,6 +114,6 @@ access(all) contract CoinToss { from: <-seedVault.withdraw(amount: 1000.0) ) - self.ReceiptStoragePath = /storage/CoinTossReceipt + self.ReceiptStoragePath = StoragePath(identifier: "CoinTossReceipt_".concat(self.account.address.toString()))! } } From 256c1b21c09f02a2a8c0a6444967ccc56f31bb35 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:28:36 -0600 Subject: [PATCH 33/34] Update CoinToss.cdc Co-authored-by: Tarak Ben Youssef <50252200+tarakby@users.noreply.github.com> --- contracts/CoinToss.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/CoinToss.cdc b/contracts/CoinToss.cdc index f2c7c10..8a974ab 100644 --- a/contracts/CoinToss.cdc +++ b/contracts/CoinToss.cdc @@ -94,7 +94,7 @@ access(all) contract CoinToss { let sourceOfRandomness = RandomBeaconHistory.sourceOfRandomness(atBlockHeight: atBlockHeight) assert(sourceOfRandomness.blockHeight == atBlockHeight, message: "RandomSource block height mismatch") - // instantiate a PRG object, seeding a source of randomness with and `salt` and returns a pseudo-random + // instantiate a PRG object, seeding a source of randomness with `salt` and returns a pseudo-random // generator object. let prg = Xorshift128plus.PRG( sourceOfRandomness: sourceOfRandomness.value, From bf00c4e918226b7a9c7a22ce878d82f82f73e315 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 7 Nov 2023 21:18:59 -0600 Subject: [PATCH 34/34] update README info dialog about WIP status --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3bb3d3..e0e05ce 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [WIP] Random Coin Toss -> :warning: This repo is still a work in progress - the underlying RandomBeaconHistory is also still a work in progress +> :warning: This repo is still a work in progress ## Overview