How an owner grants another person the right to open a lock — the components, data model,
message flows, key custody, and the trust boundaries an auditor needs to verify.
Model: owner-signed capabilitiesTrust: server = post officeUnlock: offline BLEStore: canonical asset object
00 What this system is
A smart-lock access product: an ESP32 relay on the door, a phone app, and the
Honkio backend. The lock verifies everything itself and never trusts the network.
A door is a Honkio asset owned by a user. The lock enrolls the owner's Ed25519 public key on
first pairing (trust-on-first-use). To let someone else in, the owner signs a capability bound to
that person's public key; the capability travels through the server and is presented to the lock over
Bluetooth. The lock checks the owner's signature and the grantee's proof-of-possession locally — so an
unlock works with no internet, and the server can never forge one.
Core invariant
Only a key enrolled on the lock as ROLE_OWNER can authorize access. The server stores and
forwards opaque bytes; it holds no key that opens any door.
01 Architecture
Five parties. The two phones and the lock hold keys; the server and Firebase only
move bytes between them.
Component & data flow. Keys live only on the phones and the lock. The server
exposes three canonical surfaces — the asset (with its read ACL), the user-only private store, and a
push trigger — and never sees a usable private key.
02 Data model
Everything hangs off the asset object in the app → company → user
hierarchy. There is no bespoke "device" object.
Asset-centric model. The asset carries only public material (lock pubkey, ids,
owner-signed capabilities). Every private signing seed lives in
user_merchant_private_data, readable by the owning user alone — never in the shared asset,
which grantees can read.
03 Provisioning — trust on first use
The first key enrolled on an unowned lock becomes its owner. No pairing secret is
needed because an unowned lock has nothing to protect yet.
Provisioning. After the first enrollment the lock refuses new keys unless an owner
opens the pairing window — so a stranger cannot silently add themselves. The owner seed is backed up to
the user-only store, not the asset.
04 Delegation — owner shares a door
The grantee shows an identity QR; the owner scans it, signs a capability for that
exact public key, and writes it onto the asset. A push tells the grantee at once.
QR-identity delegation. No consent round-trip and no server-side authority: the owner
signs locally and the server only stores the bytes and fires a push it has verified the owner is
entitled to send.
05 Guest unlock — verified on the lock, offline
The grantee proves possession of the granted key; the lock checks the owner's
signature on the capability and the grantee's signature on the challenge. No network involved.
Capability presentation. Five independent checks run on the lock. Any failure returns
a status code and the relay stays shut. The single nonce is consumed per attempt, so a captured
message cannot be replayed.
06 Lock ownership lifecycle
A lock is unowned, owned, or reset. Losing the owner key is recoverable only by a
physical factory reset — the safe failure direction.
Ownership states. Once owned, the enroll characteristic rejects writes
(GATT_WRITE_NOT_PERMITTED) unless the pairing window is open — preventing silent takeover.
wipe erases enrolled keys so the next enrollment re-establishes ownership.
07 Key custody & recovery
Keys are kept on the phone and fetched from the user-only server store only when a
device doesn't have them. Recovery depends on what was lost.
Key
Signs
On device
Server backup
Who can read it
Owner per-lock seed
capabilities (grants)
secure storage
private_data.owner_seeds[]
owner only
Grantee per-user seed
guest unlock (M)
secure storage
private_data.access_seed
grantee only
Lock device key
challenge (identity)
lock NVS
pubkey pinned in asset
pubkey public
Owner-signed capability
—
—
asset.data.authorizations
readers (public-safe)
Recovery matrix. As long as either the phone-local key or the user's
server backup survives, ownership is recovered without touching the lock. Only losing both forces a
physical reset.
08 Trust boundaries
The central audit claim: no server-side actor can open a door. Draw the line at what
each party can and cannot do.
Server as post office. A full database dump plus message-broker access yields no
working unlock — the private seeds sit in the user-only store and the owner key never leaves the
owner's device. This is the property auditors should attempt to break.
Why private seeds are not in the asset
The reader ACL returns the whole asset to every grantee of that door. A private seed placed there would
leak to co-grantees. Hence seeds live in user_merchant_private_data, scoped to the token's
own user_id with no admin read path.
09 Access-control matrix
Who may do what, and where it is enforced.
Action
Owner
Grantee
Company admin
Enforced by
Open the door
yes
yes
no*
lock (Ed25519)
Grant / revoke access
yes
no
no
lock owner-role gate
Read the shared asset
yes
if reader
yes
asset reader ACL
Write the asset
yes
no
yes
assets:* perm
Read another's private seed
no
no
no
token-scoped store
Factory-reset the lock
physical
no
no
serial console access
* the admin owns/can-read the asset but holds no key enrolled
on the lock, so cannot produce a valid unlock.
10 Security properties to verify
The checklist an audit should actively run, not just read.
#
Property
How to test
1
DB dump + broker access opens no door
attempt to derive a valid signature from stored data — must fail
2
Only ROLE_OWNER may sign a grant
capability signed by a member key → lock rejects
3
Capability bound to one lock
present a grant to a different lock_device_id → rejected
replay a captured 280-byte unlock → nonce consumed, rejected
6
Private seed is user-only
admin API for another user's private-data → no such path
7
No silent takeover
enroll on an owned lock without pairing window → WRITE_NOT_PERMITTED
8
Notify is authorized
push a user not in the asset's readers → 403
Auditor's one-line summary
Compromising the backend yields metadata and public keys, never access. The only paths to opening a door
are holding an enrolled key (owner/grantee) or physical possession of the lock's serial console.