> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livepeer.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Remote Signer

> How the remote signer architecture separates Ethereum payment signing from media routing in the Livepeer gateway, enabling off-chain gateway clients.

export const CenteredContainer = ({children, maxWidth = "800px", padding = "0", preset = "default", width = "", minWidth = "", marginRight = "", marginBottom = "", textAlign = "", style = {}, className = "", ...rest}) => {
  const presets = {
    default: {},
    fitContent: {
      width: "fit-content",
      minWidth: "fit-content"
    },
    readable70: {
      width: "70%",
      minWidth: "fit-content"
    },
    readable80: {
      width: "80%",
      minWidth: "fit-content"
    },
    readable90: {
      width: "90%"
    },
    wide900: {
      maxWidth: "900px"
    }
  };
  const presetStyle = presets[preset] || presets.default;
  return <div className={className} style={{
    maxWidth: presetStyle.maxWidth || maxWidth,
    margin: "0 auto",
    padding: padding,
    ...presetStyle.width ? {
      width: presetStyle.width
    } : {},
    ...presetStyle.minWidth ? {
      minWidth: presetStyle.minWidth
    } : {},
    ...width ? {
      width
    } : {},
    ...minWidth ? {
      minWidth
    } : {},
    ...marginRight ? {
      marginRight
    } : {},
    ...marginBottom ? {
      marginBottom
    } : {},
    ...textAlign ? {
      textAlign
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

export const LinkArrow = ({href, label, description, newline = true, borderColor, className = '', style = {}, ...rest}) => {
  const linkArrowStyle = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: "var(--lp-spacing-1)",
    width: 'fit-content',
    ...borderColor && ({
      borderColor
    })
  };
  return <span className={className} style={style} {...rest}>
      {newline && <br />}
      <span style={linkArrowStyle}>
        <a href={href} target="_blank" rel="noopener noreferrer">
          {label}
        </a>
        <Icon icon="arrow-up-right" size={14} color="var(--lp-color-accent)" />
      </span>
      {description && description}
      {description && <div style={{
    height: "var(--lp-spacing-3)"
  }} />}
    </span>;
};

<CenteredContainer preset="readable90">
  <Tip>The remote signer takes on the Gateway's Ethereum responsibilities (ticket generation, bookkeeping, and signing) so that Gateway implementations in Python, the browser, or mobile do not need any Ethereum dependency.</Tip>
</CenteredContainer>

***

The Livepeer Gateway is responsible for two distinct concerns: media routing (receiving video, submitting segments to Orchestrators, returning transcoded output) and Ethereum payment operations (PM bookkeeping, ticket generation, signing, ETH balance management). Historically these were coupled inside the single go-livepeer process.

The remote signer architecture, introduced in go-livepeer PRs #3791 and #3822, separates them. The signer service holds the Ethereum key and handles all payment logic. The Gateway handles media. A Gateway client makes HTTP calls to the signer for each payment operation; the signer returns signed tickets and updated payment state.

<CustomDivider />

## Remote Signing Goals

Three problems motivated the design:

**Gateway diversity.** Before remote signing, implementing a Livepeer Gateway required deep knowledge of the PM mechanism. The `livepeer-python-gateway` is the first non-Go Gateway implementation, enabled directly by this architecture.

**Security posture.** In the monolith design, the Ethereum signing key sits in the same process that handles untrusted media from users. A media-path exploit could compromise the key. Remote signing removes the key from the hot path.

**Clearinghouses.** The remote signer design allows a third-party clearinghouse to operate the signer on behalf of multiple Gateways. Each Gateway operator pays the clearinghouse in a traditional currency; the clearinghouse settles with Orchestrators via PM under the hood. Gateway operators no longer need to hold ETH or manage Arbitrum operations.

<CustomDivider />

## Signing Protocol

The signing protocol covers two operations: the `GetOrchestratorInfo` authentication signature and per-job payment tickets.

**GetOrchestratorInfo signature:** The Gateway sends a static signature with each `GetOrchestratorInfo` RPC to authenticate the request. Because this signature never changes while the key is in use, the signer returns it once on startup and the Gateway caches it.

**Payment ticket signing:** For each PM payment, the Gateway calls `signTicket` on the signer service:

```
Gateway → Signer: signTicket(signerState, ticketParams)
Signer  → Gateway: signedTicket, updatedSignerState

Gateway → Orchestrator: pay(signedTicket)
Orchestrator → Gateway: updatedTicketParams

Gateway → Signer: signTicket(updatedSignerState, updatedTicketParams)
...
```

The `signerState` carries the accumulated PM session bookkeeping (elapsed time, balance tracking, nonce counters). The signer updates this state on each call and returns it alongside the signed ticket. The Gateway stores and forwards the state; it never interprets it.

<CustomDivider />

## Stateless Signer Design

The signer is intentionally stateless across calls. It does not persist session state between requests and does not require a shared database when running multiple instances.

The client (Gateway) carries the state. On the first signing call of a session, the Gateway sends `signerState=null`; the signer creates a fresh state from the provided Orchestrator info. On each subsequent call, the Gateway sends the `signerState` returned from the previous call.

Consequences of this design:

* Multiple signer instances can run without coordination: any instance can handle any request, because the full session state arrives with every call
* Restarting the signer does not lose session state: the Gateway retains the last known state and resumes correctly
* Sending stale or null state after the first call leads to nonce repetition and invalid tickets. Gateway implementations must persist and forward state correctly

<CustomDivider />

## Current Scope

Remote signing is implemented for Live AI (`live-video-to-video`) only. Batch AI and transcoding workloads are not in scope for this initial implementation.

Transcoding handles payments differently: tickets are signed with the segment hash, placing signing in the hot path. This is incompatible with the async remote signing model, and transcoding is considered a legacy workload with no active development.

The service registry (Orchestrator discovery via Arbitrum contract) is also not handled by the remote signer. Discovery is a separate concern; local Gateway clients use explicit Orchestrator URI lists or external discovery services.

<CustomDivider />

## Security Properties

**Hot key removal.** The Ethereum key is no longer co-located with media processing. An exploit from malicious video input cannot reach the signing key.

**Reduced blast radius.** In the monolith, all Gateway instances share a single Ethereum key. With remote signing, Gateway instances do not hold the key at all. Compromise of a Gateway binary does not yield the key.

**Guardrails over open signing.** The signer implements the PM protocol with proper guardrails (balance checks, nonce tracking, fee limits) instead of blindly signing arbitrary payloads. This limits the damage from an exploited signer compared to a general signing endpoint.

<Note>
  Remote signing does not provide signing key management or rotation. The signer service must be secured, backed up, and operated with the same care as any Ethereum hot wallet holding operational funds.
</Note>

<CustomDivider />

## Related Pages

<CardGroup cols={2}>
  <Card title="Alt-Gateway Overview" icon="server" href="/v2/developers/build/alt-gateways/overview" arrow horizontal>
    livepeer-python-Gateway and the off-chain client pattern enabled by remote signing.
  </Card>

  <Card title="Per-Second Compute" icon="clock" href="/v2/developers/guides/payments/per-second-compute" arrow horizontal>
    How LivePaymentSender state flows through the signing protocol for live AI.
  </Card>

  <Card title="Probabilistic Micropayments" icon="coin" href="/v2/developers/guides/payments/probabilistic-micropayments" arrow horizontal>
    The PM mechanism whose ticket generation and bookkeeping the remote signer handles.
  </Card>

  <Card title="Payments Overview" icon="grid" href="/v2/developers/guides/payments/overview" arrow horizontal>
    On-chain vs off-chain Gateway payment modes.
  </Card>
</CardGroup>
