De pagina van Evolu Relay op de lijst van de gebruiker
Negen punten uit echt gebruik met 0.4.0, alle negen gedaan. Twee ervan waren onderzoeksvragen en die staan onderaan. Evolu Relay 0.5.0 - Een tijdvenster van twee minuten voor nieuwe eigenaars, met een stopknop. De teller zit in het relay-proces en niet in de pagina: een teller in een tabblad dat je sluit, sluit de deur niet. policy.js kreeg learningUntil, isLearningOpen en expireLearning. - decideOwner kijkt naar isLearningOpen en niet naar het veld learning. De lus die een verlopen venster opruimt loopt elke twee seconden, en in dat gat zou een onbekende alsnog binnenkomen. - Zonder STATE_VERSION te verhogen, met een toets die dat verdedigt: een verhoging zou de allowlist van de draaiende installatie laten afwijzen en de deur sluiten voor eigenaars die er al in stonden. - Labels op een eigenaar-id, in een eigen labels.json met de agent als enige schrijver. Een label zegt niets over toegang, dus de relay hoeft het niet te weten; het is daardoor meteen opgeslagen en werkt ook als de relay omligt. - Geblokkeerde en geweigerde eigenaars in een kader, met een badge die zegt welke van de twee het is. De badge staat buiten het hover-blok, anders is dat onderscheid onzichtbaar tenzij je over de regel gaat. - Maatvoering gelijk aan Electrum Gate: 1760px, hetzelfde raster, icoon van 64 pixels, dezelfde kop, versienummer erachter. Uitleg uit de kaders, knoppen pas bij hover, geen voetregel. Electrum Gate 0.0.24 - Menu-item "About this app", in beide apps. - De statuswidget zei "Answering" met "answered in 7 ms, from inside the app" en zegt nu "Running" met de meting eronder. De nuance dat de controle van container naar container loopt is verplaatst naar een eigen kopje in die dialoog, waar er ruimte voor is; vier woorden waren te weinig. Toetsen en gereedschap - tests/test_relay_agent.py (nieuw, 65 toetsen) en tests/test_paginas_parsen.mjs (nieuw). Muteertests gedraaid op de beslissende regels. - Een dollarteken-toets in test_appstore_vorm.py. Het commentaar in drie bestanden beweerde al dat die test bestond; nu is dat waar. - Een toets dat er geen werkbestanden in een app-map staan. umbreld kopieert de hele map naar het apparaat en in de back-up. - tools/voorbeeldpagina.mjs maakt van een *.template een pagina die je in een browser kunt openen. Dat vond meteen twee echte opmaakfouten. De twee onderzoeksvragen - Een geweigerde eigenaar komt niet in de database: isOwnerAllowed zit in de WebSocket-upgrade, dus het is een 401 en een gesloten socket. Het gewenste gevolg treedt wel op, via de client: die is local-first en levert bij toelating de hele geschiedenis. Blokkeren werkt daarentegen pas bij de volgende verbinding, en dat staat als open punt. - De blobs zijn niet met een xpub te ontcijferen; een OwnerId komt daar niet uit. Met de SLIP-21-node van het apparaat kan het wel, maar die geeft volledige zeggenschap, dus dat hoort niet in een relay. Als plan-punt opgenomen bij de tool in HomeGit/Trezor. Nog niet uitgerold: de image 0.5.0 moet gebouwd en geduwd worden. De digest staat daarom niet in de compose, want een oude digest onder een nieuwe tag levert stil de oude relay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -404,3 +404,90 @@ mensen hier tegenaan lopen.
|
||||
waarna de rest lokaal blijft. Dat is een totaal, en het is de grens die hun quota-manager bewaakt. Een
|
||||
zelf-gehoste relay kent die grens niet: wij begrenzen alleen één schrijfactie (zie §8), en dat is dus een
|
||||
echt verschil in wat de app oplevert en geen detail.
|
||||
|
||||
## 10. Wat er met een geweigerde eigenaar gebeurt: niets
|
||||
|
||||
Uitgezocht op 30-08-2026 op de vraag van de gebruiker of een geweigerde eigenaar "toch met de hele blow
|
||||
aan instellingen in de database komt", want dan zou hem daarna toelaten meteen werken. **Het antwoord is
|
||||
nee, en de reden maakt de vraag onbelangrijk.**
|
||||
|
||||
`isOwnerAllowed` wordt aangeroepen in de **WebSocket-upgrade** en niet bij het verwerken van een bericht.
|
||||
De relevante regels uit `packages/nodejs/src/local-first/Relay.ts`:
|
||||
|
||||
```ts
|
||||
const ownerId = requestUrl ? parseOwnerIdFromOwnerWebSocketTransportUrl(requestUrl) : undefined;
|
||||
// ...
|
||||
if (!result.value) { respondAndDestroy(401); return; }
|
||||
```
|
||||
|
||||
Wat daaruit volgt, en elk punt heeft gevolgen voor het pakket:
|
||||
|
||||
1. **de `OwnerId` staat in de URL van de verbinding.** Hij is dus bekend vóórdat er één bericht over de
|
||||
lijn is, en dat is precies waarom deze goedkope toegangscontrole kan bestaan;
|
||||
2. **een weigering is een HTTP 401 en een gesloten socket.** Er komt geen verbinding tot stand, er wordt
|
||||
geen protocolbericht verwerkt, en er wordt **niets** in de SQLite van de relay geschreven. In
|
||||
`Protocol.ts` bestaat geen eigenaarscontrole; daar zit alleen de write-key-validatie, en die code wordt
|
||||
in dit geval nooit bereikt;
|
||||
3. **de wens van de gebruiker komt alsnog uit, alleen via een andere weg.** Evolu is local-first: de
|
||||
cliënt houdt alles zelf en probeert opnieuw. Laat je de eigenaar later toe, dan komt bij de volgende
|
||||
verbinding de héle geschiedenis binnen. Het werkt dus meteen, niet omdat de relay iets bewaard had maar
|
||||
omdat de cliënt niets kwijt was;
|
||||
4. **en dit is de scherpe kant: `isOwnerAllowed` wordt voor een lopende verbinding nooit opnieuw
|
||||
gesteld.** Iemand blokkeren werkt dus pas bij zijn volgende verbinding. Zolang zijn socket openstaat,
|
||||
blijft hij schrijven. Voor een app die "Block" als knop aanbiedt is dat een grens om te kennen; hij
|
||||
staat als open punt in het plan **Umbrelapp**.
|
||||
|
||||
Bron, geraadpleegd 30-08-2026:
|
||||
[`packages/nodejs/src/local-first/Relay.ts`](https://raw.githubusercontent.com/evoluhq/evolu/main/packages/nodejs/src/local-first/Relay.ts)
|
||||
en [`packages/common/src/local-first/Protocol.ts`](https://raw.githubusercontent.com/evoluhq/evolu/main/packages/common/src/local-first/Protocol.ts).
|
||||
|
||||
## 11. De blobs ontcijferen: niet met een xpub, wél met de OwnerSecret
|
||||
|
||||
Uitgezocht op 30-08-2026 op de gedachte van de gebruiker: we hebben de broncode van Trezor Suite en de
|
||||
xpub waarmee deze `OwnerId`'s gemaakt worden, dus zou de app de blobs kunnen ontcijferen en er
|
||||
onderhoudsfuncties bij kunnen krijgen?
|
||||
|
||||
**De premisse klopt niet, en dat is het hele antwoord: een `OwnerId` wordt niet uit een xpub gemaakt.**
|
||||
Uit `suite-common/suite-sync-evolu/src/createEvoluAppOwnerFromTrezorData.ts`, in de lokale kloon:
|
||||
|
||||
```ts
|
||||
const ownerIdBytes = OwnerIdBytes.from(createSlip21(secret, ['OwnerIdBytes']).slice(0, 16));
|
||||
const ownerEncryptionKey = OwnerEncryptionKey.from(createSlip21(secret, ['OwnerEncryptionKey']));
|
||||
const ownerWriteKey = OwnerWriteKey.from(createSlip21(secret, ['OwnerWriteKey']).slice(0, 16));
|
||||
```
|
||||
|
||||
Alle drie komen met **SLIP-21** uit één `secret`, en dat secret is een SLIP-21-node die het apparaat
|
||||
aflevert op het pad `['TREZOR', 'Evolu']` (`core/src/apps/evolu/get_node.py` in de firmware). SLIP-21 is
|
||||
symmetrische afleiding uit de **seed**, langs een heel ander spoor dan de BIP32-afleiding waar een xpub in
|
||||
zit. Een xpub bevat een publieke sleutel en een chaincode van één BIP32-tak; daar is de SLIP-21-wortel niet
|
||||
uit te halen, in geen enkele richting. Dat is geen implementatiedetail maar de bedoeling: §1b legt uit
|
||||
waarom de `OwnerId` juist géén geheim hoeft te zijn.
|
||||
|
||||
**Wat wél werkt is de `OwnerSecret` zelf**, en die is bereikbaar. Suite bewaart hem:
|
||||
`suite-common/suite-sync-storage/src/owner/suiteSyncOwner.ts` heeft naast `ownerId` een veld
|
||||
`ownerSecret` als hex, met in het commentaar "This is an SLIP21 node, it is provided by Trezor Device". En
|
||||
`trezorlib` kan hem opvragen: `python/src/trezorlib/evolu.py` heeft `get_node(session, proof, ...)`, met
|
||||
`get_delegated_identity_key` voor het bewijs dat het apparaat eist.
|
||||
|
||||
**Met die 32 bytes heb je alles**: `OwnerId` om te weten welke rijen bij welke wallet horen,
|
||||
`OwnerEncryptionKey` om te ontsleutelen, `OwnerWriteKey` om te schrijven. Dat is dus geen leesrecht maar
|
||||
volledige zeggenschap over de gegevens van die wallet.
|
||||
|
||||
**En daarom hoort dit niet in deze app.** De hele reden dat je een relay bij wie dan ook kunt neerzetten,
|
||||
is dat hij de sleutel niet heeft. Zet je hem erin, dan is dat weg, en wel voor de kopie die op een
|
||||
apparaat staat dat aan het internet hangt. Onderhoudsfuncties die de inhoud moeten kennen horen aan de
|
||||
kant die de sleutel al heeft. **Dat is de tool in `HomeGit/Trezor`**, die al met `trezorlib` tegen het
|
||||
apparaat praat; het staat daar als plan-punt bij **AdvancedUI**.
|
||||
|
||||
Wat een relay-app zónder sleutel wel kan, en dat is niet niks: rijen per `OwnerId` opruimen. `ownerId` is
|
||||
een systeemkolom in `evolu_message` en `evolu_history` (§8). Dat is schrijven in andermans schema, met de
|
||||
bezwaren die daar staan, maar het vraagt geen sleutel.
|
||||
|
||||
Bronnen, geraadpleegd 30-08-2026 in de lokale klonen (zie §7):
|
||||
|
||||
- `suite-common/suite-sync-evolu/src/createEvoluAppOwnerFromTrezorData.ts`
|
||||
- `suite-common/suite-sync-evolu/src/evoluCreateSuiteSyncOwner.ts`
|
||||
- `suite-common/suite-sync/src/owner/createRetrieveSuiteSyncOwner.ts`
|
||||
- `suite-common/suite-sync-storage/src/owner/suiteSyncOwner.ts`
|
||||
- `trezor-firmware/core/src/apps/evolu/get_node.py`
|
||||
- `trezor-firmware/python/src/trezorlib/evolu.py`
|
||||
|
||||
Reference in New Issue
Block a user