Skip to main content

tempo_validator_config/
lib.rs

1//! Provides [`ValidatorConfig`] which builds the keccak256 message preimages expected by
2//! `addValidator` and `rotateValidator`, and verifies ed25519 signatures over them.
3
4#![cfg_attr(not(test), warn(unused_crate_dependencies))]
5
6use std::net::{IpAddr, SocketAddr};
7
8use alloy_primitives::{Address, B256, Keccak256};
9use commonware_codec::DecodeExt as _;
10use commonware_cryptography::{Verifier as _, ed25519};
11use tempo_contracts::precompiles::VALIDATOR_CONFIG_V2_ADDRESS;
12use tempo_precompiles::validator_config_v2::{VALIDATOR_NS_ADD, VALIDATOR_NS_ROTATE};
13
14#[derive(Debug, thiserror::Error)]
15pub enum ValidatorConfigError {
16    #[error("invalid ed25519 public key")]
17    InvalidPublicKey,
18    #[error("invalid ed25519 signature")]
19    InvalidSignature,
20    #[error("signature verification failed")]
21    SignatureVerificationFailed,
22}
23pub struct ValidatorConfig {
24    pub chain_id: u64,
25    pub validator_address: Address,
26    pub public_key: B256,
27    pub ingress: SocketAddr,
28    pub egress: IpAddr,
29}
30
31impl ValidatorConfig {
32    /// Returns the base hasher with the common preimage fields:
33    /// `chainId || contractAddr || validatorAddr || len(ingress) || ingress || len(egress) || egress`.
34    fn base_hasher(&self) -> Keccak256 {
35        let ingress = self.ingress.to_string();
36        let ingress_length = u8::try_from(ingress.len()).expect("ingress length must fit u8");
37
38        let egress = self.egress.to_string();
39        let egress_length = u8::try_from(egress.len()).expect("egress length must fit u8");
40
41        let mut hasher = Keccak256::new();
42        hasher.update(self.chain_id.to_be_bytes());
43        hasher.update(VALIDATOR_CONFIG_V2_ADDRESS.as_slice());
44        hasher.update(self.validator_address.as_slice());
45        hasher.update([ingress_length]);
46        hasher.update(ingress.as_bytes());
47        hasher.update([egress_length]);
48        hasher.update(egress.as_bytes());
49        hasher
50    }
51
52    /// Returns the message hash for `addValidator`
53    pub fn add_validator_message_hash(&self, fee_recipient: Address) -> B256 {
54        let mut hasher = self.base_hasher();
55        hasher.update(fee_recipient.as_slice());
56        hasher.finalize()
57    }
58
59    /// Returns the message hash for `rotateValidator`
60    pub fn rotate_validator_message_hash(&self) -> B256 {
61        self.base_hasher().finalize()
62    }
63
64    /// Verifies that `signature` is a valid `addValidator` ed25519 signature.
65    pub fn check_add_validator_signature(
66        &self,
67        fee_recipient: Address,
68        signature: &[u8],
69    ) -> Result<(), ValidatorConfigError> {
70        let message = self.add_validator_message_hash(fee_recipient);
71        self.check_signature(VALIDATOR_NS_ADD, &message, signature)
72    }
73
74    /// Verifies that `signature` is a valid `rotateValidator` ed25519 signature.
75    pub fn check_rotate_validator_signature(
76        &self,
77        signature: &[u8],
78    ) -> Result<(), ValidatorConfigError> {
79        let message = self.rotate_validator_message_hash();
80        self.check_signature(VALIDATOR_NS_ROTATE, &message, signature)
81    }
82
83    fn check_signature(
84        &self,
85        namespace: &[u8],
86        message: &B256,
87        signature: &[u8],
88    ) -> Result<(), ValidatorConfigError> {
89        let public_key = ed25519::PublicKey::decode(self.public_key.as_slice())
90            .map_err(|_| ValidatorConfigError::InvalidPublicKey)?;
91        let sig = ed25519::Signature::decode(signature)
92            .map_err(|_| ValidatorConfigError::InvalidSignature)?;
93        if !public_key.verify(namespace, message.as_slice(), &sig) {
94            return Err(ValidatorConfigError::SignatureVerificationFailed);
95        }
96        Ok(())
97    }
98}