The oracle network
How several independently operated oracles become one answer a chain will accept, and what has actually been demonstrated.
An attestation is worth what the party behind it is worth. A single node signing a single statement is a notary: it says one operator looked at one chain and formed one opinion. What a consuming contract usually wants is something stronger, that several operators, running separate infrastructure and holding separate keys, independently read the same source chain and independently reached the same conclusion, and that the contract will not act until enough of them have.
That is the property the network exists to sell. It is also the property that is absent in a one-node deployment, and it is worth being direct about what that means: with a single signer, every consuming contract is trusting one key on one server. The signature verifies, the digest is correct, the on-chain check passes. None of that says anything about whether the fact is true; it says one machine claimed it. A threshold is what turns a claim into a count.
How a round runs
The collector holds a list of peers and a threshold. When a request arrives
at POST /v1/verify-quorum, the round proceeds like this.
- Fan out. The collector sends the request to every configured peer
concurrently. Each peer is asked the original question,
POST /v1/verifywith the caller's presentation, not a finished answer to endorse. - Each peer decides for itself. A peer resolves the presentation against its own view of the source chain, forms its own conclusion about status, builds its own attestation and signs it with its own key.
- Group by digest. Replies are bucketed by the 32-byte digest each peer signed. Two peers that agree about every field land in the same bucket automatically, because the digest is a function of the fields alone. A peer that read a different answer lands in a bucket of its own.
- The largest group wins. The collector takes the biggest agreeing
bucket. If it holds at least
thresholdsignatures, that bucket becomes the answer. - Bundle it. The signatures are deduplicated by signer, sorted into strictly ascending signer-address order, and returned alongside one copy of the attestation they all signed.
Throughout, the collector recovers each signer's address itself from the signature and the digest. It does not accept a peer's word about who it is. A peer cannot claim a signature it does not hold the key for, and a key that is not in the on-chain registry simply fails the registry check later anyway.
What the collector deliberately does not do
This is the part that decides whether the network means anything, and it is stated in the collector's own package documentation:
What the collector deliberately does NOT do: it never sends peers a finished attestation to counter-sign. Each peer receives the original request and reads its own source chain, so N nodes are N opinions. Forwarding a conclusion would make them one node wearing N hats, the signatures would still verify on-chain and the quorum would still "pass", while proving nothing at all.
The distinction is invisible from the outside. A bundle assembled by forwarding one node's conclusion to four co-signers produces exactly the same artifact as a bundle assembled from five independent reads: five valid signatures, ascending, over one digest, accepted by every verifier on every chain. The on-chain check cannot tell them apart, because signature verification proves possession of a key, never the process behind the decision to use it.
So the property has to be established off chain, in how the round is run, and it has to be established by construction rather than by policy. The collector has no code path that forwards a conclusion. What it sends a peer is the caller's request body, with the freshness fields spliced in, and nothing else.
Why the caller supplies nonce, issuedAt and expiresAt
Counting requires byte-identical digests. Signatures can only be added together when they are signatures over the same 32 bytes; two signatures over two different digests are two unrelated facts, however much the parties behind them agree.
Six of the nine words in the digest preimage are derived from the credential
and the source chain, so independent nodes reach them independently. The
other three are not derivable: nonce, issuedAt and expiresAt. Left to
itself, every node would pick a different random nonce and stamp a different
second, so no two peers would ever sign the same digest and none of their
signatures could be counted together, no matter how completely they agreed
about the facts.
The collector therefore generates these three values once, at the top of the round, and splices them into the request body every peer receives. That is what makes a multi-node quorum possible at all. It is also the correct shape for replay protection in the pull model: the consuming application issues the nonce as a challenge and recognises it coming back.
The three arrive together or not at all. A node handed a partial set refuses rather than filling the rest from its own clock, because a half-filled set would look like it worked and then silently produce a digest no peer can match.
Independence is preserved
Supplying freshness values is not the same as being told what to say. Each node validates what it is handed against its own clock, and refuses to sign if it dislikes any of it:
| Field | Rule |
|---|---|
nonce | exactly 32 bytes, and not all zero |
issuedAt | within 300 seconds of this node's clock, in either direction |
expiresAt | strictly after issuedAt, and at most 900 seconds later |
A node that fails any of these returns 400 and contributes nothing to the
round. What it does not do is defer on the substance: it still resolves the
presentation itself, still reads the source chain itself and still decides
the status itself. It merely does not get to pick the freshness parameters
unilaterally, because unilateral freshness parameters are exactly what makes
counting impossible.
The collector's own validity window is 5 minutes. That sits inside the 900 second cap every node enforces, which is why peers accept it.
One signer, one vote
A threshold of k is meant to mean k distinct operators. Two peers configured with the same key would otherwise both count, and k-of-n would be satisfiable by fewer than k of them. The system closes that hole in three places.
- In the collector. After the winning group is chosen, outcomes are deduplicated by recovered address, keeping the first per address. If the group falls below the threshold once duplicates are removed, the round fails.
- On chain. Every verifier requires strictly ascending recovered signer addresses, which proves distinctness in a single pass. See Verifying an attestation for why that rule is shaped the way it is.
- In configuration. A node refuses to start with a duplicate peer URL in
its list, because a repeated peer would be polled twice and could satisfy
the threshold on its own. Configuration also requires at least one peer, a
thresholdbetween 1 and the number of peers, and every peer an http or https URL with a host.
Failure is survivable and legible
A peer that returns an error, refuses the freshness values, or simply times out does not abort the round. It becomes an outcome carrying an error, and the round continues with whoever answered. Losing the minority is precisely the case k-of-n exists to survive; a quorum protocol that fell over when one of five nodes went down would provide no availability benefit over the single node it replaced.
When no digest reaches the threshold, the failure names what every peer said
rather than being a bare error. The 409 body carries the threshold, the
size of the best agreeing group, and one report per peer: its digest, the
status it reported, the address recovered from its signature, or the error it
produced. An operator can read that and see whether the round failed because
two nodes were unreachable or because two nodes disagreed, which are entirely
different problems. See
Verify quorum for the response
shape.
What has actually been demonstrated
The repository carries a quorum harness, scripts/run-quorum.sh, which
stands up three oracle nodes against a running eIDChain and exercises four
scenarios end to end.
| Scenario | Result |
|---|---|
| All three nodes up | The push lands on chain with signerCount = 3 |
| One node killed | The push still lands, with signerCount = 2 |
| Two nodes killed | 409, quorum not met, and nothing is written on chain |
| One node lying | The liar is outvoted and excluded, the honest two reach quorum, and the on-chain status is valid |
The fourth scenario carries a caveat that the script itself states, and it
should not be dropped when the result is repeated. The lying node is a
purpose-built stub: it speaks the same /v1/verify contract and signs with a
real key, but it reports revoked where the others report valid. It is a
doctored node, not a doctored source. The harness does not stand up a
second, divergent eIDChain and let a genuine oracle read the wrong facts from
it. What is demonstrated is that a peer whose reply differs lands in its own
digest group, is outvoted, and is excluded from the bundle. The collector
groups by digest and has no idea why a peer disagreed, so the property under
test is unaffected by how the disagreement was induced. The claim should
still be stated at its actual size: divergent reply, not divergent chain.
Two other limits of that harness are worth stating plainly. Ethereum runs there on a local development chain rather than a public testnet, and Hyperledger Fabric is not exercised by the script at all.
For operators
Everything above describes the network from the outside, as a consumer of attestations sees it. Running a node inside one, generating a signing key, getting an address into the registry, configuring peers and a threshold, is covered in Join a quorum.