Skip to content

Add signHash for raw secp256k1 signing (needed for EIP-7702) #155

@ggonzalez94

Description

@ggonzalez94

TL;DR: There's no clean way to sign EIP-7702 authorizations at the moment. The only workaround is leverage the fact that signTransaction happens to work because it doesn't validate the input format. Add a signHash method and optionally a convenience signAuthorization.



  OWS currently has three EVM signing functions, each with a specific prefix/encoding:

  ┌─────────────────┬─────────────────────────────────────────────────────────────────┐
  │    Function     │                     What it does internally                     │
  ├─────────────────┼─────────────────────────────────────────────────────────────────┤
  │ signMessage     │ Wraps with EIP-191 prefix, then                                 │
  │                 │ secp256k1.sign(keccak256(\x19Ethereum...\n + msg))              │
  ├─────────────────┼─────────────────────────────────────────────────────────────────┤
  │ signTypedData   │ Wraps with EIP-712 prefix, then                                 │
  │                 │ secp256k1.sign(keccak256(\x19\x01 + domain + struct))           │
  ├─────────────────┼─────────────────────────────────────────────────────────────────┤
  │ signTransaction │ Hashes raw tx bytes, then secp256k1.sign(keccak256(txBytes))    │
  └─────────────────┴─────────────────────────────────────────────────────────────────┘

  EIP-7702 (account delegation) needs to sign an authorization tuple:
  secp256k1.sign(keccak256(0x05 || rlp([chainId, address, nonce]))). This is a raw hash
  signing operation — no EIP-191 prefix, no EIP-712 domain, not a transaction.

  There's no OWS function for this. You can't use signMessage because it always adds the
  EIP-191 prefix, which changes the hash and produces an invalid signature.

  Current workaround

  signTransaction happens to work because it doesn't validate the input format — it just
  hashes whatever bytes you give it and signs. So passing the authorization payload (0x05
   || rlp([...])) as a "transaction" produces a valid authorization signature. But this
  is a hack that relies on implementation details and could break if OWS adds transaction
   format validation.

  What EIP-7702 needs vs what OWS offers today

  ┌───────────────┬────────────────────────────────┬────────────────────┬───────────┐
  │   Operation   │    Signing primitive needed    │    OWS function    │  Status   │
  ├───────────────┼────────────────────────────────┼────────────────────┼───────────┤
  │ Authorization │ secp256k1.sign(keccak256(0x05  │ None (workaround:  │ Gap       │
  │  signing      │ || rlp([...])))                │ signTransaction)   │           │
  ├───────────────┼────────────────────────────────┼────────────────────┼───────────┤
  │ Type-4 tx     │ secp256k1.sign(keccak256(0x04  │ signTransaction    │ Supported │
  │ signing       │ || rlp([...])))                │                    │           │
  ├───────────────┼────────────────────────────────┼────────────────────┼───────────┤
  │ Paymaster     │ EIP-712 typed data             │ signTypedData      │ Supported(after #148  is merged) │
  │ permits       │                                │                    │           │
  ├───────────────┼────────────────────────────────┼────────────────────┼───────────┤
  │ UserOp hash   │ EIP-712 typed data             │ signTypedData      │ Supported(after #148 is merged) │
  │ signing       │                                │                    │           │
  └───────────────┴────────────────────────────────┴────────────────────┴───────────┘

  Proposed solution

  Add a signHash function that does raw secp256k1.sign() on a 32-byte hash without any
  prefix or transformation:

  function signHash(
    wallet: string,
    chain: string,
    hash: string,        // 32-byte hex-encoded hash
    passphrase?: string, // passphrase or API key token
    index?: number,
    vaultPath?: string
  ): SignResult;

  This would support EIP-7702 today and any future EIP that introduces a new signing
  prefix (the Ethereum ecosystem has done this for EIP-191, EIP-712, EIP-7702, and may do
   it again). Each time a new prefix is added, signHash just works — the caller computes
  the hash however the EIP specifies and passes it in.

  Why not just add signAuthorization?

  A dedicated signAuthorization(chainId, address, nonce) function would also work but
  only solves EIP-7702. A generic signHash is more future-proof and covers any raw
  signing need. **Both would be useful — signAuthorization as a convenience, signHash as
  the primitive.**

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions