DRAFT 0.3.0 · definitions of the BSA kinds are a draft (round 5); two pilot circles run on test infrastructure · last checked 27 Oct 2026
On this page

BSA ID and the Lana profile: technical specification DRAFT 0.3.0

Plain-words text · Technical specification · All documents

English text rendered from the project's source documents. The Slovene original is on lananostr.site. Where this text and the definitions differ, the definitions win.

Precedence: where this document and definitions 0.3.0 differ, the definitions (docs/bsa-records-0.3.0.json) win; the difference is a bug to fix in this document. The definitions pin a frozen copy of this text (docs/0.3.0/) by its sha256 (meta.normative_spec); this living text may change afterwards, and a change reaches the definitions only when they are generated again, which then needs a new yes.

Scope and status#

  • Definitions 0.3.0 (0.2.0 was never published and is superseded). RELAY_PUBLISH_OFF=1 stays until the definitions are published and the project owner says yes (proposal P7: one yes per version and per kind switched on).
  • Decided: BSA ID = x-only Nostr pubkey (HEX) of the same private key as the LANA WIF (16 Oct 2026); HEX only, never npub (16 Oct 2026; the rule "never npub" holds since 5 Oct 2026); KIND 0 mandatory and minimal, lanaWalletID filled by the app (16 Oct 2026); member home mandatory (16 Oct 2026); one app per circle, signing in the browser (16 Oct 2026); root relays only from KIND 38888 (14 Sep 2026).
  • Decided (the project owner, 16 Oct 2026, answers to questions 1–9): the ledger knows people only by HEX key; names only in KIND 0 and 30916, never in any ledger event; warning at every free-text field; no 43925 and no legal-review gate (Q5). Circle relays from day one, ≥ 4 relays with ≥ 3 operators; 30916 lives on them (Q2). One witness to start, then 4 with 4 operators, q = 3, at most one per operator; any signer of the set attests a 30916 (Q3). No bank data and no payouts in BSA; no held_out (Q6).
  • Decided (the project owner, afternoon of 16 Oct 2026, point 6a): no money deposits in BSA: no deposit acts, no custodian, no paid_in in the account line; a wave grows only from receipts.
  • Follows from the principles (16 Oct 2026): every member listed with name words; every balance and payment public in the circle's blocks (43911 v3); membership = signed acts (43916 join, leave) booked in the ledger.
  • Decided (the project owner, 17 Oct 2026, answers to phase C; _reports/2026-10-16-knjiga-na-relejih/odlocitve-2026-10-16.md, sections "Odgovori na fazo C" and "Odgovor na pojasnilo točk 5 in 1"): C5 a circle is named by its circle id, the event id of its found act (43916, t = found), signed by the founder; every other BSA event names its circle by ["c", <circle id>] (the 30916 included; the found act itself carries ["c", ""], the 30910 the circle id as d; relays are queried by #c), no BSA kind uses an a tag, and scope16 = the first 16 hex characters of the circle id. The circle key only stamps the 30910 mirror (and, once, the found act in block 1; that it signs nothing else is a proposal) and can be replaced (circle_key_change: k Heart members, change_notice_s of notice, 72 h by default since E-1, booked by the witnesses); it is not part of the circle's name, and it is unrelated to a member's key (guardians: question 11, still a proposal). C3 refusals of keys that are neither members nor applicants are answered and never booked; at most REJ_PER_AUTHOR_PER_DAY refusals of one key are booked per local day (20 in version 1; above that the intake answers 429 and books nothing); every act carries expires at most ACT_TTL_S after it was signed (7 days in version 1). Both values were world rule values of the BSA Rules (43920, C1); since 23 Oct 2026 they are circle parameters (rej_per_author_per_day, act_ttl_s) with these defaults, and 43920 is retired [Decided 23 Oct 2026, E-1].
  • Decided (the project owner, 23 Oct 2026, answers before phase E; same source file, section "Odgovori pred fazo E" with "Dopolnitev" and "Referenca"): E-8 a person is a member of one circle at a time; a key's newest 30916 names its last circle; only a member of an existing circle founds, one circle per founder key; one person with two keys is not prevented (that needs Lana's person check). E-9 nobody moves without a reference: a new circle admits a key whose newest 30916 names another circle only with that circle's reference (the reference entry; its findable copy is KIND 43921, the number a proposal); the reference is issued by itself after a leave when the balance is ≥ 0 and no OWN process is open [the OWN condition also without a debt: Decided 23 Oct 2026, E-9 (a)]; the built step moves with the person: the new circle grants the same wave and pays the same amount to the old circle's community, which closes the old account and returns it to its origin [Decided 23 Oct 2026, E-9 (b); the grant, reference_out / reference_in and close_wave, G = ceil(W × r_new / r_old) under the old circle's 43919: Proposal, §3.11 of docs/knjiga-na-relejih.md ("Membership, exclusion, the reference and closing accounts")]. E-1, E-2, E-5: the time values are circle parameters; the stewards are the Circle Heart (the technical names steward_* stay); the objection is replaced by the vote.
  • Proposals: index relays for member homes at scale (question 16); circle link/QR with the circle id, at least two relay hints and the genesis digest as an extra pin (question 10; the circle id itself is decided, C5); guardians (question 11); P10 (with bsa_home = the circle id, below), P11, P7.
  • Glossary used here: witness = signer; node = the witness that proposes (proposer); act = 43916; block = 43911 v3; seal = 43917 v3; snapshot chunk = 43918; member home = 30916 v3; circle id (rojstna številka kroga) = the event id of the circle's found act (ustanovna listina), its name for ever (C5); genesis digest (prstni odtis prvega bloka) = the digest of block 1, written "genesis fingerprint" in the English text above; project (projekt) = a public plan with an amount (project_propose, Q8); project wallet (denarnica projekta) = its keyless account, written project:<id>; reserve (rezerva) = the circle's keyless reserve; rate quote (ponudba tečaja) = 43919 v3, signed by the receiving circle's node (Q7); vote (glas) = a vote act, "for" or "against" (an "against" with a short public reason), which replaced the objection (project_object) on 23 Oct 2026 [E-5]; reference (referenca) = the reference entry of a member who left with no minus, and its findable copy KIND 43921 [E-9].
  • Labels: [M] measured 16 Oct 2026, [C] relay configuration read 16 Oct 2026, [R] earlier report, [E] estimate.

Derivation#

  • Private key k (from any WIF form: prefix 0xB0 or 0x41, compressed or uncompressed; read all, write 0xB0). Public point P = k·G.
  • BSA ID = x-coordinate of P, 32 bytes, lowercase hex (BIP-340 x-only), equal to the Nostr pubkey and to users.id in v2.
  • LANA addresses: Base58Check(0x30 ‖ RIPEMD160(SHA256(pubkey bytes))) for the compressed (02/03 ‖ x) and the uncompressed (04 ‖ x ‖ y) public key.
  • Checking an address against a BSA ID: build the 4 candidates (parity 02 and 03, each compressed and uncompressed; for the uncompressed form compute y from x for each parity) and compare. Test vectors: take them from lana-paper-wallet npm run verify:wif (both prefixes, both parities, both forms).
  • Sign-in stays as today: kind 27235 with lana_pubkey (33-byte 02/03 key); the server requires lana_pubkey[1..] = event.pubkey. Sign-in proofs and session tokens are never published.

Names (decision 5)#

  • Only two places hold a person's name: KIND 0 (name, display_name) and 30916 (n tags). KIND 0 is replaceable and 30916 addressable, so a relay keeps only the newest version of each.
  • No ledger event carries a name: 43910 notes, 43911 blocks and their account lines, 43916 acts (join, leave, invite, vouch, guardians, votes, exclusions …), 43917 seals, 43918 snapshot chunks, 43919 rate quotes, 43921 references, 43924 statements and the cross-circle copies carry HEX keys only. Apps resolve names from KIND 0 at display time and never write a resolved name into a note, act, block, seal or snapshot.
  • Free text (a note's content purpose ≤ 180 characters, the reason of an "against", the reason of an exclusion, a project's or motion's title and description) stays inside the signed event and is public for ever. Every input for it shows: "public for ever; do not write names, personal or bank data". There is no 43925 side record and no sha256-committed free text; there is no legal-review gate before the first public booking.
  • Migration: names held by the v2 server (users.display_name, the kind_0_profiles cache) are caches; they are never copied into v3 blocks, acts, seals or snapshots, including when the whole history is published from genesis.
  • Stated openly, not a question: old copies of a KIND 0 or 30916 that someone saved (archives, other relays, sync tools, screenshots) can keep an old name, and free text someone writes can contain one; the ledger itself never does.
  • No bank data anywhere in BSA (decision 6): no field, tag or act of a member's account carries bank data.

KIND 0 (next revision, read the current version from origin/main before editing)#

  • Optional content field bsa_home: "<circle id>" (64 hex: the event id of the circle's found act; proposal P10, the value follows from C5). Not the address of the circle record: that address names the circle key in office, which can be replaced (C5); readers find the circle record by #d = the circle id. A pointer only; the member's 30916 and the circle's ledger are the truth.
  • Optional bsa_id is not added (the event pubkey is the BSA ID).
  • Minimum BSA reads from KIND 0: name (and display_name if present). currency is only a suggestion; the circle's currency rules.
  • lanaWalletID: the Lana relay gate accepts a KIND 0 only with a non-empty lanaWalletID (or a 30889-registered author). When the app creates a profile for a member who has none, it sets lanaWalletID to the compressed address of the member's own key, plus name, display_name, currency (the circle's), and the ["lang",…] tag. Other Lana-required fields are left empty.
  • The app writes a KIND 0 only for a member who has none (creation), never over an existing profile, until the Lana profile forms keep unknown fields.
  • New rule for every writer: keep every content field and tag you do not manage, unchanged. MejmoSeFajn Profile.tsx and POS createAndSignKind0 must be fixed before v2 ever writes KIND 0 (separate task; check those repos' occupancy first).
  • Published as its own kinds.json revision, never bundled with the BSA 0.3.0 kinds.
  • KIND 0 stays on the Lana root relays. At 10 billion members that is ~10 TB per root relay; the smallest root relay fits ~11 million profiles today [E]. Question 16 decides where profiles of large circles live.

30916 v3 Member Home#

  • Addressable, pubkey = the member, d=letsbalance. Size ~0.8 KB [M: 739 B].
  • Tags, in this order (d, c, g, join, attest, n…, w…, v): ["d", "letsbalance"], ["c", <circle id>] (the event id of the circle's found act, C5), ["g", <genesis digest>], ["join", "<height of the rule entry (member_join) that admitted the member>"], ["attest", <signer hex>, <since: the day, YYYY-MM-DD>, <sig>], ["n", <full folded name>] and up to 8 ["n", <word>] (at most 9 n tags), optional ["w", <LANA address>], ["v", "3"].
  • sig = BIP-340 signature of the signer key over sha256("lb-home/3|" + member + "|" + circle id + "|" + genesis digest + "|" + join + "|" + since), where circle id is the 64-hex circle id of the c tag, genesis digest the digest of the g tag, join the join height and since the day of the attest tag (proposal; 0.2.0 used "lb-home/1|" + member + "|" + circle + "|" + since_day, with the circle address). The signer must be in the circle's signer set at the join height (bootstrap: the single signer, today the circle's node). Any signer of the set may attest [Decided Q3].
  • n values: Unicode NFKD, combining marks removed, lower-case, split on non-letters, empty words dropped, single spaces; taken from the verified KIND 0 name/display_name. Readers drop results whose n values are not in that KIND 0. The attestation sig does not cover the n tags, so a re-signed 30916 after a name change keeps its attestation.
  • w is accepted by readers only if it is one of the 4 candidates of the BSA ID.
  • Truth: the rule entry at height join admitting this member, and no later booked leave. Both carry the member's HEX key only. 30916 is a pointer; a 30916 whose g or join does not match the ledger of the circle its c names is ignored. Deleting a 30916 changes nothing in the ledger. Whether relays refuse a member's KIND 5 aimed at their own 30916 is not fixed; proposal (E-9): root relays never delete a 30916, since a key's newest 30916 names its last circle. One circle at a time [Decided 23 Oct 2026, E-8]: a key's newest 30916 (by created_at) names its last circle; a new circle's node reads it (on the root relays) before booking a join, and a key whose newest 30916 names another circle is admitted only with a valid reference from that circle (["e", <43921 id>, "", "reference"] in the join, checked against that circle's block; docs/upravljanje-kroga.md) [Decided 23 Oct 2026, E-9; the check: proposal]; a key with no 30916 anywhere was never a member and needs none [proposal]. Said openly: a deleted 30916 or a second key of the same person is not caught (that needs Lana's person check).
  • Joining: the member signs the join act and the 30916 in the browser; the signer attests; the proposer books the admission; joining is complete when that block is final.
  • At every sign-in the app compares the KIND 0 name with the n tags and asks the member to re-sign if they differ (the node cannot sign for the member). The relay then keeps only the new 30916, so the old name words are no longer searchable.
  • Transition: existing members stay full members until their next sign-in asks them to sign their 30916.
  • Attestations by a signer key whose window was closed because of theft are ignored; affected members re-attest. Attestations by a signer that left normally stay valid.
  • Same signed 30916 can be passed as a link or QR; it is checked exactly like a published one.
  • Where: the circle's relays from day one [Decided Q2]; also the root relays while the circle borrows them. At scale (proposal, question 16): plus index relays sharded by the first hex digits of the member key; ~15 TB of homes per copy at 10 billion members, the smallest root relay fits ~7 million [E].
  • Write rule on every relay that stores 30916 [Proposal; not in relay/ledger-write-policy.py as built, which admits a 30916 by its author like an act and of these checks makes only the last one (no expiration, no -); readers make these checks]: d=letsbalance; at most 9 n tags; attest verifies against a key in the signer set of the circle named by c (its circle id; the set read from its ledger); g equals that circle's genesis digest; no expiration, no -.

Membership check before paying#

  • S = the circle's signer set, read from its ledger (genesis and roles entries), cached ≤ 24 h.
  • On 2 circle relays in parallel: {"kinds":[43911],"authors":S,"#p":["<hex>"],"limit":1}. The member's newest line (status) in a block with a valid quorum decides; aseq continuity is checked where history is shown.
  • If the app has the member's 30916: {"kinds":[43911],"authors":S,"#h":["<scope16>:<join height>"]} confirms the admission entry (scope16 = the first 16 hex characters of the circle id in the 30916's c tag).
  • The proposer and every signer check membership of payer and receiver again at booking (not_member refusal). The refusal is booked in public when the note's author is a member or an applicant, at most rej_per_author_per_day refusals of one key a day (a circle parameter, 20 by default [E-1]; above that the intake answers 429 and books nothing); a note signed by a key that is neither a member nor an applicant is only answered, never booked [Decided 17 Oct 2026, C3].

Member safety: guardians (proposal, question 11)#

  • Only if the project owner accepts question 11. Acts (43916) from the "member safety" group:
    • guardians_set, signed by the member: names the guardian keys (HEX) and how many must sign (k of m; example 2 of 3). A new guardians_set replaces the previous one once booked.
    • freeze, signed by each guardian separately, naming the member. When k distinct named guardians' freeze acts are booked, a rule entry sets the member's line status to frozen; notes signed by the frozen key are refused in public.
    • key_move, signed by each guardian separately, naming the old and the new key. When k are booked, a rule entry closes the old line and opens the new key's line with the same balance, wave, cap, carry and debt (there is no held_out and no paid_in: no payouts and no money deposits in BSA, decisions 6 and 6a); the history stays linked through the entry.
  • Guardians never sign notes for the account. Guardians do not affect LANA on the Lana chain.
  • Not fixed: a notice period before key_move takes effect; whether the new key must co-sign; how a freeze is lifted; how notes addressed to the old key are answered; whether aseq and note_n continue or restart on the new key.

Queries#

  • By BSA ID, one REQ to 2 relays in parallel: {"kinds":[0,30916],"authors":["<hex>"]} and {"kinds":[30889],"#d":["<hex>"]}. The circle's relays as named by its ledger [Decided Q2]; while the circle borrows them, also the root relays; at scale also index relays (question 16).
  • By name: {"kinds":[30916],"#n":["janez novak"],"limit":50}, then {"kinds":[0],"authors":[<up to 50 hex>]}; drop results whose n values are not in the KIND 0 name or display_name.
  • By circle: {"kinds":[30916],"#c":["<circle id>"]} lists the member homes that name the circle (paged, below); they are pointers only, membership is read from the ledger (above).
  • "Not found" needs EOSE from ≥ 2 relays; a relay that does not answer is reported as "relay did not answer", never as "not found"; report the real "answered N of M".
  • Raw WebSocket readers, never the nostr-tools pool for these reads; at most 500 events per filter, so page with until inclusive and de-duplicate by id.

Tests (when built)#

  • BSA ID ↔ 4 addresses for both prefixes, both parities and both forms, using the lana-paper-wallet vectors.
  • Folding: "Čebašek Žiga" → cebasek ziga; writer and reader give the same result.
  • No secret input or QR prompt is labelled "ID"; npub is never rendered.
  • A silent, slow or partial relay never turns into "not found".
  • A 30916 without a matching join entry, with a wrong g, attested by a key outside the signer set, or whose attest signature does not verify over the circle id of its c tag is ignored.
  • A member with a booked leave is not a member, whatever 30916 exists; a deleted 30916 does not end membership; a join of a key whose newest 30916 names another circle is refused (not_allowed) without that circle's valid reference [E-9].
  • Payment to a non-member is refused not_member in public, also when the app's own check was skipped; a note signed by a key that is neither a member nor an applicant is answered not_member and never booked (C3).
  • No v3 ledger event (43910, 43911, 43916, 43917, 43918, 43919, 43921, 43924) contains any member's name or display_name from the cached KIND 0 profiles; a full-history publish from genesis is scanned the same way.
  • After a name change and a re-signed 30916, search by the old name words finds nothing and the member's ledger history is unchanged.
  • Every free-text input shows the "public for ever" warning.
  • If question 11 is accepted: k−1 guardians cannot freeze; a frozen key's notes are refused; key_move keeps every sum and the circle's ledger at 0; guardians cannot sign a note for the account.