remove cute language

This commit is contained in:
Evgeny @ SimpleX Chat
2026-06-27 14:32:54 +00:00
parent 9e246faa6f
commit 471a538622
6 changed files with 87 additions and 85 deletions
+62 -62
View File
@@ -12,7 +12,7 @@ Reduce the feature to the minimum coherent, secure shape:
typed `Maybe SimplexNameInfo`. No second "locally-known" copy on the entity row.
- A local **verification status** as a 3-state `Maybe Bool` (not a timestamp):
not-attempted / failed / verified. A failed check never blocks connecting.
- Connect-by-name gated by **consent** and **born verified**.
- Connect-by-name requires the entity to claim the name, and the result is created as verified.
- The **proof** (address-key signature, context-bound) is **in scope for link
contexts** — connect-by-name, 1-time invitations, contact addresses, channel
join links — so those names are verifiable in this release. Only a name with
@@ -27,17 +27,17 @@ timestamps.
## 2. Why (trust model)
A name resolves to a link via the agent (`resolveSimplexName`); a `NameRecord`
carries **lists** of links (`nrSimplexContact`, `nrSimplexChannel`), so
has **lists** of links (`nrSimplexContact`, `nrSimplexChannel`), so
verification matches against **any** of them. "Match any" is safe **only because
the namespace is an on-chain, ENS-style registry**: `nrOwner`/`nrResolver` are
Ethereum addresses (`Names/Record.hs:22-23,36`), so each name has a **single
owner** who sets all its links. An attacker can register *their* name → your
address (the offensive-name case, handled by the consent gate below) but **cannot
add a link to your name** — on-chain ownership blocks impersonation. This premise
is load-bearing; if names were not single-owner, "match any" would be exploitable.
address (the offensive-name case, handled by the claim check below) but **cannot
add a link to your name** — on-chain ownership blocks impersonation. Everything
here depends on that; if names were not single-owner, "match any" would be exploitable.
The registrant of a name controls what it points to, with no proof that the
target address consents — anyone can publish `@offensive → your address` or
target address agreed to it — anyone can publish `@offensive → your address` or
`#offensive → your channel`. Therefore:
- One-directional verification ("does `resolve(name)` equal a stored link") is
@@ -46,11 +46,11 @@ target address consents — anyone can publish `@offensive → your address` or
copy a link into a profile. **Control of a link is proven only by an actual
connection/join through it.**
- Sound verification is the intersection: `resolve(name) → link`, **and** the
entity advertises that name in its profile (consent), **and** that link is one
entity advertises that name in its profile (it claims the name), **and** that link is one
we connected/joined through (control-proven).
- Verifying a name without having connected through its link needs a **signature
by the address key over the name, bound to the presentation context** (§4.8).
This is **in scope** for link contexts: a 1-time invitation carries such a proof
This is **in scope** for link contexts: a 1-time invitation includes such a proof
bound to the invite, so resolving the name → address → address key verifies it.
Consequence: names are verifiable in this release for **channels** (join link),
@@ -130,7 +130,7 @@ soft-decode** (`decodeSimplexName` dropped for the name columns).
`Profile.simplexName`.)
- `Profile.contactDomainProof :: Maybe ClaimProof` — NEW; a flat sibling of
`contactDomain`/`contactLink`, a **wire profile field like `Profile.badge`**. The
**stored** own profile (`contact_profiles`) carries the name but **no proof**; the
**stored** own profile (`contact_profiles`) has the name but **no proof**; the
proof is generated and **added to the outgoing profile at send/save** (§4.8), exactly
as the badge is. `PHSimplexLink` (verifiable) when the profile is saved to a contact
address / 1-time invite; `PHTest` over an established connection (the gap).
@@ -142,21 +142,21 @@ soft-decode** (`decodeSimplexName` dropped for the name columns).
- `PublicGroupAccess.groupDomain :: Maybe (StrJSON "SimplexName" SimplexNameInfo)`
— RETYPE from `Maybe Text` (`Types.hs:857`); JSON string, wire-identical to the
released text (§4.1). Owner-set, broadcast in `XGrpInfo`. **Only public, relay-backed
groups** (`groups.use_relays`) can carry a name — they have a channel join link to
groups** (`groups.use_relays`) can have a name — they have a channel join link to
resolve to and an owner key (`groups.member_priv_key`, `chat_schema.sql:193`) to sign
with; p2p groups have neither. (`contactDomain` on a member is a contact attribute, unaffected.)
- `GroupInfo.groupDomainVerification :: Maybe Bool` — local status, read from the
`groups` table (there is no `LocalGroupProfile`, and `GroupProfile` is the wire
type, so the status cannot ride the profile).
type, so the status cannot be sent with the profile).
- DROP `Contact.simplexName`, `GroupInfo.simplexName`, `Connection.simplexName`,
and both `*VerifiedAt` timestamps.
Asymmetry, intentional: contact name + status both live on `contact_profiles`
(surfaced via `LocalProfile`); the group name lives on `group_profiles`
(surfaced via `GroupProfile`/`PublicGroupAccess`) but its status lives on
`groups` (surfaced via `GroupInfo`). This is forced — `group_profiles` is the
(exposed via `LocalProfile`); the group name lives on `group_profiles`
(exposed via `GroupProfile`/`PublicGroupAccess`) but its status lives on
`groups` (exposed via `GroupInfo`). This is forced — `group_profiles` is the
shared wire profile with no local columns, while `contact_profiles` already
carries local state (`local_alias`).
holds local state (`local_alias`).
### 4.3 Schema — rewrite `M20260603_simplex_name` (branch unreleased)
@@ -196,14 +196,14 @@ Verification column decode: `Maybe BoolInt → Maybe Bool` (`NULL` = not attempt
argument and the `connections.simplex_name` carrier param on `createConnection_`.
- `updateContactProfile` / `updateGroupProfile`: write `contact_domain` /
`group_domain` from the received profile. Reset the verification to `NULL`
(not-attempted) **only when the name changes**; an XInfo/XGrpInfo carrying the
(not-attempted) **only when the name changes**; an XInfo/XGrpInfo with the
**same** name keeps the existing status (a verified name stays verified), exactly as
a badge does. No conflict clearing (no UNIQUE index).
- `getContactBySimplexName` / `getGroupIdBySimplexName`: look up by the **verified**
profile name (the name column joined with verification = `Just True`); on a miss
or unverified, fall through to resolve-and-connect. Consistent with the existing
by-address lookup `getContactViaShortLinkToConnect` (`Direct.hs:963`), which
matches the link with no verified gate — a link is the identity, a name is a
matches the link with no verification check — a link is the identity, a name is a
claim that only becomes a usable pointer once verified.
### 4.5 Redaction (`Library/Internal.hs:1246`)
@@ -217,14 +217,14 @@ redactedMemberProfile g m Profile {…, contactLink, contactDomain} =
, shortDescr = removeSimplexLink =<< shortDescr -- via allowSimplexLinks
, contactLink = if allowSimplexLinks then contactLink else Nothing
, contactDomain = if allowDirect then contactDomain else Nothing
, contactDomainProof = Nothing } -- member profiles are contextless; never carry a proof
, contactDomainProof = Nothing } -- member profiles are contextless; never include a proof
```
- `allowDirect` is the single primitive (the `DirectMessages` permission); it
gates the name and is reused inside `allowSimplexLinks`. No `allowName` flag,
controls the name and is reused inside `allowSimplexLinks`. No `allowName` flag,
no second lookup.
- **Behavior changes vs current:** `contactLink` flips from unconditionally
dropped to gated on `allowSimplexLinks` (a member's contact address becomes
dropped to controlled by `allowSimplexLinks` (a member's contact address becomes
visible whenever links+DMs are allowed — the meaning of "links allowed"); the
name follows the looser `allowDirect`. Rationale: a link is one-tap-to-connect
(low friction), a name only resolves if the recipient deliberately looks it up
@@ -237,23 +237,23 @@ redactedMemberProfile g m Profile {…, contactLink, contactDomain} =
(both reduce to `groupFeatureMemberAllowed' f (memberRole (membership g))
(fullGroupPreferences g)`, `Types.hs:646652`).
- Collapses `groupUserAllowSimplexLinks` (`Types.hs:656`) and the pre-computed
`allowSimplexLinks` plumbing at the call sites (`Internal.hs:1244`,
`allowSimplexLinks` wiring at the call sites (`Internal.hs:1244`,
`Subscriber.hs:842,2817,3239`, `Commands.hs:3748,4057`). `Commands.hs:3748`
has `Maybe GroupInfo` → "no group ⇒ no redaction" at the call site.
### 4.6 Resolution + verification (`Library/Commands.hs`)
Two regimes that differ in whether verification can *fail*:
Two cases that differ in whether verification can *fail*:
**Connect-by-name** (`connectPlanName` / `dispatchResolvedRecord`) — verification is
a **precondition** of connecting. After decoding the resolved short link's embedded
profile, **add the consent gate**: require that profile's `contactDomain` /
profile, **add the claim check**: require that profile's `contactDomain` /
`groupDomain` to equal the resolved name, else fail with `CESimplexNameNotFound`
("name unknown") and **do not connect**. (The current branch decodes the profile but
never compares the name — this is the missing check.) On success the prepared
contact/group is created with the name on the profile, `connLinkToConnect` = the
resolved link, verification = `Just True` (**born verified**). There is no "failed"
outcome here — failing to resolve/consent just means no connection.
resolved link, verification = `Just True` (**created as verified**). There is no "failed"
outcome here — failing to resolve or to claim the name just means no connection.
**Connected NOT by name** (via an address link or a 1-time link) — the peer's profile
may *claim* a name; it starts **unverified** (`Nothing`). Verification is **post-hoc
@@ -264,10 +264,10 @@ and non-blocking**: keep `apiVerifySimplexName` (`/_verify simplex name`).
the usual contact-address case), check the `ClaimProof` signature over
`name <> presHeader`, and check the proof's `presHeader` link ==
`preparedContact.connLinkToConnect` (**not** `profile.contactLink` — the branch bug).
Contact addresses and 1-time invites both carry the proof.
Contact addresses and 1-time invites both include the proof.
- **Channels** verify by **presence / link-match**: `resolve(#name)` includes
`preparedGroup.connLinkToConnect` (the join link), whose owner-signed data already
carries `groupDomain` (no `ClaimProof` — §4.8).
has `groupDomain` (no `ClaimProof` — §4.8).
Result is `Just True` (holds) or `Just False` (fails) — and **`Just False` must NOT
prevent the connection**, exactly as a failed *badge* verification doesn't. **This is
why the status must be 3-state** (`Nothing` not attempted / `Just False` failed /
@@ -284,7 +284,7 @@ Keep the pure helpers `firstNameLink` (per-type link pick, cross-type rejection)
### 4.7 Display (`View.hs`)
A name is shown **when there is a proof to verify**, together with its status —
verified, failed, or not-yet-verified. All three states are surfaced (a failed or
verified, failed, or not-yet-verified. All three states are shown (a failed or
pending check still shows the name, flagged), so the user sees the name and its
trust level rather than a silent omission. Rendering those three states is **out of
scope for this PR** (UI work), but the core stores the data — the 3-state status and
@@ -295,10 +295,10 @@ name on an `XInfo` profile over an established connection — is **not shown**.
A name resolves to a **contact address** (the persistent identity link). The proof
asserts "the owner of that address asserts this name", so it is **signed by the
address's key** (the key the resolved address carries) — **not** by the
per-connection or 1-time-link key. Every proof is **bound to a presentation
context** (anti-replay), so a proof minted for one context cannot be replayed in
another; these are distinct proofs. Carry the context in a presentation header:
address's key** (the resolved address's own key) — **not** by the
per-connection or 1-time-link key. Every proof is **tied to the link it's shown
through**, so a proof made for one link can't be reused on another; these are
distinct proofs. Store the link in a presentation header:
**rename `BadgePresHeader` (`Badges.hs:212`) → `ProofPresHeader`** (now shared by
badge and name proofs) and add a **`PHSimplexLink AConnShortLink`** constructor.
`AConnShortLink` (`Agent.Protocol.hs:1536`,
@@ -320,11 +320,11 @@ variant.
}
The signature is by the key of the signer's **owner identity** in the link's owner
chain (`OwnerAuth`, `Agent/Protocol.hs:1829`); `ClaimProof` carries
chain (`OwnerAuth`, `Agent/Protocol.hs:1829`); `ClaimProof` has
**`linkOwnerId :: Maybe OwnerId`** (`OwnerId`, `:1827`) to name it, so the verifier
checks exactly that key (no iterating the owner list):
- **Channels** (which can have delegated owners) sign with the user's **owner key**,
- **Channels** (which can have owners other than the address) sign with the user's **owner key**,
`linkOwnerId = Just oid`**not** the root key — `groups.member_priv_key`
(`chat_schema.sql:193`).
- A **contact address** has a single owner = its creator, so it signs with the **root
@@ -349,46 +349,46 @@ Per presentation context:
proof-for-invites. The address can also serve as the **badge** proof's context.
- **1-time invitation**: include a proof **signed by the address key, bound to the
1-time link** as context. Feasible **now** — the 1-time link is a unique,
single-use anti-replay context; no general mechanism required. This is what makes
single-use context; no general mechanism required. This is what makes
a 1-time-invite contact verifiable.
- **`XInfo` over an established connection**: no link context → the augmentation stamps
`PHTest` (unbound) → unverifiable → not shown. The deferred gap, same as the badge's
- **`XInfo` over an established connection**: no link context → the step that adds the proof sets
`PHTest` (unbound) → unverifiable → not shown. Left for later, same as the badge's
`PHTest`. (For **group members** the proof is dropped entirely by redaction, §4.5.)
Home: **inside the profile**`Profile.contactDomainProof :: Maybe ClaimProof`, a flat
sibling of `contactDomain`/`contactLink`, exactly like `Profile.badge`. It is **not**
stored on the own profile; like the badge, it is **generated fresh and added to the
outgoing profile at send/save** — when the profile is sent to a peer (`XInfo`) or saved
to a link — by signing `name <> presHeader` with the address root key and stamping the
to a link — by signing `name <> presHeader` with the address root key and setting the
**destination as the `presHeader` context**. Because `presentUserBadge`
(`Internal.hs:2037`) already augments the outgoing profile with the badge proof at *both*
peer-sends and link-data writes, the name-proof augmentation belongs in the **same
(`Internal.hs:2037`) already adds the badge proof to the outgoing profile at *both*
peer-sends and link-data writes, the name-proof step belongs in the **same
function**. The context decides whether a proof is useful: saving to a contact address /
1-time invite stamps `PHSimplexLink(that link)` (verifiable — the in-scope cases); an
established-connection peer-send stamps `PHTest` (unbound — the deferred gap, same as the
1-time invite sets `PHSimplexLink(that link)` (verifiable — the in-scope cases); an
established-connection peer-send sets `PHTest` (unbound — left for later, same as the
badge). The receiver verifies and stores the 3-state status on
`LocalProfile.contactDomainVerification`, as `localBadge` carries the badge status.
Connect-by-name is **born verified**.
`LocalProfile.contactDomainVerification`, as `localBadge` holds the badge status.
Connect-by-name is **created as verified**.
**Scope:** the **useful** (`PHSimplexLink`) proofs — stamped when the profile is saved to
**Scope:** the **useful** (`PHSimplexLink`) proofs — set when the profile is saved to
a contact address or 1-time invite — are in this change; the verifier resolves the name →
address → address key → checks the signature and the `presHeader` link. Channels use
presence (below). The contextless established-connection case (a `PHTest` name proof) and
group members are the deferred gap.
group members are left for later.
Badges stay **in the profile** — person-scoped, presented per connection
(`presentUserBadge`, `Internal.hs:2037`), including over established connections via
`XInfo`. They share the presentation-header context type with name proofs but sign
with the **badge credential key**, not the address key. Two scopes, two homes.
The shared augmentation stamps **both** proof kinds the same way: `PHSimplexLink(link)`
The shared step sets **both** proof kinds the same way: `PHSimplexLink(link)`
when the destination is a link (contact address, 1-time invite — verifiable), `PHTest`
over an established connection (unbound — the gap). So name proofs and badges both use
`PHTest` only for the contextless established-connection case.
No group proof field: a channel is always joined via its **join link** (its own
address), whose owner-signed data already carries `groupDomain`, and there is no
"carrying link ≠ resolved address" case for channels — so channels verify by
address), whose owner-signed data already has `groupDomain`, and there is no
"advertised link ≠ resolved address" case for channels — so channels verify by
presence/link-match and need no `ClaimProof`. (If full symmetry is wanted later, add
`GroupProfile.groupDomainProof` the same way.)
@@ -410,7 +410,7 @@ add/change/remove their own name from the UI; the branch has no command for the
address).
5. Set `LocalProfile.contactDomain` and **re-publish the contact-address link data**.
The `ClaimProof` is added when the profile is saved to that link (the send/save
augmentation, §4.8) — **not** produced by the API itself. (Steps 14 are the verify;
proof-adding step, §4.8) — **not** produced by the API itself. (Steps 14 are the verify;
this step is set + re-publish.)
Needs a `ChatCommand` constructor + parser + handler.
- **Channel name** (public, relay-backed groups only) — a **separate**
@@ -423,8 +423,8 @@ add/change/remove their own name from the UI; the branch has no command for the
verified path; factor out the shared `GroupProfile`-update + `XGrpInfo` broadcast so
both commands reuse it. Verify is **TLD-dependent**: resolve+compare for `TLDSimplex`,
a different/no check for `TLDWeb` (web domains don't resolve through the namespace).
- The own name carries **no stored verification status** — the verify step checks it at
add time; the 3-state status is only for peers' names. No RNAME plumbing (out of scope).
- The own name has **no stored verification status** — the verify step checks it at
add time; the 3-state status is only for peers' names. No RNAME wiring (out of scope).
## 5. Removal checklist (from the current branch)
@@ -456,9 +456,9 @@ b. **Lookup (§4.4):** re-point `getContactBySimplexName` (its one caller is
c. **Proof (§4.8):** a flat **`Profile.contactDomainProof :: Maybe ClaimProof`**, a wire
profile field like `Profile.badge`. Not stored on the own profile; **generated fresh and
added to the outgoing profile at send/save** (peer `XInfo` or save-to-link) by the
**same augmentation function** as the badge (`presentUserBadge`, which already runs at
**same function** as the badge (`presentUserBadge`, which already runs at
peer-sends *and* link-data writes). Signed by the signer's **owner-identity key** — a
channel's delegated **owner key** (`groups.member_priv_key`, `linkOwnerId = Just oid`)
channel's **owner key** (`groups.member_priv_key`, `linkOwnerId = Just oid`)
or a contact address's **root key** (sole owner, `linkOwnerId = Nothing`) — over
`name <> presHeader`; `linkOwnerId` selects the verification key (`Nothing` = root,
allowed at validation). `PHSimplexLink` for address/invite saves, `PHTest` over
@@ -472,17 +472,17 @@ d. **`redactedMemberProfile` contactLink exposure (§4.5):** intended — a memb
e. **Verify command + status (§4.6):** keep `apiVerifySimplexName` — required to
verify a claimed name for entities connected **not** by name (address / 1-time
link). Status is **3-state** because a failed name (or badge) verification must
**not** block the connection; connect-by-name is the only born-verified path
(and there a resolve/consent failure means no connection, not a failed state).
**not** block the connection; connect-by-name is the only created-as-verified path
(and there a failure to resolve or to claim the name means no connection, not a failed state).
**Auto-verify on connect** (vs. on-demand only) is left open; it doesn't change
the 3-state requirement.
## 7. Rollout & scope
The **consent gate** (§4.6) — a name resolves only if the **resolved link's own
The **claim check** (§4.6) — a name resolves only if the **resolved link's own
data claims it** — is the anti-stray-names protection and **must ship regardless**:
a name someone registers against a real address must not "work" without that
address owner's consent. It needs no signature (the address-case presence suffices),
address owner's agreement. It needs no signature (the address-case presence suffices),
so it lands with the core change.
The signed proof (§4.8) can ship in the **same** release (add + verify together) or
@@ -497,10 +497,10 @@ context ripples through the badge code — accepted, and bounded:
(`:200, :212, :216, :226`); add the `PHSimplexLink AConnShortLink`
tag/constructor/`StrEncoding`/accepted-case; `badgeProof` (`:314`) takes the
renamed type.
- `presentUserBadge` (`Internal.hs:2037`) + its ~15 call sites — the **shared profile-
augmentation** point (already runs at peer-sends *and* link-data writes): generalize it
to stamp **both** the badge proof and the name `ClaimProof` onto the outgoing profile,
- `presentUserBadge` (`Internal.hs:2037`) + its ~15 call sites — the **shared point that adds
proofs to the outgoing profile** (already runs at peer-sends *and* link-data writes): generalize it
to set **both** the badge proof and the name `ClaimProof` onto the outgoing profile,
using `PHSimplexLink link` where the destination is a link, `PHTest` otherwise.
The pure rename can land as a **standalone prep commit** ahead of the proof; the
`PHSimplexLink` plumbing + name proof land with the proof work.
`PHSimplexLink` wiring + name proof land with the proof work.
+9 -8
View File
@@ -246,9 +246,9 @@ proofPresHeaderAccepted = \case
PHSimplexLink _ -> True
PHUnknown _ _ -> True
-- A name claim proof: signed by the address owner's key (linkOwnerId = Just oid for a
-- channel's delegated owner, Nothing = the address root key) over
-- strEncode name <> strEncode presHeader, bound to the presentation context (the link).
-- A name claim proof: signed by the address owner's key (linkOwnerId = Just oid when a channel
-- owner other than the address signs, Nothing when the address's own root key signs) over
-- strEncode name <> strEncode presHeader, tied to the link it is shown through.
data NameClaimProof = NameClaimProof
{ linkOwnerId :: Maybe (StrJSON "OwnerId" OwnerId),
presHeader :: ProofPresHeader,
@@ -256,12 +256,12 @@ data NameClaimProof = NameClaimProof
}
deriving (Eq, Show)
-- the bytes a name proof signs over: the claimed name bound to its presentation context
-- the bytes a name proof signs over: the claimed name together with the link it is shown through
nameProofPayload :: SimplexNameInfo -> ProofPresHeader -> ByteString
nameProofPayload name presHeader = strEncode name <> strEncode presHeader
-- mint a name proof: sign (name, presentation context) with the address owner key.
-- linkOwnerId names the signing owner in the link's owner chain (Nothing = root key, the contact-address case).
-- make a name proof: sign (the name and the link it is shown through) with the address owner key.
-- linkOwnerId names the signing owner in the link's owner chain (Nothing = root key, used for a contact address).
signNameProof :: C.PrivateKeyEd25519 -> Maybe OwnerId -> SimplexNameInfo -> ProofPresHeader -> NameClaimProof
signNameProof key linkOwnerId name presHeader =
NameClaimProof
@@ -271,12 +271,13 @@ signNameProof key linkOwnerId name presHeader =
}
-- verify a name proof's signature against the resolved address owner key. The caller must
-- SEPARATELY check the proof's presHeader link is the link it is presented through (anti-replay).
-- SEPARATELY check the proof's presHeader link is the one it was shown through, so a proof made
-- for one link can't be reused on another.
verifyNameProofSig :: C.PublicKeyEd25519 -> SimplexNameInfo -> NameClaimProof -> Bool
verifyNameProofSig ownerKey name NameClaimProof {presHeader, signature} =
C.verify' ownerKey signature (nameProofPayload name presHeader)
-- the link a proof is bound to (its anti-replay context), if any
-- the link a proof is tied to (the one it can be checked against), if any
proofPresHeaderLink :: ProofPresHeader -> Maybe AConnShortLink
proofPresHeaderLink = \case
PHSimplexLink lnk -> Just lnk
+12 -11
View File
@@ -4775,7 +4775,7 @@ processChatCommand cxt nm = \case
liftIO $ SharedMsgId <$> encodedRandomBytes gVar 12
-- | Dispatch a resolved NameRecord by eagerly preparing a contact/group row
-- (the resolved link's embedded profile carries contact_domain / group_domain),
-- (the resolved link's embedded profile has contact_domain / group_domain),
-- then returning the same plan shape ('CAPKnown' / 'GLPKnown') the
-- local-store-hit branch of 'connectPlanName' returns, so the resolver hit
-- reuses the same DB write path as a local-prepare hit.
@@ -4801,13 +4801,13 @@ dispatchResolvedRecord cxt nm user ni@SimplexNameInfo {nameType} NameRecord {nrS
(FixedLinkData {linkConnReq = cReq}, cData) <- getShortLinkConnReq nm user l'
ContactShortLinkData {profile} <-
liftIO (decodeLinkUserData cData) >>= maybe (throwError $ chatErrorAgent $ AGENT $ A_LINK "could not decode contact profile from RSLV link") pure
-- consent gate: the resolved profile must claim the resolved name, else "name unknown"
-- the resolved profile must actually claim this name, otherwise "name unknown"
let Profile {contactDomain} = profile
unless (contactDomain == Just (StrJSON ni)) $ throwChatError $ CESimplexNameNotFound ni
let ccLink = CCLink cReq (Just l')
accLink = ACCL SCMContact ccLink
ct <- withStore $ \db -> createPreparedContact db cxt user profile accLink Nothing
-- born verified: connecting via the resolved name verifies it
-- created as verified connecting by the resolved name verifies it
let Contact {contactId = cId} = ct
withStore' $ \db -> setContactDomainVerified db user cId True
pure (accLink, CPContactAddress (CAPKnown ct))
@@ -4817,7 +4817,7 @@ dispatchResolvedRecord cxt nm user ni@SimplexNameInfo {nameType} NameRecord {nrS
(FixedLinkData {linkConnReq = cReq}, cData@(ContactLinkData _ UserContactData {direct})) <- getShortLinkConnReq' nm user l'
GroupShortLinkData {groupProfile, publicGroupData = publicGroupData_} <-
liftIO (decodeLinkUserData cData) >>= maybe (throwError $ chatErrorAgent $ AGENT $ A_LINK "could not decode group profile from RSLV link") pure
-- consent gate: the resolved group profile must claim the resolved name
-- the resolved group profile must actually claim this name
let GroupProfile {publicGroup} = groupProfile
unless ((publicGroup >>= publicGroupAccess >>= groupDomain) == Just (StrJSON ni)) $ throwChatError $ CESimplexNameNotFound ni
let publicMemberCount_ = (\PublicGroupData {publicMemberCount} -> publicMemberCount) <$> publicGroupData_
@@ -4826,12 +4826,12 @@ dispatchResolvedRecord cxt nm user ni@SimplexNameInfo {nameType} NameRecord {nrS
gVar <- asks random
let ccLink = CCLink cReq (Just l')
(g, _hostMember_) <- withStore $ \db -> createPreparedGroup db gVar cxt user groupProfile False ccLink Nothing useRelays subRole publicMemberCount_
-- born verified: connecting via the resolved name verifies it
-- created as verified connecting by the resolved name verifies it
let GroupInfo {groupId} = g
withStore' $ \db -> setGroupDomainVerified db user groupId True
pure (ACCL SCMContact ccLink, CPGroupLink (GLPKnown g (BoolDef False) Nothing (ListDef [])))
-- Mirror the inline 'serverShortLink' helper defined in 'processChatCommand'
-- where this dispatch is invoked: RSLV-supplied short links may carry the
-- where this dispatch is invoked: RSLV-supplied short links may use the
-- agent's simplex:/ scheme, but the prepared row stores hostname-scheme,
-- and the connect-plan / known-link lookups assume the hostname form.
serverShortLink' :: ConnShortLink m -> ConnShortLink m
@@ -4854,8 +4854,9 @@ firstNameLink nameType simplexChannel simplexContact ni =
NTPublicGroup -> simplexChannel
NTContact -> simplexContact
-- sign the channel's name claim with the owner's member key, bound to the channel link
-- (linkOwnerId = Just memberId — channels have delegated owners). No-op without a name or keys.
-- sign the channel's name claim with the owner's member key, tied to the channel link
-- (linkOwnerId = Just memberId — a channel is signed by its owner, not by the address itself).
-- Does nothing without a name or keys.
signChannelNameProof :: GroupInfo -> PublicGroupProfile -> PublicGroupAccess -> PublicGroupAccess
signChannelNameProof GroupInfo {groupKeys, membership = GroupMember {memberId = MemberId mid}} PublicGroupProfile {groupLink} access@PublicGroupAccess {groupDomain} =
case (groupDomain, groupKeys) of
@@ -4870,7 +4871,7 @@ signChannelNameProof GroupInfo {groupKeys, membership = GroupMember {memberId =
data NameVerifyOutcome = NVOVerified | NVOFailed Text | NVOInconclusive Text
-- §4.6: a name verifies when the proof's presHeader is the link the peer was actually connected through
-- (anti-replay) AND the proof is signed by the key the name resolves to (the address root key, or the
-- (so the proof can't be reused on a different link) AND the proof is signed by the key the name resolves to (the address root key, or the
-- channel owner's key selected by linkOwnerId). The key comes from *resolving the name* (the address),
-- not the connected link — for a 1-time invite they differ. A network/agent error propagates as
-- ChatErrorAgent so a UI-triggered verify can retry, rather than recording a false verdict.
@@ -4898,7 +4899,7 @@ verifyName user nm claim connLink_ proof_ = case (proof_, connLink_) of
then NVOVerified
else NVOFailed "the name resolves to a different address — its owner did not sign this name proof"
-- the proof must be bound (anti-replay) to the link the peer was connected through
-- the proof must be tied to the link the peer connected through, so it can't be reused on a different link
proofBoundTo :: NameClaimProof -> AConnShortLink -> Bool
proofBoundTo NameClaimProof {presHeader} connLink =
maybe False (`sameConnShortLink` connLink) (proofPresHeaderLink presHeader)
@@ -4931,7 +4932,7 @@ nameVerifyReason = \case
NVOInconclusive r -> Just r
-- | Verify a contact's claimed name (§4.6): persist the 3-state status and return the updated contact
-- with a Nothing/Just-reason result. Network/resolver failures surface as ChatErrorAgent (retryable).
-- with a Nothing/Just-reason result. Network/resolver failures are reported as ChatErrorAgent (retryable).
apiVerifyContactName :: User -> NetworkRequestMode -> ContactId -> CM ChatResponse
apiVerifyContactName user nm contactId = do
cxt <- chatStoreCxt
+1 -1
View File
@@ -382,7 +382,7 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
-- Missing connection/entity errors here will be sent to the view but not shown as CRITICAL alert,
-- as in this case no need to ACK message - we can't process messages for this connection anyway.
-- SEDBBusyError will be re-thrown as CRITICAL (via `critical`) as it indicates a transient lock/IO
-- condition that usually resolves after app restart. Other SEDBException flavours surface as
-- condition that usually resolves after app restart. Other SEDBException kinds show up as
-- non-CRITICAL store errors.
entity <- critical agentConnId $ withStore (\db -> getConnectionEntity db cxt user $ AgentConnId agentConnId) >>= updateConnStatus
case agentMessage of
+2 -2
View File
@@ -563,7 +563,7 @@ deleteUnusedProfile_ db userId profileId =
-- | Updates the contact profile, writing the peer's contact_domain claim onto
-- the profile. Resets contact_domain_verification to NULL only when the claimed
-- name changes (the prior verification was tied to the prior claim); an XInfo
-- carrying the same name keeps the existing status, exactly as a badge does. No
-- with the same name keeps the existing status, exactly as a badge does. No
-- conflict clearing (no UNIQUE index).
updateContactProfile :: DB.Connection -> StoreCxt -> User -> Contact -> Profile -> ExceptT StoreError IO Contact
updateContactProfile db cxt user@User {userId} c p' = do
@@ -577,7 +577,7 @@ updateContactProfile db cxt user@User {userId} c p' = do
Profile {displayName = newName, contactDomain, preferences} = p'
mergedPreferences = contactUserPreferences user userPreferences preferences $ contactConnIncognito c
claimChanged = prevDomain /= (unStrJSON <$> contactDomain)
-- a name proof never rides an XInfo peer-send (we never mint or accept a PHTest name proof):
-- a name proof is never sent in a profile update (XInfo) from the peer (we never make or accept a PHTest name proof):
-- keep the stored proof when the name is unchanged, drop it when the name changes (then it's unverified)
p'' = (p' :: Profile) {contactDomainProof = if claimChanged then Nothing else prevProof}
clearVerificationIfClaimChanged =
+1 -1
View File
@@ -796,7 +796,7 @@ toLocalProfile profileId Profile {displayName, fullName, shortDescr, image, cont
fromLocalProfile :: LocalProfile -> Profile
fromLocalProfile LocalProfile {displayName, fullName, shortDescr, image, contactLink, preferences, peerType, localBadge, contactDomain} =
-- contactDomainProof is generated fresh at send (presentUserBadge) / dropped by redaction, never carried from the stored profile
-- contactDomainProof is generated fresh at send (presentUserBadge) / dropped by redaction, never copied from the stored profile
Profile {displayName, fullName, shortDescr, image, contactLink, preferences, peerType, badge = localBadge >>= wireBadge, contactDomain = StrJSON <$> contactDomain, contactDomainProof = Nothing}
where
-- any stored peer proof rides the wire (receivers verify independently); the own credential is presented fresh, and a display-only badge never sends