diff --git a/src/chat/chat_protocol.cpp b/src/chat/chat_protocol.cpp index f9db76f..bcda4af 100644 --- a/src/chat/chat_protocol.cpp +++ b/src/chat/chat_protocol.cpp @@ -275,6 +275,9 @@ HushChatTransactionExtractionResult extractHushChatTransactionMetadata( metadata.header_position = pair.header_position; metadata.payload_position = pair.payload_position; metadata.payload_size = pair.payload_memo.size(); + metadata.sender_public_key_hex = pair.header.public_key_hex; + metadata.secretstream_header_hex = pair.header.secretstream_header_hex; + metadata.payload_memo = pair.payload_memo; result.metadata.push_back(std::move(metadata)); } diff --git a/src/chat/chat_protocol.h b/src/chat/chat_protocol.h index 908241e..4105748 100644 --- a/src/chat/chat_protocol.h +++ b/src/chat/chat_protocol.h @@ -75,6 +75,11 @@ struct HushChatTransactionMetadata { std::size_t header_position = 0; std::size_t payload_position = 0; std::size_t payload_size = 0; + // Decrypt inputs carried through from the paired header + payload memos so the chat + // service can actually decrypt (a Message) or read the request (a ContactRequest). + std::string sender_public_key_hex; // header "p": peer crypto_kx public key (hex) + std::string secretstream_header_hex; // header "e": secretstream header (hex; empty for ContactRequest) + std::string payload_memo; // ciphertext hex (Message) or plaintext request text (ContactRequest) }; struct HushChatTransactionExtractionResult { diff --git a/tests/test_phase4.cpp b/tests/test_phase4.cpp index 4812665..3490e64 100644 --- a/tests/test_phase4.cpp +++ b/tests/test_phase4.cpp @@ -5617,6 +5617,51 @@ void testHushChatCrypto() EXPECT_TRUE(decryptIncoming(bob, ra.public_key_hex, headerHex, "00", plain4) == ChatCryptoStatus::CiphertextTooShort); } +// End-to-end receive path: identity -> encrypt -> HushChat memo pair -> parser harvest +// (extractHushChatTransactionMetadata now carries e/p/ciphertext, Phase 1 Step 1) -> decrypt. +void testHushChatReceivePath() +{ + using namespace dragonx::chat; + + ChatKeyPair alice, bob; + ChatIdentityResult ra = deriveChatIdentityFromSecret("alice-recv", alice, true); + ChatIdentityResult rb = deriveChatIdentityFromSecret("bob-recv", bob, true); + EXPECT_TRUE(ra.status == ChatIdentityStatus::Ready); + EXPECT_TRUE(rb.status == ChatIdentityStatus::Ready); + + // Alice encrypts to Bob, wrapped as a HushChat header memo + payload memo. + std::string e, ct; + EXPECT_TRUE(encryptOutgoing(alice, rb.public_key_hex, "gm bob", e, ct) == ChatCryptoStatus::Ok); + std::string headerMemo = std::string("{\"cid\":\"conv-42\",\"e\":\"") + e + + "\",\"h\":1,\"p\":\"" + ra.public_key_hex + + "\",\"t\":\"Memo\",\"v\":0,\"z\":\"zs-alice-reply\"}"; + + HushChatTransactionInput tx; + tx.txid = "txid-recv-1"; + tx.outputs.push_back(HushChatMemoOutput{0, headerMemo}); + tx.outputs.push_back(HushChatMemoOutput{1, ct}); + + HushChatTransactionExtractionResult ext = extractHushChatTransactionMetadata(tx, true); + EXPECT_TRUE(ext.feature_enabled); + EXPECT_EQ((int)ext.metadata.size(), 1); + const HushChatTransactionMetadata& m = ext.metadata[0]; + EXPECT_TRUE(m.type == HushChatHeaderType::Message); + EXPECT_EQ(m.sender_public_key_hex, ra.public_key_hex); + EXPECT_EQ(m.secretstream_header_hex, e); + EXPECT_EQ(m.payload_memo, ct); + + // Bob decrypts straight from the carried metadata — the whole receive path. + std::string plain; + EXPECT_TRUE(decryptIncoming(bob, m.sender_public_key_hex, m.secretstream_header_hex, + m.payload_memo, plain) == ChatCryptoStatus::Ok); + EXPECT_EQ(plain, std::string("gm bob")); + + // Feature disabled -> the harvest yields nothing. + HushChatTransactionExtractionResult off = extractHushChatTransactionMetadata(tx, false); + EXPECT_TRUE(!off.feature_enabled); + EXPECT_EQ((int)off.metadata.size(), 0); +} + } // namespace int main() @@ -5708,6 +5753,7 @@ int main() testPoolWeightedSelection(); testAtomicFileWrite(); testHushChatCrypto(); + testHushChatReceivePath(); testAddressChecksumValidation(); testLiteServerProbeLive(); testXmrigLiveInstall();