Work / Genki to Asia Co., Ltd.
KOKOMATA
A bilingual iOS and Android app in Japan, now being prepared for the stores, where a message left at a place can only be read by walking there. That rule is the product. Keeping it from turning into a way to find out where someone lives is the engineering, and the answer was to make sure the real coordinates never reach a phone at all.
- Ways a phone can read a true location
- 0
- Checks passing in the secrecy proof
- 11/11
- Tables the database guards itself
- 13
- App size, down from 110
- 24 MB

01 / The problem
Someone writes a few lines at a real place. Someone else can only read them by standing there. That needs a map, and a map of where people write things is close to a map of where people live. Products like this do not survive one stalking incident.
So the requirement was never a privacy toggle. The true location of a message had to be impossible to work out from anything the phone can see, while the map still had to feel alive enough that walking somewhere was worth doing.
02 / What I built
The app is React Native and TypeScript, built with Expo, drawing MapLibre maps against a Supabase Postgres database in Tokyo. It never draws a pin. It draws a soft circle, and every circle the phone receives has already been pulled away from the point it stands for.
Distance is not a number either. The server answers in three words, far, near and almost, so even a modified app only learns which band it is in. The three sections below are where the promise is actually kept.
03 / Deep dive
The map shows a place that is not there
Drawing a circle around a point hides nothing if the point is in the middle. Read the centre and you have it. Moving the centre randomly on every request is worse, because the true spot is the average of the samples.
So each circle's public centre is worked out once, from a keyed hash of the message's identity and a secret only the server holds. It lands somewhere between 35 and 65 percent of the way out from the middle, never dead centre, never on the edge, and it is the same answer every single time. There is nothing to average. The phone gets one point, and that point is not the place.
- HMAC-SHA256 keyed by a secret held in the database vault
- Offset band 0.35 to 0.65 of the radius, stable per pin
- Residential areas get larger circles, so homes blur hardest
04 / Deep dive
The database keeps the promise, not the app
A check written in app code is a promise that everyone who touches the code later remembers to make it. For a rule that cannot fail even once, that is not good enough. So the secret was put somewhere the app has no route to.
True locations and message text live in a separate part of the database that the app is granted nothing on. Every table refuses to answer a query unless a rule says it may, including for the owner of the table. The only door is three functions that run with the database's own authority: they check where you are, count how often you have asked, write down that you asked, and hand back the smallest possible answer. Blocking works the same way, inside those rules, so a blocked person is simply not in the results rather than filtered out afterwards.
- Private schema: revoked from PUBLIC, not in the exposed API surface
- Three SECURITY DEFINER gateways, each with a pinned search path
- Every unlock attempt writes an audit row, allowed or refused
05 / Deep dive
Two ways to get distance wrong
A server that believes whatever position an app claims will hand its secrets to anyone willing to edit a request. Now every claimed position is checked against that person's last one. If the jump would have taken a fighter jet, nothing comes back, quietly, reading one band further away than it should. The refusal says nothing and accuses nobody, because real phones do throw one wild reading now and then.
The part that makes it work is what happens to the rejected position: it is thrown away. If a refused jump became the new starting point, the next request from the same fake spot would look like standing still, and the whole check would cost an attacker one request.
The opposite mistake turned up in the field. People stood on top of a message and still could not open it. The gate had been tightened to five metres, and ordinary phone GPS is not that accurate. A gate set finer than the sensor it reads does not enforce precision, it enforces luck. It went back up to the range the design had asked for in the first place, and the app was fixed to keep asking as the answer changed instead of stopping just before it did.
- Impossible-speed threshold is configuration, not a constant in code
- A rejected position never becomes the anchor it would launder
- Anchors go stale, so a traveller is not locked out after a flight
06 / Outcomes
- Nobody but the writer can read a true coordinate, and 11 checks prove it after every change to the database
- Every screen ships in Japanese first, with Japanese and English kept in language files rather than typed into the code
- App size cut from 110 MB to 24 MB
- The reported bug about not being able to open a message was traced to a threshold finer than GPS, and closed
Stack
- Client
- React Native, Expo, TypeScript, Expo Router, MapLibre, MapTiler
- Data
- Supabase Postgres, PostGIS, row-level security, PL/pgSQL
- Platform
- Supabase Auth with PKCE, Expo Push, background geofencing, Sentry
- Ops
- EAS Build and Update, SQL proof suite, audit logging, Japanese and English
Next case study
Wheelness