mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-12 05:44:53 +00:00
Ethereum SignableTransaction, Eventuality
This commit is contained in:
parent
8f2a9301cf
commit
433beac93a
11 changed files with 390 additions and 129 deletions
|
@ -26,10 +26,18 @@ borsh = { version = "1", default-features = false, features = ["std", "derive",
|
||||||
|
|
||||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std", "secp256k1"] }
|
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std", "secp256k1"] }
|
||||||
dkg = { path = "../../crypto/dkg", default-features = false, features = ["std", "evrf-secp256k1"] }
|
dkg = { path = "../../crypto/dkg", default-features = false, features = ["std", "evrf-secp256k1"] }
|
||||||
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false }
|
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false, features = ["secp256k1"] }
|
||||||
|
|
||||||
k256 = { version = "^0.13.1", default-features = false, features = ["std"] }
|
k256 = { version = "^0.13.1", default-features = false, features = ["std"] }
|
||||||
ethereum-serai = { path = "./ethereum-serai", default-features = false, optional = true }
|
|
||||||
|
alloy-core = { version = "0.8", default-features = false }
|
||||||
|
alloy-rlp = { version = "0.3", default-features = false }
|
||||||
|
alloy-consensus = { version = "0.3", default-features = false }
|
||||||
|
|
||||||
|
alloy-rpc-types-eth = { version = "0.3", default-features = false }
|
||||||
|
alloy-simple-request-transport = { path = "../../networks/ethereum/alloy-simple-request-transport", default-features = false }
|
||||||
|
alloy-rpc-client = { version = "0.3", default-features = false }
|
||||||
|
alloy-provider = { version = "0.3", default-features = false }
|
||||||
|
|
||||||
serai-client = { path = "../../substrate/client", default-features = false, features = ["ethereum"] }
|
serai-client = { path = "../../substrate/client", default-features = false, features = ["ethereum"] }
|
||||||
|
|
||||||
|
@ -48,6 +56,11 @@ scanner = { package = "serai-processor-scanner", path = "../scanner" }
|
||||||
smart-contract-scheduler = { package = "serai-processor-smart-contract-scheduler", path = "../scheduler/smart-contract" }
|
smart-contract-scheduler = { package = "serai-processor-smart-contract-scheduler", path = "../scheduler/smart-contract" }
|
||||||
signers = { package = "serai-processor-signers", path = "../signers" }
|
signers = { package = "serai-processor-signers", path = "../signers" }
|
||||||
|
|
||||||
|
ethereum-schnorr = { package = "ethereum-schnorr-contract", path = "../../networks/ethereum/schnorr" }
|
||||||
|
ethereum-primitives = { package = "serai-processor-ethereum-primitives", path = "./primitives" }
|
||||||
|
ethereum-router = { package = "serai-processor-ethereum-router", path = "./router" }
|
||||||
|
ethereum-erc20 = { package = "serai-processor-ethereum-erc20", path = "./erc20" }
|
||||||
|
|
||||||
bin = { package = "serai-processor-bin", path = "../bin" }
|
bin = { package = "serai-processor-bin", path = "../bin" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -24,7 +24,6 @@ alloy-core = { version = "0.8", default-features = false }
|
||||||
alloy-consensus = { version = "0.3", default-features = false }
|
alloy-consensus = { version = "0.3", default-features = false }
|
||||||
|
|
||||||
alloy-sol-types = { version = "0.8", 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-rpc-types-eth = { version = "0.3", default-features = false }
|
||||||
alloy-transport = { version = "0.3", default-features = false }
|
alloy-transport = { version = "0.3", default-features = false }
|
||||||
|
|
|
@ -27,7 +27,7 @@ use ethereum_schnorr::{PublicKey, Signature};
|
||||||
use ethereum_deployer::Deployer;
|
use ethereum_deployer::Deployer;
|
||||||
use erc20::{Transfer, Erc20};
|
use erc20::{Transfer, Erc20};
|
||||||
|
|
||||||
use serai_client::{primitives::Amount, networks::ethereum::Address as SeraiAddress};
|
use serai_client::networks::ethereum::Address as SeraiAddress;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[expect(warnings)]
|
#[expect(warnings)]
|
||||||
|
@ -159,8 +159,8 @@ impl InInstruction {
|
||||||
/// A list of `OutInstruction`s.
|
/// A list of `OutInstruction`s.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct OutInstructions(Vec<abi::OutInstruction>);
|
pub struct OutInstructions(Vec<abi::OutInstruction>);
|
||||||
impl From<&[(SeraiAddress, (Coin, Amount))]> for OutInstructions {
|
impl From<&[(SeraiAddress, (Coin, U256))]> for OutInstructions {
|
||||||
fn from(outs: &[(SeraiAddress, (Coin, Amount))]) -> Self {
|
fn from(outs: &[(SeraiAddress, (Coin, U256))]) -> Self {
|
||||||
Self(
|
Self(
|
||||||
outs
|
outs
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -184,7 +184,7 @@ impl From<&[(SeraiAddress, (Coin, Amount))]> for OutInstructions {
|
||||||
Coin::Ether => [0; 20].into(),
|
Coin::Ether => [0; 20].into(),
|
||||||
Coin::Erc20(address) => address.into(),
|
Coin::Erc20(address) => address.into(),
|
||||||
},
|
},
|
||||||
value: amount.0.try_into().expect("couldn't convert u64 to u256"),
|
value: *amount,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -192,7 +192,7 @@ impl From<&[(SeraiAddress, (Coin, Amount))]> for OutInstructions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executed an command.
|
/// An action which was executed by the Router.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum Executed {
|
pub enum Executed {
|
||||||
/// Set a new key.
|
/// Set a new key.
|
||||||
|
@ -218,6 +218,44 @@ impl Executed {
|
||||||
Executed::SetKey { nonce, .. } | Executed::Batch { nonce, .. } => *nonce,
|
Executed::SetKey { nonce, .. } | Executed::Batch { nonce, .. } => *nonce,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write the Executed.
|
||||||
|
pub fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||||
|
match self {
|
||||||
|
Self::SetKey { nonce, key } => {
|
||||||
|
writer.write_all(&[0])?;
|
||||||
|
writer.write_all(&nonce.to_le_bytes())?;
|
||||||
|
writer.write_all(key)
|
||||||
|
}
|
||||||
|
Self::Batch { nonce, message_hash } => {
|
||||||
|
writer.write_all(&[1])?;
|
||||||
|
writer.write_all(&nonce.to_le_bytes())?;
|
||||||
|
writer.write_all(message_hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read an Executed.
|
||||||
|
pub fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||||
|
let mut kind = [0xff];
|
||||||
|
reader.read_exact(&mut kind)?;
|
||||||
|
if kind[0] >= 2 {
|
||||||
|
Err(io::Error::other("unrecognized type of Executed"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut nonce = [0; 8];
|
||||||
|
reader.read_exact(&mut nonce)?;
|
||||||
|
let nonce = u64::from_le_bytes(nonce);
|
||||||
|
|
||||||
|
let mut payload = [0; 32];
|
||||||
|
reader.read_exact(&mut payload)?;
|
||||||
|
|
||||||
|
Ok(match kind[0] {
|
||||||
|
0 => Self::SetKey { nonce, key: payload },
|
||||||
|
1 => Self::Batch { nonce, message_hash: payload },
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A view of the Router for Serai.
|
/// A view of the Router for Serai.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use ciphersuite::{Ciphersuite, Secp256k1};
|
use ciphersuite::{Ciphersuite, Secp256k1};
|
||||||
use dkg::ThresholdKeys;
|
use dkg::ThresholdKeys;
|
||||||
|
|
||||||
use ethereum_serai::crypto::PublicKey;
|
use ethereum_schnorr::PublicKey;
|
||||||
|
|
||||||
pub(crate) struct KeyGenParams;
|
pub(crate) struct KeyGenParams;
|
||||||
impl key_gen::KeyGenParams for KeyGenParams {
|
impl key_gen::KeyGenParams for KeyGenParams {
|
||||||
|
|
|
@ -8,12 +8,10 @@ static ALLOCATOR: zalloc::ZeroizingAlloc<std::alloc::System> =
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ethereum_serai::alloy::{
|
use alloy_core::primitives::U256;
|
||||||
primitives::U256,
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
simple_request_transport::SimpleRequest,
|
use alloy_rpc_client::ClientBuilder;
|
||||||
rpc_client::ClientBuilder,
|
use alloy_provider::{Provider, RootProvider};
|
||||||
provider::{Provider, RootProvider},
|
|
||||||
};
|
|
||||||
|
|
||||||
use serai_env as env;
|
use serai_env as env;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,9 @@ use ciphersuite::{Ciphersuite, Secp256k1};
|
||||||
use serai_client::networks::ethereum::Address;
|
use serai_client::networks::ethereum::Address;
|
||||||
|
|
||||||
use primitives::{ReceivedOutput, EventualityTracker};
|
use primitives::{ReceivedOutput, EventualityTracker};
|
||||||
|
|
||||||
|
use ethereum_router::Executed;
|
||||||
|
|
||||||
use crate::{output::Output, transaction::Eventuality};
|
use crate::{output::Output, transaction::Eventuality};
|
||||||
|
|
||||||
// We interpret 32-block Epochs as singular blocks.
|
// We interpret 32-block Epochs as singular blocks.
|
||||||
|
@ -37,9 +40,11 @@ impl primitives::BlockHeader for Epoch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub(crate) struct FullEpoch {
|
pub(crate) struct FullEpoch {
|
||||||
epoch: Epoch,
|
epoch: Epoch,
|
||||||
|
outputs: Vec<Output>,
|
||||||
|
executed: Vec<Executed>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl primitives::Block for FullEpoch {
|
impl primitives::Block for FullEpoch {
|
||||||
|
@ -54,7 +59,8 @@ impl primitives::Block for FullEpoch {
|
||||||
self.epoch.end_hash
|
self.epoch.end_hash
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan_for_outputs_unordered(&self, key: Self::Key) -> Vec<Self::Output> {
|
fn scan_for_outputs_unordered(&self, _key: Self::Key) -> Vec<Self::Output> {
|
||||||
|
// Only return these outputs for the latest key
|
||||||
todo!("TODO")
|
todo!("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +72,33 @@ impl primitives::Block for FullEpoch {
|
||||||
<Self::Output as ReceivedOutput<Self::Key, Self::Address>>::TransactionId,
|
<Self::Output as ReceivedOutput<Self::Key, Self::Address>>::TransactionId,
|
||||||
Self::Eventuality,
|
Self::Eventuality,
|
||||||
> {
|
> {
|
||||||
todo!("TODO")
|
let mut res = HashMap::new();
|
||||||
|
for executed in &self.executed {
|
||||||
|
let Some(expected) =
|
||||||
|
eventualities.active_eventualities.remove(executed.nonce().to_le_bytes().as_slice())
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
executed,
|
||||||
|
&expected.0,
|
||||||
|
"Router emitted distinct event for nonce {}",
|
||||||
|
executed.nonce()
|
||||||
|
);
|
||||||
|
/*
|
||||||
|
The transaction ID is used to determine how internal outputs from this transaction should
|
||||||
|
be handled (if they were actually internal or if they were just to an internal address).
|
||||||
|
The Ethereum integration doesn't have internal addresses, and this transaction wasn't made
|
||||||
|
by Serai. It was simply authorized by Serai yet may or may not be associated with other
|
||||||
|
actions we don't want to flag as our own.
|
||||||
|
|
||||||
|
Accordingly, we set the transaction ID to the nonce. This is unique barring someone finding
|
||||||
|
the preimage which hashes to this nonce, and won't cause any other data to be associated.
|
||||||
|
*/
|
||||||
|
let mut tx_id = [0; 32];
|
||||||
|
tx_id[.. 8].copy_from_slice(executed.nonce().to_le_bytes().as_slice());
|
||||||
|
res.insert(tx_id, expected);
|
||||||
|
}
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
pub(crate) mod output;
|
pub(crate) mod output;
|
||||||
pub(crate) mod transaction;
|
pub(crate) mod transaction;
|
||||||
pub(crate) mod block;
|
pub(crate) mod block;
|
||||||
|
|
||||||
|
pub(crate) const DAI: [u8; 20] =
|
||||||
|
match const_hex::const_decode_to_array(b"0x6B175474E89094C44Da98b954EedeAC495271d0F") {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(_) => panic!("invalid non-test DAI hex address"),
|
||||||
|
};
|
||||||
|
|
|
@ -2,10 +2,7 @@ use std::io;
|
||||||
|
|
||||||
use ciphersuite::{Ciphersuite, Secp256k1};
|
use ciphersuite::{Ciphersuite, Secp256k1};
|
||||||
|
|
||||||
use ethereum_serai::{
|
use alloy_core::primitives::U256;
|
||||||
alloy::primitives::U256,
|
|
||||||
router::{Coin as EthereumCoin, InInstruction as EthereumInInstruction},
|
|
||||||
};
|
|
||||||
|
|
||||||
use scale::{Encode, Decode};
|
use scale::{Encode, Decode};
|
||||||
use borsh::{BorshSerialize, BorshDeserialize};
|
use borsh::{BorshSerialize, BorshDeserialize};
|
||||||
|
@ -16,12 +13,9 @@ use serai_client::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use primitives::{OutputType, ReceivedOutput};
|
use primitives::{OutputType, ReceivedOutput};
|
||||||
|
use ethereum_router::{Coin as EthereumCoin, InInstruction as EthereumInInstruction};
|
||||||
|
|
||||||
const DAI: [u8; 20] =
|
use crate::DAI;
|
||||||
match const_hex::const_decode_to_array(b"0x6B175474E89094C44Da98b954EedeAC495271d0F") {
|
|
||||||
Ok(res) => res,
|
|
||||||
Err(_) => panic!("invalid non-test DAI hex address"),
|
|
||||||
};
|
|
||||||
|
|
||||||
fn coin_to_serai_coin(coin: &EthereumCoin) -> Option<Coin> {
|
fn coin_to_serai_coin(coin: &EthereumCoin) -> Option<Coin> {
|
||||||
match coin {
|
match coin {
|
||||||
|
@ -87,7 +81,7 @@ impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key(&self) -> <Secp256k1 as Ciphersuite>::G {
|
fn key(&self) -> <Secp256k1 as Ciphersuite>::G {
|
||||||
self.0.key_at_end_of_block
|
todo!("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn presumed_origin(&self) -> Option<Address> {
|
fn presumed_origin(&self) -> Option<Address> {
|
||||||
|
|
|
@ -1,101 +1,304 @@
|
||||||
use std::io;
|
use std::{io, collections::HashMap};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use ciphersuite::{group::GroupEncoding, Ciphersuite, Secp256k1};
|
use ciphersuite::{Ciphersuite, Secp256k1};
|
||||||
use frost::{dkg::ThresholdKeys, sign::PreprocessMachine};
|
use frost::{
|
||||||
|
dkg::{Participant, ThresholdKeys},
|
||||||
|
FrostError,
|
||||||
|
algorithm::*,
|
||||||
|
sign::*,
|
||||||
|
};
|
||||||
|
|
||||||
use ethereum_serai::{crypto::PublicKey, machine::*};
|
use alloy_core::primitives::U256;
|
||||||
|
|
||||||
|
use serai_client::networks::ethereum::Address;
|
||||||
|
|
||||||
|
use scheduler::SignableTransaction;
|
||||||
|
|
||||||
|
use ethereum_primitives::keccak256;
|
||||||
|
use ethereum_schnorr::{PublicKey, Signature};
|
||||||
|
use ethereum_router::{Coin, OutInstructions, Executed, Router};
|
||||||
|
|
||||||
use crate::output::OutputId;
|
use crate::output::OutputId;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub(crate) struct Transaction(pub(crate) SignedRouterCommand);
|
pub(crate) enum Action {
|
||||||
|
SetKey { chain_id: U256, nonce: u64, key: PublicKey },
|
||||||
|
Batch { chain_id: U256, nonce: u64, outs: Vec<(Address, (Coin, U256))> },
|
||||||
|
}
|
||||||
|
|
||||||
impl From<SignedRouterCommand> for Transaction {
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
fn from(signed_router_command: SignedRouterCommand) -> Self {
|
pub(crate) struct Eventuality(pub(crate) Executed);
|
||||||
Self(signed_router_command)
|
|
||||||
|
impl Action {
|
||||||
|
fn nonce(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
Action::SetKey { nonce, .. } | Action::Batch { nonce, .. } => *nonce,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn message(&self) -> Vec<u8> {
|
||||||
|
match self {
|
||||||
|
Action::SetKey { chain_id, nonce, key } => Router::update_serai_key_message(*chain_id, *nonce, key),
|
||||||
|
Action::Batch { chain_id, nonce, outs } => Router::execute_message(*chain_id, *nonce, OutInstructions::from(outs.as_ref())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn eventuality(&self) -> Eventuality {
|
||||||
|
Eventuality(match self {
|
||||||
|
Self::SetKey { chain_id: _, nonce, key } => {
|
||||||
|
Executed::SetKey { nonce: *nonce, key: key.eth_repr() }
|
||||||
|
}
|
||||||
|
Self::Batch { chain_id, nonce, outs } => Executed::Batch {
|
||||||
|
nonce: *nonce,
|
||||||
|
message_hash: keccak256(Router::execute_message(
|
||||||
|
*chain_id,
|
||||||
|
*nonce,
|
||||||
|
OutInstructions::from(outs.as_ref()),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub(crate) struct Transaction(Action, Signature);
|
||||||
impl scheduler::Transaction for Transaction {
|
impl scheduler::Transaction for Transaction {
|
||||||
fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||||
SignedRouterCommand::read(reader).map(Self)
|
/*
|
||||||
|
let buf: Vec<u8> = borsh::from_reader(reader)?;
|
||||||
|
// We can only read this from a &[u8], hence prior reading into a Vec<u8>
|
||||||
|
<TxLegacy as alloy_rlp::Decodable>::decode(&mut buf.as_slice())
|
||||||
|
.map(Self)
|
||||||
|
.map_err(io::Error::other)
|
||||||
|
*/
|
||||||
|
let action = Action::read(reader)?;
|
||||||
|
let signature = Signature::read(reader)?;
|
||||||
|
Ok(Transaction(action, signature))
|
||||||
}
|
}
|
||||||
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||||
self.0.write(writer)
|
/*
|
||||||
|
let mut buf = Vec::with_capacity(256);
|
||||||
|
<TxLegacy as alloy_rlp::Encodable>::encode(&self.0, &mut buf);
|
||||||
|
borsh::BorshSerialize::serialize(&buf, writer)
|
||||||
|
*/
|
||||||
|
self.0.write(writer)?;
|
||||||
|
self.1.write(writer)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
/// The HRAm to use for the Schnorr Solidity library.
|
||||||
pub(crate) struct SignableTransaction(pub(crate) RouterCommand);
|
///
|
||||||
|
/// This will panic if the public key being signed for is not representable within the Schnorr
|
||||||
|
/// Solidity library.
|
||||||
|
#[derive(Clone, Default, Debug)]
|
||||||
|
pub struct EthereumHram;
|
||||||
|
impl Hram<Secp256k1> for EthereumHram {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn hram(
|
||||||
|
R: &<Secp256k1 as Ciphersuite>::G,
|
||||||
|
A: &<Secp256k1 as Ciphersuite>::G,
|
||||||
|
m: &[u8],
|
||||||
|
) -> <Secp256k1 as Ciphersuite>::F {
|
||||||
|
Signature::challenge(*R, &PublicKey::new(*A).unwrap(), m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct ClonableTransctionMachine(RouterCommand, ThresholdKeys<Secp256k1>);
|
pub(crate) struct ClonableTransctionMachine(ThresholdKeys<Secp256k1>, Action);
|
||||||
|
|
||||||
|
type LiteralAlgorithmMachine = AlgorithmMachine<Secp256k1, IetfSchnorr<Secp256k1, EthereumHram>>;
|
||||||
|
type LiteralAlgorithmSignMachine =
|
||||||
|
AlgorithmSignMachine<Secp256k1, IetfSchnorr<Secp256k1, EthereumHram>>;
|
||||||
|
|
||||||
|
pub(crate) struct ActionSignMachine(PublicKey, Action, LiteralAlgorithmSignMachine);
|
||||||
|
|
||||||
|
type LiteralAlgorithmSignatureMachine =
|
||||||
|
AlgorithmSignatureMachine<Secp256k1, IetfSchnorr<Secp256k1, EthereumHram>>;
|
||||||
|
|
||||||
|
pub(crate) struct ActionSignatureMachine(PublicKey, Action, LiteralAlgorithmSignatureMachine);
|
||||||
|
|
||||||
impl PreprocessMachine for ClonableTransctionMachine {
|
impl PreprocessMachine for ClonableTransctionMachine {
|
||||||
type Preprocess = <RouterCommandMachine as PreprocessMachine>::Preprocess;
|
type Preprocess = <LiteralAlgorithmMachine as PreprocessMachine>::Preprocess;
|
||||||
type Signature = <RouterCommandMachine as PreprocessMachine>::Signature;
|
type Signature = Transaction;
|
||||||
type SignMachine = <RouterCommandMachine as PreprocessMachine>::SignMachine;
|
type SignMachine = ActionSignMachine;
|
||||||
|
|
||||||
fn preprocess<R: RngCore + CryptoRng>(
|
fn preprocess<R: RngCore + CryptoRng>(
|
||||||
self,
|
self,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
) -> (Self::SignMachine, Self::Preprocess) {
|
) -> (Self::SignMachine, Self::Preprocess) {
|
||||||
// TODO: Use a proper error here, not an Option
|
let (machine, preprocess) = AlgorithmMachine::new(IetfSchnorr::<Secp256k1, EthereumHram>::ietf(), self.0.clone())
|
||||||
RouterCommandMachine::new(self.1.clone(), self.0.clone()).unwrap().preprocess(rng)
|
.preprocess(rng);
|
||||||
|
(ActionSignMachine(PublicKey::new(self.0.group_key()).expect("signing with non-representable key"), self.1, machine), preprocess)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl scheduler::SignableTransaction for SignableTransaction {
|
impl SignMachine<Transaction> for ActionSignMachine {
|
||||||
|
type Params = <LiteralAlgorithmSignMachine as SignMachine<
|
||||||
|
<LiteralAlgorithmMachine as PreprocessMachine>::Signature,
|
||||||
|
>>::Params;
|
||||||
|
type Keys = <LiteralAlgorithmSignMachine as SignMachine<
|
||||||
|
<LiteralAlgorithmMachine as PreprocessMachine>::Signature,
|
||||||
|
>>::Keys;
|
||||||
|
type Preprocess = <LiteralAlgorithmSignMachine as SignMachine<
|
||||||
|
<LiteralAlgorithmMachine as PreprocessMachine>::Signature,
|
||||||
|
>>::Preprocess;
|
||||||
|
type SignatureShare = <LiteralAlgorithmSignMachine as SignMachine<
|
||||||
|
<LiteralAlgorithmMachine as PreprocessMachine>::Signature,
|
||||||
|
>>::SignatureShare;
|
||||||
|
type SignatureMachine = ActionSignatureMachine;
|
||||||
|
|
||||||
|
fn cache(self) -> CachedPreprocess {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
fn from_cache(
|
||||||
|
params: Self::Params,
|
||||||
|
keys: Self::Keys,
|
||||||
|
cache: CachedPreprocess,
|
||||||
|
) -> (Self, Self::Preprocess) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_preprocess<R: io::Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess> {
|
||||||
|
self.2.read_preprocess(reader)
|
||||||
|
}
|
||||||
|
fn sign(
|
||||||
|
self,
|
||||||
|
commitments: HashMap<Participant, Self::Preprocess>,
|
||||||
|
msg: &[u8],
|
||||||
|
) -> Result<(Self::SignatureMachine, Self::SignatureShare), FrostError> {
|
||||||
|
assert!(msg.is_empty());
|
||||||
|
self
|
||||||
|
.2
|
||||||
|
.sign(commitments, &self.1.message())
|
||||||
|
.map(|(machine, shares)| (ActionSignatureMachine(self.0, self.1, machine), shares))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignatureMachine<Transaction> for ActionSignatureMachine {
|
||||||
|
type SignatureShare = <LiteralAlgorithmSignatureMachine as SignatureMachine<
|
||||||
|
<LiteralAlgorithmMachine as PreprocessMachine>::Signature,
|
||||||
|
>>::SignatureShare;
|
||||||
|
|
||||||
|
fn read_share<R: io::Read>(&self, reader: &mut R) -> io::Result<Self::SignatureShare> {
|
||||||
|
self.2.read_share(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete(
|
||||||
|
self,
|
||||||
|
shares: HashMap<Participant, Self::SignatureShare>,
|
||||||
|
) -> Result<Transaction, FrostError> {
|
||||||
|
/*
|
||||||
|
match self.1 {
|
||||||
|
Action::SetKey { chain_id: _, nonce: _, key } => self.0.update_serai_key(key, signature),
|
||||||
|
Action::Batch { chain_id: _, nonce: _, outs } => self.0.execute(outs, signature),
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
self.2.complete(shares).map(|signature| {
|
||||||
|
let s = signature.s;
|
||||||
|
let c = Signature::challenge(signature.R, &self.0, &self.1.message());
|
||||||
|
Transaction(self.1, Signature::new(c, s))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignableTransaction for Action {
|
||||||
type Transaction = Transaction;
|
type Transaction = Transaction;
|
||||||
type Ciphersuite = Secp256k1;
|
type Ciphersuite = Secp256k1;
|
||||||
type PreprocessMachine = ClonableTransctionMachine;
|
type PreprocessMachine = ClonableTransctionMachine;
|
||||||
|
|
||||||
fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||||
RouterCommand::read(reader).map(Self)
|
let mut kind = [0xff];
|
||||||
|
reader.read_exact(&mut kind)?;
|
||||||
|
if kind[0] >= 2 {
|
||||||
|
Err(io::Error::other("unrecognized Action type"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut chain_id = [0; 32];
|
||||||
|
reader.read_exact(&mut chain_id)?;
|
||||||
|
let chain_id = U256::from_le_bytes(chain_id);
|
||||||
|
|
||||||
|
let mut nonce = [0; 8];
|
||||||
|
reader.read_exact(&mut nonce)?;
|
||||||
|
let nonce = u64::from_le_bytes(nonce);
|
||||||
|
|
||||||
|
Ok(match kind[0] {
|
||||||
|
0 => {
|
||||||
|
let mut key = [0; 32];
|
||||||
|
reader.read_exact(&mut key)?;
|
||||||
|
let key =
|
||||||
|
PublicKey::from_eth_repr(key).ok_or_else(|| io::Error::other("invalid key in Action"))?;
|
||||||
|
|
||||||
|
Action::SetKey { chain_id, nonce, key }
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let mut outs_len = [0; 4];
|
||||||
|
reader.read_exact(&mut outs_len)?;
|
||||||
|
let outs_len = usize::try_from(u32::from_le_bytes(outs_len)).unwrap();
|
||||||
|
|
||||||
|
let mut outs = vec![];
|
||||||
|
for _ in 0 .. outs_len {
|
||||||
|
let address = borsh::from_reader(reader)?;
|
||||||
|
let coin = Coin::read(reader)?;
|
||||||
|
|
||||||
|
let mut amount = [0; 32];
|
||||||
|
reader.read_exact(&mut amount)?;
|
||||||
|
let amount = U256::from_le_bytes(amount);
|
||||||
|
|
||||||
|
outs.push((address, (coin, amount)));
|
||||||
|
}
|
||||||
|
Action::Batch { chain_id, nonce, outs }
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||||
self.0.write(writer)
|
match self {
|
||||||
|
Self::SetKey { chain_id, nonce, key } => {
|
||||||
|
writer.write_all(&[0])?;
|
||||||
|
writer.write_all(&chain_id.as_le_bytes())?;
|
||||||
|
writer.write_all(&nonce.to_le_bytes())?;
|
||||||
|
writer.write_all(&key.eth_repr())
|
||||||
|
}
|
||||||
|
Self::Batch { chain_id, nonce, outs } => {
|
||||||
|
writer.write_all(&[1])?;
|
||||||
|
writer.write_all(&chain_id.as_le_bytes())?;
|
||||||
|
writer.write_all(&nonce.to_le_bytes())?;
|
||||||
|
writer.write_all(&u32::try_from(outs.len()).unwrap().to_le_bytes())?;
|
||||||
|
for (address, (coin, amount)) in outs {
|
||||||
|
borsh::BorshSerialize::serialize(address, writer)?;
|
||||||
|
coin.write(writer)?;
|
||||||
|
writer.write_all(&amount.as_le_bytes())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> [u8; 32] {
|
fn id(&self) -> [u8; 32] {
|
||||||
let mut res = [0; 32];
|
let mut res = [0; 32];
|
||||||
// TODO: Add getter for the nonce
|
res[.. 8].copy_from_slice(&self.nonce().to_le_bytes());
|
||||||
match self.0 {
|
|
||||||
RouterCommand::UpdateSeraiKey { nonce, .. } | RouterCommand::Execute { nonce, .. } => {
|
|
||||||
res[.. 8].copy_from_slice(&nonce.as_le_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(self, keys: ThresholdKeys<Self::Ciphersuite>) -> Self::PreprocessMachine {
|
fn sign(self, keys: ThresholdKeys<Self::Ciphersuite>) -> Self::PreprocessMachine {
|
||||||
ClonableTransctionMachine(self.0, keys)
|
ClonableTransctionMachine(keys, self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
||||||
pub(crate) struct Eventuality(pub(crate) PublicKey, pub(crate) RouterCommand);
|
|
||||||
|
|
||||||
impl primitives::Eventuality for Eventuality {
|
impl primitives::Eventuality for Eventuality {
|
||||||
type OutputId = OutputId;
|
type OutputId = OutputId;
|
||||||
|
|
||||||
fn id(&self) -> [u8; 32] {
|
fn id(&self) -> [u8; 32] {
|
||||||
let mut res = [0; 32];
|
let mut res = [0; 32];
|
||||||
match self.1 {
|
res[.. 8].copy_from_slice(&self.0.nonce().to_le_bytes());
|
||||||
RouterCommand::UpdateSeraiKey { nonce, .. } | RouterCommand::Execute { nonce, .. } => {
|
|
||||||
res[.. 8].copy_from_slice(&nonce.as_le_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookup(&self) -> Vec<u8> {
|
fn lookup(&self) -> Vec<u8> {
|
||||||
match self.1 {
|
self.0.nonce().to_le_bytes().to_vec()
|
||||||
RouterCommand::UpdateSeraiKey { nonce, .. } | RouterCommand::Execute { nonce, .. } => {
|
|
||||||
nonce.as_le_bytes().to_vec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn singular_spent_output(&self) -> Option<Self::OutputId> {
|
fn singular_spent_output(&self) -> Option<Self::OutputId> {
|
||||||
|
@ -103,15 +306,9 @@ impl primitives::Eventuality for Eventuality {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||||
let point = Secp256k1::read_G(reader)?;
|
Executed::read(reader).map(Self)
|
||||||
let command = RouterCommand::read(reader)?;
|
|
||||||
Ok(Eventuality(
|
|
||||||
PublicKey::new(point).ok_or(io::Error::other("unusable key within Eventuality"))?,
|
|
||||||
command,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||||
writer.write_all(self.0.point().to_bytes().as_slice())?;
|
self.0.write(writer)
|
||||||
self.1.write(writer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
use core::future::Future;
|
use core::future::Future;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ethereum_serai::{
|
use alloy_rpc_types_eth::{BlockTransactionsKind, BlockNumberOrTag};
|
||||||
alloy::{
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
rpc_types::{BlockTransactionsKind, BlockNumberOrTag},
|
use alloy_provider::{Provider, RootProvider};
|
||||||
simple_request_transport::SimpleRequest,
|
|
||||||
provider::{Provider, RootProvider},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use serai_client::primitives::{NetworkId, Coin, Amount};
|
use serai_client::primitives::{NetworkId, Coin, Amount};
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,23 @@
|
||||||
use serai_client::primitives::{NetworkId, Balance};
|
use alloy_core::primitives::U256;
|
||||||
|
|
||||||
use ethereum_serai::{alloy::primitives::U256, router::PublicKey, machine::*};
|
use serai_client::primitives::{NetworkId, Coin, Balance};
|
||||||
|
|
||||||
use primitives::Payment;
|
use primitives::Payment;
|
||||||
use scanner::{KeyFor, AddressFor, EventualityFor};
|
use scanner::{KeyFor, AddressFor, EventualityFor};
|
||||||
|
|
||||||
use crate::{
|
use ethereum_schnorr::PublicKey;
|
||||||
transaction::{SignableTransaction, Eventuality},
|
use ethereum_router::Coin as EthereumCoin;
|
||||||
rpc::Rpc,
|
|
||||||
};
|
use crate::{DAI, transaction::Action, rpc::Rpc};
|
||||||
|
|
||||||
|
fn coin_to_ethereum_coin(coin: Coin) -> EthereumCoin {
|
||||||
|
assert_eq!(coin.network(), NetworkId::Ethereum);
|
||||||
|
match coin {
|
||||||
|
Coin::Ether => EthereumCoin::Ether,
|
||||||
|
Coin::Dai => EthereumCoin::Erc20(DAI),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn balance_to_ethereum_amount(balance: Balance) -> U256 {
|
fn balance_to_ethereum_amount(balance: Balance) -> U256 {
|
||||||
assert_eq!(balance.coin.network(), NetworkId::Ethereum);
|
assert_eq!(balance.coin.network(), NetworkId::Ethereum);
|
||||||
|
@ -24,7 +33,7 @@ pub(crate) struct SmartContract {
|
||||||
pub(crate) chain_id: U256,
|
pub(crate) chain_id: U256,
|
||||||
}
|
}
|
||||||
impl smart_contract_scheduler::SmartContract<Rpc> for SmartContract {
|
impl smart_contract_scheduler::SmartContract<Rpc> for SmartContract {
|
||||||
type SignableTransaction = SignableTransaction;
|
type SignableTransaction = Action;
|
||||||
|
|
||||||
fn rotate(
|
fn rotate(
|
||||||
&self,
|
&self,
|
||||||
|
@ -32,16 +41,14 @@ impl smart_contract_scheduler::SmartContract<Rpc> for SmartContract {
|
||||||
retiring_key: KeyFor<Rpc>,
|
retiring_key: KeyFor<Rpc>,
|
||||||
new_key: KeyFor<Rpc>,
|
new_key: KeyFor<Rpc>,
|
||||||
) -> (Self::SignableTransaction, EventualityFor<Rpc>) {
|
) -> (Self::SignableTransaction, EventualityFor<Rpc>) {
|
||||||
let command = RouterCommand::UpdateSeraiKey {
|
let action = Action::SetKey {
|
||||||
chain_id: self.chain_id,
|
chain_id: self.chain_id,
|
||||||
nonce: U256::try_from(nonce).unwrap(),
|
nonce,
|
||||||
key: PublicKey::new(new_key).expect("rotating to an invald key"),
|
key: PublicKey::new(new_key).expect("rotating to an invald key"),
|
||||||
};
|
};
|
||||||
(
|
(action.clone(), action.eventuality())
|
||||||
SignableTransaction(command.clone()),
|
|
||||||
Eventuality(PublicKey::new(retiring_key).expect("retiring an invalid key"), command),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fulfill(
|
fn fulfill(
|
||||||
&self,
|
&self,
|
||||||
nonce: u64,
|
nonce: u64,
|
||||||
|
@ -50,40 +57,20 @@ impl smart_contract_scheduler::SmartContract<Rpc> for SmartContract {
|
||||||
) -> Vec<(Self::SignableTransaction, EventualityFor<Rpc>)> {
|
) -> Vec<(Self::SignableTransaction, EventualityFor<Rpc>)> {
|
||||||
let mut outs = Vec::with_capacity(payments.len());
|
let mut outs = Vec::with_capacity(payments.len());
|
||||||
for payment in payments {
|
for payment in payments {
|
||||||
outs.push(OutInstruction {
|
outs.push((
|
||||||
target: if let Some(data) = payment.data() {
|
payment.address().clone(),
|
||||||
// This introspects the Call serialization format, expecting the first 20 bytes to
|
(
|
||||||
// be the address
|
coin_to_ethereum_coin(payment.balance().coin),
|
||||||
// This avoids wasting the 20-bytes allocated within address
|
balance_to_ethereum_amount(payment.balance()),
|
||||||
let full_data = [<[u8; 20]>::from(*payment.address()).as_slice(), data].concat();
|
),
|
||||||
let mut reader = full_data.as_slice();
|
));
|
||||||
|
|
||||||
let mut calls = vec![];
|
|
||||||
while !reader.is_empty() {
|
|
||||||
let Ok(call) = Call::read(&mut reader) else { break };
|
|
||||||
calls.push(call);
|
|
||||||
}
|
|
||||||
// The above must have executed at least once since reader contains the address
|
|
||||||
assert_eq!(calls[0].to, <[u8; 20]>::from(*payment.address()));
|
|
||||||
|
|
||||||
OutInstructionTarget::Calls(calls)
|
|
||||||
} else {
|
|
||||||
OutInstructionTarget::Direct((*payment.address()).into())
|
|
||||||
},
|
|
||||||
value: { balance_to_ethereum_amount(payment.balance()) },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let command = RouterCommand::Execute {
|
// TODO: Per-batch gas limit
|
||||||
chain_id: self.chain_id,
|
// TODO: Create several batches
|
||||||
nonce: U256::try_from(nonce).unwrap(),
|
let action = Action::Batch { chain_id: self.chain_id, nonce, outs };
|
||||||
outs,
|
|
||||||
};
|
|
||||||
|
|
||||||
vec![(
|
vec![(action.clone(), action.eventuality())]
|
||||||
SignableTransaction(command.clone()),
|
|
||||||
Eventuality(PublicKey::new(key).expect("fulfilling payments with an invalid key"), command),
|
|
||||||
)]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue