Signer changes (#1557)

* Signer and signable

ethereum signer changes

move signers into chain config
(chains.rs)

Undo removal of traits in hyperlane-core/traits/mod.rs

fix chains.rs

validator submit

remove signers from hyperlane core

Announcements and checkpoint

Remove test_output

trait_builder.rs

hyperlane base settings mod

base settings signers

base settings mod

trait builder

multisig

Undo checkpoint syncer change for now

local storage

s3 storage

validator

metadata builder

validator submit

relayer

run locally

configs

config and deployment

fmt

* WIP

* some refactoring of signing

* fmt

* Null signer in config

* Fix after merge
pull/1568/head
Mattie Conover 2 years ago committed by GitHub
parent 1ff35f63e2
commit 65b10f1376
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      rust/agents/relayer/src/msg/metadata_builder.rs
  2. 11
      rust/agents/relayer/src/relayer.rs
  3. 12
      rust/agents/validator/src/submit.rs
  4. 11
      rust/agents/validator/src/validator.rs
  5. 6
      rust/chains/hyperlane-ethereum/src/lib.rs
  6. 146
      rust/chains/hyperlane-ethereum/src/signers.rs
  7. 7
      rust/chains/hyperlane-ethereum/src/trait_builder.rs
  8. 173
      rust/chains/hyperlane-ethereum/tests/signer_output.rs
  9. 25
      rust/config/mainnet2/mainnet2_config.json
  10. 4
      rust/config/test/test_config.json
  11. 25
      rust/config/testnet3/testnet3_config.json
  12. 10
      rust/helm/hyperlane-agent/templates/relayer-external-secret.yaml
  13. 4
      rust/helm/hyperlane-agent/templates/relayer-statefulset.yaml
  14. 8
      rust/helm/hyperlane-agent/values.yaml
  15. 148
      rust/hyperlane-base/src/settings/chains.rs
  16. 18
      rust/hyperlane-base/src/settings/mod.rs
  17. 31
      rust/hyperlane-base/src/settings/signers.rs
  18. 16
      rust/hyperlane-base/src/types/local_storage.rs
  19. 7
      rust/hyperlane-base/src/types/multisig.rs
  20. 4
      rust/hyperlane-base/src/types/s3_storage.rs
  21. 153
      rust/hyperlane-core/src/lib.rs
  22. 177
      rust/hyperlane-core/src/test_output.rs
  23. 2
      rust/hyperlane-core/src/traits/mod.rs
  24. 2
      rust/hyperlane-core/src/traits/multisig_ism.rs
  25. 95
      rust/hyperlane-core/src/traits/signing.rs
  26. 57
      rust/hyperlane-core/src/types/announcement.rs
  27. 60
      rust/hyperlane-core/src/types/checkpoint.rs
  28. 12
      rust/utils/run-locally/src/main.rs
  29. 30
      typescript/infra/src/agents/index.ts
  30. 53
      typescript/infra/src/config/agent.ts
  31. 2
      typescript/infra/src/core/deploy.ts

@ -1,17 +1,17 @@
use std::sync::Arc;
use hyperlane_base::{CachingMailbox, ChainSetup, CoreMetrics, MultisigCheckpointSyncer};
use hyperlane_core::{HyperlaneMessage, Signers};
use hyperlane_core::{Mailbox, MultisigIsm, H256};
use tokio::sync::RwLock;
use tracing::{debug, info, instrument};
use hyperlane_base::{CachingMailbox, ChainSetup, CoreMetrics, MultisigCheckpointSyncer};
use hyperlane_core::HyperlaneMessage;
use hyperlane_core::{Mailbox, MultisigIsm};
use crate::merkle_tree_builder::MerkleTreeBuilder;
#[derive(Debug, Clone)]
pub struct MetadataBuilder {
metrics: Arc<CoreMetrics>,
signer: Option<Signers>,
chain_setup: ChainSetup,
checkpoint_syncer: MultisigCheckpointSyncer,
prover_sync: Arc<RwLock<MerkleTreeBuilder>>,
@ -20,14 +20,12 @@ pub struct MetadataBuilder {
impl MetadataBuilder {
pub fn new(
metrics: Arc<CoreMetrics>,
signer: Option<Signers>,
chain_setup: ChainSetup,
checkpoint_syncer: MultisigCheckpointSyncer,
prover_sync: Arc<RwLock<MerkleTreeBuilder>>,
) -> Self {
MetadataBuilder {
metrics,
signer,
chain_setup,
checkpoint_syncer,
prover_sync,
@ -41,7 +39,11 @@ impl MetadataBuilder {
mailbox: CachingMailbox,
) -> eyre::Result<Option<Vec<u8>>> {
let ism_address = mailbox.recipient_ism(message.recipient).await?;
let multisig_ism = self.build_multisig_ism(ism_address).await?;
let multisig_ism = self
.chain_setup
.build_multisig_ism(ism_address, &self.metrics)
.await?;
let (validators, threshold) = multisig_ism.validators_and_threshold(message).await?;
let highest_known_nonce = self.prover_sync.read().await.count() - 1;
if let Some(checkpoint) = self
@ -91,10 +93,4 @@ impl MetadataBuilder {
Ok(None)
}
}
async fn build_multisig_ism(&self, address: H256) -> eyre::Result<Box<dyn MultisigIsm>> {
self.chain_setup
.build_multisig_ism(self.signer.clone(), &self.metrics, address)
.await
}
}

@ -126,19 +126,20 @@ impl BaseAgent for Relayer {
.core
.settings
.chain_setup(chain.name())
.unwrap_or_else(|_| panic!("No chain setup found for {}", chain.name()));
.unwrap_or_else(|_| panic!("No chain setup found for {}", chain.name()))
.clone();
let txsubmission = chain_setup.txsubmission;
let metadata_builder = MetadataBuilder::new(
self.core.metrics.clone(),
self.core.settings.get_signer(chain.name()).await,
chain_setup.clone(),
chain_setup,
self.multisig_checkpoint_syncer.clone(),
prover_sync.clone(),
);
tasks.push(self.run_destination_mailbox(
mailbox.clone(),
metadata_builder.clone(),
chain_setup.txsubmission,
txsubmission,
self.core.settings.gelato.as_ref(),
gas_payment_enforcer.clone(),
receive_channel,
@ -155,7 +156,7 @@ impl BaseAgent for Relayer {
self.whitelist.clone(),
self.blacklist.clone(),
metrics,
prover_sync.clone(),
prover_sync,
send_channels,
);
tasks.push(self.run_message_processor(message_processor));

@ -7,12 +7,12 @@ use tokio::{task::JoinHandle, time::sleep};
use tracing::{debug, info, info_span, instrument::Instrumented, Instrument};
use hyperlane_base::{CachingMailbox, CheckpointSyncer, CheckpointSyncers, CoreMetrics};
use hyperlane_core::{Announcement, HyperlaneDomain, Mailbox, Signers};
use hyperlane_core::{Announcement, HyperlaneDomain, HyperlaneSigner, HyperlaneSignerExt, Mailbox};
pub(crate) struct ValidatorSubmitter {
interval: u64,
reorg_period: u64,
signer: Arc<Signers>,
signer: Arc<dyn HyperlaneSigner>,
mailbox: CachingMailbox,
checkpoint_syncer: Arc<CheckpointSyncers>,
metrics: ValidatorSubmitterMetrics,
@ -23,7 +23,7 @@ impl ValidatorSubmitter {
interval: u64,
reorg_period: u64,
mailbox: CachingMailbox,
signer: Arc<Signers>,
signer: Arc<dyn HyperlaneSigner>,
checkpoint_syncer: Arc<CheckpointSyncers>,
metrics: ValidatorSubmitterMetrics,
) -> Self {
@ -55,7 +55,7 @@ impl ValidatorSubmitter {
mailbox_domain: self.mailbox.mailbox().domain().id(),
storage_metadata: self.checkpoint_syncer.announcement_metadata(),
};
let signed_announcement = announcement.sign_with(self.signer.as_ref()).await?;
let signed_announcement = self.signer.sign(announcement).await?;
self.checkpoint_syncer
.write_announcement(&signed_announcement)
.await?;
@ -132,7 +132,7 @@ impl ValidatorSubmitter {
.map(|i| i < latest_checkpoint.index)
.unwrap_or(true)
{
let signed_checkpoint = latest_checkpoint.sign_with(self.signer.as_ref()).await?;
let signed_checkpoint = self.signer.sign(latest_checkpoint).await?;
info!(signed_checkpoint = ?signed_checkpoint, signer=?self.signer, "Signed new latest checkpoint");
current_index = Some(latest_checkpoint.index);
@ -142,7 +142,7 @@ impl ValidatorSubmitter {
.await?;
self.metrics
.latest_checkpoint_processed
.set(signed_checkpoint.checkpoint.index as i64);
.set(signed_checkpoint.value.index as i64);
}
sleep(Duration::from_secs(self.interval)).await;

@ -8,7 +8,7 @@ use tracing::instrument::Instrumented;
use hyperlane_base::{
run_all, Agent, BaseAgent, CheckpointSyncers, CoreMetrics, HyperlaneAgentCore,
};
use hyperlane_core::{HyperlaneDomain, Signers};
use hyperlane_core::{HyperlaneDomain, HyperlaneSigner};
use crate::submit::ValidatorSubmitterMetrics;
use crate::{settings::ValidatorSettings, submit::ValidatorSubmitter};
@ -17,7 +17,7 @@ use crate::{settings::ValidatorSettings, submit::ValidatorSubmitter};
#[derive(Debug)]
pub struct Validator {
origin_chain: HyperlaneDomain,
signer: Arc<Signers>,
signer: Arc<dyn HyperlaneSigner>,
reorg_period: u64,
interval: u64,
checkpoint_syncer: Arc<CheckpointSyncers>,
@ -40,7 +40,12 @@ impl BaseAgent for Validator {
where
Self: Sized,
{
let signer = settings.validator.try_into_signer().await?.into();
let signer = settings
.validator
// Intentionally using hyperlane_ethereum for the validator's signer
.build::<hyperlane_ethereum::Signers>()
.await
.map(|validator| Arc::new(validator) as Arc<dyn HyperlaneSigner>)?;
let reorg_period = settings.reorgperiod.parse().expect("invalid uint");
let interval = settings.interval.parse().expect("invalid uint");
let core = settings

@ -14,7 +14,9 @@ use hyperlane_core::*;
pub use retrying::{RetryingProvider, RetryingProviderError};
#[cfg(not(doctest))]
pub use crate::{interchain_gas::*, mailbox::*, multisig_ism::*, provider::*, trait_builder::*};
pub use crate::{
interchain_gas::*, mailbox::*, multisig_ism::*, provider::*, signers::*, trait_builder::*,
};
#[cfg(not(doctest))]
mod tx;
@ -45,6 +47,8 @@ mod contracts;
/// Retrying Provider
mod retrying;
mod signers;
/// Ethereum connection configuration
#[derive(Debug, serde::Deserialize, Clone)]
#[serde(tag = "type", rename_all = "camelCase")]

@ -0,0 +1,146 @@
use async_trait::async_trait;
use ethers::prelude::{Address, Signature};
use ethers::types::transaction::eip2718::TypedTransaction;
use ethers::types::transaction::eip712::Eip712;
use ethers_signers::{AwsSigner, AwsSignerError, LocalWallet, Signer, WalletError};
use hyperlane_core::{HyperlaneSigner, HyperlaneSignerError, H160, H256};
/// Ethereum-supported signer types
#[derive(Debug, Clone)]
pub enum Signers {
/// A wallet instantiated with a locally stored private key
Local(LocalWallet),
/// A signer using a key stored in aws kms
Aws(AwsSigner<'static>),
}
impl From<LocalWallet> for Signers {
fn from(s: LocalWallet) -> Self {
Signers::Local(s)
}
}
impl From<AwsSigner<'static>> for Signers {
fn from(s: AwsSigner<'static>) -> Self {
Signers::Aws(s)
}
}
#[async_trait]
impl Signer for Signers {
type Error = SignersError;
async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
&self,
message: S,
) -> Result<Signature, Self::Error> {
match self {
Signers::Local(signer) => Ok(signer.sign_message(message).await?),
Signers::Aws(signer) => Ok(signer.sign_message(message).await?),
}
}
async fn sign_transaction(&self, message: &TypedTransaction) -> Result<Signature, Self::Error> {
match self {
Signers::Local(signer) => Ok(signer.sign_transaction(message).await?),
Signers::Aws(signer) => Ok(signer.sign_transaction(message).await?),
}
}
async fn sign_typed_data<T: Eip712 + Send + Sync>(
&self,
payload: &T,
) -> Result<Signature, Self::Error> {
match self {
Signers::Local(signer) => Ok(signer.sign_typed_data(payload).await?),
Signers::Aws(signer) => Ok(signer.sign_typed_data(payload).await?),
}
}
fn address(&self) -> Address {
match self {
Signers::Local(signer) => signer.address(),
Signers::Aws(signer) => signer.address(),
}
}
fn chain_id(&self) -> u64 {
match self {
Signers::Local(signer) => signer.chain_id(),
Signers::Aws(signer) => signer.chain_id(),
}
}
fn with_chain_id<T: Into<u64>>(self, chain_id: T) -> Self {
match self {
Signers::Local(signer) => signer.with_chain_id(chain_id).into(),
Signers::Aws(signer) => signer.with_chain_id(chain_id).into(),
}
}
}
#[async_trait]
impl HyperlaneSigner for Signers {
fn eth_address(&self) -> H160 {
Signer::address(self)
}
async fn sign_hash(&self, hash: &H256) -> Result<Signature, HyperlaneSignerError> {
let mut signature = Signer::sign_message(self, hash)
.await
.map_err(|err| HyperlaneSignerError::from(Box::new(err) as Box<_>))?;
signature.v = 28 - (signature.v % 2);
Ok(signature)
}
}
/// Error types for Signers
#[derive(Debug, thiserror::Error)]
pub enum SignersError {
/// AWS Signer Error
#[error("{0}")]
AwsSignerError(#[from] AwsSignerError),
/// Wallet Signer Error
#[error("{0}")]
WalletError(#[from] WalletError),
}
impl From<std::convert::Infallible> for SignersError {
fn from(_error: std::convert::Infallible) -> Self {
panic!("infallible")
}
}
#[cfg(test)]
mod test {
use hyperlane_core::{Checkpoint, HyperlaneSigner, HyperlaneSignerExt, H256};
use crate::signers::Signers;
#[test]
fn it_sign() {
let t = async {
let signer: Signers =
"1111111111111111111111111111111111111111111111111111111111111111"
.parse::<ethers::signers::LocalWallet>()
.unwrap()
.into();
let message = Checkpoint {
mailbox_address: H256::repeat_byte(2),
mailbox_domain: 5,
root: H256::repeat_byte(1),
index: 123,
};
let signed = signer.sign(message).await.expect("!sign");
assert!(signed.signature.v == 27 || signed.signature.v == 28);
signed.verify(signer.eth_address()).expect("!verify");
};
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(t)
}
}

@ -17,8 +17,9 @@ use ethers_prometheus::json_rpc_client::{
use ethers_prometheus::middleware::{
MiddlewareMetrics, PrometheusMiddleware, PrometheusMiddlewareConf,
};
use hyperlane_core::{ChainCommunicationError, ChainResult, ContractLocator, Signers};
use hyperlane_core::{ChainCommunicationError, ChainResult, ContractLocator};
use crate::signers::Signers;
use crate::{ConnectionConf, RetryingProvider};
// This should be whatever the prometheus scrape interval is
@ -56,7 +57,7 @@ pub trait BuildableWithProvider {
/// metrics and a signer as needed.
async fn build_with_connection_conf(
&self,
conn: ConnectionConf,
conn: &ConnectionConf,
locator: &ContractLocator,
signer: Option<Signers>,
rpc_metrics: Option<impl FnOnce() -> JsonRpcClientMetrics + Send>,
@ -110,7 +111,7 @@ pub trait BuildableWithProvider {
.map_err(EthereumProviderConnectionError::from)?;
let http_provider = Http::new_with_client(
url.parse::<Url>()
.map_err(|e| EthereumProviderConnectionError::InvalidUrl(e, url))?,
.map_err(|e| EthereumProviderConnectionError::InvalidUrl(e, url.clone()))?,
http_client,
);
let retrying_http_provider: RetryingProvider<Http> =

@ -0,0 +1,173 @@
//! Test functions that output json files
#![cfg(test)]
use std::{fs::OpenOptions, io::Write, str::FromStr};
use hex::FromHex;
use serde_json::{json, Value};
use ethers::signers::Signer;
use hyperlane_core::{
accumulator::{
merkle::{merkle_root_from_branch, MerkleTree},
TREE_DEPTH,
},
test_utils,
utils::domain_hash,
Checkpoint, HyperlaneMessage, HyperlaneSignerExt, H160, H256,
};
use hyperlane_ethereum::Signers;
/// Output proof to /vector/message.json
pub fn output_message() {
let hyperlane_message = HyperlaneMessage {
nonce: 0,
version: 0,
origin: 1000,
sender: H256::from(H160::from_str("0x1111111111111111111111111111111111111111").unwrap()),
destination: 2000,
recipient: H256::from(
H160::from_str("0x2222222222222222222222222222222222222222").unwrap(),
),
body: Vec::from_hex("1234").unwrap(),
};
let message_json = json!({
"nonce": hyperlane_message.nonce,
"version": hyperlane_message.version,
"origin": hyperlane_message.origin,
"sender": hyperlane_message.sender,
"destination": hyperlane_message.destination,
"recipient": hyperlane_message.recipient,
"body": hyperlane_message.body,
"id": hyperlane_message.id(),
});
let json = json!([message_json]).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(test_utils::find_vector("message.json"))
.expect("Failed to open/create file");
file.write_all(json.as_bytes())
.expect("Failed to write to file");
}
/// Output merkle proof test vectors
pub fn output_merkle_proof() {
let mut tree = MerkleTree::create(&[], TREE_DEPTH);
let index = 1;
// kludge. these are random message ids
tree.push_leaf(
"0xd89959d277019eee21f1c3c270a125964d63b71876880724d287fbb8b8de55f1"
.parse()
.unwrap(),
TREE_DEPTH,
)
.unwrap();
tree.push_leaf(
"0x5068ac60cb6f9c5202bbe8e7a1babdd972133ea3ad37d7e0e753c7e4ddd7ffbd"
.parse()
.unwrap(),
TREE_DEPTH,
)
.unwrap();
let proof = tree.generate_proof(index, TREE_DEPTH);
let proof_json = json!({ "leaf": proof.0, "path": proof.1, "index": index});
let json = json!({ "proof": proof_json, "root": merkle_root_from_branch(proof.0, &proof.1, 32, index)}).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(test_utils::find_vector("proof.json"))
.expect("Failed to open/create file");
file.write_all(json.as_bytes())
.expect("Failed to write to file");
}
/// Outputs domain hash test cases in /vector/domainHash.json
pub fn output_domain_hashes() {
let mailbox = H256::from(H160::from_str("0x2222222222222222222222222222222222222222").unwrap());
let test_cases: Vec<Value> = (1..=3)
.map(|i| {
json!({
"domain": i,
"mailbox": mailbox,
"expectedDomainHash": domain_hash(mailbox, i as u32)
})
})
.collect();
let json = json!(test_cases).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(test_utils::find_vector("domainHash.json"))
.expect("Failed to open/create file");
file.write_all(json.as_bytes())
.expect("Failed to write to file");
}
/// Outputs signed checkpoint test cases in /vector/signedCheckpoint.json
pub fn output_signed_checkpoints() {
let mailbox = H256::from(H160::from_str("0x2222222222222222222222222222222222222222").unwrap());
let t = async {
let signer: Signers = "1111111111111111111111111111111111111111111111111111111111111111"
.parse::<ethers::signers::LocalWallet>()
.unwrap()
.into();
let mut test_cases: Vec<Value> = Vec::new();
// test suite
for i in 1..=3 {
let signed_checkpoint = signer
.sign(Checkpoint {
mailbox_address: mailbox,
mailbox_domain: 1000,
root: H256::repeat_byte(i + 1),
index: i as u32,
})
.await
.expect("!sign_with");
test_cases.push(json!({
"mailbox": signed_checkpoint.value.mailbox_address,
"domain": signed_checkpoint.value.mailbox_domain,
"root": signed_checkpoint.value.root,
"index": signed_checkpoint.value.index,
"signature": signed_checkpoint.signature,
"signer": signer.address(),
}))
}
let json = json!(test_cases).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(test_utils::find_vector("signedCheckpoint.json"))
.expect("Failed to open/create file");
file.write_all(json.as_bytes())
.expect("Failed to write to file");
};
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(t)
}

@ -17,7 +17,8 @@
},
"index": {
"from": "16884144"
}
},
"signer": null
},
"ethereum": {
"name": "ethereum",
@ -35,7 +36,8 @@
},
"index": {
"from": "16271503"
}
},
"signer": null
},
"avalanche": {
"name": "avalanche",
@ -53,7 +55,8 @@
},
"index": {
"from": "24145479"
}
},
"signer": null
},
"polygon": {
"name": "polygon",
@ -71,7 +74,8 @@
},
"index": {
"from": "37313389"
}
},
"signer": null
},
"bsc": {
"name": "bsc",
@ -89,7 +93,8 @@
},
"index": {
"from": "24248084"
}
},
"signer": null
},
"arbitrum": {
"name": "arbitrum",
@ -107,7 +112,8 @@
},
"index": {
"from": "49073182"
}
},
"signer": null
},
"optimism": {
"name": "optimism",
@ -125,7 +131,8 @@
},
"index": {
"from": "55698988"
}
},
"signer": null
},
"moonbeam": {
"name": "moonbeam",
@ -143,10 +150,10 @@
},
"index": {
"from": "2595747"
}
},
"signer": null
}
},
"signers": {},
"db": "db_path",
"tracing": {
"level": "debug",

@ -8,6 +8,7 @@
"mailbox": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
"interchainGasPaymaster": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707"
},
"signer": null,
"protocol": "ethereum",
"finalityBlocks": "0",
"connection": {
@ -25,6 +26,7 @@
"mailbox": "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
"interchainGasPaymaster": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82"
},
"signer": null,
"protocol": "ethereum",
"finalityBlocks": "1",
"connection": {
@ -42,6 +44,7 @@
"mailbox": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44",
"interchainGasPaymaster": "0x59b670e9fA9D0A427751Af201D676719a970857b"
},
"signer": null,
"protocol": "ethereum",
"finalityBlocks": "2",
"connection": {
@ -53,7 +56,6 @@
}
}
},
"signers": {},
"db": "db_path",
"tracing": {
"level": "debug",

@ -17,7 +17,8 @@
},
"index": {
"from": "14863532"
}
},
"signer": null
},
"fuji": {
"name": "fuji",
@ -35,7 +36,8 @@
},
"index": {
"from": "16330615"
}
},
"signer": null
},
"mumbai": {
"name": "mumbai",
@ -53,7 +55,8 @@
},
"index": {
"from": "29390033"
}
},
"signer": null
},
"bsctestnet": {
"name": "bsctestnet",
@ -71,7 +74,8 @@
},
"index": {
"from": "25001629"
}
},
"signer": null
},
"goerli": {
"name": "goerli",
@ -89,7 +93,8 @@
},
"index": {
"from": "8039005"
}
},
"signer": null
},
"moonbasealpha": {
"name": "moonbasealpha",
@ -107,7 +112,8 @@
},
"index": {
"from": "3310405"
}
},
"signer": null
},
"optimismgoerli": {
"name": "optimismgoerli",
@ -125,7 +131,8 @@
},
"index": {
"from": "3055263"
}
},
"signer": null
},
"arbitrumgoerli": {
"name": "arbitrumgoerli",
@ -143,10 +150,10 @@
},
"index": {
"from": "1941997"
}
},
"signer": null
}
},
"signers": {},
"db": "db_path",
"tracing": {
"level": "debug",

@ -21,9 +21,9 @@ spec:
labels:
{{- include "agent-common.labels" . | nindent 10 }}
data:
{{- range .Values.hyperlane.relayer.signers }}
{{- if eq .keyConfig.type "hexKey" }}
HYP_BASE_SIGNERS_{{ .name | upper }}_KEY: {{ printf "'{{ .%s_signer_key | toString }}'" .name }}
{{- range .Values.hyperlane.chains }}
{{- if eq .signer.keyConfig.type "hexKey" }}
HYP_BASE_CHAINS_{{ .name | upper }}_KEY: {{ printf "'{{ .%s_signer_key | toString }}'" .name }}
{{- end }}
{{- end }}
{{- if .Values.hyperlane.relayer.aws }}
@ -34,8 +34,8 @@ spec:
HYP_RELAYER_GASPAYMENTENFORCEMENTPOLICY_COINGECKOAPIKEY: {{ print "'{{ .coingecko_api_key | toString }}'" }}
{{- end }}
data:
{{- range .Values.hyperlane.relayer.signers }}
{{- if eq .keyConfig.type "hexKey" }}
{{- range .Values.hyperlane.chains }}
{{- if eq .signer.keyConfig.type "hexKey" }}
- secretKey: {{ printf "%s_signer_key" .name }}
remoteRef:
key: {{ printf "%s-%s-key-%s-relayer" $.Values.hyperlane.context $.Values.hyperlane.runEnv $.Values.hyperlane.relayer.config.originChainName }}

@ -56,8 +56,8 @@ spec:
name: {{ include "agent-common.fullname" . }}-relayer-secret
env:
{{- include "agent-common.config-env-vars" (dict "config" .Values.hyperlane.relayer.config "agent_name" "relayer") | indent 10 }}
{{- range .Values.hyperlane.relayer.signers }}
{{- include "agent-common.config-env-vars" (dict "config" .keyConfig "agent_name" "base" "key_name_prefix" (printf "SIGNERS_%s_" (.name | upper))) | indent 10 }}
{{- range .Values.hyperlane.chains }}
{{- include "agent-common.config-env-vars" (dict "config" .keyConfig "agent_name" "base" "key_name_prefix" (printf "CHAINS_%s_SIGNER_" (.name | upper))) | indent 10 }}
{{- end }}
{{- if .Values.hyperlane.tracing.uri }}
- name: HYP_BASE_TRACING_JAEGER_NAME

@ -51,6 +51,8 @@ hyperlane:
chains:
- name: 'alfajores'
disabled: false
signer:
# aws:
addresses:
mailbox:
multisigIsm:
@ -101,11 +103,6 @@ hyperlane:
requests:
cpu: 250m
memory: 256Mi
signers:
- name: 'kovan'
# aws:
- name: 'alfajores'
# aws:
config:
originChainName:
multisigCheckpointSyncer:
@ -126,7 +123,6 @@ hyperlane:
requests:
cpu: 250m
memory: 256Mi
signers:
config:
kathy:

@ -1,4 +1,3 @@
use ethers::prelude::Signer;
use eyre::{eyre, Context, Result};
use serde::Deserialize;
@ -7,14 +6,15 @@ use ethers_prometheus::middleware::{
};
use hyperlane_core::{
ContractLocator, HyperlaneAbi, HyperlaneDomain, HyperlaneDomainProtocol, HyperlaneProvider,
InterchainGasPaymaster, InterchainGasPaymasterIndexer, Mailbox, MailboxIndexer, MultisigIsm,
Signers, H256,
HyperlaneSigner, InterchainGasPaymaster, InterchainGasPaymasterIndexer, Mailbox,
MailboxIndexer, MultisigIsm, H256,
};
use hyperlane_ethereum::{
self as h_eth, BuildableWithProvider, EthereumInterchainGasPaymasterAbi, EthereumMailboxAbi,
};
use crate::CoreMetrics;
use crate::settings::signers::BuildableWithSignerConf;
use crate::{CoreMetrics, SignerConf};
/// A connection to _some_ blockchain.
///
@ -111,6 +111,8 @@ pub struct ChainSetup {
pub name: String,
/// Chain domain identifier
pub domain: String,
/// Signer configuration for this chain
pub signer: Option<SignerConf>,
/// Number of blocks until finality
pub finality_blocks: String,
/// Addresses of contracts on the chain
@ -135,21 +137,12 @@ impl ChainSetup {
/// Try to convert the chain settings into an HyperlaneProvider.
pub async fn build_provider(
&self,
signer: Option<Signers>,
metrics: &CoreMetrics,
) -> Result<Box<dyn HyperlaneProvider>> {
let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer);
match &self.chain {
ChainConf::Ethereum(conf) => {
hyperlane_ethereum::HyperlaneProviderBuilder {}
.build_with_connection_conf(
conf.clone(),
&self.locator("0x0000000000000000000000000000000000000000")?,
signer,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), metrics_conf)),
)
let locator = self.locator("0x0000000000000000000000000000000000000000")?;
self.build_ethereum(conf, &locator, metrics, h_eth::HyperlaneProviderBuilder {})
.await
}
@ -159,24 +152,12 @@ impl ChainSetup {
}
/// Try to convert the chain setting into a Mailbox contract
pub async fn build_mailbox(
&self,
signer: Option<Signers>,
metrics: &CoreMetrics,
) -> Result<Box<dyn Mailbox>> {
let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer);
pub async fn build_mailbox(&self, metrics: &CoreMetrics) -> Result<Box<dyn Mailbox>> {
let locator = self.locator(&self.addresses.mailbox)?;
match &self.chain {
ChainConf::Ethereum(conf) => {
hyperlane_ethereum::MailboxBuilder {}
.build_with_connection_conf(
conf.clone(),
&locator,
signer,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), metrics_conf)),
)
self.build_ethereum(conf, &locator, metrics, h_eth::MailboxBuilder {})
.await
}
@ -188,23 +169,19 @@ impl ChainSetup {
/// Try to convert the chain settings into a mailbox indexer
pub async fn build_mailbox_indexer(
&self,
signer: Option<Signers>,
metrics: &CoreMetrics,
) -> Result<Box<dyn MailboxIndexer>> {
let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer);
let locator = self.locator(&self.addresses.mailbox)?;
match &self.chain {
ChainConf::Ethereum(conf) => {
hyperlane_ethereum::MailboxIndexerBuilder {
finality_blocks: self.finality_blocks(),
}
.build_with_connection_conf(
conf.clone(),
self.build_ethereum(
conf,
&locator,
signer,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), metrics_conf)),
metrics,
h_eth::MailboxIndexerBuilder {
finality_blocks: self.finality_blocks(),
},
)
.await
}
@ -218,23 +195,19 @@ impl ChainSetup {
/// contract
pub async fn build_interchain_gas_paymaster(
&self,
signer: Option<Signers>,
metrics: &CoreMetrics,
) -> Result<Box<dyn InterchainGasPaymaster>> {
let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer);
let locator = self.locator(&self.addresses.interchain_gas_paymaster)?;
match &self.chain {
ChainConf::Ethereum(conf) => {
hyperlane_ethereum::InterchainGasPaymasterBuilder {}
.build_with_connection_conf(
conf.clone(),
&locator,
signer,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), metrics_conf)),
)
.await
self.build_ethereum(
conf,
&locator,
metrics,
h_eth::InterchainGasPaymasterBuilder {},
)
.await
}
ChainConf::Fuel => todo!(),
@ -245,24 +218,20 @@ impl ChainSetup {
/// Try to convert the chain settings into a IGP indexer
pub async fn build_interchain_gas_paymaster_indexer(
&self,
signer: Option<Signers>,
metrics: &CoreMetrics,
) -> Result<Box<dyn InterchainGasPaymasterIndexer>> {
let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer);
let locator = self.locator(&self.addresses.interchain_gas_paymaster)?;
match &self.chain {
ChainConf::Ethereum(conf) => {
hyperlane_ethereum::InterchainGasPaymasterIndexerBuilder {
mailbox_address: self.addresses.mailbox.parse()?,
finality_blocks: self.finality_blocks(),
}
.build_with_connection_conf(
conf.clone(),
self.build_ethereum(
conf,
&locator,
signer,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), metrics_conf)),
metrics,
h_eth::InterchainGasPaymasterIndexerBuilder {
mailbox_address: self.addresses.mailbox.parse()?,
finality_blocks: self.finality_blocks(),
},
)
.await
}
@ -275,11 +244,9 @@ impl ChainSetup {
/// Try to convert the chain setting into a Multisig Ism contract
pub async fn build_multisig_ism(
&self,
signer: Option<Signers>,
metrics: &CoreMetrics,
address: H256,
metrics: &CoreMetrics,
) -> Result<Box<dyn MultisigIsm>> {
let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer);
let locator = ContractLocator {
domain: self.domain()?,
address,
@ -287,14 +254,7 @@ impl ChainSetup {
match &self.chain {
ChainConf::Ethereum(conf) => {
hyperlane_ethereum::MultisigIsmBuilder {}
.build_with_connection_conf(
conf.clone(),
&locator,
signer,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), metrics_conf)),
)
self.build_ethereum(conf, &locator, metrics, h_eth::MultisigIsmBuilder {})
.await
}
@ -316,9 +276,25 @@ impl ChainSetup {
.expect("could not parse finality_blocks")
}
/// Get a clone of the metrics conf with correctly configured contract
/// information.
fn metrics_conf(&self, agent_name: &str, signer: &Option<Signers>) -> PrometheusMiddlewareConf {
async fn signer<S: BuildableWithSignerConf>(&self) -> Result<Option<S>> {
if let Some(conf) = &self.signer {
Ok(Some(conf.build::<S>().await?))
} else {
Ok(None)
}
}
async fn ethereum_signer(&self) -> Result<Option<h_eth::Signers>> {
self.signer().await
}
/// Get a clone of the ethereum metrics conf with correctly configured
/// contract information.
fn metrics_conf(
&self,
agent_name: &str,
signer: &Option<impl HyperlaneSigner>,
) -> PrometheusMiddlewareConf {
let mut cfg = self.metrics_conf.clone();
if cfg.chain.is_none() {
@ -329,7 +305,7 @@ impl ChainSetup {
if let Some(signer) = signer {
cfg.wallets
.entry(signer.address())
.entry(signer.eth_address())
.or_insert_with(|| WalletInfo {
name: Some(agent_name.into()),
});
@ -363,4 +339,24 @@ impl ChainSetup {
Ok(ContractLocator { domain, address })
}
async fn build_ethereum<B>(
&self,
conf: &h_eth::ConnectionConf,
locator: &ContractLocator,
metrics: &CoreMetrics,
builder: B,
) -> Result<B::Output>
where
B: BuildableWithProvider + Sync,
{
let signer = self.ethereum_signer().await?;
let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer);
let rpc_metrics = Some(|| metrics.json_rpc_client_metrics());
let middleware_metrics = Some((metrics.provider_metrics(), metrics_conf));
let res = builder
.build_with_connection_conf(conf, locator, signer, rpc_metrics, middleware_metrics)
.await;
Ok(res?)
}
}

@ -86,7 +86,7 @@ pub use chains::{ChainConf, ChainSetup, CoreContractAddresses};
use hyperlane_core::{
db::{HyperlaneDB, DB},
HyperlaneChain, HyperlaneDomain, HyperlaneProvider, InterchainGasPaymaster,
InterchainGasPaymasterIndexer, Mailbox, MailboxIndexer, MultisigIsm, Signers, H256,
InterchainGasPaymasterIndexer, Mailbox, MailboxIndexer, MultisigIsm, H256,
};
pub use signers::SignerConf;
@ -134,8 +134,6 @@ static KMS_CLIENT: OnceCell<KmsClient> = OnceCell::new();
pub struct Settings {
/// Configuration for contracts on each chain
pub chains: HashMap<String, ChainSetup>,
/// Transaction signers
pub signers: HashMap<String, SignerConf>,
/// Gelato config
pub gelato: Option<GelatoConf>,
/// Database connection string (might be a path on the fs or a remote db)
@ -173,11 +171,6 @@ impl Settings {
})
}
/// Try to get a signer instance by name
pub async fn get_signer(&self, name: &str) -> Option<Signers> {
self.signers.get(name)?.try_into_signer().await.ok()
}
/// Try to get a map of chain name -> mailbox contract
pub async fn build_all_mailboxes(
&self,
@ -254,12 +247,11 @@ impl Settings {
pub async fn build_multisig_ism(
&self,
chain_name: &str,
metrics: &CoreMetrics,
address: H256,
metrics: &CoreMetrics,
) -> eyre::Result<Box<dyn MultisigIsm>> {
let signer = self.get_signer(chain_name).await;
let setup = self.chain_setup(chain_name)?;
setup.build_multisig_ism(signer, metrics, address).await
setup.build_multisig_ism(address, metrics).await
}
/// Try to get the chain setup for the provided chain name
@ -286,7 +278,6 @@ impl Settings {
fn clone(&self) -> Self {
Self {
chains: self.chains.clone(),
signers: self.signers.clone(),
gelato: self.gelato.clone(),
db: self.db.clone(),
metrics: self.metrics.clone(),
@ -304,9 +295,8 @@ macro_rules! delegate_fn {
chain_name: &str,
metrics: &CoreMetrics,
) -> eyre::Result<Box<$ret>> {
let signer = self.get_signer(chain_name).await;
let setup = self.chain_setup(chain_name)?;
setup.$name(signer, metrics).await
setup.$name(metrics).await
}
};
}

@ -1,13 +1,15 @@
use crate::settings::KMS_CLIENT;
use async_trait::async_trait;
use ethers::prelude::AwsSigner;
use eyre::{bail, Report};
use hyperlane_core::utils::HexString;
use hyperlane_core::Signers;
use rusoto_core::credential::EnvironmentProvider;
use rusoto_core::HttpClient;
use rusoto_kms::KmsClient;
use tracing::instrument;
use hyperlane_core::utils::HexString;
use crate::settings::KMS_CLIENT;
/// Ethereum signer types
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
@ -39,9 +41,22 @@ impl Default for SignerConf {
impl SignerConf {
/// Try to convert the ethereum signer to a local wallet
#[instrument(err)]
pub async fn try_into_signer(&self) -> Result<Signers, Report> {
match self {
SignerConf::HexKey { key } => Ok(Signers::Local(key.as_ref().parse()?)),
pub async fn build<S: BuildableWithSignerConf>(&self) -> Result<S, Report> {
S::build(self).await
}
}
/// Builder trait for signers
#[async_trait]
pub trait BuildableWithSignerConf: Sized {
async fn build(conf: &SignerConf) -> Result<Self, Report>;
}
#[async_trait]
impl BuildableWithSignerConf for hyperlane_ethereum::Signers {
async fn build(conf: &SignerConf) -> Result<Self, Report> {
Ok(match conf {
SignerConf::HexKey { key } => hyperlane_ethereum::Signers::Local(key.as_ref().parse()?),
SignerConf::Aws { id, region } => {
let client = KMS_CLIENT.get_or_init(|| {
KmsClient::new_with_client(
@ -54,9 +69,9 @@ impl SignerConf {
});
let signer = AwsSigner::new(client, id, 0).await?;
Ok(Signers::Aws(signer))
hyperlane_ethereum::Signers::Aws(signer)
}
SignerConf::Node => bail!("Node signer"),
}
})
}
}

@ -1,9 +1,9 @@
use hyperlane_core::{SignedAnnouncement, SignedCheckpoint};
use async_trait::async_trait;
use eyre::Result;
use prometheus::IntGauge;
use hyperlane_core::{SignedAnnouncement, SignedCheckpoint};
use crate::traits::CheckpointSyncer;
#[derive(Debug, Clone)]
@ -59,6 +59,7 @@ impl CheckpointSyncer for LocalStorage {
_ => Ok(None),
}
}
async fn fetch_checkpoint(&self, index: u32) -> Result<Option<SignedCheckpoint>> {
match tokio::fs::read(self.checkpoint_file_path(index)).await {
Ok(data) => {
@ -68,30 +69,33 @@ impl CheckpointSyncer for LocalStorage {
_ => Ok(None),
}
}
async fn write_checkpoint(&self, signed_checkpoint: &SignedCheckpoint) -> Result<()> {
let serialized_checkpoint = serde_json::to_string_pretty(signed_checkpoint)?;
tokio::fs::write(
self.checkpoint_file_path(signed_checkpoint.checkpoint.index),
self.checkpoint_file_path(signed_checkpoint.value.index),
&serialized_checkpoint,
)
.await?;
match self.latest_index().await? {
Some(current_latest_index) => {
if current_latest_index < signed_checkpoint.checkpoint.index {
self.write_index(signed_checkpoint.checkpoint.index).await?
if current_latest_index < signed_checkpoint.value.index {
self.write_index(signed_checkpoint.value.index).await?
}
}
None => self.write_index(signed_checkpoint.checkpoint.index).await?,
None => self.write_index(signed_checkpoint.value.index).await?,
}
Ok(())
}
async fn write_announcement(&self, signed_announcement: &SignedAnnouncement) -> Result<()> {
let serialized_announcement = serde_json::to_string_pretty(signed_announcement)?;
tokio::fs::write(self.announcement_file_path(), &serialized_announcement).await?;
Ok(())
}
fn announcement_metadata(&self) -> String {
let mut metadata: String = "file://".to_owned();
metadata.push_str(self.announcement_file_path().as_ref());

@ -100,7 +100,7 @@ impl MultisigCheckpointSyncer {
if let Ok(Some(signed_checkpoint)) = checkpoint_syncer.fetch_checkpoint(index).await
{
// If the signed checkpoint is for a different index, ignore it
if signed_checkpoint.checkpoint.index != index {
if signed_checkpoint.value.index != index {
continue;
}
// Ensure that the signature is actually by the validator
@ -114,10 +114,7 @@ impl MultisigCheckpointSyncer {
signer,
signed_checkpoint,
};
let root = signed_checkpoint_with_signer
.signed_checkpoint
.checkpoint
.root;
let root = signed_checkpoint_with_signer.signed_checkpoint.value.root;
let signature_count = match signed_checkpoints_per_root.entry(root) {
Entry::Occupied(mut entry) => {

@ -169,14 +169,14 @@ impl CheckpointSyncer for S3Storage {
async fn write_checkpoint(&self, signed_checkpoint: &SignedCheckpoint) -> Result<()> {
let serialized_checkpoint = serde_json::to_string_pretty(signed_checkpoint)?;
self.write_to_bucket(
S3Storage::checkpoint_key(signed_checkpoint.checkpoint.index),
S3Storage::checkpoint_key(signed_checkpoint.value.index),
&serialized_checkpoint,
)
.await?;
self.write_to_bucket(
S3Storage::index_key(),
&signed_checkpoint.checkpoint.index.to_string(),
&signed_checkpoint.value.index.to_string(),
)
.await?;
Ok(())

@ -5,17 +5,6 @@
#![forbid(unsafe_code)]
#![forbid(where_clauses_object_safety)]
use async_trait::async_trait;
use ethers::{
core::types::{
transaction::{eip2718::TypedTransaction, eip712::Eip712},
Address as EthAddress, Signature,
},
prelude::AwsSigner,
signers::{AwsSignerError, LocalWallet, Signer},
};
use ethers_signers::WalletError;
pub use chain::*;
pub use error::{ChainCommunicationError, ChainResult, HyperlaneProtocolError};
pub use identifiers::HyperlaneIdentifier;
@ -38,10 +27,6 @@ pub mod db;
/// Core hyperlane system data structures
mod types;
/// Test functions that output json files for Solidity tests
#[cfg(feature = "output")]
pub mod test_output;
mod chain;
mod error;
@ -57,141 +42,3 @@ pub enum ListValidity {
/// Invalid list. Contains gaps, but builds upon the correct prior element.
ContainsGaps,
}
/// Error types for Signers
#[derive(Debug, thiserror::Error)]
pub enum SignersError {
/// AWS Signer Error
#[error("{0}")]
AwsSignerError(#[from] AwsSignerError),
/// Wallet Signer Error
#[error("{0}")]
WalletError(#[from] WalletError),
}
impl From<std::convert::Infallible> for SignersError {
fn from(_error: std::convert::Infallible) -> Self {
panic!("infallible")
}
}
/// Ethereum-supported signer types
#[derive(Debug, Clone)]
pub enum Signers {
/// A wallet instantiated with a locally stored private key
Local(LocalWallet),
/// A signer using a key stored in aws kms
Aws(AwsSigner<'static>),
}
impl From<LocalWallet> for Signers {
fn from(s: LocalWallet) -> Self {
Signers::Local(s)
}
}
impl From<AwsSigner<'static>> for Signers {
fn from(s: AwsSigner<'static>) -> Self {
Signers::Aws(s)
}
}
#[async_trait]
impl Signer for Signers {
type Error = SignersError;
fn with_chain_id<T: Into<u64>>(self, chain_id: T) -> Self {
match self {
Signers::Local(signer) => signer.with_chain_id(chain_id).into(),
Signers::Aws(signer) => signer.with_chain_id(chain_id).into(),
}
}
async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
&self,
message: S,
) -> Result<Signature, Self::Error> {
match self {
Signers::Local(signer) => Ok(signer.sign_message(message).await?),
Signers::Aws(signer) => Ok(signer.sign_message(message).await?),
}
}
async fn sign_transaction(&self, message: &TypedTransaction) -> Result<Signature, Self::Error> {
match self {
Signers::Local(signer) => Ok(signer.sign_transaction(message).await?),
Signers::Aws(signer) => Ok(signer.sign_transaction(message).await?),
}
}
fn address(&self) -> EthAddress {
match self {
Signers::Local(signer) => signer.address(),
Signers::Aws(signer) => signer.address(),
}
}
fn chain_id(&self) -> u64 {
match self {
Signers::Local(signer) => signer.chain_id(),
Signers::Aws(signer) => signer.chain_id(),
}
}
async fn sign_typed_data<T: Eip712 + Send + Sync>(
&self,
payload: &T,
) -> Result<Signature, Self::Error> {
match self {
Signers::Local(signer) => Ok(signer.sign_typed_data(payload).await?),
Signers::Aws(signer) => Ok(signer.sign_typed_data(payload).await?),
}
}
}
#[async_trait]
trait SignerExt: Signer {
async fn sign_message_without_eip_155<S: Send + Sync + AsRef<[u8]>>(
&self,
message: S,
) -> Result<Signature, <Self as Signer>::Error> {
let mut signature = self.sign_message(message).await?;
signature.v = 28 - (signature.v % 2);
Ok(signature)
}
}
impl<T> SignerExt for T where T: Signer {}
#[cfg(test)]
mod test {
use super::*;
use crate::H256;
#[test]
fn it_sign() {
let t = async {
let signer: ethers::signers::LocalWallet =
"1111111111111111111111111111111111111111111111111111111111111111"
.parse()
.unwrap();
let message = Checkpoint {
mailbox_address: H256::repeat_byte(2),
mailbox_domain: 5,
root: H256::repeat_byte(1),
index: 123,
};
let signed = message.sign_with(&signer).await.expect("!sign_with");
assert!(signed.signature.v == 27 || signed.signature.v == 28);
signed.verify(signer.address()).expect("!verify");
};
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(t)
}
}

@ -1,177 +0,0 @@
/// Test functions that output json files
#[cfg(feature = "output")]
pub mod output_functions {
use std::{fs::OpenOptions, io::Write, str::FromStr};
use ethers::signers::Signer;
use hex::FromHex;
use serde_json::{json, Value};
use crate::{
accumulator::{
merkle::{merkle_root_from_branch, MerkleTree},
TREE_DEPTH,
},
test_utils::find_vector,
utils::domain_hash,
Checkpoint, HyperlaneMessage, H160, H256,
};
/// Output proof to /vector/message.json
pub fn output_message() {
let hyperlane_message = HyperlaneMessage {
nonce: 0,
version: 0,
origin: 1000,
sender: H256::from(
H160::from_str("0x1111111111111111111111111111111111111111").unwrap(),
),
destination: 2000,
recipient: H256::from(
H160::from_str("0x2222222222222222222222222222222222222222").unwrap(),
),
body: Vec::from_hex("1234").unwrap(),
};
let message_json = json!({
"nonce": hyperlane_message.nonce,
"version": hyperlane_message.version,
"origin": hyperlane_message.origin,
"sender": hyperlane_message.sender,
"destination": hyperlane_message.destination,
"recipient": hyperlane_message.recipient,
"body": hyperlane_message.body,
"id": hyperlane_message.id(),
});
let json = json!([message_json]).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(find_vector("message.json"))
.expect("Failed to open/create file");
file.write_all(json.as_bytes())
.expect("Failed to write to file");
}
/// Output merkle proof test vectors
pub fn output_merkle_proof() {
let mut tree = MerkleTree::create(&[], TREE_DEPTH);
let index = 1;
// kludge. these are random message ids
tree.push_leaf(
"0xd89959d277019eee21f1c3c270a125964d63b71876880724d287fbb8b8de55f1"
.parse()
.unwrap(),
TREE_DEPTH,
)
.unwrap();
tree.push_leaf(
"0x5068ac60cb6f9c5202bbe8e7a1babdd972133ea3ad37d7e0e753c7e4ddd7ffbd"
.parse()
.unwrap(),
TREE_DEPTH,
)
.unwrap();
let proof = tree.generate_proof(index, TREE_DEPTH);
let proof_json = json!({ "leaf": proof.0, "path": proof.1, "index": index});
let json = json!({ "proof": proof_json, "root": merkle_root_from_branch(proof.0, &proof.1, 32, index)}).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(find_vector("proof.json"))
.expect("Failed to open/create file");
file.write_all(json.as_bytes())
.expect("Failed to write to file");
}
/// Outputs domain hash test cases in /vector/domainHash.json
pub fn output_domain_hashes() {
let mailbox =
H256::from(H160::from_str("0x2222222222222222222222222222222222222222").unwrap());
let test_cases: Vec<Value> = (1..=3)
.map(|i| {
json!({
"domain": i,
"mailbox": mailbox,
"expectedDomainHash": domain_hash(mailbox, i as u32)
})
})
.collect();
let json = json!(test_cases).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(find_vector("domainHash.json"))
.expect("Failed to open/create file");
file.write_all(json.as_bytes())
.expect("Failed to write to file");
}
/// Outputs signed checkpoint test cases in /vector/signedCheckpoint.json
pub fn output_signed_checkpoints() {
let mailbox =
H256::from(H160::from_str("0x2222222222222222222222222222222222222222").unwrap());
let t = async {
let signer: ethers::signers::LocalWallet =
"1111111111111111111111111111111111111111111111111111111111111111"
.parse()
.unwrap();
let mut test_cases: Vec<Value> = Vec::new();
// test suite
for i in 1..=3 {
let signed_checkpoint = Checkpoint {
mailbox_address: mailbox,
mailbox_domain: 1000,
root: H256::repeat_byte(i + 1),
index: i as u32,
}
.sign_with(&signer)
.await
.expect("!sign_with");
test_cases.push(json!({
"mailbox": signed_checkpoint.checkpoint.mailbox_address,
"domain": signed_checkpoint.checkpoint.mailbox_domain,
"root": signed_checkpoint.checkpoint.root,
"index": signed_checkpoint.checkpoint.index,
"signature": signed_checkpoint.signature,
"signer": signer.address(),
}))
}
let json = json!(test_cases).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(find_vector("signedCheckpoint.json"))
.expect("Failed to open/create file");
file.write_all(json.as_bytes())
.expect("Failed to write to file");
};
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(t)
}
}

@ -5,6 +5,7 @@ pub use interchain_gas::*;
pub use mailbox::*;
pub use multisig_ism::*;
pub use provider::*;
pub use signing::*;
mod cursor;
mod encode;
@ -13,6 +14,7 @@ mod interchain_gas;
mod mailbox;
mod multisig_ism;
mod provider;
mod signing;
/// The result of a transaction
#[derive(Debug, Clone, Copy)]

@ -11,7 +11,7 @@ use crate::{
/// Interface for the MultisigIsm chain contract. Allows abstraction over
/// different chains
#[async_trait]
#[auto_impl(Box, Arc)]
#[auto_impl(&, Box, Arc)]
pub trait MultisigIsm: HyperlaneContract + Send + Sync + Debug {
/// Returns the validator and threshold needed to verify message
async fn validators_and_threshold(

@ -0,0 +1,95 @@
use std::fmt::Debug;
use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers::prelude::{Address, Signature};
use ethers::utils::hash_message;
use serde::{Deserialize, Serialize};
use crate::{HyperlaneProtocolError, H160, H256};
/// An error incurred by a signer
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub struct HyperlaneSignerError(#[from] Box<dyn std::error::Error + Send + Sync>);
/// A hyperlane signer for use by the validators. Currently signers will always
/// use ethereum wallets.
#[async_trait]
#[auto_impl(&, Box, Arc)]
pub trait HyperlaneSigner: Send + Sync + Debug {
/// The signer's address
fn eth_address(&self) -> H160;
/// Sign a hyperlane checkpoint hash. This must be a signature without eip
/// 155.
async fn sign_hash(&self, hash: &H256) -> Result<Signature, HyperlaneSignerError>;
}
/// Auto-implemented extension trait for HyperlaneSigner.
#[async_trait]
pub trait HyperlaneSignerExt {
/// Sign a `Signable` value
async fn sign<T: Signable + Send>(
&self,
value: T,
) -> Result<SignedType<T>, HyperlaneSignerError>;
/// Check whether a message was signed by a specific address.
fn verify<T: Signable>(&self, signed: &SignedType<T>) -> Result<(), HyperlaneProtocolError>;
}
#[async_trait]
impl<S: HyperlaneSigner> HyperlaneSignerExt for S {
async fn sign<T: Signable + Send>(
&self,
value: T,
) -> Result<SignedType<T>, HyperlaneSignerError> {
let signing_hash = value.signing_hash();
let signature = self.sign_hash(&signing_hash).await?;
Ok(SignedType { value, signature })
}
fn verify<T: Signable>(&self, signed: &SignedType<T>) -> Result<(), HyperlaneProtocolError> {
signed.verify(self.eth_address())
}
}
/// A type that can be signed. The signature will be of a hash of select
/// contents defined by `signing_hash`.
#[async_trait]
pub trait Signable: Sized {
/// A hash of the contents.
/// The EIP-191 compliant version of this hash is signed by validators.
fn signing_hash(&self) -> H256;
/// EIP-191 compliant hash of the signing hash.
fn eth_signed_message_hash(&self) -> H256 {
hash_message(self.signing_hash())
}
}
/// A signed type. Contains the original value and the signature.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SignedType<T: Signable> {
/// The value which was signed
pub value: T,
/// The signature for the value
pub signature: Signature,
}
impl<T: Signable> SignedType<T> {
/// Recover the Ethereum address of the signer
pub fn recover(&self) -> Result<Address, HyperlaneProtocolError> {
Ok(self
.signature
.recover(self.value.eth_signed_message_hash())?)
}
/// Check whether a message was signed by a specific address
pub fn verify(&self, signer: Address) -> Result<(), HyperlaneProtocolError> {
Ok(self
.signature
.verify(self.value.eth_signed_message_hash(), signer)?)
}
}

@ -1,12 +1,8 @@
use ethers::{
prelude::{Address, Signature},
utils::hash_message,
};
use ethers_signers::Signer;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use sha3::{Digest, Keccak256};
use crate::{utils::domain_hash, HyperlaneProtocolError, SignerExt, H256};
use crate::{utils::domain_hash, Signable, SignedType, H256};
/// An Hyperlane checkpoint
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
@ -29,10 +25,9 @@ impl std::fmt::Display for Announcement {
}
}
impl Announcement {
/// A hash of the checkpoint contents.
/// The EIP-191 compliant version of this hash is signed by validators.
pub fn signing_hash(&self) -> H256 {
#[async_trait]
impl Signable for Announcement {
fn signing_hash(&self) -> H256 {
// sign:
// domain_hash(mailbox_address, mailbox_domain) || metadata
H256::from_slice(
@ -43,45 +38,7 @@ impl Announcement {
.as_slice(),
)
}
/// EIP-191 compliant hash of the signing hash of the checkpoint.
pub fn eth_signed_message_hash(&self) -> H256 {
hash_message(self.signing_hash())
}
/// Sign an checkpoint using the specified signer
pub async fn sign_with<S: Signer>(self, signer: &S) -> Result<SignedAnnouncement, S::Error> {
let signature = signer
.sign_message_without_eip_155(self.signing_hash())
.await?;
Ok(SignedAnnouncement {
announcement: self,
signature,
})
}
}
/// A Signed Hyperlane checkpoint
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SignedAnnouncement {
/// The announcement
pub announcement: Announcement,
/// The signature
pub signature: Signature,
}
impl SignedAnnouncement {
/// Recover the Ethereum address of the signer
pub fn recover(&self) -> Result<Address, HyperlaneProtocolError> {
Ok(self
.signature
.recover(self.announcement.eth_signed_message_hash())?)
}
/// Check whether a message was signed by a specific address
pub fn verify(&self, signer: Address) -> Result<(), HyperlaneProtocolError> {
Ok(self
.signature
.verify(self.announcement.eth_signed_message_hash(), signer)?)
}
}
/// An announcement that has been signed.
pub type SignedAnnouncement = SignedType<Announcement>;

@ -1,12 +1,9 @@
use ethers::{
prelude::{Address, Signature},
utils::hash_message,
};
use ethers_signers::Signer;
use async_trait::async_trait;
use ethers::prelude::{Address, Signature};
use serde::{Deserialize, Serialize};
use sha3::{Digest, Keccak256};
use crate::{utils::domain_hash, HyperlaneProtocolError, SignerExt, H256};
use crate::{utils::domain_hash, Signable, SignedType, H256};
/// An Hyperlane checkpoint
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
@ -31,10 +28,11 @@ impl std::fmt::Display for Checkpoint {
}
}
impl Checkpoint {
#[async_trait]
impl Signable for Checkpoint {
/// A hash of the checkpoint contents.
/// The EIP-191 compliant version of this hash is signed by validators.
pub fn signing_hash(&self) -> H256 {
fn signing_hash(&self) -> H256 {
// sign:
// domain_hash(mailbox_address, mailbox_domain) || root || index (as u32)
H256::from_slice(
@ -46,48 +44,10 @@ impl Checkpoint {
.as_slice(),
)
}
/// EIP-191 compliant hash of the signing hash of the checkpoint.
pub fn eth_signed_message_hash(&self) -> H256 {
hash_message(self.signing_hash())
}
/// Sign an checkpoint using the specified signer
pub async fn sign_with<S: Signer>(self, signer: &S) -> Result<SignedCheckpoint, S::Error> {
let signature = signer
.sign_message_without_eip_155(self.signing_hash())
.await?;
Ok(SignedCheckpoint {
checkpoint: self,
signature,
})
}
}
/// A Signed Hyperlane checkpoint
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SignedCheckpoint {
/// The checkpoint
pub checkpoint: Checkpoint,
/// The signature
pub signature: Signature,
}
impl SignedCheckpoint {
/// Recover the Ethereum address of the signer
pub fn recover(&self) -> Result<Address, HyperlaneProtocolError> {
Ok(self
.signature
.recover(self.checkpoint.eth_signed_message_hash())?)
}
/// Check whether a message was signed by a specific address
pub fn verify(&self, signer: Address) -> Result<(), HyperlaneProtocolError> {
Ok(self
.signature
.verify(self.checkpoint.eth_signed_message_hash(), signer)?)
}
}
/// A checkpoint that has been signed.
pub type SignedCheckpoint = SignedType<Checkpoint>;
/// An individual signed checkpoint with the recovered signer
#[derive(Clone, Debug)]
@ -138,10 +98,10 @@ impl TryFrom<&Vec<SignedCheckpointWithSigner>> for MultisigSignedCheckpoint {
}
// Get the first checkpoint and ensure all other signed checkpoints are for
// the same checkpoint
let checkpoint = signed_checkpoints[0].signed_checkpoint.checkpoint;
let checkpoint = signed_checkpoints[0].signed_checkpoint.value;
if !signed_checkpoints
.iter()
.all(|c| checkpoint == c.signed_checkpoint.checkpoint)
.all(|c| checkpoint == c.signed_checkpoint.value)
{
return Err(MultisigSignedCheckpointError::InconsistentCheckpoints());
}

@ -152,12 +152,12 @@ fn main() -> ExitCode {
"HYP_BASE_TRACING_FMT" => "pretty",
"HYP_BASE_TRACING_LEVEL" => "info",
"HYP_BASE_DB" => relayer_db.to_str().unwrap(),
"HYP_BASE_SIGNERS_TEST1_KEY" => "8166f546bab6da521a8369cab06c5d2b9e46670292d85c875ee9ec20e84ffb61",
"HYP_BASE_SIGNERS_TEST1_TYPE" => "hexKey",
"HYP_BASE_SIGNERS_TEST2_KEY" => "f214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897",
"HYP_BASE_SIGNERS_TEST2_TYPE" => "hexKey",
"HYP_BASE_SIGNERS_TEST3_KEY" => "701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82",
"HYP_BASE_SIGNERS_TEST3_TYPE" => "hexKey",
"HYP_BASE_CHAINS_TEST1_SIGNER_KEY" => "8166f546bab6da521a8369cab06c5d2b9e46670292d85c875ee9ec20e84ffb61",
"HYP_BASE_CHAINS_TEST1_SIGNER_TYPE" => "hexKey",
"HYP_BASE_CHAINS_TEST2_SIGNER_KEY" => "f214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897",
"HYP_BASE_CHAINS_TEST2_SIGNER_TYPE" => "hexKey",
"HYP_BASE_CHAINS_TEST3_SIGNER_KEY" => "701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82",
"HYP_BASE_CHAINS_TEST3_SIGNER_TYPE" => "hexKey",
"HYP_RELAYER_GASPAYMENTENFORCEMENTPOLICY_TYPE" => "none",
"HYP_RELAYER_ORIGINCHAINNAME" => "test1",
"HYP_RELAYER_DESTINATIONCHAINNAMES" => "test2,test3",

@ -55,6 +55,8 @@ async function helmValuesForChain<Chain extends ChainName>(
};
}
const signers = await chainAgentConfig.signers();
return {
image: {
repository: agentConfig.docker.repo,
@ -66,16 +68,15 @@ async function helmValuesForChain<Chain extends ChainName>(
baseConfig: `${agentConfig.runEnv}_config.json`,
aws: !!agentConfig.aws,
gelatoApiKeyRequired,
chains: agentConfig.environmentChainNames.map((envChainName) => {
return {
name: envChainName,
disabled: !agentConfig.contextChainNames.includes(envChainName),
txsubmission: {
type: chainAgentConfig.transactionSubmissionType(envChainName),
},
connection: baseConnectionConfig,
};
}),
chains: agentConfig.environmentChainNames.map((envChainName) => ({
name: envChainName,
disabled: !agentConfig.contextChainNames.includes(envChainName),
signer: signers[envChainName],
txsubmission: {
type: chainAgentConfig.transactionSubmissionType(envChainName),
},
connection: baseConnectionConfig,
})),
validator: {
enabled: chainAgentConfig.validatorEnabled,
configs: await chainAgentConfig.validatorConfigs(),
@ -83,7 +84,6 @@ async function helmValuesForChain<Chain extends ChainName>(
relayer: {
enabled: chainAgentConfig.relayerEnabled,
aws: await chainAgentConfig.relayerRequiresAwsCredentials(),
signers: await chainAgentConfig.relayerSigners(),
config: chainAgentConfig.relayerConfig,
},
},
@ -142,11 +142,13 @@ export async function getAgentEnvVars<Chain extends ChainName>(
if (role === KEY_ROLE_ENUM.Relayer) {
chainNames.forEach((name) => {
envVars.push(
`HYP_BASE_SIGNERS_${name.toUpperCase()}_KEY=${utils.strip0x(
`HYP_BASE_CHAINS_${name.toUpperCase()}_SIGNER_KEY=${utils.strip0x(
gcpKeys[keyId].privateKey,
)}`,
);
envVars.push(`HYP_BASE_SIGNERS_${name.toUpperCase()}_TYPE=hexKey`);
envVars.push(
`HYP_BASE_CHAINS_${name.toUpperCase()}_SIGNER_TYPE=hexKey`,
);
});
} else if (role === KEY_ROLE_ENUM.Validator) {
const privateKey = gcpKeys[keyId].privateKey;
@ -201,7 +203,7 @@ export async function getAgentEnvVars<Chain extends ChainName>(
configEnvVars(
key.keyConfig,
'BASE',
`SIGNERS_${chainName.toUpperCase()}_`,
`CHAINS_${chainName.toUpperCase()}_SIGNER_`,
),
);
});

@ -256,6 +256,7 @@ export type RustCoreAddresses = {
export type RustChainSetup = {
name: ChainName;
domain: string;
signer?: RustSigner | null;
finalityBlocks: string;
addresses: RustCoreAddresses;
protocol: 'ethereum' | 'fuel';
@ -268,8 +269,6 @@ export type RustConfig<Chain extends ChainName> = {
chains: Partial<ChainMap<Chain, RustChainSetup>>;
// TODO: Separate DBs for each chain (fold into RustChainSetup)
db: string;
// TODO: Fold this into RustChainSetup
signers?: Partial<ChainMap<Chain, RustSigner>>;
tracing: {
level: string;
fmt: 'json';
@ -295,11 +294,28 @@ export class ChainAgentConfig<Chain extends ChainName> {
};
}
signers(role: KEY_ROLE_ENUM) {
return this.agentConfig.contextChainNames.map((name) => ({
name,
keyConfig: this.keyConfig(role),
}));
// Get the signer configuration for each chain by the chain name.
async signers(): Promise<Record<string, KeyConfig>> {
if (!this.awsKeys) {
Object.fromEntries(
this.agentConfig.contextChainNames.map((name) => [
name,
this.keyConfig(KEY_ROLE_ENUM.Relayer),
]),
);
}
const awsUser = new AgentAwsUser(
this.agentConfig.environment,
this.agentConfig.context,
KEY_ROLE_ENUM.Relayer,
this.agentConfig.aws!.region,
this.chainName,
);
await awsUser.createIfNotExists();
const key = await awsUser.createKeyIfNotExists(this.agentConfig);
return Object.fromEntries(
this.agentConfig.contextChainNames.map((name) => [name, key.keyConfig]),
);
}
async validatorConfigs(): Promise<Array<ValidatorConfig> | undefined> {
@ -389,29 +405,6 @@ export class ChainAgentConfig<Chain extends ChainName> {
return false;
}
async relayerSigners() {
if (!this.relayerEnabled) {
return undefined;
}
if (!this.awsKeys) {
return this.signers(KEY_ROLE_ENUM.Relayer);
}
const awsUser = new AgentAwsUser(
this.agentConfig.environment,
this.agentConfig.context,
KEY_ROLE_ENUM.Relayer,
this.agentConfig.aws!.region,
this.chainName,
);
await awsUser.createIfNotExists();
const key = await awsUser.createKeyIfNotExists(this.agentConfig);
return this.agentConfig.contextChainNames.map((name) => ({
name,
keyConfig: key.keyConfig,
}));
}
get relayerConfig(): RelayerConfig | undefined {
if (!this.relayerEnabled) {
return undefined;

@ -74,7 +74,6 @@ export class HyperlaneCoreInfraDeployer<
const rustConfig: RustConfig<Chain> = {
environment: this.environment,
chains: {},
signers: {},
db: 'db_path',
tracing: {
level: 'debug',
@ -101,6 +100,7 @@ export class HyperlaneCoreInfraDeployer<
mailbox: contracts.mailbox.contract.address,
interchainGasPaymaster: contracts.interchainGasPaymaster.address,
},
signer: null,
protocol: 'ethereum',
finalityBlocks: metadata.blocks.reorgPeriod.toString(),
connection: {

Loading…
Cancel
Save