Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Sodium with CryptoSwift #140

Merged
merged 1 commit into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"pins" : [
{
"identity" : "cryptoswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
"state" : {
"revision" : "7892a123f7e8d0fe62f9f03728b17bbd4f94df5c",
"version" : "1.8.1"
}
},
{
"identity" : "secp256k1.swift",
"kind" : "remoteSourceControl",
Expand All @@ -26,14 +35,6 @@
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
"version" : "1.0.0"
}
},
{
"identity" : "swift-sodium",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jedisct1/swift-sodium.git",
"state" : {
"revision" : "63240810df971557fe9badc557257bdfbfeb90a3"
}
}
],
"version" : 2
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let package = Package(
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.0.0"),
.package(url: "https://github.com/GigaBitcoin/secp256k1.swift", from: "0.12.2"),
.package(url: "https://github.com/jedisct1/swift-sodium.git", revision: "63240810df971557fe9badc557257bdfbfeb90a3")
.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMajor(from: "1.8.1"))
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
Expand All @@ -26,7 +26,7 @@ let package = Package(
name: "NostrSDK",
dependencies: [
.product(name: "secp256k1", package: "secp256k1.swift"),
.product(name: "Clibsodium", package: "swift-sodium")
"CryptoSwift"
]
),
.testTarget(
Expand Down
67 changes: 30 additions & 37 deletions Sources/NostrSDK/NIP44v2Encrypting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
//

import Foundation
import Clibsodium
import CryptoKit
import CryptoSwift
import secp256k1

public enum NIP44v2EncryptingError: Error {
Expand Down Expand Up @@ -144,7 +144,7 @@ extension NIP44v2Encrypting {
throw NIP44v2EncryptingError.paddingInvalid
}

let unpadded = padded.bytes[2..<2+unpaddedLength]
let unpadded = toBytes(from: padded)[2..<2+unpaddedLength]
let paddedLength = try calculatePaddedLength(unpaddedLength)

guard unpaddedLength > 0,
Expand Down Expand Up @@ -199,16 +199,21 @@ extension NIP44v2Encrypting {

let combined = aad + message

return Data(HMAC<CryptoKit.SHA256>.authenticationCode(for: combined, using: SymmetricKey(data: key)).bytes)
return Data(CryptoKit.HMAC<CryptoKit.SHA256>.authenticationCode(for: combined, using: SymmetricKey(data: key)))
}

private func toBytes(from data: Data) -> [UInt8] {
bryanmontz marked this conversation as resolved.
Show resolved Hide resolved
data.withUnsafeBytes { bytesPointer in Array(bytesPointer) }
}

private func preparePublicKeyBytes(from publicKey: PublicKey) throws -> [UInt8] {
guard let publicKeyBytes = publicKey.hex.hexDecoded?.bytes else {
guard let publicKeyHexDecoded = publicKey.hex.hexDecoded else {
throw NIP44v2EncryptingError.publicKeyInvalid
}
let publicKeyBytes = toBytes(from: publicKeyHexDecoded)

let prefix = Data([2])
let prefixBytes = prefix.bytes
let prefixBytes = toBytes(from: prefix)

return prefixBytes + publicKeyBytes
}
Expand Down Expand Up @@ -240,27 +245,29 @@ extension NIP44v2Encrypting {
/// Calculates long-term key between users A and B.
/// The conversation key of A's private key and B's public key is equal to the conversation key of B's private key and A's public key.
func conversationKey(privateKeyA: PrivateKey, publicKeyB: PublicKey) throws -> ContiguousBytes {
guard let privateKeyABytes = privateKeyA.hex.hexDecoded?.bytes else {
guard let privateKeyAHexDecoded = privateKeyA.hex.hexDecoded else {
throw NIP44v2EncryptingError.privateKeyInvalid
}
let privateKeyABytes = toBytes(from: privateKeyAHexDecoded)
let publicKeyBBytes = try preparePublicKeyBytes(from: publicKeyB)
let parsedPublicKeyB = try parsePublicKey(from: publicKeyBBytes)
let sharedSecret = try computeSharedSecret(using: parsedPublicKeyB, and: privateKeyABytes)

return HKDF<CryptoKit.SHA256>.extract(inputKeyMaterial: SymmetricKey(data: sharedSecret), salt: Data("nip44-v2".utf8))
return CryptoKit.HKDF<CryptoKit.SHA256>.extract(inputKeyMaterial: SymmetricKey(data: sharedSecret), salt: Data("nip44-v2".utf8))
}

/// Calculates unique per-message key.
func messageKeys(conversationKey: ContiguousBytes, nonce: Data) throws -> MessageKeys {
guard conversationKey.bytes.count == 32 else {
throw NIP44v2EncryptingError.conversationKeyLengthInvalid(conversationKey.bytes.count)
let conversationKeyByteCount = conversationKey.bytes.count
guard conversationKeyByteCount == 32 else {
throw NIP44v2EncryptingError.conversationKeyLengthInvalid(conversationKeyByteCount)
}

guard nonce.count == 32 else {
throw NIP44v2EncryptingError.nonceLengthInvalid(nonce.count)
}

let keys = HKDF<CryptoKit.SHA256>.expand(pseudoRandomKey: conversationKey, info: nonce, outputByteCount: 76)
let keys = CryptoKit.HKDF<CryptoKit.SHA256>.expand(pseudoRandomKey: conversationKey, info: nonce, outputByteCount: 76)
let keysBytes = keys.bytes

let chaChaKey = Data(keysBytes[0..<32])
Expand All @@ -281,31 +288,25 @@ extension NIP44v2Encrypting {

let messageKeys = try messageKeys(conversationKey: conversationKey, nonce: nonceData)
let padded = try pad(plaintext)
let paddedBytes = padded.bytes
let paddedBytes = toBytes(from: padded)

let chaChaKey = messageKeys.chaChaKey.bytes
let chaChaNonce = messageKeys.chaChaNonce.bytes
let chaChaKey = toBytes(from: messageKeys.chaChaKey)
let chaChaNonce = toBytes(from: messageKeys.chaChaNonce)

var ciphertext = Data(count: padded.count)
try ciphertext.withUnsafeMutableBytes { (pointer: UnsafeMutableRawBufferPointer) in
guard let ciphertextPointer = pointer.bindMemory(to: UInt8.self).baseAddress else {
throw NIP44v2EncryptingError.chaCha20EncryptionFailed
}

crypto_stream_chacha20_ietf_xor(ciphertextPointer, paddedBytes, UInt64(padded.count), chaChaNonce, chaChaKey)
}
let ciphertext = try ChaCha20(key: chaChaKey, iv: chaChaNonce).encrypt(paddedBytes)
let ciphertextData = Data(ciphertext)

let mac = try hmacAad(key: messageKeys.hmacKey, message: ciphertext, aad: nonceData)
let mac = try hmacAad(key: messageKeys.hmacKey, message: ciphertextData, aad: nonceData)

let data = Data([2]) + nonceData + ciphertext + mac
let data = Data([2]) + nonceData + ciphertextData + mac
return data.base64EncodedString()
}

func decrypt(payload: String, conversationKey: ContiguousBytes) throws -> String {
let decodedPayload = try decodePayload(payload)
let nonce = decodedPayload.nonce
let ciphertext = decodedPayload.ciphertext
let ciphertextBytes = ciphertext.bytes
let ciphertextBytes = toBytes(from: ciphertext)
let mac = decodedPayload.mac

let messageKeys = try messageKeys(conversationKey: conversationKey, nonce: nonce)
Expand All @@ -316,20 +317,12 @@ extension NIP44v2Encrypting {
throw NIP44v2EncryptingError.macInvalid
}

let chaChaNonce = messageKeys.chaChaNonce.bytes
let chaChaKey = messageKeys.chaChaKey.bytes
let chaChaNonce = toBytes(from: messageKeys.chaChaNonce)
let chaChaKey = toBytes(from: messageKeys.chaChaKey)

let ciphertextLength = ciphertext.count
var paddedPlaintext = Data(count: ciphertextLength)

try paddedPlaintext.withUnsafeMutableBytes { (pointer: UnsafeMutableRawBufferPointer) in
guard let paddedPlaintextPointer = pointer.bindMemory(to: UInt8.self).baseAddress else {
throw NIP44v2EncryptingError.chaCha20DecryptionFailed
}

crypto_stream_chacha20_ietf_xor(paddedPlaintextPointer, ciphertextBytes, UInt64(ciphertextLength), chaChaNonce, chaChaKey)
}
let paddedPlaintext = try ChaCha20(key: chaChaKey, iv: chaChaNonce).decrypt(ciphertextBytes)
let paddedPlaintextData = Data(paddedPlaintext.bytes)

return try unpad(paddedPlaintext)
return try unpad(paddedPlaintextData)
}
}
Loading