Footnote is a local-first personal wiki of markdown documents with support for mirror across your own devices and sharing with known, trusted peers.
Your notes are a markdown wiki. Notes live in a vault and only ever exist on your devices. They are always encrypted in transit over the network, and only transmitted to known devices and known peers.
A vault is folder on a filesystem that represents your own notes and the notes that have been shared with you.
Your vault has a special directory called footnotes. Inside footnotes are a
list of directories, one per trusted contact, listed under the name you want to
call that person. Trust is established with a one-time, out-of-band setup
process. Each document has an owner, the owner updates them as neded and sends
them to you when there are changes.
An example of something you might have in your vault is: footnotes/mom/recipes.md
"Share" is the exchange of documents among trusted peers. The view of your documents is unique to each person you share with.
"Mirror" is defined as coordinating an eventually consistent view of all your notes across your owned devices, including things shared with you. The goal of a mirror is that you can update the log of your running activities while you're out and about, and process them on your desktop when you home later.
The internet infrastucture required to facilitate data exchanges are Iroh signaling servers. An end user just needs a device that is on somewhere.
Each user can have multiple devices (laptop, phone, tablet, desktop). Every device:
- Has its own Iroh endpoint ID for network connectivity
- Is cryptographically signed by the user's master identity key
- Has a human-friendly name (e.g., "laptop", "phone")
- Can be verified as belonging to that user
A collection of devices owned by a user. Devices in a device group intend to mirror each other.
Only one of the devices maintains the list of devices in the group. The leader is where you make changes, the leader distributes the list of members to other members in the group. The leader device can transfer leadership to a different device.
The device group works best if one of the devices can be online somewhere. It's not required, but having somewhere to back your notes when you're out and about is nice.
This works fine. The device will only attempt to sync when it's online and the app is in use.
This works fine. The mobile device will only attempt to sync when it's online. The desktop device will be online as much as you want it to be. If it's always on, every mobile edit will replicated off the mobile device in short order. As a user, if you're working on your phone and want to start working on your desktop, it's easy to ensure your most recent work gets sync'd to your desktop by opening the app in both places.
In all setups, if you have a mobile device it works best as your device leader. It has the most mechanisms to connect (nearby share, air drop, qr code). Generally, it's worst as an "often on" device as mobile devices tend to prioritize battery usage over everything. Still, phones are able to notify you of new email, new texts, etc, so it's probably possible to make this efficient on mobile as well.
From here the cases are mostly the same. Mobile for device list management, some other device for "often on" connectivity.
Sharing between users favors the often-on devices.
Identity is created on an as-needed basis. When you create a vault, it is in stand alone mode. You are free to create and link notes and research without ever making an identity. To mirror these notes on multiple devices, each devices get an identity and name. Devices are joined together in a device group manually.
To trade notes with another person, a public key identity and contact record will be created. The contact record contains all devices the device group, the group leader and some other metadata. Contact records can be shared between group leaders via a few in band and many out OOB mechanisms.
The records are verified by signing key fields into a digitial signature:
- Username
- Public Key
- Devices
- Iroh Endpoint Id
- Name
After contact records are exchanged, each user has a spot in their notes to
reference their peers' notes. When the contact is imported, the importing user
decides what to call that person. That nickname their directory name in
footnotes/. If you import Mom's contact record and use "mom" for her nickname,
the directory her shared files will be in is footnotes/mom/.
- Records are verifably self consistent
- Public key is not identity, but two records signed with the same key are expected to be created by the same secret key.
- The contact record exchange mechanism is a potential weak point.
- Iroh protocol identifiers are sent in plain text.
- The overall design goal is data ownership, vendor neutrality, no data mining for marketing, etc. The app is not anonymous, you should know your trusted contacts as you will be allowing them to write files to your hard drive.
Key signature fails
Alice creates and exports record:
- Username: Alice
- Public Key: aaa-key-aaa
- Device address: aaa-device-aaa
Charlie creates and exports record:
- Username: Charlie
- Public Key: ccc-key-ccc
- Device address: ccc-device-ccc
Bob wants to sit in the middle of these two. Bob creates
Bob-Alice:
- Username: Alice
- Public Key: aaa-bbb-key-bbb-aaa
- Device Address: aaa-bbb-device-bbb-aaa
Bob-Charlie:
- Username: Charlie
- Public Key: ccc-bbb-key-bbb-ccc
- Device Address: ccc-bbb-device-bbb-ccc
This does seem possible. The mechanism by which you transfer contact records would have to be compromised. You could add additional layer of verification by posting your public key somewhere, or calling up your peer to spot check key fields.
Key signature fails
Alice and Charlie are legitimately connected. Bob wants to listen at ccc-key-ccc. To do this, Bob would need ccc-key-ccc. This is true of any public/private key pair. If this occurs because Charlie's machine is compromised, no deeper level of access is granted.
To accidentally share your full vault with an unintended device, the device's iroh endpoint id would need to be signed in to your user record, and would need to pass verificiation. Internal to the software, the pairing occurs with a one-time pairing code. For an incorrect device to be there unintentionally, a user would need to set up their device to listen, then send the one time code to a peer.
For a 3rd party to insert a false record, they would need hard drive access, which is also where all the files are. The record would appear in the UI.
The sharing protocol:
- Alice wants to share with Charlie
- Alice tags files with here nickname for Charlie (Chuck)
- Alice connects to Chuck, send list of available files
- Charlie calls back and asks for advertised file
- Alice's device checks each contact file for the iroh endpoint id
- Alice's device gets the nickname for the associated user
- Alice's device verifies the contact record
- Alice's device verifies the nickname is in the file
- Alice's device sends the file
The mechanism relies on the contact exchange and iroh's endpoint verifications.
vault_root/
├── .footnotes/
│ ├── device_key # Iroh SecretKey for this device
│ ├── id_key # Ed25519 master key (primary only)
│ ├── user.json # Usrname and verified devices
│ └── contacts/
│ ├── alice.json # Alice's contact record
│ └── bob.json # Bob's contact record
├── home.md
├── inbox.md
├── outbox.md
└── interesting_ideas.md
└── footnotes/ # Trusted users' shared notes
├── alice/
│ ├── research.md
│ └── thoughts.md
└── bob/
└── movie_list.md
---
uuid: 550e8400-e29b-41d4-a716-446655440000
share_with:
- alice
- bob
---
# My Research Notes
I found Alice's research[1] really insightful.
This connects to my earlier thoughts in [[interesting_ideas]].
[1]: footnote.wiki://450332400-e29b-41d4-a716-446655440000
- uuid: Canonical identifier for the document (stable across renames/moves)
- share_with: Array of petnames for users who should receive this document
Linking builds on markdown's footnote style links.
Trust is managed through contact records in JSON format:
{
"nickname": "@alice-jones",
"username": "alice",
"master_public_key": "...",
"primary_device_name": "laptop",
"devices": [
{
"name": "laptop",
"iroh_endpoint_id": "...",
"timestamp": "..."
},
{
"name": "phone",
"iroh_endpoint_id": "...",
"timestamp": "..."
}
],
"timestamp": "...",
"signature": "..."
}A user creates their own record by initializing a vault on disk, which writes out the initial metadata for the primary device. Secondary devices join the first device, building up the full contact record for a user. A user can then share their contact record with a peer. A different user can then import that document.
The contact record has a public key and checksum of the data tied to the public key. All iroh connections are verified connections, tied to particular devices. The user contact record ties together devices with a common public key.
Layers of security:
- Device only listens when you want
- Connections come from endpoints verified by crypto key
- First step in connection is to connection vs imported contact
- Importing contacts is manual
- Trigger mirror immediately on save
- directory level sharing
- drag/drop sharing: in contact_view, ability to include/exclude files
- groups for sharing
- automated testing across supported platforms
- scale testing (targeting 200 peers max)
- "as if" view. browser your files as if you are a user you share with
- view/edit modes
- better distrbuted writes
- automerge, CRDT
- diff-patch-merge https://crates.io/crates/diff-match-patch-rs
- Integration with a more sophisticated sharing, sync or permission system
- 2 way sync (maybe. maybe push only sync is actually a feature)
There is cli testing for the core functionality in tests/ that uses the command line. By hand testing has been done during development on:
- Linux
- android
- macos
- iphone
- windows