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=1stays 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,
lanaWalletIDfilled 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_inin 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 asd; relays are queried by#c), no BSA kind uses anatag, 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_sof 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 mostREJ_PER_AUTHOR_PER_DAYrefusals of one key are booked per local day (20 in version 1; above that the intake answers 429 and books nothing); every act carriesexpiresat mostACT_TTL_Safter 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
referenceentry; its findable copy is KIND 43921, the number a proposal); the reference is issued by itself after aleavewhen 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 itsorigin[Decided 23 Oct 2026, E-9 (b); the grant,reference_out/reference_inandclose_wave, G = ceil(W × r_new / r_old) under the old circle's 43919: Proposal, §3.11 ofdocs/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 namessteward_*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, writtenproject:<id>; reserve (rezerva) = the circle's keylessreserve; rate quote (ponudba tečaja) = 43919 v3, signed by the receiving circle's node (Q7); vote (glas) = avoteact, "for" or "against" (an "against" with a short public reason), which replaced the objection (project_object) on 23 Oct 2026 [E-5]; reference (referenca) = thereferenceentry 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 pointP = k·G. - BSA ID = x-coordinate of
P, 32 bytes, lowercase hex (BIP-340 x-only), equal to the Nostrpubkeyand tousers.idin 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
yfromxfor each parity) and compare. Test vectors: take them from lana-paper-walletnpm 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 requireslana_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 (ntags). 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
contentpurpose ≤ 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, thekind_0_profilescache) 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_idis not added (the eventpubkeyis the BSA ID). - Minimum BSA reads from KIND 0:
name(anddisplay_nameif present).currencyis only a suggestion; the circle's currency rules. lanaWalletID: the Lana relay gate accepts a KIND 0 only with a non-emptylanaWalletID(or a 30889-registered author). When the app creates a profile for a member who has none, it setslanaWalletIDto the compressed address of the member's own key, plusname,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.tsxand POScreateAndSignKind0must 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 9ntags), optional["w", <LANA address>],["v", "3"]. sig= BIP-340 signature of the signer key oversha256("lb-home/3|" + member + "|" + circle id + "|" + genesis digest + "|" + join + "|" + since), wherecircle idis the 64-hex circle id of thectag,genesis digestthe digest of thegtag,jointhe join height andsincethe day of theattesttag (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 thejoinheight (bootstrap: the single signer, today the circle's node). Any signer of the set may attest [Decided Q3].nvalues: Unicode NFKD, combining marks removed, lower-case, split on non-letters, empty words dropped, single spaces; taken from the verified KIND 0name/display_name. Readers drop results whosenvalues are not in that KIND 0. The attestationsigdoes not cover thentags, so a re-signed 30916 after a name change keeps its attestation.wis accepted by readers only if it is one of the 4 candidates of the BSA ID.- Truth: the
ruleentry at heightjoinadmitting this member, and no later bookedleave. Both carry the member's HEX key only. 30916 is a pointer; a 30916 whosegorjoindoes not match the ledger of the circle itscnames 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 (bycreated_at) names its last circle; a new circle's node reads it (on the root relays) before booking ajoin, and a key whose newest 30916 names another circle is admitted only with a valid reference from that circle (["e", <43921 id>, "", "reference"]in thejoin, 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
joinact 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
ntags 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.pyas built, which admits a 30916 by its author like an act and of these checks makes only the last one (noexpiration, no-); readers make these checks]:d=letsbalance; at most 9ntags;attestverifies against a key in the signer set of the circle named byc(its circle id; the set read from its ledger);gequals that circle's genesis digest; noexpiration, no-.
Membership check before paying#
- S = the circle's signer set, read from its ledger (genesis and
rolesentries), 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;aseqcontinuity 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'sctag). - The proposer and every signer check membership of payer and receiver again at booking (
not_memberrefusal). The refusal is booked in public when the note's author is a member or an applicant, at mostrej_per_author_per_dayrefusals 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 newguardians_setreplaces the previous one once booked.freeze, signed by each guardian separately, naming the member. When k distinct named guardians'freezeacts are booked, aruleentry sets the member's linestatusto 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, aruleentry closes the old line and opens the new key's line with the same balance, wave, cap, carry and debt (there is noheld_outand nopaid_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_movetakes effect; whether the new key must co-sign; how a freeze is lifted; how notes addressed to the old key are answered; whetheraseqandnote_ncontinue 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 whosenvalues 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
untilinclusive 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
joinentry, with a wrongg, attested by a key outside the signer set, or whoseattestsignature does not verify over the circle id of itsctag is ignored. - A member with a booked
leaveis not a member, whatever 30916 exists; a deleted 30916 does not end membership; ajoinof 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_memberin 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 answerednot_memberand never booked (C3). - No v3 ledger event (43910, 43911, 43916, 43917, 43918, 43919, 43921, 43924) contains any member's
nameordisplay_namefrom 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_movekeeps every sum and the circle's ledger at 0; guardians cannot sign a note for the account.