How KEA works

KEA is a signer you control. It reads a transaction, checks it against rules you published, and signs the ones that match. Its authority is the rule; its answer is a signature.

The one idea

Assembling a transaction and approving one are separate jobs, done by separate parties. Whoever builds it hands it on; whoever holds a key approves what matches a rule published in advance.

Five jobs, and the useful part is that five different parties can do them — the author of a rule, the one proposing against it, and the one holding a key are free to be three different people:

JobWho does itWhat they hold
Write the ruleanyone — a business, a person, a scriptWrites down what they are willing to pay, and on what conditions, and publishes it. That document is the authority everything else answers to.
Aska bot, a page, a personProposes. That is the whole job — approval is a separate step, taken by someone else.
Assemble and holdthe pool (kpool)The transaction itself, in the open, while approvals arrive. It relays the bytes onward exactly as they were signed.
ApproveKEA — one or more devicesA key, used for one purpose: adding one approval to a transaction that matches a published rule.
SettleHederaThe ledger. It counts the approvals against the account's own rule and settles once they satisfy it.
The sentence worth remembering

You publish what you are willing to do; someone proposes it; a venue assembles it; and a signer you control approves it when it matches what you published. Your key stays with you throughout.

A signature is one approval

A signer adds one signature and hands it back. The ledger keeps count, and settles the moment the approvals satisfy the account's own rule — so with two of three devices, the first signature moves the transaction one step closer and the second completes it.

That is what makes approvers cheap to add and safe to spread around: each one carries a share of the decision, and the ledger does the arithmetic.

How the pieces connect

How an application, a raiser, the pool, KEA and Hedera connect The application publishes a deal catalogue. A raiser asks the pool to build a transaction. The pool holds it unsigned. KEA reads the catalogue and the pool, checks the transaction, and posts a signature back to the pool. The pool submits to Hedera, which settles. The pool holds the transaction while approvals arrive. Application publishes the rules Deal catalogue hashed · public · pinned Raiser a bot, a page, a person The pool · kpool builds it, holds it HOLDS THE TRANSACTION asks KEA holds the key checks, then signs reads signature pins Hedera settles, or refuses submits
KEA takes its authority from its own config — a list of deal ids, each the hash of its document. That is what lets the pool be treated as a convenience rather than a trusted party, and what makes "approve" a job of its own.

KEA talks outbound only: it opens a connection to the pool and to a public Hedera mirror node, and everything it needs arrives over those. That is what lets a signer live on a phone behind an ordinary home router.

The machine: what happens to one transaction

This is the part worth understanding properly. Think of a transaction as a ball dropped into the top of a machine. It falls through a series of gates, and each gate either passes it on or sets it aside with a named reason. A ball that clears every gate reaches the bottom and gets signed.

The gates a transaction passes through before KEA will sign it A transaction enters at the top and falls through ten gates in order: is it a deal, policy, authority, inputs, decode, observe, derive and require, reproduce, caps, and signers. Each gate can reject it with a named reason, shown to the right. Only a transaction clearing every gate is signed and returned to the pool. a transaction is pooled 1 Is this even a deal? not a deal — ignored quietly 2 policy — hash the rules dealId mismatch 3 authority — is it MINE? not a deal this signer signs 4 inputs & decode the bytes unexpected input, bad bytes 5 observe — go look myself could not read the chain 6 require — do the rules hold? a condition is not met 7 reproduce — rebuild & compare stale or tampered 8 caps — under my hourly limit? cap exceeded 9 signers — is my key needed? nothing here for me to sign approved — signature to the pool one approval added — the pool submits once the ledger’s key is satisfied
Gates 3 and 7 carry the most weight: authority is the signer consulting its own list, and reproduce is the signer rebuilding the transaction from the rules and requiring an exact match, to the unit.

What each gate is actually doing

GateThe questionWhy it matters
policyWhat is the identity of these rules?The rule document is hashed, and that hash is its id. The signer computes it from the document in front of it, so the name always follows the contents.
authorityIs this id on my list?The allowlist belongs to the signer. Whatever arrives, the answer comes from the signer's own list — which is what lets it listen to an open venue.
inputsWere only the declared inputs supplied?A rule declares exactly what it accepts, and the signer hands it precisely those inputs.
decodeWhat do these bytes actually move?Read straight from the raw transaction — the bytes are the thing that will execute, so the bytes are what gets examined.
observeWhat does the chain say right now?The signer fetches every fact for itself from a public mirror node, so its verdict rests on what it read.
derive / requireDo the conditions hold?The rule's own conditions — a deadline, a balance, a winner, an owner — evaluated against what was just observed.
reproduceIf I built this myself, would I get the same thing?The strongest check. The signer constructs the transaction from the rules and compares it leg by leg with what it was handed; an exact match is the condition for going on.
capsIs this more than I agreed to approve in an hour?A ceiling per asset, counting what this signer has already approved this hour. Your backstop, set by you.
signersDoes this even need my key?Derived from the bytes and the chain: which accounts are debited or pay the fee, and whether one of the keys this deal is signed with is on their key list. Those are the keys I use — or all of them, if the deal is marked witness.
This is already machine-readable

Every gate emits { id, status, detail } as the transaction passes — pass, fail, refuse, skip or error — and that list comes back on every decision. A picture of the ball falling through the machine is therefore a rendering job: the data is already there, waiting to be drawn.

Two rules that hold

Every refusal is loud, and names its gate. A verdict always says which gate produced it, so an answer you did not expect is traceable to the line that made it.

Authority lives in the rule; a hint is only a shortcut. Filters and notifications narrow what a signer looks at first. The signer then re-checks everything it reads against its own rules, so the verdict is the same either way.

How signatures add up

KEA's job ends at the signature: it holds a key, it signs, it posts the signature back. Counting the approvals is the ledger's job.

A Hedera account states its own requirement — "any one of these three", "any two of these three", or "this one, or any two of those three". So a payout can be made to require two separate devices held by two separate people, each contributing a share of the approval. That is a change to the account's key; the signer carries on exactly as before.

Why that is the interesting part

The approval rule lives on the ledger and on your devices. "Sign transactions matching these published rules, under these limits, when two of these three devices agree" is an authorisation policy enforced where the funds are, by the ledger itself.

Ways to run a signer

A signer is its own thing, running beside your application. It watches the pool and holds a key your application's accounts depend on. That separation is the point: your application can be rewritten and redeployed as often as you like while the key stays exactly where you put it.

ShapeGood forStatus
Node.js process
a server, a Raspberry Pi, a phone in Termux
Unattended signing. The usual choice for a service that must keep running.Working today.
Browser / PWAA person approving on screen, or a device that signs while somebody has it open. Installable straight from the browser.Working today as a web app; the same decision code as the Node body.
Native appA phone as a dedicated signing device, with the key held by the platform's own secure storage.Planned.
Edge / small nodesSeveral cheap signers in different places, so reaching a threshold takes agreement across locations.Follows naturally — a signer is outbound-only and needs no inbound address.

All of these run the same decision. The checks in the machine above are one pure piece of code; each shape supplies the surroundings around it — how it hears about a transaction, where the key is kept, how it reads the chain. That is why a phone and a server reach identical verdicts, and why a new shape is a new set of surroundings rather than a new rulebook.

Running more than one

Each signer works alone: it hears about the same transaction, decides for itself, and posts its own signature. Two signers reaching the same conclusion is the point — that is the agreement the ledger counts. When they differ, the dashboard shows you both verdicts side by side.

Using KEA yourself

Work in progress

KEA currently lives inside a larger backend repository and is being separated into its own public one. Everything described above is running today; what is still in progress is the packaging. The shape below is what integrating will look like, and the repository link and package name land here when the split does.

What you will need

  1. Rules you have published. One document per thing you are willing to pay for, each identified by its own hash. A catalogue is just how others find them.
  2. An account whose key you control, set to require whichever devices you want — one, or two of three, or whatever suits what is at stake.
  3. A key on each device, generated on that device. The public half goes onto the account; the private half stays where it was made.
  4. A config file per signer: which keys it holds (as references), which deals it will sign and with which of those keys, and where the deal documents are. Nothing else about your application.

The shape of it

{
  "network": "testnet",
  "pool":    { "base": "https://<the pool>" },
  "mirror":  "https://testnet.mirrornode.hedera.com/api/v1",

  "keysets": { "mine": ["env:MY_SIGNING_KEY"] },
  "resolve": ["vendor/<your app>/deals"],

  "deals": {
    "<dealId>": { "sign": ["mine"], "limits": { "<asset>": 1000000 } },
    "<dealId>": { "sign": ["mine"], "witness": true }
  },

  "report":   "refusals",
  "presence": { "everyMs": 30000 }
}

Three things in that file carry most of the safety:

Running one — the short way

bin/kea is the launcher (put bin/ on your PATH and it is just kea go). It defaults to the remote config — a phone is never the box — and to the name this device remembers:

bin/kea name honor      remember what this device is called — once, ever
bin/kea check           is this device ready to sign?  (the doctor: config, keys, chain, pool, doorbell, who else is on)
bin/kea                 watch only — decide and log, never sign      (dry run)
bin/kea go              sign what passes
bin/kea go quiet        sign, but announce nothing this run
bin/kea who             who else is announcing right now

Stopping one: Ctrl-C says goodbye — one last signed announcement, mode: "stopped", so the board shows stopped rather than a device that silently went stale — and exits. Ctrl-\ exits at once and says nothing: the quiet restart. The long form is node kea/node.js --config <file> [--go] [--quiet|--announce]; there is no default config.

Bringing one up

  1. Check before you run. A doctor command answers the questions in the order they bite: is the config valid, is the key present, does the chain accept it, can this device reach the pool — and how much weight this key carries on its own, as against counting towards a group.
  2. Watch first. Start in dry run: it decides on everything and reports each verdict — to its log, and, if you let it, back to the pool where the dashboard shows who refused what and at which step — so you can read those against real traffic first.
  3. Expect most of the pool to be none of its business. A shared pool carries everyone's transactions. A signer stays silent about the ones that were never aimed at it (see below) — silence there is the filter working, not a fault.
  4. Then let it sign. One flag.
  5. Rehearse the threshold. Stop the signers that can act alone, and confirm the remaining ones behave as you expect. Do this while it is still a rehearsal.
Announcing is optional, and off by default

A signer can announce itself so it appears on the dashboard — its name, whether it will sign, which keys it holds and which accounts list them, which deals it will sign and with what, and the exact filters it is subscribed to. You turn that on in its config or on its command line. Everything announced is already public on its own; what announcing adds is the correlation — these keys, this device, awake at this minute — which is why it is yours to switch on deliberately. Announcing is observation only: signing carries on the same either way.

Addressed, or none of my business

The nine checks fall into two halves, and only one of them is worth anyone's attention.

halfchecksthe questionwhat a refusal means
addressingpolicy, authority, signersIs this aimed at me? Nothing. It is not a deal, or not one of my deals, or it needs no key of mine. On a pool with many signers, most traffic is this for most signers — a hundred watchers would otherwise post a hundred shrugs against every raise. The signer says nothing and counts it.
judgementinputs, decode, observe, derive, require, reproduce, capIs it valid, and will I sign it? News. This transaction was mine to decide and I turned it down — the rules did not hold, the chain had moved, or it was over my own limit. This is what gets reported and what a dashboard shows.

The addressing checks run before any chain read, so deciding that something was never yours costs nothing. A signer chooses how much of its judgement to publish with report: none, refusals (the default), all, or debug — the last one also publishes the transactions that were never its business, flagged as such, which is only useful while working out why a signer is or is not being offered something.

Reading an announcement

What /kpool/signers returns per device is the signed announcement, kept whole, plus four things the pool adds. Nothing derived is added beside it: the accounts are the values of keys, the deal ids are the keys of deals.

fieldwho says someaning
bysignedThe device's identity — the key it chose to be known by (its identity ref, else its first signing key). The signature proves this. Everything else in the announcement is this identity's claim.
keyssigned{ publicKey: [accounts] } — the signing keys it holds, each with the accounts whose key list the chain showed it in. Claimed, not proven: a signing key is proven by a signature the pool accepts on a transaction.
dealssigned{ dealId: [publicKeys] } — for this deal, I will sign with these keys. The signer's config, aliases resolved.
subscriptionsignedThe literal kpool_watch filters on its socket — what it is actually rung for. null from a device that has not pulled the version that says so.
modesignedgo, dry-run, or stopped (it said goodbye).
version, startedAt, label, at, networksignedRuntime and git sha of the code; when it started (a moved start time is flagged restarted); its nickname; the announcement's own clock; the network.
sig, vsignedThe ed25519 signature and the wire version it covers. v: 1 means an older device, normalised into this shape by the pool; its signature covered a shape this record no longer holds, so it is not returned.
firstSeen, restarted, ageSeconds, freshthe poolBookkeeping: first sight of this key, whether startedAt moved, seconds since at, and whether that is within freshSeconds.

To verify one yourself (v2): build the JSON object { v, network, label, mode, at, startedAt, version, keys, deals, subscription } with the keys in exactly that order and the values as returned (startedAt, version, subscription as null when absent), take JSON.stringify of it as UTF-8 bytes, and check sig against by as a raw ed25519 signature over those bytes. That is the same primitive that signs a transaction body; the pool did exactly this before storing the record.

Words used here

WordMeans
dealA published document saying what you are willing to pay, to whom, and under what conditions. Its hash is its name.
catalogueThe list of deals an application publishes — how a signer's operator finds them. The signer itself trusts only the deal ids in its own config.
pool / kpoolWhere transactions wait for signatures. It assembles and holds them, and keeps them for about an hour.
raiserWhoever asked for the transaction to be built.
mirror nodeA public read-only view of Hedera. How a signer checks facts for itself.
presenceA signer announcing that it is here, signed with a key it claims to hold — so the claim carries its own evidence.
dry runDecide everything, report the verdict, hold the signature.
limitThe most a signer will approve per asset per hour, whatever the rules allow — its own private budget, outside the rules.
verdictA signer's signed report of what it decided about one transaction, and at which step it refused — for transactions addressed to it. Observation only; never a vote.
addressedA transaction that got past a signer's first three checks: it is one of that signer's deals and needs one of its keys. Anything else was never that signer's business, and it says nothing about it.
witnessSigning a deal's transactions even when none of your keys is required — attesting rather than releasing.

Questions this raises

Everything above says what each part does. These are the questions that tend to follow, and the answers are the boundaries of it.

QuestionAnswer
Does the pool hold keys?No. It holds transactions and relays them. It has no key for your accounts and adds no signature of its own, which is why it can be treated as a convenience rather than a trusted party.
Can whoever raised the request approve it?Only if they also hold a key the account requires. Raising and approving are separate steps, and being the one who asked carries no weight at the approval step.
Does one signature move the money?Only when the account requires exactly one. With two of three, a single signature settles nothing on its own — the transaction waits for a second.
What if a signer goes offline?Nothing is stuck by it, so long as enough other approvers remain to satisfy the account. This is the reason to choose the threshold from how many losses you should survive.
What if a device is stolen?That key can approve whatever the account's rule lets it approve alone — which is exactly why thresholds and hourly caps exist. Rotate the account's key list to retire it.
Does KEA build transactions?No. It reads one that already exists and answers yes or no. Building is the venue's job, proposing is the raiser's.
Does KEA hold funds?No. It holds a key for accounts that hold funds. The funds stay in the accounts throughout.
Can a bad rule still be approved?Yes. The signer guarantees the transaction matches the rule; judging whether the rule was sensible is the review you do once, in advance. The hourly cap is the backstop for a rule that turns out to be wrong.
Can the venue trick a signer by sending something misleading?It can send anything it likes. The signer computes the rule's identity from the document, re-reads the facts from the chain, and rebuilds the transaction itself, so what arrives alongside the bytes carries no authority.
Is a signer that stays quiet a problem?A quiet signer signs exactly as much as a talkative one — announcing is observation only. It simply appears on the dashboard under its signatures rather than in the roster.
How long does the pool remember?About an hour from a transaction's start time. Anything you want as a durable record should come from the chain.