In short: four claims in the 7 September update did not hold as written. Two were fixed the same day it went up, one is closed since, and one is still open and says why. Since then the larger change is that a device now checks the payment is the one the quorum approved, not just that it signs what it recomputed, which is proven on mainnet with change. Nineteen verifiable mainnet transactions, up from sixteen.
Everything below is the detail behind those sentences, including what is merged and not yet deployed, and what is still not claimed.
Corrections to the 7 September update, and what has landed since
Four claims in that post did not hold as written. All four are addressed below with their current status, and then what came after.
“Both the helper and the relay now run a worker pool with a bounded body read.”
True of the relay only. The helper read request bodies with no ceiling at all, so one POST could make it buffer without limit, on the service every vault’s balance, proposal and send goes through. The asymmetry had been filed on 23 August, in an issue whose own body reads “the relay and bridge already cap at 2 MiB; the helper does not”, and it was open while the release notes said otherwise.
Fixed in v0.4.0. The ceiling lives in one crate both servers read, and a test refuses a body read that has none. A unit test could not have caught it: the cap worked and its tests passed the whole time. The rule was never “does it work”, it was “does every server call it”.
“The export now carries the viewing key and the wallet birthday.”
True of the export taken from Settings. The backup handed to you the moment a vault is created carried neither, and that is the copy most members keep. It also recorded a blank address, permanently, for every vault made on the web.
The export itself is fixed in v0.4.0. The address backfill is NOT in that release: it merged forty minutes after the tag was cut, so it reached the web app, which ships continuously, and no desktop release carries it yet. That distinction matters more than it looks, and it comes up again below.
And when the copy still comes out incomplete, and it can, what says so is the command-line recovery tool, not a screen. An earlier draft of this correction said the screen reports it. No screen does.
“A leaked id answers 401.”
True per vault, and the post did not say per vault.
Re-measured on 15 September against the live coordinator, GET only: of the nine vaults addressable there, four are behind the gate, four are not, and one is a stray testnet vault whose wallet is broken. The four that are not answer a request holding only their id. Every one of them is empty, and all of them are test vaults, mine and one collaborator’s, who knows. No third party’s vault is in that set. That does not make the claim in the post true. It is the reason the answer is a flow to build rather than an incident to run.
Still not fixed, and the reason is worth stating rather than hiding: it cannot be automatic. Making an open vault protected means re-creating it and moving the funds with a signed send, and an automatic version would have to mint the vault’s secret somewhere its members are not. New vaults are protected from creation, including one created on 15 September.
“An outsider can no longer take a seat or forge room messages.”
The first half was true. The second was not, and this is the correction that changed most since.
The signing room is readable and writable by design, and the ceremony’s own messages were not authenticated. That left two ways in. An unproven rejoin could take a seat whose device was offline and then contribute something no share had produced, which made the signature fail to verify and the payment not go out. And the second round of a signature, where one member assembles a package and hands it to the others, was accepted from anyone: each device checked that the package matched the transaction it had approved and that its own seat was listed, and never checked who sent it.
That second one is worse than it sounds. Anyone able to write to the room could assemble a package over the commitments already sitting there, and every device would sign it, spend the single use value it may only use once, and have nothing left when the real member’s package arrived.
Both are closed now. A device refuses a package from a member who does not hold the seat that opens the round. And when the set the coordinator would otherwise pick still needs a seat nobody vouched for, it defers once and chooses after the rest of the round’s contributions have landed, preferring seats that proved they hold their share. It does not always wait, and saying it did would be the same kind of overstatement this post exists to correct.
The part that took longest on the second fix was not the check. It was working out that “the wrong seat” and “a seat I have not learned yet” are different questions. A device learns its peers’ seats from their own announcements, and an announcement can legitimately arrive after the package does. Refusing both cases would have traded a stall for payments that stop silently, which is worse.
What is still true of that sentence, stated plainly so it is not claimed again. The round’s messages are still not themselves signed. Round one and round two contributions carry no signature, and an unproven rejoin can still take a genuinely empty seat, which is deliberate so that a vault whose members run an older build keeps working. What changed is that neither of those is a way to reach the outcome any more: an unvouched seat is not preferred when a real one is on its way, and a package from a tag that does not hold the coordinator’s seat is refused.
Also corrected from that post, and fixed the same day it went up
Creating a proposal and firing a send were unauthenticated. A vault id was enough to fill a vault’s desk with proposals its members then had to read and refuse, and to trigger the broadcast of a payment that had already reached quorum. The funds go where the members decided either way, but the deliberate confirm a send is supposed to require could be fired from outside the room. Both are signed writes now, by the key derived from the seat’s share.
That sentence has the same shape as the one corrected above, so it gets the same treatment: it is true PER VAULT. The gate turns on for a vault when the first device on it registers a write key, which is what lets existing vaults keep working instead of stopping on a flag day. Probed on 8 September: of the three vaults then behind the read gate, one refused an unsigned proposal and two still accepted it. That fraction has not been re-measured for this post, because measuring it means attempting a write against a live vault.
What has landed since
A device now checks that the payment is the one you approved. This is the larger of the two, and it closes a gap that had been open since the beginning.
Since August every device has recomputed the transaction’s own signature hash from the copy of the transaction it holds, and refused to sign if that disagreed with what it was asked to sign. That stops the transaction being swapped underneath a signer, and it holds. What it never did was say anything about whether that transaction is the one the quorum approved. A coordinator assembling a perfectly valid transaction to its own address produces a request that passes every check, and the only thing standing between that and a signature was a human reading a preview and noticing an address they did not recognise.
Each device now compares what the transaction actually pays against what it approved, before it contributes anything.
The hard part is not the comparison, it is change. A payment normally sends money to two places: the recipient, and the vault’s own change. Change goes to an internal address that is not the one the vault publishes for receiving, so a naive check reads it as an unapproved payment and refuses every legitimate send. Getting it wrong in that direction is worse than the hole, because payments stop silently.
Proven on mainnet, deliberately with change: a 2-of-2 vault holding 0.0003 ZEC sent 0.0001, which after the network fee leaves change. Both devices signed, first attempt. Sending the maximum instead would have produced a transaction with no change at all and passed without testing the thing that matters. The chain cannot show the check, and the proof entry says so: what attests it is that the change went to the vault’s own internal receiver, a different address from its published one, and the device recognised it as its own instead of refusing.
On a transaction spending from two internal pools, the browser signed half of it. A vault mid-migration can hold funds in two pools. The part of the app that decides which pieces of such a payment need this device’s signature was answering that question itself instead of asking the one place that knows, and its answer was to take one pool and ignore the rest. The ignored parts were never signed and nothing was raised; the payment failed later, reporting something else.
There is one answer to that question now and every part of the app asks it, and the balance stops offering the sum of both pools as though one payment could spend it. Both of those are live: they are browser code, and the web app ships continuously.
A third piece is merged and NOT live, and this post would rather say so than let it be assumed. The refusal that arrives while the payment is still being composed, instead of after the quorum has approved it, lives in the hosted coordinator, whose binaries were last rebuilt on 24 August. It is in the repository and it is not in production. There is a real difference between those and it is the same difference the first correction above was about.
Vaults holding funds in a single pool, which is every vault in use today, are unaffected by any of this.
The payment screen announced one network fee and refused you with another. It printed an estimate beside the amount and then blocked the proposal using a figure half again as large, so a vault holding exactly the announced fee was told it could not afford a payment that the arithmetic on the same screen said it could. Found by a member hitting it on a real vault, which is the only reason it was found: every test in that area checked the rule, and the rule was right. Nothing compared the rule to the sentence printed next to it.
Two screens told a member something false, and both were found on live vaults by their owners rather than by a test. They are not cosmetic, which is why they are here rather than in the list below.
A vote could go out UNSIGNED and the member was told the server was at fault. Signing a governance write calls into the cryptography module, and on a proposal screen nothing else had loaded it. The call threw, a catch written so that signing could never block a vote swallowed the throw, the vote went out without a signature, and the coordinator refused it with “this vault requires a signed vote”. True, and useless: it names a rule on the server while the cause is an uninitialised module in the browser, and the member had just typed their passphrase. That swallowed catch is the same shape as the one further up this post, and it is worth saying that they were found in the same week by looking for it.
And every refusal to record a vote was reported as “the proposal already changed state, or there is a conflicting vote”. A sentence about consensus, shown for a 401 from a device that simply could not prove it holds its seat. Found while its owner was approving a payment. There was no conflict. On a money screen a wrong reason is worse than no reason, because it sends someone looking for something that does not exist.
Smaller things a member would notice. Every page load printed a deprecation warning to everyone’s console, next to the diagnostics that mean something. Code and WASM files are served under names containing a hash of their contents, so the browser is now told they never change. A vault created before 0.4.0 kept reporting a blank address however many times it was exported. The backup checker did not mention the per-vault secret that lets a restore read the vault, so a backup restoring a device able to sign but not to see anything passed as complete; the same tool could not open a version-1 backup at all, and answered a missing file with a Node stack trace, which is the last thing someone wants when they are running it because their laptop is dead. Two panels broke their own sentences across four lines, and the ceremony drawer called the other signers “member 1” and “member 2” whenever a roster read failed, while the device held the names all along.
Proof
The post above said sixteen mainnet transactions, which was right when it was written. Three have been added since: a send where every governance write was authenticated, a 2-of-3 vault signing with one seat deliberately absent after the seat-poisoning fix landed, and the money gate send described above. Nineteen now.
That middle row was rewritten this week, and how is worth a sentence. It asserted the quorum shape and the absent seat as though the chain showed them. The chain shows neither. The claim turned out to be true, and the operator confirmed it, but true and attested are different things and a proof file trades on the second. The row now splits them: the 2-of-3 shape IS publicly checkable, because the coordinator reports a vault’s threshold and member count to anyone holding its id, with no credential. That a seat was left absent rests on the operator’s word and says so.
node scripts/verify-proof.mjs
It talks only to public block explorers, has no dependencies and no knowledge of Konclave’s internals. It got two changes this week that are about honesty rather than features. It used to return the same failure code for “this transaction is not on the chain” and “an explorer did not answer”, so a flaky network read as a failed proof; those are different answers now. And it prints the count, which it never did, so the number nobody had to go looking for is the number you quote. It also compares that count against what the README and the project docs claim, and warns when they disagree, because they did: the README said eight while the file listed seventeen.
Its stated limits are real and unchanged. On-chain data proves a transaction exists and is mined and, being shielded, reveals nothing about amounts or parties. It does NOT prove the threshold nature of the signature: a FROST-aggregated Orchard signature is meant to be indistinguishable from an ordinary single-signer one, and that indistinguishability is the privacy property. The threshold nature is attested by the build and the ceremony, not by the chain.
Still open
- The vaults created before the read gate stay readable by their id until they are re-created. Four of the nine on the coordinator, all empty, all test vaults.
- The ceremony’s round messages are not signed. The two ways that was reachable are closed; the property itself is not claimed.
- A payment that genuinely needs funds from both internal pools is refused, not supported.
- Live per-platform hardware validation of the desktop shell has still not happened.
- The engine binaries the hosted coordinator runs were last rebuilt on 24 August, so a fix merged now in that layer does not reach production until they are rebuilt. That is tracked now rather than assumed, and this week’s fixes were deliberately written to land in layers that do ship.
On how the corrections above were found. Not from a bug report. From pointing audits at the project’s own notes rather than at the code, after statements from those notes reached this thread as fact. The notes said write endpoints were authenticated when two of four were. They said staging was half built when both halves had been answering for two days. They said fifteen verifiable transactions while the file listed sixteen and the checker checked sixteen.
Doing it again this week found three more, which is the part worth reporting. A line saying the coordinator authenticated two kinds of write was true for TWENTY-TWO MINUTES: the correction landed at 18:11 and the fix that authenticated the rest landed at 18:33, so for nine days the notes understated the project’s own security. A comment in the signing code described a live control as groundwork that does not fire yet, which stopped being true when the control started firing. And the proof file asserted a detail about one transaction that nothing attested; it happened to be correct, and the operator confirmed it, but a proof file trades on attestation rather than on being right.
Two of those are now held by tests rather than by attention, because a note that COUNTS something goes stale the moment the thing is counted again and nobody notices: prose has no compiler. The suite now reads the list of authenticated writes, and the list of reads behind the access gate, out of both the notes and the source, and fails when they differ.