LegalRecorder
All the nerdy details

How the proof works.

Every claim LegalRecorder makes about a recording rests on standard, public cryptography that anyone can check. Here is each piece, what it proves, and why that matters when someone questions your recording.

The recorder

A ring buffer made of audio files

The app runs as an Android foreground service of the microphone type, with a persistent notification, so Android lets it record with the screen off. Audio is encoded as 16 kHz mono AAC and written as short pieces (segments) of a couple of minutes each. When the total passes the window you chose, the oldest piece is deleted. Keeping copies a range of pieces out of the loop into protected storage.

14:0014:02 14:0414:0614:08 14:1014:12 oldest:deleted kept: copied out,never recorded over being written ← older · · · newer →
About 14 MB an hour. Six hours fits in under 100 MB; the window can be anything from 15 minutes to two days.
  • Crash-safe pieces. Each piece is a self-contained file, so if Android kills the app, everything up to the last piece survives and the service restarts itself. The gap is visible in the chain.
  • Plays anywhere. A kept recording is joined into one standard .m4a, and its original pieces travel with it in the evidence pack.
  • Scheduled windows are protected. Pieces inside a scheduled keep's window are exempt from pruning until it's kept, however long the hearing runs.

What it gets you: the moment was recorded before anyone knew it mattered, and you only spend storage on what you choose to keep.

Five layers of proof

Unchanged, made on this phone, and existed by a certain time.

Each layer stands alone and is checked independently. An attacker would have to defeat all of them, including ones run by organisations that have nothing to do with us.

Layer 1 · Integrity

A SHA-256 hash chain

Each audio piece is hashed with SHA-256, a fingerprint that changes completely if a single bit of the audio does. Each piece's link then hashes the previous link together with that fingerprint, the piece's name and its start and end times. It's the same idea that holds a blockchain together, without the blockchain.

link 13a6a…ec88b link 240ca…45096 link 3e1b0…9c2f1 link 477c2…0ab3e piece 1piece 2 ✎piece 3piece 4
Edit piece 2 and its hash changes, so link 2 changes, so links 3 and 4 change. Every link after an edit breaks, including the ones already signed and timestamped.
link = sha256( prev | sha256(audio) | name | startMs | endMs | ctx )
prev   3a6a5288…ec88b
audio  9f04c1d7…b2e10
link   40caf634…45096

ctx commits to the optional surroundings for that piece (see Where and who), so they're as tamper-evident as the audio.

What it gets you: nobody, including you, can cut out a sentence, swap a section or reorder the audio without it showing. Gaps from when recording stopped are visible, not hidden.

Layer 2 · Origin

Signed in hardware, attested by Google

On first run the app creates an ECDSA P-256 key in the Android Keystore, inside the phone's trusted execution environment, or a StrongBox security chip where the phone has one. The private key can sign but can never be read out, not even by the app. Every chain link is signed with it.

The key comes with a key attestation certificate chain, rooted in Google, that describes it from the hardware's point of view:

key held in      trusted execution environment
made by package  com.tonyandfam.legalrecorder
app signed by    5d4d11cb…efb7af
bootloader       locked, verified boot
revocation list  not revoked

The verifier checks the chain against Google's attestation roots and its live revocation list, and that the attested app signature is ours.

What it gets you: the recording provably came from this app, on one particular physical phone running unmodified Android. A copy of the app on a rooted phone, or a script on a laptop, can't produce these signatures.

Layer 3 · Time

RFC 3161 trusted timestamps

A phone's clock can be set to anything. So every ten minutes, and on every keep, the app sends the latest chain link (only the hash) to a public timestamp authority: Sectigo, with DigiCert as a fallback. The authority signs a token saying "I saw this hash at this time", following RFC 3161, the standard used to timestamp code signatures and signed PDFs. Kept recordings get their joined recording.m4a timestamped too.

authority Sectigo Public Time Stamping Signer
time      2026-10-02 22:43:53 UTC
for hash  40caf634…45096
signature valid, chains to trusted root

No signal?

Requests queue on the phone (Android WorkManager) and go out as soon as there's a connection. Because a link commits to everything before it, one later stamp still covers the whole recording up to then.

What it gets you: an independent, commercial certificate authority vouches that the recording existed by a given time, so it can't have been made up afterwards. The token is signed, so it can be checked years later without contacting anyone.

Layer 4 · Transparency

An append-only Merkle-tree log

The same hashes, signed by the phone's key, also go to the LegalRecorder log. Every ten minutes the server gathers the new entries into a Merkle tree and gets the tree's root timestamped by the same authorities. The phone then fetches its Merkle path: the few sibling hashes that lead from its entry up to the root. It's the design behind Certificate Transparency, which keeps the web's HTTPS certificates honest.

root 9b1e…41d0a timestamped 22:50:50 h(1,2)h(3,4) sibling entry 1 sib.your entryentry 3entry 4
To prove your entry is in batch #2, you need only the two outlined siblings. Hash up the gold path and you get the timestamped root.

What it gets you: a second, independent record of when each recording existed, that the people running it (us) can't quietly rewrite. Adding a backdated entry would change a root that's already been timestamped by someone else.

Layer 5 · Portability

The evidence pack

Share sends two things: a playable recording.m4a, and a .zip evidence pack that carries everything needed to check it, offline, forever.

recording.m4aThe kept recording, trimmed to the range you chose.
Original piecesThe untouched audio pieces it was cut from, which the chain hashes cover. They run a little past the trimmed recording at each end.
ManifestThe chain: every piece's hash, times, link and the phone's signature on it, plus the key attestation.
proof/The RFC 3161 tokens and the log's Merkle paths and batch timestamps.
surroundings/Only if turned on, and only the fields you chose to include. The rest go in as digests.

What it gets you: the proof travels with the recording. Email it, put it on a USB stick or file it with the court; it doesn't depend on our servers, our company or the app still existing.

Checking

Verify in the browser, or with OpenSSL

The verify page

Drop the pack on the verify page. Every check runs in your browser with its built-in Web Crypto; the recording is never uploaded. You get plain-language results, a map if location was included, and the satellite and hum analyses.

Offline, for experts

tools/verify.py needs only Python and OpenSSL, and reads trusted roots from the repository. An expert witness can audit every step.

$ python3 tools/verify.py pack.zip
[PASS] 9 of 9 original audio pieces unchanged
[PASS] The hash chain is intact (9 links)
[PASS] Phone key attested by Google, held in the TEE
[PASS] Chain timestamped by Sectigo, 22:43:53 UTC
[PASS] In the LegalRecorder log, batch #2

What it gets you: nobody has to take our word for anything. The other side's expert can check your recording with their own tools.

Where and who · optional, off by default

Context that's as hard to fake as the audio.

Each is a separate switch in Settings. While recording, observations are sealed into the same chain as the audio, so they get the same signatures and timestamps.

Surroundings

Location, cells, Wi-Fi and Bluetooth

Every two minutes

  • A GPS fix and a network fix, with accuracy, satellites used and Android's mock-location flag
  • The mobile cells in range and a Wi-Fi scan
  • A short Bluetooth LE scan of nearby devices

Cross-checked

A GPS fix's time comes from the satellites, which also checks the phone's clock. The verify page can compare the Wi-Fi networks and cells against BeaconDB, an open database of where networks are, through a server endpoint that keeps nothing.

Selective disclosure

Observations are sealed per audio piece. The link's ctx is a hash over one salted digest per field:

ctx = sha256( "=" sha256(salt | field | data) "\n" ... )   # fields sorted by name

When sharing you pick which fields to include. The rest go in as digests only: the chain still verifies, and you can reveal them later. The random salt stops anyone guessing a withheld field (say, which cell tower) by trying candidates against its digest.

What it gets you: you can prove where you were without giving away more than you need to, and you can't be accused of adding location afterwards: it was sealed while recording.

Raw satellite measurements

Recomputing the position from the signals

A location fix is only the phone's answer, and an app can fake it. So every ten minutes the app can save fifteen seconds of the GPS chip's raw inputs (Android GnssMeasurement): each satellite's signal transmit time, Doppler shift, carrier phase and strength, plus the receiver clock and gain.

solved from signals + orbits vs. the phone's own fix: typically 10–30 m apart ✓ pseudorange + Doppler from each satellite
Orbits come from that day's IGS broadcast ephemeris (from BKG, via the server).
  • Physics checks. The verifier checks the measurements behave like real signals: distance and Doppler agree, distances are physically possible, strengths vary between satellites, receiver gain is steady.
  • Independent position. It solves the position from the measurements and the satellites' published orbits and compares it with the location the phone reported. On a real Pixel 7 log, that lands within about 10 m, as does RTKLIB run on the RINEX export.
  • Offline too. node tools/gnss.mjs pack.zip --rinex out.obs exports standard RINEX for anyone's GNSS software.

What it gets you: a mock-location app can't fake this. It would take a radio transmitter that fakes every satellite at once, consistent with real orbits for that day.

Witness beacons

Two phones vouching for each other

Each phone broadcasts a short token over Bluetooth LE, and logs the tokens it hears from other LegalRecorder phones.

token = sha256(secret)[0..16)     # fresh random secret every 10 minutes
phone A  commits secret in its signed chain, broadcasts token
phone B  logs "heard token" in its own timestamped chain
later    A reveals secret → sha256(secret) matches B's log ✓

B's timestamped record of hearing the token, plus A's signed commitment to the secret behind it, shows the two phones were within Bluetooth range at that time. Tokens are random and change every ten minutes, so they can't be used to track anyone.

The one way to fake it is a relay: someone near A passing the token to someone near B in real time.

What it gets you: two independent recordings that corroborate each other, from two different phones with two different keys.

Electrical network frequency

The hum of the power grid

Mains power hums at about 50 Hz, but the exact frequency drifts by millihertz from moment to moment, in step across a whole grid, and grid operators log it. Recordings made near anything mains-powered pick up that hum. Matching it is an established forensic technique.

50.000 Hz jump: a possible cut recording's humgrid operator's log

"Analyse the hum" on the verify page, or node tools/enf.mjs pack.zip --reference grid.csv offline, traces the hum once a second, flags jumps, and finds where the trace matches a grid frequency log. Phone microphones often filter out 50 Hz itself, so it looks at harmonics up to the 4th too.

It only works if something mains-powered was near the phone.

What it gets you: a date and time written into the sound itself by the national grid, completely independent of the phone, the app and us.

Privacy

Only hashes leave the phone

A SHA-256 hash is one-way: it reveals nothing about the audio, and the audio can't be reconstructed from it. That's all the timestamp authorities and our log ever see.

What leaves the phone

  • SHA-256 hashes of the audio
  • The phone key's signature on each
  • Whatever you choose to share, when you share it

What never does

  • The audio, unless you share a recording yourself
  • Your names, tags and notes
  • Calendar contents (calendar sync reads them on the phone)
  • Anything to an advertiser or analytics service

Online timestamps can be turned off in Settings. Recordings then rely on the hash chain and the phone's signature alone.

Limits

What the proof shows, and what it doesn't

Evidence is only as good as the claims made about it. These are ours.

It shows

  • The audio hasn't changed since it was recorded
  • It existed by the time of its timestamp
  • It was made on a particular phone, by this app
  • Where the recording has gaps, for instance when it was stopped

It doesn't show

  • Who is speaking, or what was meant
  • What was said outside the part that was kept
  • The exact start time beyond the phone's clock; timestamps bound it from above
  • Anything from a time the recorder was off
  • Whether a court will admit it. That is for the court to decide.

See it for yourself.

Drop an evidence pack on the verify page. Every check above runs in your browser, and nothing is uploaded.