// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation import Sodium import SessionUtilitiesKit extension SnodeAPI { public class ONSResolveResponse: SnodeResponse { private struct Result: Codable { enum CodingKeys: String, CodingKey { case nonce case encryptedValue = "encrypted_value" } fileprivate let nonce: String? fileprivate let encryptedValue: String } enum CodingKeys: String, CodingKey { case result } private let result: Result // MARK: - Initialization required init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) result = try container.decode(Result.self, forKey: .result) try super.init(from: decoder) } // MARK: - Convenience func sessionId(sodium: Sodium, nameBytes: [UInt8], nameHashBytes: [UInt8]) throws -> String { let ciphertext: [UInt8] = Data(hex: result.encryptedValue).bytes // Handle old Argon2-based encryption used before HF16 guard let hexEncodedNonce: String = result.nonce else { let salt: [UInt8] = Data(repeating: 0, count: sodium.pwHash.SaltBytes).bytes guard let key: [UInt8] = sodium.pwHash.hash( outputLength: sodium.secretBox.KeyBytes, passwd: nameBytes, salt: salt, opsLimit: sodium.pwHash.OpsLimitModerate, memLimit: sodium.pwHash.MemLimitModerate, alg: .Argon2ID13 ) else { throw SnodeAPIError.hashingFailed } let nonce: [UInt8] = Data(repeating: 0, count: sodium.secretBox.NonceBytes).bytes guard let sessionIdAsData: [UInt8] = sodium.secretBox.open(authenticatedCipherText: ciphertext, secretKey: key, nonce: nonce) else { throw SnodeAPIError.decryptionFailed } return sessionIdAsData.toHexString() } let nonceBytes: [UInt8] = Data(hex: hexEncodedNonce).bytes // xchacha-based encryption // key = H(name, key=H(name)) guard let key: [UInt8] = sodium.genericHash.hash(message: nameBytes, key: nameHashBytes) else { throw SnodeAPIError.hashingFailed } guard // Should always be equal in practice ciphertext.count >= (SessionId.byteCount + sodium.aead.xchacha20poly1305ietf.ABytes), let sessionIdAsData = sodium.aead.xchacha20poly1305ietf.decrypt( authenticatedCipherText: ciphertext, secretKey: key, nonce: nonceBytes ) else { throw SnodeAPIError.decryptionFailed } return sessionIdAsData.toHexString() } } }