Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[randomness part 2] improve usafeRandom and update math/rand in FVM #4067

Merged
merged 6 commits into from
Mar 27, 2023

Conversation

tarakby
Copy link
Contributor

@tarakby tarakby commented Mar 21, 2023

( result of splitting #4052 into several PRs)

  • prepare for Go1.20 update by removing deprecated math/rand functions Seed and Read.
  • improve FVM's unsafeRandom implementation: (@AlexHentschel @pattyshack)
    • extract seed from block ID using a KDF (key derivation function)
    • use a Crypto-Secure PRG to generate deterministic randomness, instead of weak math/rand PRG. Advantages are:
      • seed is 128 bits (minimum required in this case) instead of 64 bits
      • state of PRG cannot be recovered based on arbitrary number of outputs, which means the future outputs cannot be known or biased based on the past outputs.
      • ChachCha20 PRG offers better output uniformity and statistical property of outputs.
  • improve unsafeRandom tests:
    • replace invalid uniformity tests
    • add missing determinicity test

Todo in another PR: diversify the seed per transaction.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 21, 2023

FVM Benchstat comparison

This branch with compared with the base branch onflow:master commit b500369

The command (for i in {1..7}; do go test ./fvm ./engine/execution/computation --bench . --tags relic -shuffle=on --benchmem --run ^$; done) was used.

Collapsed results for better readability

old.txtnew.txt
time/opdelta
ComputeBlock/16/cols/128/txes-24.29s ± 0%4.32s ± 0%~(p=1.000 n=1+1)
 
us/txdelta
ComputeBlock/16/cols/128/txes-22.10k ± 0%2.11k ± 0%~(p=1.000 n=1+1)
 
alloc/opdelta
ComputeBlock/16/cols/128/txes-21.21GB ± 0%1.22GB ± 0%~(p=1.000 n=1+1)
 
allocs/opdelta
ComputeBlock/16/cols/128/txes-217.4M ± 0%17.4M ± 0%~(p=1.000 n=1+1)
 

@tarakby tarakby changed the title remove deprecated math/rand.Seed and Read [randomness part 2] improve usafeRandom and update math/rand in FVM Mar 21, 2023
@codecov-commenter
Copy link

codecov-commenter commented Mar 21, 2023

Codecov Report

Merging #4067 (27ad90b) into master (b500369) will decrease coverage by 0.01%.
The diff coverage is 50.00%.

@@            Coverage Diff             @@
##           master    #4067      +/-   ##
==========================================
- Coverage   53.46%   53.46%   -0.01%     
==========================================
  Files         835      835              
  Lines       78129    78150      +21     
==========================================
+ Hits        41769    41779      +10     
- Misses      33014    33022       +8     
- Partials     3346     3349       +3     
Flag Coverage Δ
unittests 53.46% <50.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
fvm/environment/unsafe_random_generator.go 59.64% <50.00%> (-9.80%) ⬇️

... and 6 files with indirect coverage changes

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@tarakby tarakby marked this pull request as ready for review March 21, 2023 03:13
@tarakby tarakby self-assigned this Mar 21, 2023
@bluesign
Copy link
Contributor

@tarakby can you explain a bit more on this?

  • Is block ID predictable ? or can it be influenced

extract seed from block ID using a KDF (key derivation function)

why KDF necessary here ?

state of PRG cannot be recovered based on arbitrary number of outputs, which means the future outputs cannot be known or biased based on the past outputs.

as we seed on each block, what does this mean ?

ChachCha20 PRG offers better output uniformity and statistical property of outputs.

considering number of randoms we can use in a transaction, is this important?

}
stdev := stat.StdDev(distribution, nil)
mean := stat.Mean(distribution, nil)
assert.Greater(t, tolerance*mean, stdev, fmt.Sprintf("basic randomness test failed. stdev %v, mean %v", stdev, mean))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this check seems weird. std dev is meaningless for uniform distribution. you want something like

expectedMean := math.MaxUint64 / 2
expectedMean * (1 - tolerance) <= mean <= expectedMean * (1 + tolerance)

instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have two comments to your suggestion:

  • Imagine a dice with the possible outputs [1,2,3,4,5,6] and the random variable being the value we read after throwing the dice. In this case the expected mean according to your formula is 3,5. The uniform distribution we would like a perfect dice to have is [1/6, 1/6, 1/6, 1/6, 1/6, 1/6]. How can we verify that with a practical statistical test? Let's say we throw the dice 100 times, and we get 50 times 1, and 50 times 6. The mean in this case is exactly the expected mean 3,5 ( (150+650)/100 ), and we pass the inequality above with any small tolerance we chose. However the dice we tested is clearly biased and its distribution is far from uniform.

  • The test of the PR takes a different approach. It throws the dice 100 times and computes the frequency of each possible output in {1,2,3,4,5,6}. If the dice is perfect, we would get an array approximately [16 ,16, 16, 16, 16 ,16]. In other terms, the expected mean is 100/6 = 16,66 and we would like to test that each value of the array is close to that expected mean. The standard deviation is a basic statistical tool to measure that: it quantifies the dispersion of many values compared to their mean. A low standard deviation means that all values are close to the mean, a high standard deviation means the values are spread out from the mean. The biased dice in the example above gives [50, 0, 0, 0, 0, 50] and it gives a very large standard deviation. This test remains a basic statistical test that aims to find obvious issues or software bugs, it's relatively quick so that it can run as a unit test. But of course there are deeper randomness statistical tests that are beyond the scope of a unit test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to plug this into some existing test suite for random generators? like https://github.com/terrillmoore/NIST-Statistical-Test-Suite in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are an example of the deep statistical tests that I mentioned above. They are heavy (take time and write in lots of memory) and are used to test a TRG (true random generator) or a new implementation of a PRG. In our case we are using a known and established PRG underneath, called ChaCha20. Such PRG has been already studied on a cryptanalysis level (theory level) and statistical level (running tests on its outputs). Our code repo tests do not mean to re-evaluate ChaCha20, they only try to detect biases resulting from implementation bugs, while running relatively fast.

Btw, the crypto lib (where ChaCha20 is implemented) already checks that the implementation is compliant to https://www.rfc-editor.org/rfc/rfc7539, so we don't have to repeat it here.

for i := 1; i < N; i++ {
allEqual = allEqual && numbers[i] == numbers[0]
// tests that unsafeRandom is PRG based and hence has deterministic outputs.
t.Run("PRG-based UnsafeRandom", func(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a test to verify each transaction with the block has a deterministic / unique seed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, in the current implementation, the seed isn't diversified per transaction, it is only diversified per block, so such a test would fail.
We can add the test you described in the PR where we include the transaction index in the PRG construction.

source := rand.NewSource(int64(binary.BigEndian.Uint64(id[:])))
gen.rng = rand.New(source)
// extract the entropy from `id` and expand it into the required seed
hkdf := hkdf.New(func() hash.Hash { return sha256.New() }, id[:], nil, nil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

txn index as the salt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep the idea is to include the transaction index somewhere in the PRG construction (as a KDF salt, as a PRG customizer..).
I only wanted this PR to remove math/rand and use a safer PRG. Wiring the transaction index down the stack till the generator could be done in another PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sgtm. add a todo as a reminder

Copy link
Contributor

@pattyshack pattyshack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe address my expectedMean comment. I don't feel strongly about it.

@tarakby
Copy link
Contributor Author

tarakby commented Mar 21, 2023

@bluesign good questions!

Is block ID predictable ? or can it be influenced?

Block ID can be biased by a consensus node. It is possible for consensus nodes to loop over possible blocks and choose one with a block ID with the random outputs they prefer.
The protocol has a safer entropy source which is the random beacon, that is unpredictable and unbiasable (based on BLS threshold signatures). The plan is to wire its output to Cadence to export a safe randomness.

why KDF necessary here ?

Here we have an entropy source as an input (block ID + soon a transaction index) and we would like to extract a seed for a PRG (ChaCha20). We need a function to link both information, and achieve 2 properties:

  • the PRG seed must have a uniform entropy, and we can't assume the entropy source achieves that. We should then use a function to "smoothen" the entropy and uniformize it over all the bits. Such function can be a hash function, a MAC, KDF..
  • the PRG seed must be of some specific length (in the current case 32 bytes). For better abstraction, we should not assume the entropy input has exactly that length. We can use a function with a variable output size (whatever the PRG we use need), regardless of the input size.
    KDF, some MACs.. satisfy the properties above, I chose HKDF (a specific instance of KDF), but other functions are possible too.

I'll improve the code doc to reflect the above.

as we seed on each block, what does this mean ?

PRGs have an internal state, that is initialized with a seed, and is updated each time it produces a random. If the state is known, all future outputs can be known. The state of some PRGs can be recovered based on querying multiple successive outputs. Imagine a transaction that doesn't know what seed is used but queries many randoms to recover the state, and predict the future outputs. This is not possible using a CSPRG like ChaCha20, i.e it's not possible to recover the state.
Note that we eventually want to include the transaction index to seed the PRG per transaction, not per block.

considering number of randoms we can use in a transaction, is this important?

Probably not if the calls aren't many. I'm not sure how many calls a transaction can make, or maybe some applications can gather a large amount of randoms off-chain through multiple txs/blocks.
Since we can output better quality randoms without extra cost, I think it's worth it to replace the lower quality one.

@bluesign
Copy link
Contributor

bluesign commented Mar 21, 2023

Sorry I am maybe asking too much, but this is my favourite subject

Block ID can be biased by a consensus node. It is possible for consensus nodes to loop over possible blocks and choose one with a block ID with the random outputs they prefer.

If blockID can be biased and we do v = chacha(kdf(blockID)) or v = chacha(kdf(blockID (+) transaction_index )) doesn't solve the attack there. ( I mean still they can choose what they want ) so in any case we need to have a random uniform blockID. ( as it is hash of block I think unless attacked it should be also uniform )

Considering we have random uniform block ID;

KDF should be unnecessary I think ( unless we need chacha specifially )

So we end up with KDF + chaha depends on chacha benefits. ( so if we have benefit from chacha + not manipulated blockID )

PRGs have an internal state, that is initialized with a seed, and is updated each time it produces a random. If the state is known, all future outputs can be known. The state of some PRGs can be recovered based on querying multiple successive outputs. Imagine a transaction that doesn't know what seed is used but queries many randoms to recover the state, and predict the future outputs. This is not possible using a CSPRG like ChaCha20, i.e it's not possible to recover the state.
Note that we eventually want to include the transaction index to seed the PRG per transaction, not per block.

As you state each transaction will run run with an independent seed ( so previous transaction cannot influence the next transaction's random state, actually even with the blockID currently cannot ) What will be the benefit from unpredictable next random here? It would make sense if we would use blockID as seed then not reseed, but reseed on every tx ( with blockID or in the future with blockID(+)tx_index)

As en example let's say I naively made a RNG with "S = sha3(seed)" and "S_next = sha3(S)", how does it compare to the current one ?

}
stdev := stat.StdDev(distribution, nil)
mean := stat.Mean(distribution, nil)
assert.Greater(t, tolerance*mean, stdev, fmt.Sprintf("basic randomness test failed. stdev %v, mean %v", stdev, mean))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to plug this into some existing test suite for random generators? like https://github.com/terrillmoore/NIST-Statistical-Test-Suite in the future?

@AlexHentschel
Copy link
Member

AlexHentschel commented Mar 23, 2023

If blockID can be biased and we do v = chacha(kdf(blockID)) or v = chacha(kdf(blockID (+) transaction_index )) doesn't solve the attack there. ( I mean still they can choose what they want ) so in any case we need to have a random uniform blockID. ( as it is hash of block I think unless attacked it should be also uniform )

This is a technically very subtle topic @bluesign, so I am glad we are thinking it through properly. I am strongly of the opinion that the answer is: no it doesn't solve the attack. The block hash is inherently not a good source of entropy, which is why most blockchains out there cannot provide safe randomness. Flow is one of the very few networks that has a unpredictable and relatively unbiasable source of entropy, aka our Random Beacon.

Let me explain:

  • Conceptually, consensus nodes construct the block, they have influence on which transactions go in the block and potentially can know what transactions are in the block.
  • A consensus node first constructs the block locally, before broadcasting it. Lets say we have a single malicious consensus nodes, and lets call it Eve. Eve can locally construct 1000 different variants of a block (e.g. by just varying what it includes and what not ... typically there is combinatorially a very large degree of freedom).
    • Given a block, the calculation to get the random number generator seed for each transaction is fully deterministic, and the algorithm is open source. Meaning, Eve can locally calculate the random seed for each transaction.
    • This is essentially like hash-grinding (or proof of work 😉 ). It takes a lot of computation, Eve is frequently unsuccessful, but with a small but non-zero probability she can post-select blocks where the random seed for one particular transaction is favourable.
  • Lets say you are a web-3 casino. Typically, your margins are razor thin. You loose with probability 49% and win with 51%. It might be that somebody walks out of your casino with a million dollar. But that is an outlier, statistics is on your side. Averaged over many games and many people, you will statistically earn more money than you loose ... as long as your source of entropy is unbiased.
    • Now Eve walks into your casino. 😅 She has enough money to play many games. Some games she looses. She mainly submits gambling transactions around the time it is her turn to propose a block. And when it is her turn to propose, she publishes that block (out of the many she builds internally) that seeds the random number generator for her transaction favourably. Hence, Eve is able to bias the probability of winning slightly ... just enough so the odds of winning are higher for her than your Casino. Slowly but surely, she will bleed your casino of all funds. 💀

The underlying insight is: Any data object that a single node constructs and where the node has any influence how the byte representation looks as a bad source of entropy. The message creator can always construct multiple variants and only release favourable ones.

@bluesign
Copy link
Contributor

thanks for the detailed answer @AlexHentschel

Just a quick question; why do you think it is small probability?

Eve is frequently unsuccessful, but with a small but non-zero probability she can post-select blocks where the random seed for one particular transaction is favourable.

I read the paper (https://arxiv.org/pdf/2002.07403.pdf) again, 4.3.4 source of randomness section mentions something like, t signatures. considering t<N number of consensus nodes, doesn't primary have option to choose from any combination of (N, t) ?

@AlexHentschel
Copy link
Member

AlexHentschel commented Mar 24, 2023

@bluesign good question.

block hash as entropy source

In my comment above, I was talking about the scenario where we are using the block hash as entropy source:

  • Here, Eve constructs the block and can thereby "grind" the hash. You are right that the probability for eve to be successful might not be small here, if she has very very large computing resources available.

  • Though, what I was trying to illustrate:

    Even if Eve's success probability to post-select favourable seed is relatively small, it can still make a materially large difference.

Threshold signature (aka random beacon) as entropy source

Section 4.3.4 in our paper describes the random beacon, which is conceptually very different than the block hash:

  1. As the following figure illustrates, the source of randomness generated by the random beacon is not part of the block, it is a threshold signature over the block QC

    So if Eve is constructing block A, she has to publish it first. Meaning at the time of block construction, the source of randomness is unknown (first difference to block hash).

  2. The random beacon uses a threshold signature scheme. You are right that a consensus node has potentially many ways to choose t signature shares out of N received votes. Nevertheless, this is the beauty of the deterministic [thanks Tarak for the correction] unique threshold signature scheme, which we use for the random beacon:

    • The resulting group signature is independent of the set of signers. It is binary identical, no matter which t of the N possible signatures you use. It only depends on the message that is being signed (block A in our example).

Therefore, the consensus node constructing the block can't predict the source of randomness for its block. It has to commit and publish the block first. Furthermore, the node constructing the child block (B in our example) containing the source of randomness for the parent (A in our example) also can't influence the source of randomness, because the threshold signature only depends on the signed block A.

Additional Details

  • Btw, this is specifically the reason, why a random beacon / Threshold signature is so complicated: the private key shares that each node locally holds have to have a very specific relationship to each other. Nodes can't just independently create their private key locally. Instead, they need to generate their keys in a collaborative multi-step process (a "distributed key generation" [DKG] protocol). Such a DKG is very challenging and complex to implement, and requires a technically very knowledgable cryptographer (thank you Tarak, I am so glad we have you). The DKG needs to run every epoch (when the set of consensus nodes potentially changes) and needs to be very deeply integrated into the blockchain protocol. We did all this work, because it yields an unpredictable and relatively unbiasable source of entropy. To the best of my knowledge, there are only 3 blockchains in the world that have a secure source of entropy: Flow and Dfinity being two of them.

  • Just for completeness, there is a remaining edge case which I want to mention:

    • The consensus node constructing block B is the first one that finds out what the source of randomness is for block A, because they are constructing it locally. They have two choices: they release block B including the Quorum Certificate [QC] for block A. Alternatively, they can discard the QC (including the source of randomness for block A contained therein) and just construct a fork attempting to orphan A.
    • Hence, there is still a theoretical possibility for malicious nodes to attempt bias the random beacon by orphaning blocks they don't like. However:
      • When using the block hash as entropy, a malicious node can construct easily thousands or millions of candidates and publish the one it likes best. In contrast, with a threshold signature, an attacker only has one shot: they build on top of the block or orphan it. This reduces the success probability for an attacker by several orders of magnitude! This is the most important point, and the reason why a threshold signature is so crucial for a unbiasable entropy.
      • Using the random beacon, the node who determines the content of the block (proposer of A) is generally different than the node constructing the source of randomness (proposer of B). This decoupling adds more variability and probably makes a successful biasing attack harder. In contrast, when we use the hash, it is the same node.
      • The time window is extremely small (fraction of a second).

    Therefore, my gut feeling is that biasing the random beacon is largely theoretical with vanishing probability to be successful in practise.

@bluesign
Copy link
Contributor

@AlexHentschel thank you so much, I learned a lot from this.

@tarakby
Copy link
Contributor Author

tarakby commented Mar 25, 2023

Sorry I am maybe asking too much, but this is my favourite subject

@bluesign I'm glad you're asking, this is a fun but complex topic, discussing it helps improving the design.

Let me first clarify that this PR isn't making UnsafeRandom reliable. The function is still called Unsafe, the outputs are still bias-able by a malicious consensus primary. This PR only improves the current implementation, makes it more robust, and prepares for the future Safe source (as described by the PR description).

If blockID can be biased and we do v = chacha(kdf(blockID)) or v = chacha(kdf(blockID (+) transaction_index )) doesn't solve the attack there.

Yes correct, the source of entropy here is the block, and that's always bias-able. Applying any composition of deterministic functions (here CSPRG(KDF(ID(block))) obviously doesn't add anything. Adding the transaction ID also doesn't help because a consensus primary can always compute the block before broadcasting it.

KDF should be unnecessary I think ( unless we need chacha specifially ). So we end up with KDF + chaha depends on chacha benefits.

I didn't get your point here, but let me add some details, hopefully they help:

  • the source of entropy here is the block ID. That's currently a hash and hence its entropy is uniform over the 256 bits. However I don't want to make assumptions in this FMV file about how an ID is computed. I also don't want to assume this file will always use a hash as entropy source. Btw, in the future when we switch to the beacon output, uniformizing the source with a KDF or hash is required, because the beacon is a BLS signature (that's unfortunately not uniform). I added the KDF here so that the seeding logic is ready whatever entropy source is plugged in. That reduces the risk of future updates missing this important point.
  • KDF and ChaCha20 are just implementation details. The important flow is entropy_source ---> seed --> PRG. The seed length depends on the PRG chosen. Going from entropy_source to seed needs the two points I mentioned earlier (entropy smoothing + extracting an arbitrary-length seed). Maybe I should refactor the code to show the abstraction clearly. If we use an alternative to HKDF or ChaCha20, the logic remains the same.

As you state each transaction will run run with an independent seed ( so previous transaction cannot influence the next transaction's random state, actually even with the blockID currently cannot ) What will be the benefit from unpredictable next random here?

You're right and are touching a good point!
It's true that I said there will most likely be a fresh PRG per transaction. However, we did explore the possibility of using a PRG per block or per chunk (a chunk is a list of successive transactions, a block is a list of chunks).
A PRG per block is slightly more complicated because verification of a block is done per chunk. A PRG per chunk makes things easier but makes parallel execution of transactions more complex. A decision about this design is not finalized yet.

Now we have the option to choose between a CSPRG or a non-crypto PRG: If we use a PRG per transaction, a CSPRG may not be very useful as you pointed (* I wanna come back to this). We could just go with a non-crypto PRG as biasing the PRG state wouldn't help to mount an attack. However, if we go with a PRG per block/chunk, a CSPRG becomes required to stop biasing attacks. At this point, I don't want to make assumptions about future evolutions of the implementation, so I prefer to go with the safer choice. A future developer updating the design may not differentiate a CSPRG and a non-crypto PRG, and if a CSPRG is already in place, we won't be exposed when the design uses a PRG per chunk for instance.

(*) back to this assumption: if we use a PRG per transaction, is biasing the PRG state really fine? I'm not sure about the answer. What if the transaction starts by calling a malicious function bias_PRG_state() that loops over a few outputs, recovers the state and stops at a state where the future random is favourable (for instance, it makes the tx signer wins a betting game). Now the transaction calls the betting contract (with a biased PRG) and the signer wins. Is that a possible scenario?

As en example let's say I naively made a RNG with "S = sha3(seed)" and "S_next = sha3(S)", how does it compare to the current one ?

That's an example of a non-crypto PRG, because the internal state of the PRG is the output itself (a single call to the PRG recovers the state). We can use it if we don't care about the attacks that recover the state.

@tarakby
Copy link
Contributor Author

tarakby commented Mar 25, 2023

Thanks Alex for the complete description of how the random beacon works!

Thanks to your recent work on the Jolteon protocol and the new 2-block finalization, It's great news that biasing the random beacon is reduced to choosing between 2 options only.

this is the beauty of the deterministic threshold signature scheme, which we use for the random beacon: The resulting group signature is independent of the set of signers

For the sake of technical accuracy, I wanted to add a slight correction. The threshold signature is indeed independent of the set of signers, thanks for the underlying signature scheme (BLS) being unique. Unique is stronger than deterministic and that what we would require for the random beacon design. Determinicity only isn't enough.

@bluesign
Copy link
Contributor

bluesign commented Mar 25, 2023

the source of entropy here is the block ID. That's currently a hash and hence its entropy is uniform over the 256 bits. However I don't want to make assumptions in this FMV file about how an ID is computed. I also don't want to assume this file will always use a hash as entropy source. Btw, in the future when we switch to the beacon output, uniformizing the source with a KDF or hash is required, because the beacon is a BLS signature (that's unfortunately not uniform). I added the KDF here so that the seeding logic is ready whatever entropy source is plugged in. That reduces the risk of future updates missing this important point.

@tarakby thank you very much, I just understood the point here, it is a very strong one.

Btw when you say "A PRG per block" do you mean it is seeded per block? ( never seeded again ) Or is it is as is now, seeded with same seed on every transaction of the block? I think whatever algorithm we use, seeding with block ID on every transaction ( like current implementation ) losing the uniformity ( not sure if this is the correct term )

Consider I am minting an NFT, let's say 100 NFTs, 1 legendary, 99 common. ( 1 in a 100 change I will mark legendary ) If I send this in 100 transactions in same block. Either I will mint 100 legendary or 100 common. Also even with unpredictable PRG, as transactions can communicate with each other, they can decide to buy or not to buy. Is my thinking correct here?

back to this assumption: if we use a PRG per transaction, is biasing the PRG state really fine? I'm not sure about the answer. What if the transaction starts by calling a malicious function bias_PRG_state() that loops over a few outputs, recovers the state and stops at a state where the future random is favourable (for instance, it makes the tx signer wins a betting game). Now the transaction calls the betting contract (with a biased PRG) and the signer wins. Is that a possible scenario?

hmm this is a very good point, I think if PRG is predictable, is a risk, but my initial gut feeling tells me this is a very minor one.

@AlexHentschel
Copy link
Member

thanks Tarak for your correction. Really really appreciate you contributions with keeping things technically accurate 💚

@tarakby
Copy link
Contributor Author

tarakby commented Mar 27, 2023

Btw when you say "A PRG per block" do you mean it is seeded per block?

Yes I meant creating only one instance of PRG per block execution, seeding it once and never reseed it.
In the current implementation, there is a PRG instance created for each transaction of the block, seeded with the same seed.

If I send this in 100 transactions in same block. Either I will mint 100 legendary or 100 common.

In the current implementation, this is correct.

Also even with unpredictable PRG, as transactions can communicate with each other, they can decide to buy or not to buy. Is my thinking correct here?

I guess you mean the transaction can be reverted after the random is revealed. Yes that's possible regardless of the PRG properties. I think this is an inherent issue to any platform that allows rolling back a transaction result, and I think it's beyond the randomness quality. I believe smart contract developers need to take this into account, maybe with a commit-reveal scheme, like mentioned here.

@bluesign
Copy link
Contributor

I guess you mean the transaction can be reverted after the random is revealed

I was referring to seeding with same seed, on each transaction; one is generating random and saving to storage, second transaction is checking storage, if result is win, calling the contract function. ( as you said we need per tx seed or running block seed for sure )

@tarakby
Copy link
Contributor Author

tarakby commented Mar 27, 2023

bors merge

@bors bors bot merged commit 98f7a79 into master Mar 27, 2023
@bors bors bot deleted the tarak/randomness-part2-fvm branch March 27, 2023 23:38
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants