Skip to content

Latest commit

 

History

24 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

COIA 2.0 — Conventions for Opaque Identifier Aliases

Supersedes COIA 1.x. See CHANGES for what moved and why.

Opaque identifiers — DIDs, KERI AIDs, SSH and PGP keys, payment addresses, UUIDs, passkeys, account numbers — are hard for people to remember, speak, compare and search. Management tools paper over this with a free-text label field and offer no guidance about what to put in it. Users invent ad hoc conventions or none at all.

COIA is a convention for that label. It is not an identifier, not a namespace, and not a protocol.

COIA aliases look like this:

  • cecilia-second-violin-vienna-symphony
  • moi-directeur-général-l-oréal — reflexive: me, as CEO at L'Oréal
  • hans-müller-buchhalter — no scope, because none is interesting here
  • トヨタ-購買者-サプライチェーン,0Toyota, purchaser, supply chain, flagged unverified
  • علي-شريك-تجاري,1Ali, business partner, flagged as intended for one relationship
  • bob-payee-bitcoin,9 — flagged compromised, so it is shown but not used

Every one of those is real output from the reference implementation, not an illustration. You can generate your own in the browser, or run any of the six ports — the oracle in python, plus javascript, go, java, rust and swift. All six are held byte-for-byte to the same vectors; see IMPLEMENTATIONS.

The three fields are always the same three, in the same order, in every language: who, role, scope (§4.1). Anything after a comma is a flag (§6) — a safety-relevant warning about the identifier, not part of the label.

Flag placement and the template both changed in 2.0:

0-cecilia-as-ceo-at-acme      <- COIA 1.x
cecilia-ceo-acme,0            <- COIA 2.0

1. Scope and non-goals

An alias improves UX for the person who creates it. It is not a commitment to meaning for anyone else. An alias may change at its creator's whim, is unresolvable outside the creator's context, and parsing someone else's alias for strong meaning is a dangerous antipattern.

This specification defines:

  • how an alias is generated from three answers and a language;
  • a normalization algorithm that maps any string to a canonical form;
  • a flag syntax and registry for safety-relevant qualifications;
  • a matching rule for lookup.

It does not define: how aliases are stored, whether generation inputs are retained, how collisions between identical aliases are resolved, or how an application ranks results beyond the default order in §7.

2. Goals

G1 — intuitable. Given a handful of examples and no explicit instruction, a user develops accurate intuition about how the conventions work.

G2 — memorable. Encountering an alias they created, a user immediately remembers who is identified and in what context.

G3 — predictable. Given a context and an actor, a user correctly predicts what a relevant alias might look like, and can therefore search with confidence.

G4 — resident. An alias is usable as-is in the places aliases live: the label or comment field of a wallet, password manager, config file, or certificate wizard; and the search box that finds it again.

G5 — durable in transit. If an alias is copied elsewhere — an email, a chat message, a word processor, a log file, a shell, a spreadsheet, a Markdown document — it survives as a single token, and it can be read aloud with high confidence.

G5 is a robustness claim, not an endorsement. §1 says aliases are not made to be reshared. G5 says that when one is copied anyway, it should not be mangled.

Known limitations of G5. U+002D HYPHEN-MINUS has line-break class HY (UAX #14), so a renderer may break an alias at any hyphen, and double-click word selection does not select the whole token. Aliases MUST NOT be used as DNS labels when flagged (§6.6), nor as unescaped CSS or XML identifiers. In scriptio-continua languages (Chinese, Japanese, Thai) an alias is a single unsegmented token and the read-aloud clause of G5 does not apply.

3. Conformance classes

An implementation MAY conform as any subset of:

  • Normalizer — implements §5. MUST export normalization as a callable operation; matching is defined in terms of it, and a consumer cannot implement §7 without it.
  • Generator — implements §4 and §6, and normalizes via a conforming Normalizer.
  • Matcher — implements §7, and normalizes via a conforming Normalizer.

Every MUST in this specification binds one of these three classes. A requirement that constrains a human's judgment, a user interface, or third-party software is written as guidance, not as a normative keyword.

The golden vectors in Appendix C are normative. Where prose and vectors disagree, that is a defect in this specification; report it.

4. Creating an alias

4.1 Answer three questions

  • who — which subject the identifier refers to. Enough of a name to be meaningful in the creator's context.
  • role — what responsibility, posture or behaviour distinguishes this facet of the subject's identity from other facets.
  • scope — which environment, context or relationship defines the role. MAY be empty when the context is unconstrained or uninteresting.

Software often knows all three when it helps a user create or accept an identifier.

For a reflexive alias — one whose subject is the creator — the caller supplies a distinguished sentinel rather than a name, and the generator substitutes the pronoun for the chosen language (Appendix A.2). A generator MUST NOT use the empty string as this sentinel: a caller that omits who by accident would otherwise mint a reflexive alias for a third party's identifier.

4.2 Expand the template

A generator MUST reject a language it does not support rather than falling back to another. Templates and their per-language rules are in Appendix A, which is normative for the Generator class.

Substitution MUST be a single simultaneous pass. Substituted values MUST NOT be rescanned, and MUST NOT be interpreted as replacement patterns. A who of {role} produces the literal text; a who containing $& produces those characters.

4.3 Preconditions

A generator MUST evaluate preconditions on normalized values, not raw input:

  • role MUST be non-empty after normalization. (A raw role of . normalizes to the empty string and MUST be rejected, even though the raw value is non-empty.)
  • who MUST be non-empty after normalization, unless the alias is reflexive.
  • The complete alias MUST be non-empty after normalization.

4.4 Post-conditions

A generator's output MUST satisfy all of:

  • normalize(alias) == alias — normalization is idempotent on a generated alias.
  • The body matches the grammar in §8.
  • No leading or trailing hyphen, and no doubled hyphen.

5. Normalization

Normalization maps any string to canonical form. Step order is normative.

  1. Apply Unicode Normalization Form KC (NFKC).
  2. Apply Unicode Default Case Folding — full, non-Turkic, no locale tailoring — using the CASEFOLD table in Appendix B.6. An implementation MUST NOT substitute its runtime's lowercase operation. Case mapping and case folding are different operations, and every runtime tested diverges somewhere: Go's strings.ToLower applies simple mapping (İi rather than i + U+0307) and omits the Final_Sigma rule; Java's toLowerCase() is locale-sensitive and yields ıstanbul under tr_TR; and no lowercase operation expands ß to ss.
  3. Process the result one character at a time, in order, and for each character:
    • if it is in the SPLIT table (Appendix B.1), emit U+0020 SPACE;
    • else if it is U+0640 ARABIC TATWEEL, emit nothing;
    • else if its General_Category is Cf, emit it only if §5.1 licenses it, else nothing;
    • else if its General_Category is Me, emit nothing;
    • else if its General_Category is Mn or Mc, emit it only if the previous emitted character was a letter, number or mark, else nothing;
    • else if its General_Category is L* or N*, emit it, applying the folds in Appendix B.4;
    • else emit nothing.
  4. Split the result on runs of U+0020 and join the parts with U+002D HYPHEN-MINUS, discarding empty parts.

The character policy is therefore an allowlist: letters, numbers and orthographic marks are kept, a listed set becomes separators, and everything else is discarded. This is deliberate. Naming Unicode properties directly is not portable — the Rust regex crate rejects \p{Cs}, and neither Go nor Java can express \p{Dash} — and a property-based definition makes the alias a function of each runtime's Unicode version.

Modifier letters (Lm) are kept. NFKC already folds the phonetic and superscript modifiers; what survives is orthography — the katakana prolonged sound mark, kana and ideographic iteration marks, the okina, and tone and length marks in Miao, Lisu, Ol Chiki, N'Ko and others.

Combining marks are kept, except enclosing marks (Me). The thirteen Me characters are enclosing circles, keycaps and Cyrillic liturgical number signs — decorative or symbolic, not part of any orthography, and the keycap is an emoji component that would otherwise survive where every other emoji is discarded. Deleting the other marks destroys Yoruba and Igbo tone marking and every Indic and Southeast Asian script, while protecting nothing: NFKC recomposes European diacritics into single codepoints before this step runs, so José and João are unaffected either way.

Compatibility decompositions are accepted as-is. NFKC turns 94 letters into base plus mark. All 94 are Unicode composition exclusions, so no recomposition pass restores them, and this specification does not attempt one. Both spellings converge, so a user typing either finds the alias.

5.1 Joiners

U+200D ZERO WIDTH JOINER and U+200C ZERO WIDTH NON-JOINER are Cf, and are discarded except where orthography requires them. Modelled on IDNA2008 CONTEXTJ (RFC 5892 A.1, A.2):

  • ZWJ is kept if and only if the preceding character is in the VIRAMA table (Appendix B.2).
  • ZWNJ is kept if and only if the preceding character is in the VIRAMA table, or both neighbouring characters are in the ARABIC ranges (Appendix B.3).

This preserves Sinhala ශ්‍රී, Devanagari conjuncts, Malayalam chillus, and Persian and Urdu word-internal breaks, while discarding a bare joiner between Latin letters, which would otherwise produce two aliases that render identically.

The Arabic clause is an approximation. RFC 5892 tests Joining_Type, which is unreachable from Go and JavaScript regular expressions and would add several hundred table entries. It over-permits slightly, within Arabic-script text only.

6. Flags

A flag qualifies the alias assertion: that the identifier is controlled by the party the creator calls who; that this party acts in the capacity role; and that it does so within the context scope. A flag qualifies all three components, not only the subject.

6.1 Syntax

alias := body [ "," group1 [ "," group2 ] ]

group1 holds registry digits (§6.3). group2 is private use. Each group is a run of ASCII decimal digits. A generator MUST emit at most two groups.

Both groups MUST have duplicates collapsed and MUST be sorted in descending numeric order, so that the most serious flag appears first. (COIA 1.x sorted ascending. See CHANGES.)

An empty group1 with a populated group2 is written body,,digits. Canonical form omits trailing empty groups: body,, is not canonical and normalizes to body.

Because normalization (§5) discards all punctuation, a comma in a generated alias is always a delimiter and never content.

6.2 Ordering of operations

A matcher or reader MUST split flag groups off before normalizing the body. The reverse order destroys the delimiter.

On input, a reader MUST accept U+3001, U+060C and U+FF0C as group delimiters in addition to U+002C, so a user retyping an alias on a CJK or Arabic keyboard is understood. (U+FF0C folds to U+002C under NFKC in any case.) A generator MUST emit U+002C.

Similarly, a generator MUST emit ASCII digits, folding any Unicode decimal digit to its ASCII equivalent; a reader MUST accept any Unicode decimal digit.

6.3 The registry

0  unverified     doubt about the alias assertion is unresolved
1  pairwise       for one relationship only; do not share
2  —              reserved
3  —              reserved
4  unfit          technical posture weak for high-stakes use, and not yet accepted
5  second-hand    imported, restored, synced, or accepted from another party
6  test           throwaway, test, demo; no real-world consequence
7  do-not-use     the creator has decided not to transact
8  retired        no longer in service; historical references still resolve
9  compromised    positive evidence that the wrong party controls it

Full definitions, including what sets and clears each flag and what an application must decide for itself, are in Appendix D.

Digits are ordered by seriousness, ascending. This ordering carries meaning: a reader encountering an unrecognized digit MAY use its position as a severity hint.

Absence is never a guarantee. For every flag, absence means only that the flag was not set; it never asserts the negation. An application MUST NOT render an absent flag as a positive assurance.

A generator MUST NOT emit a reserved digit. A reader encountering an unrecognized digit in group1 MUST surface it rather than ignore it — it is a warning from a later version of this specification. A reader MUST NOT interpret group2.

Reflexive aliases MUST NOT carry 0.

6.4 Clearing flags

Setting and clearing are decisions, not observations. Clearing 0 does not assert that evidence exists; it records that a person, or software acting under that person's policy, judged the remaining risk not worth tracking.

4 is set by software from facts it can derive, and cleared by a deliberate acceptance of the identified weakness. Because those facts remain true after acceptance, software re-checking them will observe them again. An implementation MUST have a policy governing when 4 is re-set. This specification does not define one; see Appendix D.4 for a non-normative example.

4 does not travel between applications: it is the only flag whose presence depends on local policy rather than on facts about the world.

6.5 What flags are not

Facts an application can derive from an identifier's own record — witness count, signature threshold, delegation, key type, whether control is multi-signature — are not flags. Software should read them fresh rather than cache them in a string that can go stale. What the registry records is the acceptance state attached to such a fact, which is a decision, and decisions are never derivable.

6.6 Flags and DNS

An unflagged alias is a valid IDNA label in every language this specification supports, subject to length. A flagged alias is not: the comma is not permitted in a label, and a flag digit at the start of a right-to-left label violates the Bidi Rule (RFC 5893). Since the delimiter appears only on flagged aliases, this is self-enforcing.

7. Comparing and matching

A query is normalized (§5) and its flag groups discarded before comparison.

Predicate. A matcher MUST report a match if and only if every term of the normalized query is a substring of the normalized, flag-stripped alias, where a term is a hyphen-delimited part of the normalized query. Term order does not matter.

Substring rather than segment comparison, because in scriptio-continua languages the whole alias is a single segment and segment comparison could not find it by any word inside it.

Order. A matcher MUST return matches ordered by, in priority order:

  1. the number of query terms matching a whole segment, descending;
  2. the position of the earliest matching term, ascending;
  3. coverage — the sum of matched term lengths divided by alias length — descending;
  4. the alias itself, in codepoint order.

Key 4 leaves no ties, so the order is total and testable. This order is a default that is correct with no information beyond the strings. An application holding its own signals — recency, frequency, favourites — MAY re-rank.

Key 3 divides by length in characters, which favours short aliases. CJK aliases are shorter in characters than Latin aliases carrying the same content, so a mixed-language corpus will float them upward. This is a known bias, accepted in preference to normalizing by segment count, which is biased the other way.

There is no minimum query length. A single-character query matching most of a corpus is correct behaviour for incremental search, and CJK names are routinely one character.

Uniqueness is out of scope. Two identical aliases for different identifiers are not a lookup failure: the user searches, receives both, and chooses. COIA 1.x described an optional numeric suffix; it was never implemented and is removed. See CHANGES.

8. Grammar

alias   := body [ "," group [ "," group ] ]
body    := segment ( "-" segment )*
segment := ( L | N | Mn | Mc )+
group   := DIGIT*

where L, N, M are Unicode General_Category letter, number and mark, plus the joiners licensed by §5.1.

As a regular expression, for the body:

^[\p{L}\p{N}\p{M}]+(-[\p{L}\p{N}\p{M}]+)*$

A permissive variant for user input, tolerating hyphen alternatives a keyboard may produce:

^[\p{L}\p{N}\p{M}]+([-‐‑‒–−][\p{L}\p{N}\p{M}]+)*$

9. Privacy considerations

An alias is plaintext, and its structure is regular. who, role and scope always appear in the same positions, so a backup, a sync service, a crash report, a screenshot or a subpoena yields structured relationship data rather than idiosyncratic notes.

This is worse than an arbitrary label, not neutral, and the trade is deliberate: the structure is what makes an alias memorable and searchable. An alias such as alice-smith-hiv-patient-mercy-clinic,1 is a plausible one to create and a damaging one to leak.

Applications should consider: whether alias text is included in crash reports and telemetry; whether it appears in logs that leave the device; whether it is visible on a lock screen; and whether sensitive aliases warrant a separate store.

This specification does not require or forbid retaining generation inputs. Storage is outside the conformance classes.

10. Versioning and stability

This specification uses semantic versioning, with these meanings.

MAJOR — any change to normalization, to the grammar, or to the meaning of an existing flag digit. A stored alias may stop matching a freshly normalized query.

MINOR — adding a language, assigning a reserved flag digit, or clarifying prose. A MINOR change MUST NOT change the alias produced for any previously valid input.

Implementations MUST record the COIA version and the Unicode version used to produce an alias. A matcher MUST compare against the stored alias as it stands, and MUST NOT assume it was produced by the current version.

Normalization is deliberately defined by explicit tables rather than by Unicode properties, so a Unicode upgrade does not silently change an alias: new characters are preserved by default because they are letters, and the SPLIT and VIRAMA tables change only when this specification changes.

Appendices

  • Appendix A — Localization: the template, the reflexive pronouns, and why there are no per-language rules. Normative for generators.
  • Appendix B — Character tables: SPLIT, VIRAMA, ARABIC, folds, CASEFOLD. Normative.
  • Appendix C — Golden vectors. Normative.
  • Appendix D — Flag definitions and application guidance.

And, non-normative:

About

conventions for identifier aliases

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages