Skip to content

Commit c055c5f

Browse files
committed
feat: added decrypt for community wrapping
1 parent 9ffcc9d commit c055c5f

File tree

5 files changed

+248
-5
lines changed

5 files changed

+248
-5
lines changed

include/multi_encrypt/multi_encrypt.hpp

Lines changed: 182 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <napi.h>
4+
#include <oxenc/base64.h>
45
#include <oxenc/hex.h>
56

67
#include <algorithm>
@@ -16,6 +17,38 @@
1617

1718
namespace session::nodeapi {
1819

20+
namespace log = oxen::log;
21+
22+
auto cat = log::Cat("multi_encrypt");
23+
24+
template <>
25+
struct toJs_impl<session::ProProof> {
26+
Napi::Object operator()(const Napi::Env& env, const session::ProProof pro_proof) {
27+
auto obj = Napi::Object::New(env);
28+
29+
obj["version"] = toJs(env, pro_proof.version);
30+
obj["genIndexHashB64"] = toJs(env, oxenc::to_base64(pro_proof.gen_index_hash));
31+
obj["rotatingPubkeyHex"] = toJs(env, oxenc::to_hex(pro_proof.rotating_pubkey));
32+
obj["expiryMs"] = toJs(env, pro_proof.expiry_unix_ts.time_since_epoch().count());
33+
34+
return obj;
35+
}
36+
};
37+
38+
template <>
39+
struct toJs_impl<session::Envelope> {
40+
Napi::Object operator()(const Napi::Env& env, const session::Envelope envelope) {
41+
auto obj = Napi::Object::New(env);
42+
43+
obj["timestampMs"] = toJs(env, envelope.timestamp.count());
44+
obj["source"] = envelope.source.size() ? toJs(env, envelope.source) : env.Null();
45+
obj["proSigHex"] =
46+
envelope.pro_sig.size() ? toJs(env, oxenc::to_hex(envelope.pro_sig)) : env.Null();
47+
48+
return obj;
49+
}
50+
};
51+
1952
inline std::vector<unsigned char> extractPlaintext(
2053
const Napi::Object& obj, const std::string identifier) {
2154

@@ -106,6 +139,34 @@ inline std::optional<std::span<const unsigned char>> extractProRotatingEd25519Pr
106139
return std::nullopt;
107140
}
108141

142+
inline std::vector<unsigned char> extractContentOrEnvelope(
143+
const Napi::Object& obj, const std::string identifier) {
144+
assertIsUInt8Array(obj.Get("contentOrEnvelope"), identifier);
145+
auto contentOrEnvelope = toCppBuffer(obj.Get("contentOrEnvelope"), identifier);
146+
147+
return contentOrEnvelope;
148+
}
149+
150+
inline std::chrono::sys_time<std::chrono::milliseconds> extractNowSysMs(
151+
const Napi::Object& obj, const std::string identifier) {
152+
assertIsNumber(obj.Get("nowMs"), identifier);
153+
auto nowMs = toCppSysMs(obj.Get("nowMs"), identifier);
154+
155+
return nowMs;
156+
}
157+
158+
inline session::array_uc32 extractProBackendPubkeyHex(
159+
const Napi::Object& obj, const std::string identifier) {
160+
assertIsString(obj.Get("proBackendPubkeyHex"));
161+
162+
auto proBackendPubkeyHex = toCppString(obj.Get("proBackendPubkeyHex"), identifier);
163+
assert_length(proBackendPubkeyHex, 64, identifier);
164+
165+
auto arr = from_hex_to_array<32>(proBackendPubkeyHex);
166+
167+
return arr;
168+
}
169+
109170
class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
110171
public:
111172
MultiEncryptWrapper(const Napi::CallbackInfo& info) :
@@ -156,6 +217,17 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
156217
"encryptForGroup",
157218
static_cast<napi_property_attributes>(
158219
napi_writable | napi_configurable)),
220+
221+
// Destination decrypt
222+
// StaticMethod<&MultiEncryptWrapper::encryptFor1o1>(
223+
// "encryptFor1o1",
224+
// static_cast<napi_property_attributes>(
225+
// napi_writable | napi_configurable)),
226+
227+
StaticMethod<&MultiEncryptWrapper::decryptForCommunity>(
228+
"decryptForCommunity",
229+
static_cast<napi_property_attributes>(
230+
napi_writable | napi_configurable)),
159231
});
160232
}
161233

@@ -244,6 +316,12 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
244316
});
245317
};
246318

319+
/**
320+
* ===========================================
321+
* =========== ATTACHMENTS CALLS =============
322+
* ===========================================
323+
*/
324+
247325
static Napi::Value attachmentEncrypt(const Napi::CallbackInfo& info) {
248326
return wrapResult(info, [&] {
249327
assertInfoLength(info, 1);
@@ -337,9 +415,15 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
337415
});
338416
};
339417

418+
/**
419+
* ===========================================
420+
* ============= ENCRYPT CALLS ===============
421+
* ===========================================
422+
*/
423+
340424
static Napi::Value encryptFor1o1(const Napi::CallbackInfo& info) {
341425
return wrapResult(info, [&] {
342-
// we expect an single argument which is an array of objects with the following
426+
// we expect a single argument which is an array of objects with the following
343427
// properties:
344428
// {
345429
// "plaintext": Uint8Array,
@@ -384,7 +468,7 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
384468

385469
static Napi::Value encryptForCommunityInbox(const Napi::CallbackInfo& info) {
386470
return wrapResult(info, [&] {
387-
// we expect an single argument which is an array of objects with the following
471+
// we expect a single argument which is an array of objects with the following
388472
// properties:
389473
// {
390474
// "plaintext": Uint8Array,
@@ -435,7 +519,7 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
435519

436520
static Napi::Value encryptForCommunity(const Napi::CallbackInfo& info) {
437521
return wrapResult(info, [&] {
438-
// we expect an single argument which is an array of objects with the following
522+
// we expect a single argument which is an array of objects with the following
439523
// properties:
440524
// {
441525
// "plaintext": Uint8Array,
@@ -474,7 +558,7 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
474558

475559
static Napi::Value encryptForGroup(const Napi::CallbackInfo& info) {
476560
return wrapResult(info, [&] {
477-
// we expect an single argument which is an array of objects with the following
561+
// we expect a single argument which is an array of objects with the following
478562
// properties:
479563
// {
480564
// "plaintext": Uint8Array,
@@ -532,5 +616,99 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
532616
return ret;
533617
});
534618
};
619+
620+
/**
621+
* ===========================================
622+
* ============= DECRYPT CALLS ===============
623+
* ===========================================
624+
*/
625+
626+
static Napi::Value decryptForCommunity(const Napi::CallbackInfo& info) {
627+
return wrapResult(info, [&] {
628+
// we expect two arguments that match:
629+
// first: [{
630+
// "contentOrEnvelope": Uint8Array,
631+
// }],
632+
// second: {
633+
// "nowMs": number,
634+
// "proBackendPubkeyHex": Hexstring,
635+
// }
636+
//
637+
638+
assertInfoLength(info, 2);
639+
assertIsArray(info[0], "decryptForCommunity info[0]");
640+
assertIsObject(info[1]);
641+
642+
auto first = info[0].As<Napi::Array>();
643+
644+
if (first.IsEmpty())
645+
throw std::invalid_argument("decryptForCommunity first received empty");
646+
647+
auto second = info[1].As<Napi::Array>();
648+
649+
if (second.IsEmpty())
650+
throw std::invalid_argument("decryptForCommunity second received empty");
651+
652+
auto nowMs = extractNowSysMs(second, "decryptForCommunity.second.nowMs");
653+
auto proBackendPubkeyHex = extractProBackendPubkeyHex(
654+
second, "decryptForCommunity.second.proBackendPubkeyHex");
655+
656+
std::vector<DecodedCommunityMessage> decrypted(first.Length());
657+
658+
for (uint32_t i = 0; i < first.Length(); i++) {
659+
auto itemValue = first.Get(i);
660+
if (!itemValue.IsObject()) {
661+
throw std::invalid_argument(
662+
"decryptForCommunity itemValue is not an "
663+
"object");
664+
}
665+
auto obj = itemValue.As<Napi::Object>();
666+
667+
try {
668+
decrypted[i] = session::decode_for_community(
669+
extractContentOrEnvelope(
670+
obj, "decryptForCommunity.obj.contentOrEnvelope"),
671+
nowMs,
672+
proBackendPubkeyHex);
673+
674+
} catch (const std::exception& e) {
675+
log::warning(
676+
cat,
677+
"decryptForCommunity: Failed to decrypt "
678+
"message at index {}",
679+
i);
680+
}
681+
}
682+
683+
auto ret = Napi::Array::New(info.Env(), decrypted.size());
684+
uint32_t i = 0;
685+
for (auto& d : decrypted) {
686+
auto to_insert = Napi::Object::New(info.Env());
687+
std::span<unsigned char> content_plaintext_unpadded =
688+
std::span(d.content_plaintext)
689+
.subspan(0, d.content_plaintext_unpadded_size);
690+
691+
to_insert.Set(
692+
"contentPlaintextUnpadded", toJs(info.Env(), content_plaintext_unpadded));
693+
to_insert.Set(
694+
"envelope", d.envelope ? toJs(info.Env(), *d.envelope) : info.Env().Null());
695+
696+
if (d.pro_sig)
697+
to_insert.Set("proSigHex", toJs(info.Env(), oxenc::to_hex(*d.pro_sig)));
698+
else
699+
to_insert.Set("proSigHex", info.Env().Null());
700+
701+
if (!d.pro.has_value())
702+
to_insert.Set("proProof", info.Env().Null());
703+
else
704+
to_insert.Set("proProof", toJs(info.Env(), d.pro->proof));
705+
706+
ret.Set(i, to_insert);
707+
i++;
708+
}
709+
710+
return ret;
711+
});
712+
};
535713
};
536714
}; // namespace session::nodeapi

include/pro/types.hpp

Whitespace-only changes.

include/utilities.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ std::optional<std::chrono::sys_seconds> maybeNonemptySysSeconds(
6565
Napi::Value x, const std::string& identifier);
6666

6767
std::chrono::sys_seconds toCppSysSeconds(Napi::Value x, const std::string& identifier);
68+
std::chrono::sys_time<std::chrono::milliseconds> toCppSysMs(
69+
Napi::Value x, const std::string& identifier);
6870
std::chrono::milliseconds toCppMs(Napi::Value x, const std::string& identifier);
6971

7072
bool toCppBoolean(Napi::Value x, const std::string& identifier);
@@ -373,6 +375,8 @@ std::array<uint8_t, N> from_hex_to_array(std::string x) {
373375
return result;
374376
}
375377

378+
std::vector<unsigned char> from_hex_to_vector(std::string_view x);
379+
376380
// Concept to match containers with a size() method
377381
template <typename T>
378382
concept HasSize = requires(T t) {

src/utilities.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,17 @@ std::chrono::sys_seconds toCppSysSeconds(Napi::Value x, const std::string& ident
163163
throw std::invalid_argument{"toCppSysSeconds with invalid type, called from " + identifier};
164164
}
165165

166+
std::chrono::sys_time<std::chrono::milliseconds> toCppSysMs(
167+
Napi::Value x, const std::string& identifier) {
168+
169+
if (x.IsNumber()) {
170+
auto num = x.As<Napi::Number>().Int64Value();
171+
return std::chrono::sys_time<std::chrono::milliseconds>{std::chrono::milliseconds{num}};
172+
}
173+
174+
throw std::invalid_argument{"toCppSysMs with invalid type, called from " + identifier};
175+
}
176+
166177
std::chrono::milliseconds toCppMs(Napi::Value x, const std::string& identifier) {
167178

168179
if (x.IsNumber()) {
@@ -332,6 +343,10 @@ std::span<const uint8_t> from_hex_to_span(std::string_view x) {
332343
return session::to_span(oxenc::from_hex(x));
333344
}
334345

346+
std::vector<unsigned char> from_hex_to_vector(std::string_view x) {
347+
return session::to_vector(oxenc::from_hex(x));
348+
}
349+
335350
template <std::size_t N>
336351
std::array<uint8_t, N> spanToArray(std::span<const unsigned char> span) {
337352
if (span.size() != N) {

types/multi_encrypt/multi_encrypt.d.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,44 @@ declare module 'libsession_util_nodejs' {
1919
type WithGroupEncKey = { groupEncKey: string };
2020
type WithProRotatingEd25519PrivKey = { proRotatingEd25519PrivKey: string | null };
2121

22+
type WithContentOrEnvelope = { contentOrEnvelope: Uint8Array };
23+
type WithContentPlaintext = {
24+
contentPlaintextUnpadded: Uint8Array;
25+
};
26+
type WithNowMs = { nowMs: number };
27+
type WithProBackendPubkey = {
28+
/**
29+
* HexString
30+
*/
31+
proBackendPubkeyHex: string;
32+
};
33+
34+
type ProProof = {
35+
version: number;
36+
genIndexHashB64: string;
37+
rotatingPubkeyHex: string;
38+
expiryMs: number;
39+
};
40+
41+
type WithProProof = {
42+
proProof: ProProof;
43+
};
44+
type Envelope = {
45+
timestampMs: number;
46+
/**
47+
* HexString, 33 bytes, 66 chars
48+
*/
49+
source: string | null;
50+
/**
51+
* HexString
52+
*/
53+
proSigHex: string | null;
54+
};
55+
56+
type WithDecryptedEnvelope = {
57+
envelope: Envelope | null;
58+
};
59+
2260
type MultiEncryptWrapper = {
2361
multiEncrypt: (opts: {
2462
/**
@@ -90,6 +128,11 @@ declare module 'libsession_util_nodejs' {
90128
WithProRotatingEd25519PrivKey
91129
>
92130
) => { encryptedData: Array<Uint8Array> };
131+
132+
decryptForCommunity: (
133+
first: Array<WithContentOrEnvelope>,
134+
second: WithNowMs & WithProBackendPubkey
135+
) => Array<WithProProof & WithDecryptedEnvelope & WithContentPlaintext>;
93136
};
94137

95138
export type MultiEncryptActionsCalls = MakeWrapperActionCalls<MultiEncryptWrapper>;
@@ -106,6 +149,8 @@ declare module 'libsession_util_nodejs' {
106149
public static encryptForCommunityInbox: MultiEncryptWrapper['encryptForCommunityInbox'];
107150
public static encryptForCommunity: MultiEncryptWrapper['encryptForCommunity'];
108151
public static encryptForGroup: MultiEncryptWrapper['encryptForGroup'];
152+
153+
public static decryptForCommunity: MultiEncryptWrapper['decryptForCommunity'];
109154
}
110155

111156
/**
@@ -121,5 +166,6 @@ declare module 'libsession_util_nodejs' {
121166
| MakeActionCall<MultiEncryptWrapper, 'encryptFor1o1'>
122167
| MakeActionCall<MultiEncryptWrapper, 'encryptForCommunityInbox'>
123168
| MakeActionCall<MultiEncryptWrapper, 'encryptForCommunity'>
124-
| MakeActionCall<MultiEncryptWrapper, 'encryptForGroup'>;
169+
| MakeActionCall<MultiEncryptWrapper, 'encryptForGroup'>
170+
| MakeActionCall<MultiEncryptWrapper, 'decryptForCommunity'>;
125171
}

0 commit comments

Comments
 (0)