|
|
@@ -16,14 +16,16 @@ import "./EntropyStructConverter.sol";
|
|
|
// Entropy implements a secure 2-party random number generation procedure. The protocol
|
|
|
// is an extension of a simple commit/reveal protocol. The original version has the following steps:
|
|
|
//
|
|
|
-// 1. Two parties A and B each draw a random number x_{A,B}
|
|
|
-// 2. A and B then share h_{A,B} = hash(x_{A,B})
|
|
|
-// 3. A and B reveal x_{A,B}
|
|
|
-// 4. Both parties verify that hash(x_{A, B}) == h_{A,B}
|
|
|
-// 5. The random number r = hash(x_A, x_B)
|
|
|
+// 1. Two parties A and B each randomly sample a contribution x_{A,B} to the random number
|
|
|
+// 2. A commits to their number by sharing h_A = hash(x_A)
|
|
|
+// 3. B reveals x_B
|
|
|
+// 4. A reveals x_A
|
|
|
+// 5. B verifies that hash(x_{A}) == h_A
|
|
|
+// 6. The random number r = hash(x_A, x_B)
|
|
|
//
|
|
|
// This protocol has the property that the result is random as long as either A or B are honest.
|
|
|
-// Thus, neither party needs to trust the other -- as long as they are themselves honest, they can
|
|
|
+// Honesty means that (1) they draw their value at random, and (2) for A, they keep x_A a secret until
|
|
|
+// step 4. Thus, neither party needs to trust the other -- as long as they are themselves honest, they can
|
|
|
// ensure that the result r is random.
|
|
|
//
|
|
|
// Entropy implements a version of this protocol that is optimized for on-chain usage. The
|
|
|
@@ -37,26 +39,19 @@ import "./EntropyStructConverter.sol";
|
|
|
// verified against the previous one in the sequence by hashing it, i.e., hash(x_i) == x_{i - 1}
|
|
|
//
|
|
|
// Request: To produce a random number, the following steps occur.
|
|
|
-// 1. The user draws a random number x_U, and submits h_U = hash(x_U) to this contract
|
|
|
-// 2. The contract remembers h_U and assigns it an incrementing sequence number i, representing which
|
|
|
+// 1. The user randomly samples their contribution x_U and submits it to the contract
|
|
|
+// 2. The contract remembers x_U and assigns it an incrementing sequence number i, representing which
|
|
|
// of the provider's random numbers the user will receive.
|
|
|
-// 3. The user submits an off-chain request (e.g. via HTTP) to the provider to reveal the i'th random number.
|
|
|
-// 4. The provider checks the on-chain sequence number and ensures it is > i. If it is not, the provider
|
|
|
-// refuses to reveal the ith random number. The provider should wait for a sufficient number of block confirmations
|
|
|
-// to ensure that the request does not get re-orged out of the blockchain.
|
|
|
-// 5. The provider reveals x_i to the user.
|
|
|
-// 6. The user submits both the provider's revealed number x_i and their own x_U to the contract.
|
|
|
-// 7. The contract verifies hash(x_i) == x_{i-1} to prove that x_i is the i'th random number. The contract also checks that hash(x_U) == h_U.
|
|
|
+// 3. The provider submits a transaction to the contract revealing their contribution x_i to the contract.
|
|
|
+// 4. The contract verifies hash(x_i) == x_{i-1} to prove that x_i is the i'th random number.
|
|
|
// The contract stores x_i as the i'th random number to reuse for future verifications.
|
|
|
-// 8. If both of the above conditions are satisfied, the random number r = hash(x_i, x_U).
|
|
|
-// (Optional) as an added security mechanism, this step can further incorporate the blockhash of the block that the
|
|
|
-// request transaction landed in: r = hash(x_i, x_U, blockhash).
|
|
|
+// 5. If the condition above is satisfied, the random number r = hash(x_i, x_U).
|
|
|
+// 6. The contract submits a callback to the calling contract with the random number `r`.
|
|
|
//
|
|
|
// This protocol has the same security properties as the 2-party randomness protocol above: as long as either
|
|
|
-// the provider or user is honest, the number r is random. Honesty here means that the participant keeps their
|
|
|
-// random number x a secret until the revelation phase (step 5) of the protocol. Note that providers need to
|
|
|
-// be careful to ensure their off-chain service isn't compromised to reveal the random numbers -- if this occurs,
|
|
|
-// then users will be able to influence the random number r.
|
|
|
+// the provider or user is honest, the number r is random. Note that this analysis assumes that
|
|
|
+// providers cannot frontrun user transactions -- a dishonest provider who frontruns user transaction can
|
|
|
+// manipulate the result.
|
|
|
//
|
|
|
// The Entropy implementation of the above protocol allows anyone to permissionlessly register to be a
|
|
|
// randomness provider. Users then choose which provider to request randomness from. Each provider can set
|
|
|
@@ -71,13 +66,6 @@ import "./EntropyStructConverter.sol";
|
|
|
// a compromised sequence. On rotation, any in-flight requests continue to use the pre-rotation commitment.
|
|
|
// Providers can use the sequence number of the request along with the event log of their registrations to determine
|
|
|
// which hash chain contains the requested random number.
|
|
|
-//
|
|
|
-// Warning to integrators:
|
|
|
-// An important caveat of this protocol is that the user can compute the random number r before
|
|
|
-// revealing their own number to the contract. This property means that the user can choose to halt the
|
|
|
-// protocol prior to the random number being revealed (i.e., prior to step (6) above). Integrators should ensure that
|
|
|
-// the user is always incentivized to reveal their random number, and that the protocol has an escape hatch for
|
|
|
-// cases where the user chooses not to reveal.
|
|
|
abstract contract Entropy is IEntropy, EntropyState {
|
|
|
using ExcessivelySafeCall for address;
|
|
|
|
|
|
@@ -357,24 +345,24 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
// Note that excess value is *not* refunded to the caller.
|
|
|
function requestWithCallback(
|
|
|
address provider,
|
|
|
- bytes32 userRandomNumber
|
|
|
+ bytes32 userContribution
|
|
|
) public payable override returns (uint64) {
|
|
|
return
|
|
|
requestV2(
|
|
|
provider,
|
|
|
- userRandomNumber,
|
|
|
+ userContribution,
|
|
|
0 // Passing 0 will assign the request the provider's default gas limit
|
|
|
);
|
|
|
}
|
|
|
|
|
|
function requestV2(
|
|
|
address provider,
|
|
|
- bytes32 userRandomNumber,
|
|
|
+ bytes32 userContribution,
|
|
|
uint32 gasLimit
|
|
|
) public payable override returns (uint64) {
|
|
|
EntropyStructsV2.Request storage req = requestHelper(
|
|
|
provider,
|
|
|
- constructUserCommitment(userRandomNumber),
|
|
|
+ constructUserCommitment(userContribution),
|
|
|
// If useBlockHash is set to true, it allows a scenario in which the provider and miner can collude.
|
|
|
// If we remove the blockHash from this, the provider would have no choice but to provide its committed
|
|
|
// random number. Hence, useBlockHash is set to false.
|
|
|
@@ -387,14 +375,14 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
provider,
|
|
|
req.requester,
|
|
|
req.sequenceNumber,
|
|
|
- userRandomNumber,
|
|
|
+ userContribution,
|
|
|
EntropyStructConverter.toV1Request(req)
|
|
|
);
|
|
|
emit EntropyEventsV2.Requested(
|
|
|
provider,
|
|
|
req.requester,
|
|
|
req.sequenceNumber,
|
|
|
- userRandomNumber,
|
|
|
+ userContribution,
|
|
|
uint32(req.gasLimit10k) * TEN_THOUSAND,
|
|
|
bytes("")
|
|
|
);
|
|
|
@@ -406,14 +394,14 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
// current commitment and returns the generated random number.
|
|
|
function revealHelper(
|
|
|
EntropyStructsV2.Request storage req,
|
|
|
- bytes32 userRevelation,
|
|
|
- bytes32 providerRevelation
|
|
|
+ bytes32 userContribution,
|
|
|
+ bytes32 providerContribution
|
|
|
) internal returns (bytes32 randomNumber, bytes32 blockHash) {
|
|
|
bytes32 providerCommitment = constructProviderCommitment(
|
|
|
req.numHashes,
|
|
|
- providerRevelation
|
|
|
+ providerContribution
|
|
|
);
|
|
|
- bytes32 userCommitment = constructUserCommitment(userRevelation);
|
|
|
+ bytes32 userCommitment = constructUserCommitment(userContribution);
|
|
|
if (
|
|
|
keccak256(bytes.concat(userCommitment, providerCommitment)) !=
|
|
|
req.commitment
|
|
|
@@ -436,8 +424,8 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
}
|
|
|
|
|
|
randomNumber = combineRandomValues(
|
|
|
- userRevelation,
|
|
|
- providerRevelation,
|
|
|
+ userContribution,
|
|
|
+ providerContribution,
|
|
|
blockHash
|
|
|
);
|
|
|
|
|
|
@@ -446,7 +434,7 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
];
|
|
|
if (providerInfo.currentCommitmentSequenceNumber < req.sequenceNumber) {
|
|
|
providerInfo.currentCommitmentSequenceNumber = req.sequenceNumber;
|
|
|
- providerInfo.currentCommitment = providerRevelation;
|
|
|
+ providerInfo.currentCommitment = providerContribution;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -455,7 +443,7 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
function advanceProviderCommitment(
|
|
|
address provider,
|
|
|
uint64 advancedSequenceNumber,
|
|
|
- bytes32 providerRevelation
|
|
|
+ bytes32 providerContribution
|
|
|
) public override {
|
|
|
EntropyStructsV2.ProviderInfo storage providerInfo = _state.providers[
|
|
|
provider
|
|
|
@@ -473,14 +461,14 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
);
|
|
|
bytes32 providerCommitment = constructProviderCommitment(
|
|
|
numHashes,
|
|
|
- providerRevelation
|
|
|
+ providerContribution
|
|
|
);
|
|
|
|
|
|
if (providerCommitment != providerInfo.currentCommitment)
|
|
|
revert EntropyErrors.IncorrectRevelation();
|
|
|
|
|
|
providerInfo.currentCommitmentSequenceNumber = advancedSequenceNumber;
|
|
|
- providerInfo.currentCommitment = providerRevelation;
|
|
|
+ providerInfo.currentCommitment = providerContribution;
|
|
|
if (
|
|
|
providerInfo.currentCommitmentSequenceNumber >=
|
|
|
providerInfo.sequenceNumber
|
|
|
@@ -508,8 +496,8 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
function reveal(
|
|
|
address provider,
|
|
|
uint64 sequenceNumber,
|
|
|
- bytes32 userRevelation,
|
|
|
- bytes32 providerRevelation
|
|
|
+ bytes32 userContribution,
|
|
|
+ bytes32 providerContribution
|
|
|
) public override returns (bytes32 randomNumber) {
|
|
|
EntropyStructsV2.Request storage req = findActiveRequest(
|
|
|
provider,
|
|
|
@@ -528,13 +516,13 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
bytes32 blockHash;
|
|
|
(randomNumber, blockHash) = revealHelper(
|
|
|
req,
|
|
|
- userRevelation,
|
|
|
- providerRevelation
|
|
|
+ userContribution,
|
|
|
+ providerContribution
|
|
|
);
|
|
|
emit Revealed(
|
|
|
EntropyStructConverter.toV1Request(req),
|
|
|
- userRevelation,
|
|
|
- providerRevelation,
|
|
|
+ userContribution,
|
|
|
+ providerContribution,
|
|
|
blockHash,
|
|
|
randomNumber
|
|
|
);
|
|
|
@@ -554,8 +542,8 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
function revealWithCallback(
|
|
|
address provider,
|
|
|
uint64 sequenceNumber,
|
|
|
- bytes32 userRandomNumber,
|
|
|
- bytes32 providerRevelation
|
|
|
+ bytes32 userContribution,
|
|
|
+ bytes32 providerContribution
|
|
|
) public override {
|
|
|
EntropyStructsV2.Request storage req = findActiveRequest(
|
|
|
provider,
|
|
|
@@ -573,8 +561,8 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
bytes32 randomNumber;
|
|
|
(randomNumber, ) = revealHelper(
|
|
|
req,
|
|
|
- userRandomNumber,
|
|
|
- providerRevelation
|
|
|
+ userContribution,
|
|
|
+ providerContribution
|
|
|
);
|
|
|
|
|
|
// If the request has an explicit gas limit, then run the new callback failure state flow.
|
|
|
@@ -613,8 +601,8 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
if (success) {
|
|
|
emit RevealedWithCallback(
|
|
|
EntropyStructConverter.toV1Request(req),
|
|
|
- userRandomNumber,
|
|
|
- providerRevelation,
|
|
|
+ userContribution,
|
|
|
+ providerContribution,
|
|
|
randomNumber
|
|
|
);
|
|
|
emit EntropyEventsV2.Revealed(
|
|
|
@@ -622,6 +610,8 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
req.requester,
|
|
|
req.sequenceNumber,
|
|
|
randomNumber,
|
|
|
+ userContribution,
|
|
|
+ providerContribution,
|
|
|
false,
|
|
|
ret,
|
|
|
SafeCast.toUint32(gasUsed),
|
|
|
@@ -642,8 +632,8 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
provider,
|
|
|
req.requester,
|
|
|
sequenceNumber,
|
|
|
- userRandomNumber,
|
|
|
- providerRevelation,
|
|
|
+ userContribution,
|
|
|
+ providerContribution,
|
|
|
randomNumber,
|
|
|
ret
|
|
|
);
|
|
|
@@ -652,6 +642,8 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
req.requester,
|
|
|
sequenceNumber,
|
|
|
randomNumber,
|
|
|
+ userContribution,
|
|
|
+ providerContribution,
|
|
|
true,
|
|
|
ret,
|
|
|
SafeCast.toUint32(gasUsed),
|
|
|
@@ -692,8 +684,8 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
|
|
|
emit RevealedWithCallback(
|
|
|
reqV1,
|
|
|
- userRandomNumber,
|
|
|
- providerRevelation,
|
|
|
+ userContribution,
|
|
|
+ providerContribution,
|
|
|
randomNumber
|
|
|
);
|
|
|
emit EntropyEventsV2.Revealed(
|
|
|
@@ -701,6 +693,8 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|
|
callAddress,
|
|
|
sequenceNumber,
|
|
|
randomNumber,
|
|
|
+ userContribution,
|
|
|
+ providerContribution,
|
|
|
false,
|
|
|
bytes(""),
|
|
|
gasUsed,
|