Skip to content

refactor(op-rbuilder): Replace Custom Signer with Alloy's PrivateKeySigner #390

@refcell

Description

@refcell

Summary

The op-rbuilder crate contains a custom Signer struct that manually implements transaction and message signing using the secp256k1 crate. This functionality is already provided by alloy-signer-local's PrivateKeySigner, which the codebase already depends on and uses elsewhere.

Consolidating on alloy-signer-local would reduce maintenance burden, leverage well-tested upstream code, and provide additional features like EIP-712 typed data signing, HSM support, and mnemonic/keystore support.

Current Implementation

The custom Signer wraps secp256k1 directly:

pub struct Signer {
    pub address: Address,
    pub pubkey: PublicKey,    // secp256k1::PublicKey
    pub secret: SecretKey,    // secp256k1::SecretKey
}

Key methods:

Alloy Provides Equivalent Functionality

Custom Signer Method Alloy Equivalent
sign_message(B256) SignerSync::sign_hash_sync(&hash)
sign_tx(OpTypedTransaction) TxSignerSync::sign_transaction_sync(&mut tx)
try_from_secret(B256) PrivateKeySigner::from_bytes(&fixed_bytes)
random() PrivateKeySigner::random()
.address Signer::address(&self)

Evidence: Codebase Already Uses Both

The codebase already has a hybrid approach where custom Signer is converted to PrivateKeySigner:

  1. tx_manager.rs:71-73 — Converts custom Signer to PrivateKeySigner for provider integration:
let wallet = PrivateKeySigner::from_bytes(&self.builder_signer.secret.secret_bytes().into())
  1. accounts.rs:58-64 — Uses PrivateKeySigner directly for test accounts

  2. test_utils.rs — Uses TxSignerSync::sign_transaction_sync() from alloy

Files Using Custom Signer

Benefits of Migration

  1. Reduced maintenance — ~100 lines of custom crypto code removed
  2. Well-tested upstreamalloy-signer-local is battle-tested
  3. Additional features available:
    • EIP-712 typed data signing (useful for flashtestations permits)
    • Async signing support (Signer trait)
    • Chain ID management
    • HSM support via yubihsm feature
    • Mnemonic support via mnemonic feature
    • Keystore support via keystore feature
  4. secp256k1 backend available — If exact parity with current implementation is needed, alloy-signer-local provides Secp256k1Signer via the secp256k1 feature flag

Migration Notes

The one unique aspect of the custom Signer is sign_tx() returning Recovered<OpTransactionSigned> directly. With alloy, this becomes:

// Current custom implementation (one call)
let recovered = signer.sign_tx(tx)?;

// With alloy (explicit steps, or wrap in helper)
let signature = signer.sign_hash_sync(&tx.signature_hash())?;
let signed = OpTransactionSigned::new_unhashed(tx, signature);
let recovered = Recovered::new_unchecked(signed, signer.address());

A helper trait or function can preserve the ergonomic API if desired.

Proposed Changes

  1. Replace Signer struct with PrivateKeySigner (possibly with a type alias or newtype wrapper for FromStr CLI parsing)
  2. Update sign_message(B256) calls to sign_hash_sync(&hash)
  3. Create a helper for sign_tx() pattern if needed
  4. Remove tx_signer.rs or reduce to just the helper
  5. Update all import sites

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions