Files
UmbrelApps/Docs/Referenties/Upstream-evolu-relay.md
T
HarmenandClaude Opus 5 b3b1881af2 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>
2026-08-30 12:26:56 +02:00

30 KiB

Upstream: trezor/trezor-suite-sync en Evolu Relay

Naslag, geen plan. Nagetrokken tegen de bron op 25-08-2026, met de URL's onderaan. Dat is een andere status dan de eerste versie van dit document had: die leunde op vooronderzoek van dezelfde dag en meldde per regel dat het niet geverifieerd was. Wat hieronder staat komt uit de bestanden zelf.

Op drie punten week de werkelijkheid af van het vooronderzoek, en alle drie raken het pakket. Ze staan in §2, §3 en §4.

0. Let op: er zijn twee relays, en wij pakketteerden de zware

Toegevoegd 25-08-2026, aan het eind van de dag, nadat de gebruiker evolu.dev/docs/relay aandroeg. Dit staat bovenaan omdat het de rest van dit document in een ander licht zet.

Evolu is een eigen project (evoluhq/evolu) en heeft een eigen relay. trezor/trezor-suite-sync is niet dat project maar Trezor's inzet ervan, met een eigen opslaglaag en de quota-manager eromheen. Het verschil is groot:

Evolu, apps/relay Trezor, trezor-suite-sync
Containers één drie: relay, quota-manager, Postgres
Image gepubliceerd: docker.io/evoluhq/relay:latest geen; eigen Amazon ECR, dus zelf bouwen
Opslag een datavolume op /app/data; de documentatie noemt de relay stateless en geschikt voor serverless Postgres
Toegangscontrole niets over gedocumenteerd eigenaar zonder limietenrij wordt geweigerd
Poort 4000, ws:// 4000

De vraag die hieruit volgt is de belangrijkste die dit project nu heeft: praat Trezor Suite met een kale Evolu-relay? Zo ja, dan vervalt vrijwel alles wat dit pakket ingewikkeld maakt: geen bouwstap, geen eigen register, geen onderhoudsplicht op een image, geen Postgres, geen wachtwoord, en vooral geen eigenaarsregistratie, wat nu de blokkade is.

Wat de cliëntkant zegt, en dat is beslissend

Nagelezen in de broncode van Trezor Suite zelf, die lokaal staat; zie §7. Drie bevindingen, en de tweede is de belangrijkste van dit hele document.

1. Suite kan naar een eigen relay wijzen, en het zijn twee losse adressen. In de instellingen onder dev-utils staan een relay-URL en een quota-manager-URL, elk met een eigen opslaan-knop. De e2e-test van de mobiele app vult ze met http://10.0.2.2:4000 en http://10.0.2.2:4001, dus http:// volstaat en de poorten zijn precies die van ons. Het aanzetten van de synchronisatie vraagt daarna een bevestiging op het Trezor-apparaat zelf.

2. Met een eigen relay-URL negeert Suite de quota-manager volledig. Letterlijk, uit suite-common/suite-sync-quota-manager/src/createSuiteSyncQuotaManagerCompositionRoot.ts:

// We only want to use QM for our own relay servers. In case custom URL has been set, QM is ignored,
// unless enforceQuotaManager is set (used for e2e tests with a local relay).
const getIsQuotaManagerEnabled = () =>
    deps.getIsUsingTrezorRelay() || selectEnforceQuotaManager(deps.getState());

enforceQuotaManager staat standaard op false en bestaat voor hun eigen e2e-tests.

Dat is een klem, en hij zit in Trezor's relay en niet in ons pakket. De cliënt registreert geen eigenaar zodra je een eigen relay gebruikt, maar de relay van Trezor blijft isOwnerAllowed() doen en weigert iedereen zonder limietenrij (§3). Die rij komt er dus nooit, want het enige dat hem zou maken wordt door de cliënt overgeslagen. Trezor's relay is daarmee als zelf-gehoste relay in de kern onbruikbaar, tenzij je de rij met de hand in de database zet of de quota-manager buitenom aanroept.

3. En dat maakt de kale Evolu-relay niet alleen eenvoudiger maar waarschijnlijk de enige die werkt. Daar is geen limietentabel en geen eigenaarscontrole, en de cliënt wil sowieso geen quota-manager bij een eigen relay. De twee kanten passen op elkaar.

Wat nog steeds gemeten moet worden, want dit is redeneren uit code en geen proef: of de protocolversie klopt. Suite gebruikt @evolu/web@3.0.0-next.1 met een eigen patch in .yarn/patches/, en docker.io/evoluhq/relay:latest hoeft daar niet bij te passen. Dat is nu het enige echte risico, en het is in minuten te weerleggen: docker run --rm -p 4000:4000 docker.io/evoluhq/relay:latest, Suite ernaartoe wijzen, label maken.

4. De relay-URL bevat een pad, en de cliënt gebruikt hem letterlijk. Toegevoegd 28-08-2026 uit suite-common/suite-sync/src/relay/relayUrl.test.ts. Hun eigen standaardwaarden zijn https://suite-sync.trezor.io/evolu/ en lokaal http://127.0.0.1:4000/evolu/: de relay van Trezor hangt dus achter een pad, en een eigen URL wordt overgenomen zoals hij is. De kale Evolu-relay luistert op de wortel; dat is gemeten met http://10.0.0.11:4000 zonder pad.

Dat opent een goedkope toegangscontrole die in geen van de drie wegen van Bereikbaarheid §4b stond: een onraadbaar pad in de URL, afgedwongen door de reverse proxy die er toch al staat. Geen eigen image, geen regel code.

En het verklaart de foutmelding op iOS. De URL wordt gevalideerd met een .url()-regel uit yup (relayServerSettings.ts), en die accepteert ws:// niet. "Enter a valid url" ging dus over het schema en niet over het adres.

5. Eén OwnerId per wallet, niet per apparaat en niet per installatie. Toegevoegd 28-08-2026, nadat er op de eigen relay tot verrassing twéé eigenaars verschenen.

De eigenaar komt van het apparaat zelf: createRetrieveSuiteSyncOwner.ts roept trezorConnect.evoluGetNode aan met een proof of delegated identity onder de header EvoluGetNode. De afleiding gebeurt dus op de Trezor. createEnsureSuiteSyncOwner.ts bewaart het resultaat vervolgens per device.state.staticSessionId, en dat is in Suite een device state: een wachtwoordzin geeft een andere state.

Wat daaruit volgt en wat makkelijk verkeerd gedacht wordt:

  • twee installaties met dezelfde Trezor en dezelfde wallet komen op dezelfde OwnerId uit. Dat is wat synchroniseren tussen apparaten mogelijk maakt zonder dat die apparaten elkaar kennen, en het betekent dat een tweede apparaat géén nieuwe eigenaar oplevert;
  • meer dan één OwnerId betekent meer dan één wallet, bijvoorbeeld een verborgen wallet achter een wachtwoordzin. Niet meer dan één app.

Voor een relay met een eigenaars-allowlist is dat bepalend: het aantal eigenaars dat je toelaat is het aantal wallets dat je synchroniseert. "De eerste eigenaar die verbindt" is dus te smal zodra iemand een tweede wallet gebruikt, en dat is op 28-08-2026 meteen gebeurd.

Dat het huidige pakket de zware variant bevat is geen fout maar het gevolg van de volgorde waarin het gevonden is: de laag van Trezor was eerder zichtbaar dan de laag eronder.

1. Wat het is

Trezor Suite kan labels en accountnamen synchroniseren tussen apparaten. De server die dat doet heet in de interface "custom sync server"; het onderdeel zelf heet Evolu Relay en staat in de repo trezor/trezor-suite-sync (standaardbranch main, licentie in LICENSE.md, door GitHub geclassificeerd als "other").

Evolu is een local-first synchronisatielaag: de client houdt zijn eigen kopie bij en de relay is een doorgeefluik voor versleutelde wijzigingen. Volgens Trezor is de data client-side end-to-end versleuteld, dus de relay ziet niets leesbaars. Zelf hosten haalt Trezor uit de vergelijking, het verandert de privacygaranties niet.

1b. Wat een relay is, wat hij ziet, en waarom hij niets hoeft te vertrouwen

Uitgezocht op 25-08-2026 op de vraag van de gebruiker of iemand met kennis van de API de labels kan opvragen als de relay op een domein staat. Bronnen: evolu.dev/docs/privacy, Owner en Protocol.

Het is geen key-value-store. De cliënt houdt zijn eigen SQLite bij; de relay bewaart versleutelde CRDT-berichten per eigenaar en helpt twee kanten met range-based set reconciliation uitzoeken wat ze allebei al hebben. Dichter bij een append-only berichtenlog dan bij een sleutel-waardetabel.

Wat de relay ziet: een OwnerId, tijdstempels, IP-adressen, en versleutelde blobs die gevuld zijn om hun omvang te verhullen. Meer niet.

Drie waarden, met SLIP-21 afgeleid uit één OwnerSecret:

Waarde Wat het doet
OwnerId de publieke identificatie
OwnerEncryptionKey ontsleutelt de data
OwnerWriteKey rotatable token dat schrijven autoriseert; zonder is een cliënt ReadonlyOwner

Het antwoord op de vraag is dus nee, en de reden is scherper dan "het is versleuteld". De OwnerId is expres niet geheim: hij gaat onversleuteld naar de relay en de beheerder ziet hem. De beveiliging leunt er niet op dat niemand hem kent. Dat is het tegenovergestelde van een systeem waar een onraadbare URL de grens is, en het is precies waarom je deze relay bij een onbekende partij kunt neerzetten.

Dat de drie waarden onafhankelijk zijn, is wat dat mogelijk maakt: je geeft de relay je identiteit zonder iets over je sleutel prijs te geven, en je kunt de write key roteren zonder je identiteit te verliezen. De protocol-documentatie zegt dat de write key gecontroleerd wordt vóór het verwerken van berichten.

Twee dingen die hier níet uit volgen, en die je niet moet gladstrijken:

  1. Of iemand die een OwnerId kent de versleutelde blobs kan ophálen, staat nergens. De protocolpagina zwijgt erover. Kan het, dan leert zo iemand dat die eigenaar bestaat, wanneer hij actief is en bij benadering hoeveel data er is. Niet de inhoud.
  2. Onraadbaarheid van de OwnerId beschermt tegen aflopen van de relay, niet tegen lezen. Het is verkeersbescherming en geen vertrouwelijkheid.

En het echte risico van een publieke relay is een ander dan vertrouwelijkheid: misbruik als opslag. Een kale Evolu-relay kent geen accounts en geen toegangscontrole, dus iedereen kan een eigen owner aanmaken en hem als gratis versleutelde opslag gebruiken. Je schijf groeit en je kunt niet zien van wie het is, want dat is nu juist het ontwerp. Dit verklaart met terugwerkende kracht waarom Trezor een quota-manager heeft: die is niet alleen voor facturering maar ook hun misbruikbeheersing. Wie hem eruit haalt, haalt die tweede functie mee weg. Zie het masterplan Bereikbaarheid.

2. Eén codebase, vier processen, en een compose die de app niet draait

Correctie op het vooronderzoek. Daar stond dat er een Dockerfile en een docker-compose.yaml in de repo staan en dat containeriseren dus "al gedeeltelijk gedaan is door Trezor zelf". Het eerste klopt, het tweede niet: de compose van Trezor draait de relay niet. Er staan precies twee diensten in, en dat is een ontwikkelopstelling:

  • db: image: postgres, ongepind, met PGDATA: /data/postgres en 5432:5432 naar buiten;
  • prometheus: network_mode: host, die localhost:4003 afloopt.

De README bevestigt de bedoeling: docker compose up in de ene terminal voor de database, en in de andere yarn start-quota-manager, yarn start-evolu-relay en yarn start-metrics. Wat Trezor zélf uitrolt staat niet in de compose maar in .k8s/, met Kustomize-overlays voor dev en prod.

Alle processen komen uit dezelfde codebase en dus uit dezelfde image. Dat is de belangrijkste pakketteerwinst van dit hele onderzoek: je hebt geen drie images nodig, maar één image met per service een ander command. De Dockerfile is node:24-alpine, twee fasen, Yarn 4.12.0, met EXPOSE 4000 4001 en CMD ["yarn", "start"]. Bij dat CMD staat bovenstrooms zelf een commentaar dat het misschien start-quota-manager of start-evolu-relay had moeten zijn, dus reken er niet op dat yarn start het juiste doet; zet het commando expliciet.

De poorten, met hun standaardwaarde uit src/config.ts:

Proces Poort Waarvoor
evolu-relay 4000 de sync-relay, dit is wat we nodig hebben
quota-manager 4001 quota- en betaalserver
health 4002 statuscontrole
metrics 4003 Prometheus

In .env.sample staan ze alle vier uitgecommentarieerd, dus de standaardwaarden gelden tenzij je ze zet. DATA_DIR heeft standaard de waarde data.

3. Is de quota-manager verplicht? Ja, in de praktijk wel

Dit is de vraag waar het vooronderzoek op hoopte dat het antwoord nee zou zijn. Dat is het niet, en de reden is preciezer dan "de relay heeft de quota-manager nodig".

De relay bevat twee controles, in src/evoluRelay/createEvoluRelay.ts:

  • isOwnerAllowed() vraagt getLimitsForOwner({ ownerId }) op en staat de eigenaar alleen toe als daar iets terugkomt dat niet leeg is;
  • isOwnerWithinQuota() controleert bij schrijven of usedBytes + requiredBytes binnen de limiet blijft.

De strekking: een eigenaar zonder rij in de limietentabel wordt geweigerd. En de quota-manager is precies het onderdeel dat die rijen aanmaakt. Zonder hem is de relay dus niet "open", maar dicht voor iedereen.

Twee dingen die dit relativeren en die het pakket bepalen:

  • er is geen HTTP-koppeling tussen de twee. In src/config.ts staat geen URL of hostnaam voor de quota-manager, alleen een poort waarop hij zelf luistert. Ze delen de Postgres. Praktisch gevolg: de relay hoeft de quota-manager niet te kunnen bereiken, alleen de tabel moet gevuld zijn;
  • daarmee zijn er twee wegen, en ze zijn allebei legitiem: de quota-manager meepakketteren en er één eigenaar in registreren, of de limietenrij één keer zelf in de database zetten. Het tweede is minder bewegende delen, maar het is wél zelf in andermans schema schrijven, en dat breekt bij een migratie.

Wat hier niet is uitgezocht, en het is de eerste vraag voor wie hier verder gaat: hoe je bij de quota-manager een eigenaar registreert, en of daar iets extern voor nodig is. De README verwijst voor de API-specificatie naar Notion en voor het uitproberen naar Bruno; er zit een bruno-collection/ in de repo die dat waarschijnlijk laat zien.

De onopgeloste tegenspraak rond dev en prod

In .env.sample staat SERVER_ENV="dev" # dev | prod (PROD enables evolu relay authentication). Dat suggereert dat de controles in dev uit staan, en dat zou een self-hosted relay eenvoudig maken.

Dat is niet terug te vinden in de code die ik gelezen heb. In src/config.ts wordt isDevServer = SERVER_ENV === 'dev' gezet, en in createEvoluRelay.ts bepaalt die vlag alleen het logniveau (debug tegenover info). createEvoluRelayCompositionRoot.ts hangt getLimitsForOwner onvoorwaardelijk in, zonder tak voor dev.

Dus: óf het commentaar in .env.sample gaat over iets anders dan deze twee controles, óf het is achterhaald. Ga er niet van uit dat SERVER_ENV=dev de deur opent, en ga er ook niet van uit dat hij dicht is: dit is precies het soort aanname waar een middag in gaat zitten. Het is met één keer starten te meten en dat hoort in Proefopstelling fase 2.

4. Er is geen publieke image, en dat is de grootste consequentie

Tweede correctie op het vooronderzoek, en de duurste. Trezor bouwt wel een image, maar publiceert hem niet ergens waar jij hem kunt halen.

Uit .github/workflows/build-and-push-image.yml: de image gaat via aws-actions/amazon-ecr-login naar een Amazon ECR in eu-central-1, met de registry-hostnaam als uitvoer van die stap en de naam als invoerparameter. Er is geen Docker Hub en geen ghcr in dat werkproces. Onder de namespace trezor op Docker Hub staat niets dat hierop lijkt.

Wat daaruit volgt voor dit project:

  • je bouwt en publiceert de image zelf, of er is geen Umbrel-app. Dat is geen eenmalige handeling maar een doorlopende verplichting: bij elke nieuwe versie van Trezor bouw je opnieuw;
  • linux/arm64 is geen risico meer. Datzelfde werkproces bouwt platforms: linux/amd64,linux/arm64 met QEMU, dus de Dockerfile is bovenstrooms bewezen op arm64. Dat was punt 2 van de oude versie van dit document en die vraag is hiermee weg;
  • de licentie wordt nu wél een vraag. GitHub classificeert LICENSE.md als "other". Zolang je alleen zelf draait is dat academisch; zodra je een image publiceert, distribueer je hun software. Lees dat bestand voordat er iets in een openbaar register komt.

5. Wat er nog helemaal niet uitgezocht is

Deze staan hier omdat ze het pakket bepalen en omdat er in deze ronde geen antwoord op kwam. Ze zijn geen taak (dat zijn ze in Proefopstelling en Umbrelapp).

  1. Kan Trezor Suite überhaupt naar een eigen sync-server wijzen, en op welk platform? Onveranderd de goedkoopste weerlegging van het hele project, en niets in deze repo zegt er iets over: dit is de serverkant.
  2. Accepteert Trezor Suite een http://-adres, of eist het TLS? Daar hangt het masterplan Bereikbaarheid aan.
  3. Hoe registreer je een eigenaar bij de quota-manager, en heeft die daarvoor iets extern nodig? Zie §3. Begin bij bruno-collection/.
  4. Wat doet SERVER_ENV=prod dat dev niet doet? Zie de tegenspraak in §3.
  5. Wat staat er in LICENSE.md? Zie §4.

6. Bronnen

Alles hierboven komt uit deze bestanden, geraadpleegd op 25-08-2026:

Het oorspronkelijke vooronderzoek van 25-08-2026 staat ongewijzigd in Vooronderzoek.PLAN.md. Wat daar niet meer klopt, staat hierboven in §2 en §4.

7. De broncode van Trezor Suite staat lokaal

Aangedragen door de gebruiker op 25-08-2026, en het beantwoordde in een half uur wat uit de serverkant alleen niet te halen was. In een ander project van de gebruiker staat een kloon van de cliënt:

D:\HomeGit\Trezor\Repos\trezor-suite

Dat is de tegenkant van deze relay, en bij een vraag als "verwacht de cliënt dit eigenlijk wel" is dat de snelste bron. De plekken die hier iets opleverden:

Pad Wat je er vindt
suite-common/suite-sync-quota-manager/src/createSuiteSyncQuotaManagerCompositionRoot.ts de regel dat de quota-manager genegeerd wordt bij een eigen relay-URL
suite-native/app/e2e/pageObjects/settingsActions.ts hoe je het in de interface instelt, met werkende voorbeeld-URL's op 4000 en 4001
suite-native/module-settings/src/hooks/useSuiteSyncRelayUrlForm.ts het formulier: keuze tussen de standaardserver en een eigen URL
suite-native/suite-sync/src/createSuiteSyncNativeCompositionRoot.ts hoe de Evolu-cliënt wordt samengesteld
.yarn/patches/@evolu-web-npm-3.0.0-next.1-*.patch dat Trezor de Evolu-cliënt patcht, en op welke versie

Twee waarschuwingen bij die bron. Het meeste hierboven komt uit suite-native, dus de mobiele app; de desktopversie kan andere instellingen op een andere plek hebben, en dat is niet nagekeken. En het is een kloon op een moment in de tijd: bij twijfel de datum van die kloon opzoeken voordat je er een conclusie op bouwt.

8. De uitbreidpunten van de kale relay

Nagetrokken 28-08-2026 in de broncode van evoluhq/evolu, nadat de proef bewees dat de kale relay bruikbaar is. Dit is wat een eigen limiter mogelijk maakt, en het is aanzienlijk minder werk dan Bereikbaarheid §4b aannam.

Er wordt geen relay nagebouwd; er worden twee functies meegegeven. createRelay uit @evolu/nodejs heeft deze vorm:

createRelay({ port = 443, name, isOwnerAllowed, isOwnerWithinQuota }): Task<Relay, never, RelayDeps>

isOwnerAllowed(ownerId) beslist of een eigenaar er überhaupt in mag, isOwnerWithinQuota(ownerId, requiredBytes) of een schrijfactie past. Beide zijn asynchroon. De opslag wordt intern gemaakt met createRelaySqliteStorage(deps)({ isOwnerWithinQuota }), dus SQLite, en createRelayDeps() levert de driver.

Trezor doet exact dit en niets meer. Hun createEvoluRelay.ts roept de relay aan met die twee functies, en beide kijken in hun limietentabel via getLimitsForOwner(). De hele quota-manager plus Postgres bestaat dus alleen om die tabel te vullen. Dat is het bewijs dat ons pakket met dezelfde twee haken hetzelfde kan zonder die machinerie.

De gepubliceerde image vult ze zelf al deels in, en dat is een valstrik om te kennen. In apps/relay/src/index.ts staat isOwnerAllowed uitgecommentarieerd (dus iedereen mag erin) en isOwnerWithinQuota op 1 MB per eigenaar. Dat is geen theorie maar de image die wij draaien:

  • die grens geldt per schrijfactie en niet per eigenaar. De relay geeft door hoeveel bytes déze actie nodig heeft; er wordt niets opgeteld. Trezor telt wél op, maar dat doen ze zelf uit hun eigen tabel, en die tabel is precies wat een kale relay niet heeft. Een labelgeschiedenis kan hier dus niet tegenaan lopen;
  • wat het wél tegenhoudt is één grote schrijfactie. Dat dempt misbruik als gratis opslag, maar het is geen totaalquotum. Wie dat wil, moet in de opslag van de relay kijken hoeveel een eigenaar al gebruikt, en dat is schrijven noch lezen in je eigen schema.

De relay zet zijn data in een map data naast het programma (mkdirSync plus process.chdir), en dat is het volume /app/data uit de image. In de database zitten onder meer evolu_message en evolu_history, en ownerId is een systeemkolom. Data per eigenaar wissen is daarmee mogelijk maar het is schrijven in andermans schema, met dezelfde bezwaren als bij Trezor's limietentabel.

Bronnen, geraadpleegd 28-08-2026:

9. Wat Trezor er zelf over publiceert

Nagetrokken 28-08-2026 op de eigen documentatiepagina's, nadat op iOS niets bleek te werken. Drie feiten die het pakket raken, en ze staan verspreid over pagina's die elkaar niet noemen.

Een eigen relay is een gedocumenteerde functie en geen ontwikkelaarstruc. De pagina Suite Sync in Trezor Suite beschrijft "Custom server" als keuze in de interface, met een invoerveld voor je eigen adres, ook op mobiel. Dat is prettig voor dit plan: wat wij verpakken is een ondersteund gebruikspatroon en geen omweg.

Op 28-08-2026 is via beide wegen getest: op de desktop via het dev-utils-veld, op iOS via de gewone "Custom server"-instelling. Dat is de moeite van het vastleggen waard, want het sluit een verklaring uit die anders was blijven hangen: iOS deed niets terwijl daar juist de officiële, ondersteunde weg gebruikt werd. Wat er overblijft is de verklaring uit het plan Umbrelapp, OPEN.md punt 9: zonder een apparaat dat aan de telefoon kan, is er geen sleutel en dus niets om te synchroniseren.

Suite Sync werkt op Safe 3, Safe 5 en Safe 7, en aanzetten vraagt altijd een bevestiging op het apparaat, ook op mobiel: "Confirm the action on your Trezor hardware wallet". Model One en Model T gebruiken het oude labelformaat en doen niet mee.

Op iOS werkt alleen de Safe 7. Die verbindt over Bluetooth; alle andere modellen gaan over USB en dat ondersteunt iOS niet, waar alleen view-only via XPUB overblijft. Zie de Trezor Suite Lite FAQ.

De combinatie van die twee wordt nergens genoemd, en dat is precies waar deze store een dag aan kwijt was: de Suite Sync-pagina beschrijft de mobiele stappen alsof ze op elk toestel werken, terwijl de bevestiging op een model dat niet aan een iPhone kan nooit gegeven kan worden. Er bestaat een forumdraad "Suite Sync: confusion about device compatibility", niet leesbaar zonder inloggen, wat erop wijst dat meer mensen hier tegenaan lopen.

En één getal om te onthouden: hun eigen dienst kapt af op 1 MB, ongeveer 2500 bewerkingen per apparaat, 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:

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 en 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:

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