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-bitcoin-processor \
|
||||
-p serai-processor-ethereum-contracts \
|
||||
-p serai-processor-ethereum-primitives \
|
||||
-p serai-processor-ethereum-deployer \
|
||||
-p ethereum-serai \
|
||||
-p serai-ethereum-processor \
|
||||
-p serai-monero-processor \
|
||||
-p tendermint-machine \
|
||||
|
|
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -8721,6 +8721,32 @@ dependencies = [
|
|||
"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]]
|
||||
name = "serai-processor-frost-attempt-manager"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -88,6 +88,8 @@ members = [
|
|||
"processor/bin",
|
||||
"processor/bitcoin",
|
||||
"processor/ethereum/contracts",
|
||||
"processor/ethereum/primitives",
|
||||
"processor/ethereum/deployer",
|
||||
"processor/ethereum/ethereum-serai",
|
||||
"processor/ethereum",
|
||||
"processor/monero",
|
||||
|
|
|
@ -59,8 +59,10 @@ exceptions = [
|
|||
{ allow = ["AGPL-3.0"], name = "serai-processor-signers" },
|
||||
|
||||
{ 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-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-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 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 const BYTECODE: &str =
|
||||
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"
|
||||
description = "An Ethereum library supporting Schnorr signing and on-chain verification"
|
||||
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>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
|
|
@ -15,11 +15,9 @@ use frost::{
|
|||
|
||||
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 crate::abi::router::{Signature as AbiSignature};
|
||||
|
||||
pub(crate) fn keccak256(data: &[u8]) -> [u8; 32] {
|
||||
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())
|
||||
}
|
||||
|
||||
pub fn address(point: &ProjectivePoint) -> [u8; 20] {
|
||||
pub(crate) fn address(point: &ProjectivePoint) -> [u8; 20] {
|
||||
let encoded_point = point.to_encoded_point(false);
|
||||
// Last 20 bytes of the hash of the concatenated x and y coordinates
|
||||
// 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()
|
||||
**Address::from_raw_public_key(&encoded_point.as_ref()[1 .. 65])
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
pub struct EthereumHram {}
|
||||
impl Hram<Secp256k1> for EthereumHram {
|
||||
#[allow(non_snake_case)]
|
||||
fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar {
|
||||
let x_coord = A.to_affine().x();
|
||||
|
||||
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())
|
||||
Signature::challenge(*R, &PublicKey::new(*A).unwrap(), m)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ pub mod alloy {
|
|||
|
||||
pub mod crypto;
|
||||
|
||||
/*
|
||||
pub(crate) mod abi {
|
||||
pub use contracts::erc20;
|
||||
pub use contracts::deployer;
|
||||
|
@ -37,3 +38,4 @@ pub enum Error {
|
|||
#[error("couldn't make call/send TX")]
|
||||
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)]
|
||||
pub struct Call {
|
||||
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