mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-05-26 01:04:44 +00:00
decrypt link data
This commit is contained in:
@@ -66,7 +66,7 @@ Bottom-up, function-by-function. Each TypeScript function tested against its Has
|
||||
|
||||
**Project location**: `simplexmq-2/smp-web/`
|
||||
**Tests**: `simplexmq-2/tests/SMPWebTests.hs` — reuses `callNode`/`jsOut`/`jsUint8` from XFTPWebTests (generalized, not copied)
|
||||
**xftp-web**: npm dependency (encoding, crypto, padding imported directly)
|
||||
**xftp-web**: npm dependency via `file:../xftp-web` (encoding, crypto, padding imported directly). Note: libsodium-wrappers-sumo is xftp-web's dependency; tests must init the same sodium instance that xftp-web's secretbox uses. If xftp-web is ever published to npm, libsodium should become a peerDependency.
|
||||
**File structure**: mirrors Haskell module hierarchy (see RFC section 2)
|
||||
|
||||
**Pattern for each function**:
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Short link key derivation.
|
||||
// Short link key derivation and decryption.
|
||||
// Mirrors: Simplex.Messaging.Crypto.ShortLink
|
||||
|
||||
import {hkdf} from "../crypto.js"
|
||||
import {cbDecrypt} from "@simplex-chat/xftp-web/dist/crypto/secretbox.js"
|
||||
import {Decoder, decodeBytes} from "@simplex-chat/xftp-web/dist/protocol/encoding.js"
|
||||
|
||||
const emptySalt = new Uint8Array(0)
|
||||
|
||||
@@ -20,3 +22,29 @@ export function contactShortLinkKdf(linkKey: Uint8Array): {linkId: Uint8Array; s
|
||||
export function invShortLinkKdf(linkKey: Uint8Array): Uint8Array {
|
||||
return hkdf(emptySalt, linkKey, "SimpleXInvLink", 32)
|
||||
}
|
||||
|
||||
// decryptLinkData (Crypto/ShortLink.hs:100-125)
|
||||
// Decrypts both EncDataBytes blobs, strips signature prefix, returns raw data.
|
||||
// Signature verification is skipped for spike.
|
||||
export function decryptLinkData(
|
||||
sbKey: Uint8Array,
|
||||
encFixedData: Uint8Array,
|
||||
encUserData: Uint8Array
|
||||
): {fixedData: Uint8Array; userData: Uint8Array} {
|
||||
return {
|
||||
fixedData: decryptSigned(sbKey, encFixedData),
|
||||
userData: decryptSigned(sbKey, encUserData),
|
||||
}
|
||||
}
|
||||
|
||||
// EncDataBytes format: [nonce 24 bytes][ciphertext with prepended Poly1305 tag]
|
||||
// After decrypt+unpad: [sig ByteString (1-byte len + 64 bytes)][data]
|
||||
function decryptSigned(sbKey: Uint8Array, encData: Uint8Array): Uint8Array {
|
||||
const nonce = encData.subarray(0, 24)
|
||||
const ct = encData.subarray(24)
|
||||
const plaintext = cbDecrypt(sbKey, nonce, ct)
|
||||
// Skip signature: decodeBytes reads 1-byte length + that many bytes
|
||||
const d = new Decoder(plaintext)
|
||||
decodeBytes(d) // signature, discarded
|
||||
return d.takeAll()
|
||||
}
|
||||
|
||||
+29
-1
@@ -54,7 +54,11 @@ impAgentProto :: String
|
||||
impAgentProto = "import { connShortLinkStrP } from './dist/agent/protocol.js';"
|
||||
|
||||
impCryptoShortLink :: String
|
||||
impCryptoShortLink = "import { contactShortLinkKdf, invShortLinkKdf } from './dist/crypto/shortLink.js';"
|
||||
impCryptoShortLink = "import { contactShortLinkKdf, invShortLinkKdf, decryptLinkData } from './dist/crypto/shortLink.js';"
|
||||
|
||||
-- Init sodium from xftp-web's copy (same instance secretbox.ts uses)
|
||||
impSodium :: String
|
||||
impSodium = "import sodium from '@simplex-chat/xftp-web/node_modules/libsodium-wrappers-sumo/dist/modules-sumo/libsodium-wrappers.js'; await sodium.ready;"
|
||||
|
||||
jsStr :: B.ByteString -> String
|
||||
jsStr bs = "'" <> BC.unpack bs <> "'"
|
||||
@@ -176,6 +180,30 @@ smpWebTests = describe "SMP Web Client" $ do
|
||||
<> jsOut ("invShortLinkKdf(" <> jsUint8 (B.pack [50..81]) <> ")")
|
||||
tsResult `shouldBe` hsKey
|
||||
|
||||
describe "decryptLinkData" $ do
|
||||
it "TypeScript decrypts Haskell-encrypted data" $ do
|
||||
let sbKey = C.unsafeSbKey $ B.pack [1..32]
|
||||
nonce = C.cbNonce $ B.pack [1..24]
|
||||
-- Simulate encodeSign: smpEncode signature <> plaintext
|
||||
fakeSig = B.pack [1..64] -- 64-byte "signature"
|
||||
fixedPlain = "fixed-data-here"
|
||||
userPlain = "user-data-here"
|
||||
signedFixed = smpEncode fakeSig <> fixedPlain
|
||||
signedUser = smpEncode fakeSig <> userPlain
|
||||
case (,) <$> C.sbEncrypt sbKey nonce signedFixed 2008
|
||||
<*> C.sbEncrypt sbKey nonce signedUser 13784 of
|
||||
Left e -> expectationFailure $ "encrypt failed: " <> show e
|
||||
Right (ctFixed, ctUser) -> do
|
||||
let encFixed = C.unCbNonce nonce <> ctFixed
|
||||
encUser = C.unCbNonce nonce <> ctUser
|
||||
tsResult <- callNode $ impSodium <> impCryptoShortLink
|
||||
<> "const r = decryptLinkData("
|
||||
<> jsUint8 (C.unSbKey sbKey) <> ","
|
||||
<> jsUint8 encFixed <> ","
|
||||
<> jsUint8 encUser <> ");"
|
||||
<> jsOut ("new Uint8Array([...r.fixedData, 0, ...r.userData])")
|
||||
tsResult `shouldBe` (fixedPlain <> B.singleton 0 <> userPlain)
|
||||
|
||||
describe "agent/protocol" $ do
|
||||
describe "ConnShortLink" $ do
|
||||
it "parses simplex: contact link" $ do
|
||||
|
||||
Reference in New Issue
Block a user