Hello,
I know the answer is too late for you but I am leaving it in case that someone finds it useful.
What you've mentioned so far is correct you do combine multiple keys to create 3 or 4 shared secrets (depending on if you have the Onte time pre-keys or not). DH or Diffie-Hellman is a key agreement. And you should treat it as such by using the KeyAgreement of an appropriate Public-key cryptography curve like (P256, Curve25519,...). This will get you 3 or 4 shared secrets that you will concatenate together in one string.
Here is a rough outline:
let s1 = try self?.myIdKeyPair?.privateKey.sharedSecretFromKeyAgreement(with: otherSignedPubKey)
let s2 = try self?.oneTimeKey?.privateKey.sharedSecretFromKeyAgreement(with: otherIdPub)
let s3 = try self?.oneTimeKey?.privateKey.sharedSecretFromKeyAgreement(with: otherSignedPubKey)
let s4 = try self?.oneTimeKey?.privateKey.sharedSecretFromKeyAgreement(with: otherOneTimeKey)
let computedSharedSecretString = s1!.description.split(separator: " ")[1] +
s2!.description.split(separator: " ")[1] +
s3!.description.split(separator: " ")[1] +
s4!.description.split(separator: " ")[1]
You'd want to use this computedSharedSecretString and run it through a KDF function like HKDF and hash the output afterwards (SHA256)
func hkdf<H: HashFunction>(_ seed: Data, salt: Data, info: Data, outputSize: Int = 32, hashFunction: H) -> Data? {
let hashByteCount = H.Digest.byteCount
let iterations = UInt8(ceil(Double(outputSize) / Double(hashByteCount)))
guard iterations <= 255 else {
return nil
}
let prk = HMAC<H>.authenticationCode(for: seed, using: SymmetricKey(data: salt))
let key = SymmetricKey(data: prk)
var hkdf = Data()
var value = Data()
for i in 1...iterations {
value.append(info)
value.append(i)
let code = HMAC<H>.authenticationCode(for: value, using: key)
hkdf.append(contentsOf: code)
value = Data(code)
}
return hkdf.prefix(outputSize)
}