diff --git a/protocol/simplex-messaging.md b/protocol/simplex-messaging.md index 86fdb4491..c73cdaa4b 100644 --- a/protocol/simplex-messaging.md +++ b/protocol/simplex-messaging.md @@ -1465,15 +1465,13 @@ rslv = %s"RSLV" SP json-bytes ; json-bytes consumes the remainder of the trans **Server-side validation.** The names router parses `name` as a fully-qualified domain (TLD required — bare labels are rejected), extracts the TLD, and looks -up the expected SNRC contract address in its INI whitelist -(`registry_tld_simplex`, `registry_tld_testing`, `registry_tld_all`). -`registry_tld_all` is the catch-all used when no TLD-specific entry matches -the requested TLD (and the only entry that can resolve web domains). If no -whitelist entry matches the TLD, or if the client-supplied `contract` differs -from the configured address, the server replies with `ERR AUTH` without -contacting the chain. This lets one names router safely host multiple TLDs -(each backed by its own SNRC contract) and reject clients pointing at a -contract the operator doesn't run. +up the expected SNRC contract address in a whitelist hardcoded in the server +binary (TLD-specific addresses with an optional catch-all for unspecified +TLDs and web domains). If no whitelist entry matches the TLD, or if the +client-supplied `contract` differs from the configured address, the server +replies with `ERR AUTH` without contacting the chain. This lets one names +router safely host multiple TLDs (each backed by its own SNRC contract) and +reject clients pointing at a contract the operator doesn't run. The names router responds with either a `NAME` response carrying the resolved record, or `ERR AUTH` collapsing every failure mode (name not found, malformed diff --git a/src/Simplex/Messaging/Protocol.hs b/src/Simplex/Messaging/Protocol.hs index 1943eb123..d7ec0666e 100644 --- a/src/Simplex/Messaging/Protocol.hs +++ b/src/Simplex/Messaging/Protocol.hs @@ -754,7 +754,7 @@ instance J.ToJSON NameOwner where instance J.FromJSON NameOwner where parseJSON = J.withText "NameOwner" $ \t -> do - -- Accept "0x" and "0X" prefixes (matches Server/Main.hs:parseEthAddr via fromHex). + -- Accept "0x" and "0X" prefixes (matches the Server-side hex decoder). let hex = fromMaybe t (T.stripPrefix "0x" t <|> T.stripPrefix "0X" t) case BAE.convertFromBase BAE.Base16 (encodeUtf8 hex) of Left e -> fail e diff --git a/src/Simplex/Messaging/Server/Main.hs b/src/Simplex/Messaging/Server/Main.hs index dccf01743..6435c6483 100644 --- a/src/Simplex/Messaging/Server/Main.hs +++ b/src/Simplex/Messaging/Server/Main.hs @@ -77,9 +77,8 @@ import Simplex.Messaging.Server.Web (EmbeddedWebParams (..), WebHttpsParams (..) import Simplex.Messaging.Server.MsgStore.Journal (JournalMsgStore (..), QStoreCfg (..), stmQueueStore) import Simplex.Messaging.Server.MsgStore.Types (MsgStoreClass (..), SQSType (..), SMSType (..), newMsgStore) import Network.URI (URI (..), URIAuth (..), parseAbsoluteURI) -import Simplex.Messaging.Protocol (mkNameOwner, NameOwner) +import Simplex.Messaging.Protocol (mkNameOwner) import Simplex.Messaging.Server.Names (NamesConfig (..), RpcAuth (..), TldRegistries (..)) -import Simplex.Messaging.Server.Names.Eth.RPC (fromHex) import Simplex.Messaging.Server.QueueStore.Postgres.Config import Simplex.Messaging.Server.StoreLog.ReadWrite (readQueueStore) import Simplex.Messaging.Transport (supportedProxyClientSMPRelayVRange, alpnSupportedSMPHandshakes, supportedServerSMPRelayVRange) @@ -807,11 +806,10 @@ readNamesConfig ini | otherwise = let rpcAuth_ = either (error . ("[NAMES] rpc_auth: " <>)) Just . parseRpcAuth =<< eitherToMaybe (lookupValue "NAMES" "rpc_auth" ini) endpoint = requiredText "ethereum_endpoint" - registries = readTldRegistries in Just NamesConfig { ethereumEndpoint = either (error . ("[NAMES] ethereum_endpoint: " <>)) id (validateUrl endpoint rpcAuth_), - tldRegistries = registries, + tldRegistries = hardcodedTldRegistries, rpcAuth = rpcAuth_, rpcTimeoutMs = boundedIniInt 3000 100 60000 "rpc_timeout_ms", rpcMaxResponseBytes = boundedIniInt 262144 1024 16777216 "rpc_max_response_bytes", @@ -833,18 +831,22 @@ readNamesConfig ini n | n >= floor_ && n <= ceiling_ -> n | otherwise -> error $ "[NAMES] " <> T.unpack key <> " must be in [" <> show floor_ <> ".." <> show ceiling_ <> "] (got " <> show n <> ")" - readTldRegistries = - let regs = TldRegistries - { tldSimplex = optionalAddr "registry_tld_simplex", - tldTesting = optionalAddr "registry_tld_testing", - tldAll = optionalAddr "registry_tld_all" - } - in case (tldSimplex regs, tldTesting regs, tldAll regs) of - (Nothing, Nothing, Nothing) -> - error "[NAMES] at least one of registry_tld_simplex, registry_tld_testing, registry_tld_all is required" - _ -> regs - optionalAddr key = - either (error . (("[NAMES] " <> T.unpack key <> ": ") <>)) Just . parseEthAddr =<< eitherToMaybe (lookupValue "NAMES" key ini) + +-- | Hardcoded SNRC contract whitelist. Placeholder addresses until the +-- launch contracts are deployed; replaced in code rather than INI so +-- operators can't accidentally point a names router at the wrong contract +-- during the bootstrap phase. The TldRegistries shape + lookup precedence +-- (TLD-specific then `tldAll` catch-all) is unchanged from the previous +-- INI-driven form. +hardcodedTldRegistries :: TldRegistries +hardcodedTldRegistries = + TldRegistries + { tldSimplex = Just (placeholderAddr '\x11'), + tldTesting = Just (placeholderAddr '\x22'), + tldAll = Nothing + } + where + placeholderAddr c = either error id $ mkNameOwner (B.replicate 20 c) -- | Validate the ethereum_endpoint URL: -- * scheme must be http: or https: @@ -913,15 +915,6 @@ validateUrl url auth_ = do '0' : 'x' : rest -> all isHexDigit rest lh -> not (null lh) && all isDigit lh --- | Parse a 20-byte Ethereum address as text "0x[hex40]" or "[hex40]". --- EIP-55 mixed-case checksum verification is a follow-up. -parseEthAddr :: Text -> Either String NameOwner -parseEthAddr t = do - bs <- fromHex (encodeUtf8 t) - if B.length bs == 20 - then mkNameOwner bs - else Left "expected a 20-byte address (40 hex characters, optionally 0x-prefixed)" - -- | Parse an rpc_auth INI value. Scheme keyword is case-insensitive so -- "Bearer " / "BEARER " (Caddy / RFC 7235 convention) work -- as well as the lowercase form. diff --git a/src/Simplex/Messaging/Server/Main/Init.hs b/src/Simplex/Messaging/Server/Main/Init.hs index c5ac52cad..9ec67bc17 100644 --- a/src/Simplex/Messaging/Server/Main/Init.hs +++ b/src/Simplex/Messaging/Server/Main/Init.hs @@ -168,14 +168,9 @@ iniFileContent cfgPath logPath opts host basicAuth controlPortPwds = \# Central Reth via Caddy:\n\ \# ethereum_endpoint: https://eth.simplex.chat:443\n\ \# rpc_auth: basic :\n\ - \# Per-TLD SNRC contract whitelist. At least one entry must be set.\n\ - \# Each RSLV carries the contract address the client wants queried;\n\ - \# the server only accepts it if it matches the address configured for\n\ - \# that TLD (or registry_tld_all as catch-all for any unspecified TLD,\n\ - \# including web domains).\n\ - \# registry_tld_simplex: 0x\n\ - \# registry_tld_testing: 0x\n\ - \# registry_tld_all: 0x\n\ + \# The SNRC contract addresses are hardcoded in the server binary; each\n\ + \# RSLV's contract field is verified against the binary's whitelist for\n\ + \# the requested TLD. Operators do NOT configure registries here.\n\ \# rpc_timeout_ms: 3000\n\ \# rpc_max_response_bytes: 262144\n\ \# rpc_max_concurrency: 8\n\n\