From 34483c9755f8fff2efa12e8b7affc59a1bbe83c7 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Mon, 6 Nov 2023 10:00:29 -0800 Subject: [PATCH 1/3] grr --- fortuna/Cargo.lock | 3 +- fortuna/Cargo.toml | 3 +- fortuna/src/abi.json | 78 +++---------------- fortuna/src/command/register_provider.rs | 26 +++++-- fortuna/src/command/run.rs | 24 +++++- fortuna/src/state.rs | 11 +-- .../contracts/contracts/entropy/Entropy.sol | 3 +- .../contracts/forge-test/Entropy.t.sol | 30 ++----- .../entropy_sdk/solidity/EntropyStructs.sol | 3 +- .../entropy_sdk/solidity/IEntropy.sol | 2 +- .../entropy_sdk/solidity/package.json | 2 +- 11 files changed, 72 insertions(+), 113 deletions(-) diff --git a/fortuna/Cargo.lock b/fortuna/Cargo.lock index 9377d1271..c110fc1eb 100644 --- a/fortuna/Cargo.lock +++ b/fortuna/Cargo.lock @@ -1446,12 +1446,13 @@ dependencies = [ [[package]] name = "fortuna" -version = "1.0.0" +version = "1.1.0" dependencies = [ "anyhow", "axum", "axum-macros", "base64 0.21.4", + "bincode", "byteorder", "clap", "ethabi", diff --git a/fortuna/Cargo.toml b/fortuna/Cargo.toml index f16641dd6..12966916d 100644 --- a/fortuna/Cargo.toml +++ b/fortuna/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fortuna" -version = "1.0.0" +version = "1.1.0" edition = "2021" [dependencies] @@ -8,6 +8,7 @@ anyhow = "1.0.75" axum = { version = "0.6.20", features = ["json", "ws", "macros"] } axum-macros = { version = "0.3.8" } base64 = { version = "0.21.0" } +bincode = "1.3.3" byteorder = "1.5.0" clap = { version = "4.4.6", features = ["derive", "cargo", "env"] } ethabi = "18.0.0" diff --git a/fortuna/src/abi.json b/fortuna/src/abi.json index ca46d6a01..13b4aa9b3 100644 --- a/fortuna/src/abi.json +++ b/fortuna/src/abi.json @@ -1,45 +1,4 @@ [ - { - "inputs": [ - { - "internalType": "uint256", - "name": "pythFeeInWei", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "AssertionFailure", - "type": "error" - }, - { - "inputs": [], - "name": "IncorrectProviderRevelation", - "type": "error" - }, - { - "inputs": [], - "name": "IncorrectUserRevelation", - "type": "error" - }, - { - "inputs": [], - "name": "InsufficientFee", - "type": "error" - }, - { - "inputs": [], - "name": "NoSuchProvider", - "type": "error" - }, - { - "inputs": [], - "name": "OutOfRandomness", - "type": "error" - }, { "anonymous": false, "inputs": [ @@ -66,9 +25,9 @@ "type": "uint64" }, { - "internalType": "bytes32", + "internalType": "bytes", "name": "commitmentMetadata", - "type": "bytes32" + "type": "bytes" }, { "internalType": "uint64", @@ -92,7 +51,7 @@ } ], "indexed": false, - "internalType": "struct PythRandomStructs.ProviderInfo", + "internalType": "struct EntropyStructs.ProviderInfo", "name": "provider", "type": "tuple" } @@ -130,11 +89,6 @@ "name": "providerCommitmentSequenceNumber", "type": "uint64" }, - { - "internalType": "bytes32", - "name": "providerCommitmentMetadata", - "type": "bytes32" - }, { "internalType": "uint256", "name": "blockNumber", @@ -142,7 +96,7 @@ } ], "indexed": false, - "internalType": "struct PythRandomStructs.Request", + "internalType": "struct EntropyStructs.Request", "name": "request", "type": "tuple" } @@ -180,11 +134,6 @@ "name": "providerCommitmentSequenceNumber", "type": "uint64" }, - { - "internalType": "bytes32", - "name": "providerCommitmentMetadata", - "type": "bytes32" - }, { "internalType": "uint256", "name": "blockNumber", @@ -192,7 +141,7 @@ } ], "indexed": false, - "internalType": "struct PythRandomStructs.Request", + "internalType": "struct EntropyStructs.Request", "name": "request", "type": "tuple" }, @@ -337,9 +286,9 @@ "type": "uint64" }, { - "internalType": "bytes32", + "internalType": "bytes", "name": "commitmentMetadata", - "type": "bytes32" + "type": "bytes" }, { "internalType": "uint64", @@ -362,7 +311,7 @@ "type": "uint64" } ], - "internalType": "struct PythRandomStructs.ProviderInfo", + "internalType": "struct EntropyStructs.ProviderInfo", "name": "info", "type": "tuple" } @@ -412,18 +361,13 @@ "name": "providerCommitmentSequenceNumber", "type": "uint64" }, - { - "internalType": "bytes32", - "name": "providerCommitmentMetadata", - "type": "bytes32" - }, { "internalType": "uint256", "name": "blockNumber", "type": "uint256" } ], - "internalType": "struct PythRandomStructs.Request", + "internalType": "struct EntropyStructs.Request", "name": "req", "type": "tuple" } @@ -444,9 +388,9 @@ "type": "bytes32" }, { - "internalType": "bytes32", + "internalType": "bytes", "name": "commitmentMetadata", - "type": "bytes32" + "type": "bytes" }, { "internalType": "uint64", diff --git a/fortuna/src/command/register_provider.rs b/fortuna/src/command/register_provider.rs index 513a3938d..c1f568de9 100644 --- a/fortuna/src/command/register_provider.rs +++ b/fortuna/src/command/register_provider.rs @@ -11,6 +11,12 @@ use { std::sync::Arc, }; +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct CommitmentMetadata { + pub seed: [u8; 32], + pub chain_length: u64, +} + /// Register as a randomness provider. This method will generate and commit to a new random /// hash chain from the configured secret & a newly generated random value. pub async fn register_provider(opts: &RegisterProviderOptions) -> Result<()> { @@ -25,21 +31,29 @@ pub async fn register_provider(opts: &RegisterProviderOptions) -> Result<()> { // Create a new random hash chain. let random = rand::random::<[u8; 32]>(); - let mut chain = PebbleHashChain::from_config(&opts.randomness, &opts.chain_id, random)?; + let commitment_length = opts.randomness.chain_length; + let mut chain = PebbleHashChain::from_config( + &opts.randomness.secret, + &opts.chain_id, + &random, + commitment_length, + )?; // Arguments to the contract to register our new provider. let fee_in_wei = opts.fee; let commitment = chain.reveal()?; - // Store the random seed in the metadata field so that we can regenerate the hash chain - // at-will. (This is secure because you can't generate the chain unless you also have the secret) - let commitment_metadata = random; - let commitment_length = opts.randomness.chain_length; + // Store the random seed and chain length in the metadata field so that we can regenerate the hash + // chain at-will. (This is secure because you can't generate the chain unless you also have the secret) + let commitment_metadata = CommitmentMetadata { + seed: random, + chain_length: commitment_length, + }; if let Some(r) = contract .register( fee_in_wei, commitment, - commitment_metadata, + bincode::serialize(&commitment_metadata)?.into(), commitment_length, ) .send() diff --git a/fortuna/src/command/run.rs b/fortuna/src/command/run.rs index 12b22cec5..21b36ae29 100644 --- a/fortuna/src/command/run.rs +++ b/fortuna/src/command/run.rs @@ -1,6 +1,7 @@ use { crate::{ api, + command::register_provider::CommitmentMetadata, config::{ Config, RunOptions, @@ -48,11 +49,14 @@ pub async fn run(opts: &RunOptions) -> Result<()> { )] struct ApiDoc; + println!("load config"); let config = Config::load(&opts.config.config)?; + println!("load chains"); let mut chains = HashMap::new(); for (chain_id, chain_config) in &config.chains { let contract = Arc::new(PythContract::from_config(&chain_config)?); + // FIXME: this is broken now let provider_info = contract.get_provider_info(opts.provider).call().await?; // Reconstruct the hash chain based on the metadata and check that it matches the on-chain commitment. @@ -62,8 +66,24 @@ pub async fn run(opts: &RunOptions) -> Result<()> { // TODO: we may want to load the hash chain in a lazy/fault-tolerant way. If there are many blockchains, // then it's more likely that some RPC fails. We should tolerate these faults and generate the hash chain // later when a user request comes in for that chain. - let random: [u8; 32] = provider_info.commitment_metadata; - let hash_chain = PebbleHashChain::from_config(&opts.randomness, &chain_id, random)?; + // NOTE: this logic is implemented in a backward compatible way to handle contracts where the commitment is + // just a bytes32 + println!("{:?}", provider_info.commitment_metadata); + let metadata = if provider_info.commitment_metadata.len() == 32 { + CommitmentMetadata { + seed: provider_info.commitment_metadata[0..32].try_into()?, + chain_length: opts.randomness.chain_length, + } + } else { + bincode::deserialize::(&provider_info.commitment_metadata)? + }; + + let hash_chain = PebbleHashChain::from_config( + &opts.randomness.secret, + &chain_id, + &metadata.seed, + metadata.chain_length, + )?; let chain_state = HashChainState { offsets: vec![provider_info .original_commitment_sequence_number diff --git a/fortuna/src/state.rs b/fortuna/src/state.rs index 8f72264ce..b454c85be 100644 --- a/fortuna/src/state.rs +++ b/fortuna/src/state.rs @@ -35,17 +35,18 @@ impl PebbleHashChain { } pub fn from_config( - opts: &RandomnessOptions, + secret: &str, chain_id: &ChainId, - random: [u8; 32], + random: &[u8; 32], + chain_length: u64, ) -> Result { let mut input: Vec = vec![]; - input.extend_from_slice(&hex::decode(opts.secret.clone())?); + input.extend_from_slice(&hex::decode(secret)?); input.extend_from_slice(&chain_id.as_bytes()); - input.extend_from_slice(&random); + input.extend_from_slice(random); let secret: [u8; 32] = Keccak256::digest(input).into(); - Ok(Self::new(secret, opts.chain_length.try_into()?)) + Ok(Self::new(secret, chain_length.try_into()?)) } /// Reveal the next hash in the chain using the previous proof. diff --git a/target_chains/ethereum/contracts/contracts/entropy/Entropy.sol b/target_chains/ethereum/contracts/contracts/entropy/Entropy.sol index 0da17f84a..f7e0ffa72 100644 --- a/target_chains/ethereum/contracts/contracts/entropy/Entropy.sol +++ b/target_chains/ethereum/contracts/contracts/entropy/Entropy.sol @@ -95,7 +95,7 @@ contract Entropy is IEntropy, EntropyState { function register( uint feeInWei, bytes32 commitment, - bytes32 commitmentMetadata, + bytes calldata commitmentMetadata, uint64 chainLength ) public override { if (chainLength == 0) revert EntropyErrors.AssertionFailure(); @@ -186,7 +186,6 @@ contract Entropy is IEntropy, EntropyState { req.providerCommitment = providerInfo.currentCommitment; req.providerCommitmentSequenceNumber = providerInfo .currentCommitmentSequenceNumber; - req.providerCommitmentMetadata = providerInfo.commitmentMetadata; if (useBlockHash) { req.blockNumber = block.number; diff --git a/target_chains/ethereum/contracts/forge-test/Entropy.t.sol b/target_chains/ethereum/contracts/forge-test/Entropy.t.sol index e59c901cf..1363de02b 100644 --- a/target_chains/ethereum/contracts/forge-test/Entropy.t.sol +++ b/target_chains/ethereum/contracts/forge-test/Entropy.t.sol @@ -44,19 +44,14 @@ contract EntropyTest is Test { random.register( provider1FeeInWei, provider1Proofs[0], - bytes32(keccak256(abi.encodePacked(uint256(0x0100)))), + hex"0100", provider1ChainLength ); bytes32[] memory hashChain2 = generateHashChain(provider2, 0, 100); provider2Proofs = hashChain2; vm.prank(provider2); - random.register( - provider2FeeInWei, - provider2Proofs[0], - bytes32(keccak256(abi.encodePacked(uint256(0x0200)))), - 100 - ); + random.register(provider2FeeInWei, provider2Proofs[0], hex"0200", 100); } function generateHashChain( @@ -323,12 +318,7 @@ contract EntropyTest is Test { 10 ); vm.prank(provider1); - random.register( - provider1FeeInWei, - newHashChain[0], - bytes32(keccak256(abi.encodePacked(uint256(0x0100)))), - 10 - ); + random.register(provider1FeeInWei, newHashChain[0], hex"0100", 10); assertInvariants(); EntropyStructs.ProviderInfo memory info1 = random.getProviderInfo( provider1 @@ -397,12 +387,7 @@ contract EntropyTest is Test { // Check that overflowing the fee arithmetic causes the transaction to revert. vm.prank(provider1); - random.register( - MAX_UINT256, - provider1Proofs[0], - bytes32(keccak256(abi.encodePacked(uint256(0x0100)))), - 100 - ); + random.register(MAX_UINT256, provider1Proofs[0], hex"0100", 100); vm.expectRevert(); random.getFee(provider1); } @@ -452,12 +437,7 @@ contract EntropyTest is Test { // Reregistering updates the required fees vm.prank(provider1); - random.register( - 12345, - provider1Proofs[0], - bytes32(keccak256(abi.encodePacked(uint256(0x0100)))), - 100 - ); + random.register(12345, provider1Proofs[0], hex"0100", 100); assertRequestReverts(pythFeeInWei + 12345 - 1, provider1, 42, false); requestWithFee(user2, pythFeeInWei + 12345, provider1, 42, false); diff --git a/target_chains/ethereum/entropy_sdk/solidity/EntropyStructs.sol b/target_chains/ethereum/entropy_sdk/solidity/EntropyStructs.sol index 8e4980bb8..24bf575c3 100644 --- a/target_chains/ethereum/entropy_sdk/solidity/EntropyStructs.sol +++ b/target_chains/ethereum/entropy_sdk/solidity/EntropyStructs.sol @@ -20,7 +20,7 @@ contract EntropyStructs { uint64 originalCommitmentSequenceNumber; // Metadata for the current commitment. Providers may optionally use this field to to help // manage rotations (i.e., to pick the sequence number from the correct hash chain). - bytes32 commitmentMetadata; + bytes commitmentMetadata; // The first sequence number that is *not* included in the current commitment (i.e., an exclusive end index). // The contract maintains the invariant that sequenceNumber <= endSequenceNumber. // If sequenceNumber == endSequenceNumber, the provider must rotate their commitment to add additional random values. @@ -43,7 +43,6 @@ contract EntropyStructs { bytes32 userCommitment; bytes32 providerCommitment; uint64 providerCommitmentSequenceNumber; - bytes32 providerCommitmentMetadata; // If nonzero, the randomness requester wants the blockhash of this block to be incorporated into the random number. uint256 blockNumber; } diff --git a/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol b/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol index d1088006b..1c597de8e 100644 --- a/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol +++ b/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol @@ -12,7 +12,7 @@ interface IEntropy is EntropyEvents { function register( uint feeInWei, bytes32 commitment, - bytes32 commitmentMetadata, + bytes calldata commitmentMetadata, uint64 chainLength ) external; diff --git a/target_chains/ethereum/entropy_sdk/solidity/package.json b/target_chains/ethereum/entropy_sdk/solidity/package.json index 5cfa1ae9e..da5c9cf13 100644 --- a/target_chains/ethereum/entropy_sdk/solidity/package.json +++ b/target_chains/ethereum/entropy_sdk/solidity/package.json @@ -1,6 +1,6 @@ { "name": "@pythnetwork/entropy-sdk-solidity", - "version": "0.1.1", + "version": "1.0.0", "description": "Generate secure random numbers with Pyth Entropy", "repository": { "type": "git", From 883d6ea54f8b59cb17d814fd456a650e7c9e3345 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Sat, 25 Nov 2023 14:24:28 -0800 Subject: [PATCH 2/3] cleanup --- fortuna/src/command/run.rs | 16 ++-------------- fortuna/src/state.rs | 5 +---- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/fortuna/src/command/run.rs b/fortuna/src/command/run.rs index 21b36ae29..f1b1cc62e 100644 --- a/fortuna/src/command/run.rs +++ b/fortuna/src/command/run.rs @@ -49,14 +49,11 @@ pub async fn run(opts: &RunOptions) -> Result<()> { )] struct ApiDoc; - println!("load config"); let config = Config::load(&opts.config.config)?; - println!("load chains"); let mut chains = HashMap::new(); for (chain_id, chain_config) in &config.chains { let contract = Arc::new(PythContract::from_config(&chain_config)?); - // FIXME: this is broken now let provider_info = contract.get_provider_info(opts.provider).call().await?; // Reconstruct the hash chain based on the metadata and check that it matches the on-chain commitment. @@ -66,17 +63,8 @@ pub async fn run(opts: &RunOptions) -> Result<()> { // TODO: we may want to load the hash chain in a lazy/fault-tolerant way. If there are many blockchains, // then it's more likely that some RPC fails. We should tolerate these faults and generate the hash chain // later when a user request comes in for that chain. - // NOTE: this logic is implemented in a backward compatible way to handle contracts where the commitment is - // just a bytes32 - println!("{:?}", provider_info.commitment_metadata); - let metadata = if provider_info.commitment_metadata.len() == 32 { - CommitmentMetadata { - seed: provider_info.commitment_metadata[0..32].try_into()?, - chain_length: opts.randomness.chain_length, - } - } else { - bincode::deserialize::(&provider_info.commitment_metadata)? - }; + let metadata = + bincode::deserialize::(&provider_info.commitment_metadata)?; let hash_chain = PebbleHashChain::from_config( &opts.randomness.secret, diff --git a/fortuna/src/state.rs b/fortuna/src/state.rs index b454c85be..fa7f918d2 100644 --- a/fortuna/src/state.rs +++ b/fortuna/src/state.rs @@ -1,8 +1,5 @@ use { - crate::{ - api::ChainId, - config::RandomnessOptions, - }, + crate::api::ChainId, anyhow::{ ensure, Result, From eaf3589809c7aaa2f80fbb463d7e483ef5f6d852 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Sat, 25 Nov 2023 14:30:38 -0800 Subject: [PATCH 3/3] fix semver --- fortuna/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fortuna/Cargo.toml b/fortuna/Cargo.toml index 3d050cdb4..a4979a872 100644 --- a/fortuna/Cargo.toml +++ b/fortuna/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fortuna" -version = "1.1.0" +version = "2.0.0" edition = "2021" [dependencies]