Skip to content

Commit

Permalink
Allow envelopes to be decrypted but not decoded (#189)
Browse files Browse the repository at this point in the history
* Allow envelopes to be decrypted but not decoded

* Bump podspec
  • Loading branch information
nakajima authored Nov 15, 2023
1 parent 1afe5bd commit 3d5103e
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 47 deletions.
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
"location" : "https://github.com/xmtp/xmtp-rust-swift",
"state" : {
"branch" : "main",
"revision" : "e857176b7e368c51e1dadcbbcce648bb20432f26"
"revision" : "eb931c2f467c2a71a621f54d7ae22887b234c13a"
}
}
],
Expand Down
9 changes: 9 additions & 0 deletions Sources/XMTP/Conversation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,15 @@ public enum Conversation: Sendable {
}
}

public func decryptedMessages(limit: Int? = nil, before: Date? = nil, after: Date? = nil, direction: PagingInfoSortDirection? = .descending) async throws -> [DecryptedMessage] {
switch self {
case let .v1(conversationV1):
return try await conversationV1.decryptedMessages(limit: limit, before: before, after: after, direction: direction)
case let .v2(conversationV2):
return try await conversationV2.decryptedMessages(limit: limit, before: before, after: after, direction: direction)
}
}

var client: Client {
switch self {
case let .v1(conversationV1):
Expand Down
25 changes: 21 additions & 4 deletions Sources/XMTP/ConversationV1.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,17 @@ public struct ConversationV1 {
}
}

func decryptedMessages(limit: Int? = nil, before: Date? = nil, after: Date? = nil, direction: PagingInfoSortDirection? = .descending) async throws -> [DecryptedMessage] {
let pagination = Pagination(limit: limit, before: before, after: after, direction: direction)

let envelopes = try await client.apiClient.envelopes(
topic: Topic.directMessageV1(client.address, peerAddress).description,
pagination: pagination
)

return try envelopes.map { try decrypt(envelope: $0) }
}

func messages(limit: Int? = nil, before: Date? = nil, after: Date? = nil, direction: PagingInfoSortDirection? = .descending) async throws -> [DecodedMessage] {
let pagination = Pagination(limit: limit, before: before, after: after, direction: direction)

Expand All @@ -198,19 +209,25 @@ public struct ConversationV1 {
}
}

public func decode(envelope: Envelope) throws -> DecodedMessage {
func decrypt(envelope: Envelope) throws -> DecryptedMessage {
let message = try Message(serializedData: envelope.message)
let decrypted = try message.v1.decrypt(with: client.privateKeyBundleV1)

let encodedMessage = try EncodedContent(serializedData: decrypted)
let header = try message.v1.header

return DecryptedMessage(id: generateID(from: envelope), encodedContent: encodedMessage, senderAddress: header.sender.walletAddress, sentAt: message.v1.sentAt)
}

public func decode(envelope: Envelope) throws -> DecodedMessage {
let decryptedMessage = try decrypt(envelope: envelope)

var decoded = DecodedMessage(
client: client,
topic: envelope.contentTopic,
encodedContent: encodedMessage,
senderAddress: header.sender.walletAddress,
sent: message.v1.sentAt
encodedContent: decryptedMessage.encodedContent,
senderAddress: decryptedMessage.senderAddress,
sent: decryptedMessage.sentAt
)

decoded.id = generateID(from: envelope)
Expand Down
25 changes: 14 additions & 11 deletions Sources/XMTP/ConversationV2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,30 @@ public struct ConversationV2 {
return try await prepareMessage(encodedContent: encoded, options: options)
}

func messages(limit: Int? = nil, before: Date? = nil, after: Date? = nil, direction: PagingInfoSortDirection? = .descending) async throws -> [DecodedMessage] {
let pagination = Pagination(limit: limit, before: before, after: after, direction: direction)
func messages(limit: Int? = nil, before: Date? = nil, after: Date? = nil, direction: PagingInfoSortDirection? = .descending) async throws -> [DecodedMessage] {
let pagination = Pagination(limit: limit, before: before, after: after, direction: direction)
let envelopes = try await client.apiClient.envelopes(topic: topic.description, pagination: pagination)

return envelopes.compactMap { envelope in
do {
return try decode(envelope: envelope)
return try decode(envelope: envelope)
} catch {
print("Error decoding envelope \(error)")
return nil
}
}
}

func decryptedMessages(limit: Int? = nil, before: Date? = nil, after: Date? = nil, direction: PagingInfoSortDirection? = .descending) async throws -> [DecryptedMessage] {
let pagination = Pagination(limit: limit, before: before, after: after, direction: direction)
let envelopes = try await client.apiClient.envelopes(topic: topic.description, pagination: pagination)

return try envelopes.map { envelope in
let message = try Message(serializedData: envelope.message)
return try MessageV2.decrypt(generateID(from: envelope), topic, message.v2, keyMaterial: keyMaterial, client: client)
}
}

var ephemeralTopic: String {
topic.replacingOccurrences(of: "/xmtp/0/m", with: "/xmtp/0/mE")
}
Expand Down Expand Up @@ -168,15 +178,8 @@ public struct ConversationV2 {

public func decode(envelope: Envelope) throws -> DecodedMessage {
let message = try Message(serializedData: envelope.message)
var decoded = try decode(message.v2)

decoded.id = generateID(from: envelope)

return decoded
}

private func decode(_ message: MessageV2) throws -> DecodedMessage {
try MessageV2.decode(message, keyMaterial: keyMaterial, client: client)
return try MessageV2.decode(generateID(from: envelope), topic, message.v2, keyMaterial: keyMaterial, client: client)
}

@discardableResult func send<T>(content: T, options: SendOptions? = nil) async throws -> String {
Expand Down
16 changes: 16 additions & 0 deletions Sources/XMTP/DecodedMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ public struct DecodedMessage: Sendable {

public var client: Client

init(
id: String,
client: Client,
topic: String,
encodedContent: EncodedContent,
senderAddress: String,
sent: Date
) {
self.id = id
self.client = client
self.topic = topic
self.encodedContent = encodedContent
self.senderAddress = senderAddress
self.sent = sent
}

public init(
client: Client,
topic: String,
Expand Down
16 changes: 16 additions & 0 deletions Sources/XMTP/Messages/DecryptedMessage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// DecryptedMessage.swift
//
//
// Created by Pat Nakajima on 11/14/23.
//

import Foundation

public struct DecryptedMessage {
var id: String
var encodedContent: EncodedContent
var senderAddress: String
var sentAt: Date
var topic: String = ""
}
67 changes: 40 additions & 27 deletions Sources/XMTP/Messages/MessageV2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,42 +22,55 @@ extension MessageV2 {
self.ciphertext = ciphertext
}

static func decode(_ message: MessageV2, keyMaterial: Data, client: Client) throws -> DecodedMessage {
do {
let decrypted = try Crypto.decrypt(keyMaterial, message.ciphertext, additionalData: message.headerBytes)
let signed = try SignedContent(serializedData: decrypted)
static func decrypt(_ id: String, _ topic: String, _ message: MessageV2, keyMaterial: Data, client: Client) throws -> DecryptedMessage {
let decrypted = try Crypto.decrypt(keyMaterial, message.ciphertext, additionalData: message.headerBytes)
let signed = try SignedContent(serializedData: decrypted)

guard signed.sender.hasPreKey, signed.sender.hasIdentityKey else {
throw MessageV2Error.decodeError("missing sender pre-key or identity key")
}

guard signed.sender.hasPreKey, signed.sender.hasIdentityKey else {
throw MessageV2Error.decodeError("missing sender pre-key or identity key")
}
let senderPreKey = try PublicKey(signed.sender.preKey)
let senderIdentityKey = try PublicKey(signed.sender.identityKey)

// This is a bit confusing since we're passing keyBytes as the digest instead of a SHA256 hash.
// That's because our underlying crypto library always SHA256's whatever data is sent to it for this.
if !(try senderPreKey.signature.verify(signedBy: senderIdentityKey, digest: signed.sender.preKey.keyBytes)) {
throw MessageV2Error.decodeError("pre-key not signed by identity key")
}

let senderPreKey = try PublicKey(signed.sender.preKey)
let senderIdentityKey = try PublicKey(signed.sender.identityKey)
// Verify content signature
let key = try PublicKey.with { key in
key.secp256K1Uncompressed.bytes = try KeyUtilx.recoverPublicKeySHA256(from: signed.signature.rawData, message: Data(message.headerBytes + signed.payload))
}

// This is a bit confusing since we're passing keyBytes as the digest instead of a SHA256 hash.
// That's because our underlying crypto library always SHA256's whatever data is sent to it for this.
if !(try senderPreKey.signature.verify(signedBy: senderIdentityKey, digest: signed.sender.preKey.keyBytes)) {
throw MessageV2Error.decodeError("pre-key not signed by identity key")
}
if key.walletAddress != (try PublicKey(signed.sender.preKey).walletAddress) {
throw MessageV2Error.invalidSignature
}

// Verify content signature
let key = try PublicKey.with { key in
key.secp256K1Uncompressed.bytes = try KeyUtilx.recoverPublicKeySHA256(from: signed.signature.rawData, message: Data(message.headerBytes + signed.payload))
}
let encodedMessage = try EncodedContent(serializedData: signed.payload)
let header = try MessageHeaderV2(serializedData: message.headerBytes)

if key.walletAddress != (try PublicKey(signed.sender.preKey).walletAddress) {
throw MessageV2Error.invalidSignature
}
return DecryptedMessage(
id: id,
encodedContent: encodedMessage,
senderAddress: try signed.sender.walletAddress,
sentAt: Date(timeIntervalSince1970: Double(header.createdNs / 1_000_000) / 1000),
topic: topic
)
}

let encodedMessage = try EncodedContent(serializedData: signed.payload)
let header = try MessageHeaderV2(serializedData: message.headerBytes)
static func decode(_ id: String, _ topic: String, _ message: MessageV2, keyMaterial: Data, client: Client) throws -> DecodedMessage {
do {
let decryptedMessage = try decrypt(id, topic, message, keyMaterial: keyMaterial, client: client)

return DecodedMessage(
id: id,
client: client,
topic: header.topic,
encodedContent: encodedMessage,
senderAddress: try signed.sender.walletAddress,
sent: Date(timeIntervalSince1970: Double(header.createdNs / 1_000_000) / 1000)
topic: decryptedMessage.topic,
encodedContent: decryptedMessage.encodedContent,
senderAddress: decryptedMessage.senderAddress,
sent: decryptedMessage.sentAt
)
} catch {
print("ERROR DECODING: \(error)")
Expand Down
2 changes: 1 addition & 1 deletion Tests/XMTPTests/MessageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class MessageTests: XCTestCase {
let encodedContent = try encoder.encode(content: "Yo!", client: client)
let message1 = try await MessageV2.encode(client: client, content: encodedContent, topic: invitationv1.topic, keyMaterial: invitationv1.aes256GcmHkdfSha256.keyMaterial)

let decoded = try MessageV2.decode(message1, keyMaterial: invitationv1.aes256GcmHkdfSha256.keyMaterial, client: client)
let decoded = try MessageV2.decode("", "", message1, keyMaterial: invitationv1.aes256GcmHkdfSha256.keyMaterial, client: client)
let result: String = try decoded.content()
XCTAssertEqual(result, "Yo!")
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/XMTPTests/RemoteAttachmentTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class RemoteAttachmentTests: XCTestCase {
XCTAssertThrowsError(try RemoteAttachment(url: tempFileURL.absoluteString, encryptedEncodedContent: encryptedEncodedContent)) { error in
switch error as! RemoteAttachmentError {
case let .invalidScheme(message):
XCTAssertEqual(message, "scheme must be https://")
XCTAssertEqual(message, "scheme must be https")
default:
XCTFail("did not raise correct error")
}
Expand Down
2 changes: 1 addition & 1 deletion XMTP.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Pod::Spec.new do |spec|
#

spec.name = "XMTP"
spec.version = "0.6.7-alpha0"
spec.version = "0.6.8-alpha0"
spec.summary = "XMTP SDK Cocoapod"

# This description is used to generate tags and improve search results.
Expand Down
1 change: 0 additions & 1 deletion dev/local/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ version: "3.8"
services:
wakunode:
image: xmtp/node-go
platform: linux/arm64
environment:
- GOWAKU-NODEKEY=8a30dcb604b0b53627a5adc054dbf434b446628d4bd1eccc681d223f0550ce67
command:
Expand Down

0 comments on commit 3d5103e

Please sign in to comment.