hkdf for short links

This commit is contained in:
Evgeny @ SimpleX Chat
2026-03-27 15:44:59 +00:00
parent bda906c8fb
commit be8d70e289
3 changed files with 47 additions and 2 deletions
@@ -218,7 +218,9 @@ Each step produces working, tested code. Steps 1-11 work without block encryptio
**Done**. Function: `connShortLinkStrP` in `agent/protocol.ts`. Uses `base64urlDecode` from xftp-web `description.ts`.
**Future**: add long link parsing (`ConnectionRequestUri`) and an either-parser that handles both short and long links.
**Future**:
- Add long link parsing (`ConnectionRequestUri`) and an either-parser that handles both short and long links.
- Add `restoreShortLink`: preset servers are shortened to host-only (`SMPServerOnlyHost` - no port, no keyHash). After parsing, `restoreShortLink` looks up the full server by hostname from a preset servers list. Without this, connections to preset servers will fail. See `Agent/Protocol.hs:1692`.
### Step 7: HKDF Key Derivation
+21
View File
@@ -0,0 +1,21 @@
// Short link key derivation.
// Mirrors: Simplex.Messaging.Crypto.ShortLink
import {hkdf} from "@noble/hashes/hkdf"
import {sha512} from "@noble/hashes/sha512"
// contactShortLinkKdf (Agent/Protocol.hs:47-50)
// hkdf("", linkKey, "SimpleXContactLink", 56) -> (linkId[24], sbKey[32])
export function contactShortLinkKdf(linkKey: Uint8Array): {linkId: Uint8Array; sbKey: Uint8Array} {
const derived = hkdf(sha512, linkKey, new Uint8Array(0), "SimpleXContactLink", 56)
return {
linkId: derived.slice(0, 24),
sbKey: derived.slice(24, 56),
}
}
// invShortLinkKdf (Agent/Protocol.hs:52-53)
// hkdf("", linkKey, "SimpleXInvLink", 32) -> sbKey[32]
export function invShortLinkKdf(linkKey: Uint8Array): Uint8Array {
return hkdf(sha512, linkKey, new Uint8Array(0), "SimpleXInvLink", 32)
}
+23 -1
View File
@@ -18,9 +18,10 @@ import Data.List.NonEmpty (NonEmpty (..))
import Data.Word (Word16)
import qualified Simplex.Messaging.Agent.Protocol as AP
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.ShortLink (contactShortLinkKdf, invShortLinkKdf)
import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String (strEncode)
import Simplex.Messaging.Protocol (SMPServer, pattern SMPServer)
import Simplex.Messaging.Protocol (EntityId (..), SMPServer, pattern SMPServer)
import Simplex.Messaging.Server.Env.STM (AStoreType (..))
import Simplex.Messaging.Server.MsgStore.Types (SMSType (..), SQSType (..))
import Simplex.Messaging.Server.Web (attachStaticAndWS)
@@ -52,6 +53,9 @@ impWS = "import { connectSMP, sendBlock, receiveBlock } from './dist/transport/w
impAgentProto :: String
impAgentProto = "import { connShortLinkStrP } from './dist/agent/protocol.js';"
impCryptoShortLink :: String
impCryptoShortLink = "import { contactShortLinkKdf, invShortLinkKdf } from './dist/crypto/shortLink.js';"
jsStr :: B.ByteString -> String
jsStr bs = "'" <> BC.unpack bs <> "'"
@@ -154,6 +158,24 @@ smpWebTests = describe "SMP Web Client" $ do
<> "})")
tsEncoded `shouldBe` hsEncoded
describe "crypto/shortLink" $ do
describe "contactShortLinkKdf" $ do
it "TypeScript produces same linkId and sbKey as Haskell" $ do
let linkKey = AP.LinkKey $ B.pack [1..32]
(EntityId hsLinkId, C.SbKey hsKey) = contactShortLinkKdf linkKey
tsResult <- callNode $ impCryptoShortLink
<> "const r = contactShortLinkKdf(" <> jsUint8 (B.pack [1..32]) <> ");"
<> jsOut ("new Uint8Array([...r.linkId, ...r.sbKey])")
tsResult `shouldBe` (hsLinkId <> hsKey)
describe "invShortLinkKdf" $ do
it "TypeScript produces same sbKey as Haskell" $ do
let linkKey = AP.LinkKey $ B.pack [50..81]
C.SbKey hsKey = invShortLinkKdf linkKey
tsResult <- callNode $ impCryptoShortLink
<> jsOut ("invShortLinkKdf(" <> jsUint8 (B.pack [50..81]) <> ")")
tsResult `shouldBe` hsKey
describe "agent/protocol" $ do
describe "ConnShortLink" $ do
it "parses simplex: contact link" $ do