mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-12 05:44:53 +00:00
Break Ethereum Deployer into crate
This commit is contained in:
parent
eb9bce6862
commit
4bcea31c2a
20 changed files with 411 additions and 74 deletions
3
.github/workflows/tests.yml
vendored
3
.github/workflows/tests.yml
vendored
|
@ -53,6 +53,9 @@ jobs:
|
||||||
-p serai-processor-bin \
|
-p serai-processor-bin \
|
||||||
-p serai-bitcoin-processor \
|
-p serai-bitcoin-processor \
|
||||||
-p serai-processor-ethereum-contracts \
|
-p serai-processor-ethereum-contracts \
|
||||||
|
-p serai-processor-ethereum-primitives \
|
||||||
|
-p serai-processor-ethereum-deployer \
|
||||||
|
-p ethereum-serai \
|
||||||
-p serai-ethereum-processor \
|
-p serai-ethereum-processor \
|
||||||
-p serai-monero-processor \
|
-p serai-monero-processor \
|
||||||
-p tendermint-machine \
|
-p tendermint-machine \
|
||||||
|
|
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -8721,6 +8721,32 @@ dependencies = [
|
||||||
"syn-solidity",
|
"syn-solidity",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serai-processor-ethereum-deployer"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"alloy-consensus",
|
||||||
|
"alloy-core",
|
||||||
|
"alloy-provider",
|
||||||
|
"alloy-rpc-types-eth",
|
||||||
|
"alloy-simple-request-transport",
|
||||||
|
"alloy-sol-macro",
|
||||||
|
"alloy-sol-types",
|
||||||
|
"alloy-transport",
|
||||||
|
"build-solidity-contracts",
|
||||||
|
"serai-processor-ethereum-primitives",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serai-processor-ethereum-primitives"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"alloy-consensus",
|
||||||
|
"alloy-core",
|
||||||
|
"group",
|
||||||
|
"k256",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serai-processor-frost-attempt-manager"
|
name = "serai-processor-frost-attempt-manager"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -88,6 +88,8 @@ members = [
|
||||||
"processor/bin",
|
"processor/bin",
|
||||||
"processor/bitcoin",
|
"processor/bitcoin",
|
||||||
"processor/ethereum/contracts",
|
"processor/ethereum/contracts",
|
||||||
|
"processor/ethereum/primitives",
|
||||||
|
"processor/ethereum/deployer",
|
||||||
"processor/ethereum/ethereum-serai",
|
"processor/ethereum/ethereum-serai",
|
||||||
"processor/ethereum",
|
"processor/ethereum",
|
||||||
"processor/monero",
|
"processor/monero",
|
||||||
|
|
|
@ -59,8 +59,10 @@ exceptions = [
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-processor-signers" },
|
{ allow = ["AGPL-3.0"], name = "serai-processor-signers" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-bitcoin-processor" },
|
{ allow = ["AGPL-3.0"], name = "serai-bitcoin-processor" },
|
||||||
{ allow = ["AGPL-3.0"], name = "ethereum-serai" },
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-processor-ethereum-contracts" },
|
{ allow = ["AGPL-3.0"], name = "serai-processor-ethereum-contracts" },
|
||||||
|
{ allow = ["AGPL-3.0"], name = "serai-processor-ethereum-primitives" },
|
||||||
|
{ allow = ["AGPL-3.0"], name = "serai-processor-ethereum-deployer" },
|
||||||
|
{ allow = ["AGPL-3.0"], name = "ethereum-serai" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-ethereum-processor" },
|
{ allow = ["AGPL-3.0"], name = "serai-ethereum-processor" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-monero-processor" },
|
{ allow = ["AGPL-3.0"], name = "serai-monero-processor" },
|
||||||
|
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
pragma solidity ^0.8.26;
|
|
||||||
|
|
||||||
/*
|
|
||||||
The expected deployment process of the Router is as follows:
|
|
||||||
|
|
||||||
1) A transaction deploying Deployer is made. Then, a deterministic signature is
|
|
||||||
created such that an account with an unknown private key is the creator of
|
|
||||||
the contract. Anyone can fund this address, and once anyone does, the
|
|
||||||
transaction deploying Deployer can be published by anyone. No other
|
|
||||||
transaction may be made from that account.
|
|
||||||
|
|
||||||
2) Anyone deploys the Router through the Deployer. This uses a sequential nonce
|
|
||||||
such that meet-in-the-middle attacks, with complexity 2**80, aren't feasible.
|
|
||||||
While such attacks would still be feasible if the Deployer's address was
|
|
||||||
controllable, the usage of a deterministic signature with a NUMS method
|
|
||||||
prevents that.
|
|
||||||
|
|
||||||
This doesn't have any denial-of-service risks and will resolve once anyone steps
|
|
||||||
forward as deployer. This does fail to guarantee an identical address across
|
|
||||||
every chain, though it enables letting anyone efficiently ask the Deployer for
|
|
||||||
the address (with the Deployer having an identical address on every chain).
|
|
||||||
|
|
||||||
Unfortunately, guaranteeing identical addresses aren't feasible. We'd need the
|
|
||||||
Deployer contract to use a consistent salt for the Router, yet the Router must
|
|
||||||
be deployed with a specific public key for Serai. Since Ethereum isn't able to
|
|
||||||
determine a valid public key (one the result of a Serai DKG) from a dishonest
|
|
||||||
public key, we have to allow multiple deployments with Serai being the one to
|
|
||||||
determine which to use.
|
|
||||||
|
|
||||||
The alternative would be to have a council publish the Serai key on-Ethereum,
|
|
||||||
with Serai verifying the published result. This would introduce a DoS risk in
|
|
||||||
the council not publishing the correct key/not publishing any key.
|
|
||||||
*/
|
|
||||||
|
|
||||||
contract Deployer {
|
|
||||||
event Deployment(bytes32 indexed init_code_hash, address created);
|
|
||||||
|
|
||||||
error DeploymentFailed();
|
|
||||||
|
|
||||||
function deploy(bytes memory init_code) external {
|
|
||||||
address created;
|
|
||||||
assembly {
|
|
||||||
created := create(0, add(init_code, 0x20), mload(init_code))
|
|
||||||
}
|
|
||||||
if (created == address(0)) {
|
|
||||||
revert DeploymentFailed();
|
|
||||||
}
|
|
||||||
// These may be emitted out of order upon re-entrancy
|
|
||||||
emit Deployment(keccak256(init_code), created);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,11 +9,6 @@ mod abigen;
|
||||||
pub mod erc20 {
|
pub mod erc20 {
|
||||||
pub use super::abigen::erc20::IERC20::*;
|
pub use super::abigen::erc20::IERC20::*;
|
||||||
}
|
}
|
||||||
pub mod deployer {
|
|
||||||
pub const BYTECODE: &str =
|
|
||||||
include_str!(concat!(env!("OUT_DIR"), "/serai-processor-ethereum-contracts/Deployer.bin"));
|
|
||||||
pub use super::abigen::deployer::Deployer::*;
|
|
||||||
}
|
|
||||||
pub mod router {
|
pub mod router {
|
||||||
pub const BYTECODE: &str =
|
pub const BYTECODE: &str =
|
||||||
include_str!(concat!(env!("OUT_DIR"), "/serai-processor-ethereum-contracts/Router.bin"));
|
include_str!(concat!(env!("OUT_DIR"), "/serai-processor-ethereum-contracts/Router.bin"));
|
||||||
|
|
34
processor/ethereum/deployer/Cargo.toml
Normal file
34
processor/ethereum/deployer/Cargo.toml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
[package]
|
||||||
|
name = "serai-processor-ethereum-deployer"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "The deployer for Serai's Ethereum contracts"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/processor/ethereum/deployer"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
rust-version = "1.79"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
alloy-core = { version = "0.8", default-features = false }
|
||||||
|
alloy-consensus = { version = "0.3", default-features = false }
|
||||||
|
|
||||||
|
alloy-sol-types = { version = "0.8", default-features = false }
|
||||||
|
alloy-sol-macro = { version = "0.8", default-features = false }
|
||||||
|
|
||||||
|
alloy-rpc-types-eth = { version = "0.3", default-features = false }
|
||||||
|
alloy-transport = { version = "0.3", default-features = false }
|
||||||
|
alloy-simple-request-transport = { path = "../../../networks/ethereum/alloy-simple-request-transport", default-features = false }
|
||||||
|
alloy-provider = { version = "0.3", default-features = false }
|
||||||
|
|
||||||
|
ethereum-primitives = { package = "serai-processor-ethereum-primitives", path = "../primitives", default-features = false }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
build-solidity-contracts = { path = "../../../networks/ethereum/build-contracts", default-features = false }
|
15
processor/ethereum/deployer/LICENSE
Normal file
15
processor/ethereum/deployer/LICENSE
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
AGPL-3.0-only license
|
||||||
|
|
||||||
|
Copyright (c) 2022-2024 Luke Parker
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
23
processor/ethereum/deployer/README.md
Normal file
23
processor/ethereum/deployer/README.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Ethereum Smart Contracts Deployer
|
||||||
|
|
||||||
|
The deployer for Serai's Ethereum contracts.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
It should be possible to efficiently locate the Serai Router on an blockchain with the EVM, without
|
||||||
|
relying on any centralized (or even federated) entities. While deploying and locating an instance of
|
||||||
|
the Router would be trivial, by using a fixed signature for the deployment transaction, the Router
|
||||||
|
must be constructed with the correct key for the Serai network (or set to have the correct key
|
||||||
|
post-construction). Since this cannot be guaranteed to occur, the process must be retryable and the
|
||||||
|
first successful invocation must be efficiently findable.
|
||||||
|
|
||||||
|
## Methodology
|
||||||
|
|
||||||
|
We define a contract, the Deployer, to deploy the router. This contract could use `CREATE2` with the
|
||||||
|
key representing Serai as the salt, yet this would be open to collision attacks with just 2**80
|
||||||
|
complexity. Instead, we use `CREATE` which would require 2**80 on-chain transactions (infeasible) to
|
||||||
|
use as the basis of a collision.
|
||||||
|
|
||||||
|
In order to efficiently find the contract for a key, the Deployer contract saves the addresses of
|
||||||
|
deployed contracts (indexed by the initialization code hash). This allows using a single call to a
|
||||||
|
contract with a known address to find the proper Router.
|
5
processor/ethereum/deployer/build.rs
Normal file
5
processor/ethereum/deployer/build.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
fn main() {
|
||||||
|
let artifacts_path =
|
||||||
|
std::env::var("OUT_DIR").unwrap().to_string() + "/serai-processor-ethereum-deployer";
|
||||||
|
build_solidity_contracts::build(&[], "contracts", &artifacts_path).unwrap();
|
||||||
|
}
|
81
processor/ethereum/deployer/contracts/Deployer.sol
Normal file
81
processor/ethereum/deployer/contracts/Deployer.sol
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The expected deployment process of the Router is as follows:
|
||||||
|
|
||||||
|
1) A transaction deploying Deployer is made. Then, a deterministic signature is
|
||||||
|
created such that an account with an unknown private key is the creator of
|
||||||
|
the contract. Anyone can fund this address, and once anyone does, the
|
||||||
|
transaction deploying Deployer can be published by anyone. No other
|
||||||
|
transaction may be made from that account.
|
||||||
|
|
||||||
|
2) Anyone deploys the Router through the Deployer. This uses a sequential nonce
|
||||||
|
such that meet-in-the-middle attacks, with complexity 2**80, aren't feasible.
|
||||||
|
While such attacks would still be feasible if the Deployer's address was
|
||||||
|
controllable, the usage of a deterministic signature with a NUMS method
|
||||||
|
prevents that.
|
||||||
|
|
||||||
|
This doesn't have any denial-of-service risks and will resolve once anyone steps
|
||||||
|
forward as deployer. This does fail to guarantee an identical address across
|
||||||
|
every chain, though it enables letting anyone efficiently ask the Deployer for
|
||||||
|
the address (with the Deployer having an identical address on every chain).
|
||||||
|
|
||||||
|
Unfortunately, guaranteeing identical addresses aren't feasible. We'd need the
|
||||||
|
Deployer contract to use a consistent salt for the Router, yet the Router must
|
||||||
|
be deployed with a specific public key for Serai. Since Ethereum isn't able to
|
||||||
|
determine a valid public key (one the result of a Serai DKG) from a dishonest
|
||||||
|
public key, we have to allow multiple deployments with Serai being the one to
|
||||||
|
determine which to use.
|
||||||
|
|
||||||
|
The alternative would be to have a council publish the Serai key on-Ethereum,
|
||||||
|
with Serai verifying the published result. This would introduce a DoS risk in
|
||||||
|
the council not publishing the correct key/not publishing any key.
|
||||||
|
*/
|
||||||
|
|
||||||
|
contract Deployer {
|
||||||
|
struct Deployment {
|
||||||
|
uint64 block_number;
|
||||||
|
address created_contract;
|
||||||
|
}
|
||||||
|
mapping(bytes32 => Deployment) public deployments;
|
||||||
|
|
||||||
|
error Reentrancy();
|
||||||
|
error PriorDeployed();
|
||||||
|
error DeploymentFailed();
|
||||||
|
|
||||||
|
function deploy(bytes memory init_code) external {
|
||||||
|
// Prevent re-entrancy
|
||||||
|
// If we did allow it, one could deploy the same contract multiple times (with one overwriting
|
||||||
|
// the other's set value in storage)
|
||||||
|
bool called;
|
||||||
|
// This contract doesn't have any other use of transient storage, nor is to be inherited, making
|
||||||
|
// this usage of the zero address safe
|
||||||
|
assembly { called := tload(0) }
|
||||||
|
if (called) {
|
||||||
|
revert Reentrancy();
|
||||||
|
}
|
||||||
|
assembly { tstore(0, 1) }
|
||||||
|
|
||||||
|
// Check this wasn't prior deployed
|
||||||
|
bytes32 init_code_hash = keccak256(init_code);
|
||||||
|
Deployment memory deployment = deployments[init_code_hash];
|
||||||
|
if (deployment.created_contract == address(0)) {
|
||||||
|
revert PriorDeployed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deploy the contract
|
||||||
|
address created_contract;
|
||||||
|
assembly {
|
||||||
|
created_contract := create(0, add(init_code, 0x20), mload(init_code))
|
||||||
|
}
|
||||||
|
if (created_contract == address(0)) {
|
||||||
|
revert DeploymentFailed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the dpeloyment to storage
|
||||||
|
deployment.block_number = uint64(block.number);
|
||||||
|
deployment.created_contract = created_contract;
|
||||||
|
deployments[init_code_hash] = deployment;
|
||||||
|
}
|
||||||
|
}
|
104
processor/ethereum/deployer/src/lib.rs
Normal file
104
processor/ethereum/deployer/src/lib.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use alloy_core::primitives::{hex::FromHex, Address, U256, Bytes, TxKind};
|
||||||
|
use alloy_consensus::{Signed, TxLegacy};
|
||||||
|
|
||||||
|
use alloy_sol_types::SolCall;
|
||||||
|
|
||||||
|
use alloy_rpc_types_eth::{TransactionInput, TransactionRequest};
|
||||||
|
use alloy_transport::{TransportErrorKind, RpcError};
|
||||||
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[expect(warnings)]
|
||||||
|
#[expect(needless_pass_by_value)]
|
||||||
|
#[expect(clippy::all)]
|
||||||
|
#[expect(clippy::ignored_unit_patterns)]
|
||||||
|
#[expect(clippy::redundant_closure_for_method_calls)]
|
||||||
|
mod abi {
|
||||||
|
alloy_sol_macro::sol!("contracts/Deployer.sol");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Deployer contract for the Serai Router contract.
|
||||||
|
///
|
||||||
|
/// This Deployer has a deterministic address, letting it be immediately identified on any
|
||||||
|
/// compatible chain. It then supports retrieving the Router contract's address (which isn't
|
||||||
|
/// deterministic) using a single call.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Deployer;
|
||||||
|
impl Deployer {
|
||||||
|
/// Obtain the transaction to deploy this contract, already signed.
|
||||||
|
///
|
||||||
|
/// The account this transaction is sent from (which is populated in `from`) must be sufficiently
|
||||||
|
/// funded for this transaction to be submitted. This account has no known private key to anyone
|
||||||
|
/// so ETH sent can be neither misappropriated nor returned.
|
||||||
|
pub fn deployment_tx() -> Signed<TxLegacy> {
|
||||||
|
pub const BYTECODE: &str =
|
||||||
|
include_str!(concat!(env!("OUT_DIR"), "/serai-processor-ethereum-deployer/Deployer.bin"));
|
||||||
|
let bytecode =
|
||||||
|
Bytes::from_hex(BYTECODE).expect("compiled-in Deployer bytecode wasn't valid hex");
|
||||||
|
|
||||||
|
let tx = TxLegacy {
|
||||||
|
chain_id: None,
|
||||||
|
nonce: 0,
|
||||||
|
// 100 gwei
|
||||||
|
gas_price: 100_000_000_000u128,
|
||||||
|
// TODO: Use a more accurate gas limit
|
||||||
|
gas_limit: 1_000_000u128,
|
||||||
|
to: TxKind::Create,
|
||||||
|
value: U256::ZERO,
|
||||||
|
input: bytecode,
|
||||||
|
};
|
||||||
|
|
||||||
|
ethereum_primitives::deterministically_sign(&tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain the deterministic address for this contract.
|
||||||
|
pub(crate) fn address() -> Address {
|
||||||
|
let deployer_deployer =
|
||||||
|
Self::deployment_tx().recover_signer().expect("deployment_tx didn't have a valid signature");
|
||||||
|
Address::create(&deployer_deployer, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a new view of the Deployer.
|
||||||
|
pub async fn new(
|
||||||
|
provider: Arc<RootProvider<SimpleRequest>>,
|
||||||
|
) -> Result<Option<Self>, RpcError<TransportErrorKind>> {
|
||||||
|
let address = Self::address();
|
||||||
|
let code = provider.get_code_at(address).await?;
|
||||||
|
// Contract has yet to be deployed
|
||||||
|
if code.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Ok(Some(Self))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the deployment of a contract.
|
||||||
|
pub async fn find_deployment(
|
||||||
|
&self,
|
||||||
|
provider: Arc<RootProvider<SimpleRequest>>,
|
||||||
|
init_code_hash: [u8; 32],
|
||||||
|
) -> Result<Option<abi::Deployer::Deployment>, RpcError<TransportErrorKind>> {
|
||||||
|
let call = TransactionRequest::default().to(Self::address()).input(TransactionInput::new(
|
||||||
|
abi::Deployer::deploymentsCall::new((init_code_hash.into(),)).abi_encode().into(),
|
||||||
|
));
|
||||||
|
let bytes = provider.call(&call).await?;
|
||||||
|
let deployment = abi::Deployer::deploymentsCall::abi_decode_returns(&bytes, true)
|
||||||
|
.map_err(|e| {
|
||||||
|
TransportErrorKind::Custom(
|
||||||
|
format!("node returned a non-Deployment for function returning Deployment: {e:?}").into(),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
._0;
|
||||||
|
|
||||||
|
if deployment.created_contract == [0; 20] {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Ok(Some(deployment))
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ name = "ethereum-serai"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "An Ethereum library supporting Schnorr signing and on-chain verification"
|
description = "An Ethereum library supporting Schnorr signing and on-chain verification"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/ethereum"
|
repository = "https://github.com/serai-dex/serai/tree/develop/processor/ethereum/ethereum-serai"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>", "Elizabeth Binks <elizabethjbinks@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>", "Elizabeth Binks <elizabethjbinks@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
|
@ -15,11 +15,9 @@ use frost::{
|
||||||
|
|
||||||
pub use ethereum_schnorr_contract::*;
|
pub use ethereum_schnorr_contract::*;
|
||||||
|
|
||||||
use alloy_core::primitives::{Parity, Signature as AlloySignature};
|
use alloy_core::primitives::{Parity, Signature as AlloySignature, Address};
|
||||||
use alloy_consensus::{SignableTransaction, Signed, TxLegacy};
|
use alloy_consensus::{SignableTransaction, Signed, TxLegacy};
|
||||||
|
|
||||||
use crate::abi::router::{Signature as AbiSignature};
|
|
||||||
|
|
||||||
pub(crate) fn keccak256(data: &[u8]) -> [u8; 32] {
|
pub(crate) fn keccak256(data: &[u8]) -> [u8; 32] {
|
||||||
alloy_core::primitives::keccak256(data).into()
|
alloy_core::primitives::keccak256(data).into()
|
||||||
}
|
}
|
||||||
|
@ -28,11 +26,9 @@ pub(crate) fn hash_to_scalar(data: &[u8]) -> Scalar {
|
||||||
<Scalar as Reduce<KU256>>::reduce_bytes(&keccak256(data).into())
|
<Scalar as Reduce<KU256>>::reduce_bytes(&keccak256(data).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn address(point: &ProjectivePoint) -> [u8; 20] {
|
pub(crate) fn address(point: &ProjectivePoint) -> [u8; 20] {
|
||||||
let encoded_point = point.to_encoded_point(false);
|
let encoded_point = point.to_encoded_point(false);
|
||||||
// Last 20 bytes of the hash of the concatenated x and y coordinates
|
**Address::from_raw_public_key(&encoded_point.as_ref()[1 .. 65])
|
||||||
// We obtain the concatenated x and y coordinates via the uncompressed encoding of the point
|
|
||||||
keccak256(&encoded_point.as_ref()[1 .. 65])[12 ..].try_into().unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deterministically sign a transaction.
|
/// Deterministically sign a transaction.
|
||||||
|
@ -64,18 +60,15 @@ pub fn deterministically_sign(tx: &TxLegacy) -> Signed<TxLegacy> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The HRAm to use for the Schnorr contract.
|
/// The HRAm to use for the Schnorr Solidity library.
|
||||||
|
///
|
||||||
|
/// This will panic if the public key being signed for is not representable within the Schnorr
|
||||||
|
/// Solidity library.
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct EthereumHram {}
|
pub struct EthereumHram {}
|
||||||
impl Hram<Secp256k1> for EthereumHram {
|
impl Hram<Secp256k1> for EthereumHram {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar {
|
fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar {
|
||||||
let x_coord = A.to_affine().x();
|
Signature::challenge(*R, &PublicKey::new(*A).unwrap(), m)
|
||||||
|
|
||||||
let mut data = address(R).to_vec();
|
|
||||||
data.extend(x_coord.as_slice());
|
|
||||||
data.extend(m);
|
|
||||||
|
|
||||||
<Scalar as Reduce<KU256>>::reduce_bytes(&keccak256(&data).into())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ pub mod alloy {
|
||||||
|
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
|
|
||||||
|
/*
|
||||||
pub(crate) mod abi {
|
pub(crate) mod abi {
|
||||||
pub use contracts::erc20;
|
pub use contracts::erc20;
|
||||||
pub use contracts::deployer;
|
pub use contracts::deployer;
|
||||||
|
@ -37,3 +38,4 @@ pub enum Error {
|
||||||
#[error("couldn't make call/send TX")]
|
#[error("couldn't make call/send TX")]
|
||||||
ConnectionError,
|
ConnectionError,
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -25,6 +25,19 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The HRAm to use for the Schnorr Solidity library.
|
||||||
|
///
|
||||||
|
/// This will panic if the public key being signed for is not representable within the Schnorr
|
||||||
|
/// Solidity library.
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct EthereumHram {}
|
||||||
|
impl Hram<Secp256k1> for EthereumHram {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar {
|
||||||
|
Signature::challenge(*R, &PublicKey::new(*A).unwrap(), m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Call {
|
pub struct Call {
|
||||||
pub to: [u8; 20],
|
pub to: [u8; 20],
|
||||||
|
|
24
processor/ethereum/primitives/Cargo.toml
Normal file
24
processor/ethereum/primitives/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "serai-processor-ethereum-primitives"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Primitives for Serai's Ethereum Processor"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/processor/ethereum/primitives"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
rust-version = "1.79"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
group = { version = "0.13", default-features = false }
|
||||||
|
k256 = { version = "^0.13.1", default-features = false, features = ["std", "arithmetic"] }
|
||||||
|
|
||||||
|
alloy-core = { version = "0.8", default-features = false }
|
||||||
|
alloy-consensus = { version = "0.3", default-features = false, features = ["k256"] }
|
15
processor/ethereum/primitives/LICENSE
Normal file
15
processor/ethereum/primitives/LICENSE
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
AGPL-3.0-only license
|
||||||
|
|
||||||
|
Copyright (c) 2022-2024 Luke Parker
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
3
processor/ethereum/primitives/README.md
Normal file
3
processor/ethereum/primitives/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Ethereum Processor Primitives
|
||||||
|
|
||||||
|
This library contains miscellaneous primitives and helper functions.
|
49
processor/ethereum/primitives/src/lib.rs
Normal file
49
processor/ethereum/primitives/src/lib.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
use group::ff::PrimeField;
|
||||||
|
use k256::{elliptic_curve::ops::Reduce, U256, Scalar};
|
||||||
|
|
||||||
|
use alloy_core::primitives::{Parity, Signature};
|
||||||
|
use alloy_consensus::{SignableTransaction, Signed, TxLegacy};
|
||||||
|
|
||||||
|
/// The Keccak256 hash function.
|
||||||
|
pub fn keccak256(data: impl AsRef<[u8]>) -> [u8; 32] {
|
||||||
|
alloy_core::primitives::keccak256(data.as_ref()).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deterministically sign a transaction.
|
||||||
|
///
|
||||||
|
/// This function panics if passed a transaction with a non-None chain ID.
|
||||||
|
pub fn deterministically_sign(tx: &TxLegacy) -> Signed<TxLegacy> {
|
||||||
|
pub fn hash_to_scalar(data: impl AsRef<[u8]>) -> Scalar {
|
||||||
|
<Scalar as Reduce<U256>>::reduce_bytes(&keccak256(data).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
tx.chain_id.is_none(),
|
||||||
|
"chain ID was Some when deterministically signing a TX (causing a non-deterministic signer)"
|
||||||
|
);
|
||||||
|
|
||||||
|
let sig_hash = tx.signature_hash().0;
|
||||||
|
let mut r = hash_to_scalar([sig_hash.as_slice(), b"r"].concat());
|
||||||
|
let mut s = hash_to_scalar([sig_hash.as_slice(), b"s"].concat());
|
||||||
|
loop {
|
||||||
|
// Create the signature
|
||||||
|
let r_bytes: [u8; 32] = r.to_repr().into();
|
||||||
|
let s_bytes: [u8; 32] = s.to_repr().into();
|
||||||
|
let v = Parity::NonEip155(false);
|
||||||
|
let signature = Signature::from_scalars_and_parity(r_bytes.into(), s_bytes.into(), v).unwrap();
|
||||||
|
|
||||||
|
// Check if this is a valid signature
|
||||||
|
let tx = tx.clone().into_signed(signature);
|
||||||
|
if tx.recover_signer().is_ok() {
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-hash until valid
|
||||||
|
r = hash_to_scalar(r_bytes);
|
||||||
|
s = hash_to_scalar(s_bytes);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue