kona_sources/signer/remote/
handler.rs

1use std::sync::Arc;
2
3use alloy_primitives::{Address, B256, ChainId, SignatureError};
4use alloy_rpc_client::RpcClient;
5use alloy_signer::Signature;
6use notify::RecommendedWatcher;
7use op_alloy_rpc_types_engine::PayloadHash;
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10use tokio::sync::RwLock;
11
12/// Request parameters for signing a block payload
13#[derive(Debug, Serialize)]
14#[serde(rename_all = "camelCase")]
15struct BlockPayloadArgs {
16    domain: B256,
17    chain_id: u64,
18    payload_hash: B256,
19    sender_address: Address,
20}
21
22/// Response from the remote signer
23#[derive(Debug, Deserialize)]
24struct SignResponse {
25    signature: String,
26}
27
28/// Remote signer that communicates with an external signing service via JSON-RPC
29#[derive(Debug)]
30pub struct RemoteSignerHandler {
31    /// The JSON-RPC client.
32    pub(super) client: Arc<RwLock<RpcClient>>,
33    /// The address of the signer.
34    pub(super) address: Address,
35    /// The watcher handle for certificate watching.
36    pub(super) watcher_handle: Option<RecommendedWatcher>,
37}
38
39/// Errors that can occur when using the remote signer
40#[derive(Debug, Error)]
41pub enum RemoteSignerError {
42    /// JSON-RPC transport error
43    #[error("JSON-RPC transport error: {0}")]
44    SigningRPCError(#[from] alloy_transport::TransportError),
45    /// JSON serialization error
46    #[error("JSON serialization error: {0}")]
47    JsonError(#[from] serde_json::Error),
48    /// Failed to ping signer
49    #[error("Failed to ping signer: {0}")]
50    PingError(alloy_transport::TransportError),
51    /// Invalid signature hex encoding
52    #[error("Invalid signature hex encoding: {0}")]
53    InvalidSignatureHex(alloy_primitives::hex::FromHexError),
54    /// Invalid signature length
55    #[error("Invalid signature length, expected 65 bytes, got {0}")]
56    InvalidSignatureLength(usize),
57    /// Signature error
58    #[error("Signature error: {0}")]
59    SignatureError(#[from] SignatureError),
60    /// Invalid address
61    #[error(
62        "Unsafe block signer address does not match remote signer address: {unsafe_block_signer} != {remote_signer}"
63    )]
64    InvalidAddress {
65        /// The unsafe block signer address.
66        unsafe_block_signer: Address,
67        /// The remote signer address.
68        remote_signer: Address,
69    },
70}
71
72impl RemoteSignerHandler {
73    /// Returns true if certificate watching is enabled
74    pub const fn is_certificate_watching_enabled(&self) -> bool {
75        self.watcher_handle.is_some()
76    }
77
78    /// Signs a block payload hash using the remote signer via JSON-RPC
79    pub async fn sign_block_v1(
80        &self,
81        payload_hash: PayloadHash,
82        chain_id: ChainId,
83        sender_address: Address,
84    ) -> Result<Signature, RemoteSignerError> {
85        if sender_address != self.address {
86            return Err(RemoteSignerError::InvalidAddress {
87                unsafe_block_signer: sender_address,
88                remote_signer: self.address,
89            });
90        }
91
92        let params = BlockPayloadArgs {
93            // For v1 payloads, the domain is always zero
94            domain: B256::ZERO,
95            chain_id,
96            payload_hash: payload_hash.0,
97            sender_address,
98        };
99
100        // Make JSON-RPC call to the custom method
101        let response: SignResponse = {
102            self.client
103                .read()
104                .await
105                .request("opsigner_signBlockPayload", &params)
106                .await
107                .map_err(RemoteSignerError::SigningRPCError)?
108        };
109
110        // Parse the hex signature
111        let signature_bytes =
112            alloy_primitives::hex::decode(response.signature.trim_start_matches("0x"))
113                .map_err(RemoteSignerError::InvalidSignatureHex)?;
114
115        if signature_bytes.len() != 65 {
116            return Err(RemoteSignerError::InvalidSignatureLength(signature_bytes.len()));
117        }
118
119        let signature = Signature::from_raw(signature_bytes.as_slice())
120            .map_err(RemoteSignerError::SignatureError)?;
121
122        Ok(signature)
123    }
124}