Api

Signer API

Sign and verify messages and transactions with the wallet's and node's keys

The Signer API exposes Hydra App's SignerService — signing with the two key families a node holds, and verifying signatures produced by anyone.

JSON-RPC namespace: signer

Every method takes a network, because the network decides the signing scheme: a Bitcoin network signs the Bitcoin way, an EVM network the EVM way. The same message signed on two networks produces two different signatures.

Endpoints


The two key families

A node holds two unrelated sets of keys, and picking the wrong one produces a valid signature that the verifier rejects.

KeySigned withWhat it is
Wallet (on-chain)SignMessageThe key that holds and spends on-chain funds — a Bitcoin P2WPKH key, an Ethereum EOA key. This is the key an address belongs to
Node (off-chain)NodeSignMessageThe key that identifies this node to its peers — the Lightning node key, the Lithium node key. This is the node_id half of a <node_id>@<host>:<port> peer string

Neither is the identity key that authenticates to the Hydranet services — that one is Ed25519, derived from the seed by HKDF, and is not exposed for signing. Read it with app.GetPublicKey.

The Message type

Both signing calls take a Message, a oneof over the three things a chain might ask you to sign:

ArmPayloadUse for
data{ data: bytes }Raw bytes. The signer applies the network's own message prefix / domain separation
hash{ hash: bytes }A pre-computed digest, signed directly
typed{ payload: bytes }A structured payload, e.g. EIP-712 typed data

⚠️ hash skips the prefix — that is the point, and the hazard

data is what you want for an ordinary "prove you own this address" message: the signer wraps it the way the chain expects, so wallets and verifiers agree. hash signs exactly the 32 bytes you hand over, with no wrapping — which is what a protocol needs, and also what makes a stray hash signature usable as a transaction signature. Only ever sign a hash you computed yourself, over data you fully control.


Sign Message

Signs a message with the wallet's on-chain key for a network.

Method: SignMessage

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network — determines the signing scheme
messageMessageYESWhat to sign — see Message

Response:

FieldTypeDescription
signaturebytesThe signature. Base64 over JSON-RPC, raw bytes over gRPC

Example Request:

import { SignerServiceClient } from './proto/SignerServiceClientPb'
import { SignMessageRequest } from './proto/signer_pb'

const signer = new SignerServiceClient('http://localhost:5003')

const request = new SignMessageRequest()
request.setNetwork({ protocol: 2, id: '11155111' })
request.setMessage({ data: { data: new TextEncoder().encode('hello hydra') } })

const sig = (await signer.signMessage(request, {})).getSignature_asU8()
console.log('signature:', Buffer.from(sig).toString('hex'))

Node Sign Message

Signs a message with the node's off-chain key for a network — the key peers know this node by.

Method: NodeSignMessage

Parameters and response: identical to SignMessage; only the key differs.

Use this to prove control of a node_id — for instance to a counterparty that knows you only by your peer string. A wallet-key signature will not verify against a node_id.


Verify Signature

Checks a signature against a message and a public key. Verifies anyone's signature, not just this node's.

Method: VerifySignature

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network — determines the verification scheme
messageMessageYESThe message that was signed. Must be the same arm the signer used
public_keystringYESThe signer's public key, hex or the protocol's own format
signaturebytesYESThe signature to check

Response:

FieldTypeDescription
validbooltrue when the signature is valid for that message and key

A false is a plain answer, not an error. The call succeeds; the boolean is the result. Treat an RPC error as "could not check", which is a different outcome from "checked and invalid".

Verifying a data signature against a hash message (or vice versa) returns false even when the underlying bytes match — the arm is part of what was signed.


Sign Transaction

Signs an unsigned transaction offline and returns the serialized signed bytes. Does not broadcast.

Method: SignTransaction

Parameters:

NameTypeRequiredDescription
networkNetworkYESTarget network
raw_txbytesYESThe serialized unsigned transaction request — the signable_data from a Create*Transaction response

Response:

FieldTypeDescription
signed_txbytesThe serialized signed transaction, ready to broadcast

signed_tx is bytes, not a structured message. It changed from a SignedTransactionRequest to serialized bytes on 2026-06-11 — see the changelog.

This fails for a broadcast-only signer. A signer that cannot produce standalone signed bytes — MetaMask and anything else that signs and broadcasts as one action — is rejected here. Use SignAndBroadcastTx for those.

Feed the result to client.FinalizeAndBroadcastTransaction, or broadcast it yourself with blockchain.BroadcastRawTransaction.


Sign And Broadcast Tx

Signs (or authorises) an unsigned transaction and makes sure it reaches the chain, returning the txid.

Method: SignAndBroadcastTx

Parameters: identical to SignTransactionnetwork and raw_tx.

Response:

FieldTypeDescription
txidstringThe broadcast transaction id

This is the universal path. An offline signer signs and the node broadcasts; a self-broadcasting authority signs and broadcasts in one step. It works for every signer, which SignTransaction does not — prefer it unless you specifically need the signed bytes in hand.


Error Handling

Error CodeDescriptionSolution
INVALID_ARGUMENTNo message arm set, malformed raw_tx, or an unparseable public_keySet exactly one arm; pass the signable_data from a Create*Transaction unmodified
FAILED_PRECONDITIONSignTransaction on a broadcast-only signerUse SignAndBroadcastTx
UNIMPLEMENTEDThe network does not support that signing schemeCheck the arm against the protocol — typed is EVM-shaped

Best Practices

  1. Pick the key family deliberately — wallet key for an address, node key for a node_id. A signature from the wrong one verifies against nothing.
  2. Prefer data over hash. Let the signer apply the network's prefix; sign a bare digest only for a protocol that requires it, and only over bytes you produced.
  3. Prefer SignAndBroadcastTx unless you need the signed bytes — it is the one path that works for every signer.
  4. Treat valid: false as an answer, not a failure, and an RPC error as "unknown".
  5. Never log a signature alongside the message and key in a place you would not log a credential; together they are a reusable proof.

← Back to Blockchain API | Next: Orderbook API →


Copyright © 2025