@@ -261,6 +261,41 @@ inline session::array_uc32 extractEd25519PrivateKeyHex(
261261 return arr;
262262}
263263
264+ std::vector<unsigned char > extractEd25519GroupPubkeyHex (
265+ const Napi::Object& obj, const std::string& identifier) {
266+ assertIsString (obj.Get (" ed25519GroupPubkeyHex" ), identifier);
267+ auto ed25519GroupPubkeyHex = toCppString (obj.Get (" ed25519GroupPubkeyHex" ), identifier);
268+ assert_length (ed25519GroupPubkeyHex, 66 , identifier);
269+
270+ auto arr = from_hex_to_vector (ed25519GroupPubkeyHex);
271+
272+ return arr;
273+ }
274+
275+ std::vector<std::vector<unsigned char >> extractGroupEncKeys (
276+ const Napi::Object& obj, const std::string& identifier) {
277+ assertIsArray (obj.Get (" groupEncKeys" ), identifier);
278+
279+ auto asArray = obj.Get (" groupEncKeys" ).As <Napi::Array>();
280+ std::vector<std::vector<unsigned char >> groupEncKeys;
281+ groupEncKeys.reserve (asArray.Length ());
282+
283+ for (uint32_t i = 0 ; i < asArray.Length (); i++) {
284+ auto itemValue = asArray.Get (i);
285+ assertIsUInt8Array (itemValue, " extractGroupEncKeys" );
286+
287+ auto encKey = itemValue.As <Napi::Uint8Array>();
288+
289+ std::vector<unsigned char > cppEncKey =
290+ toCppBuffer (encKey, " extractGroupEncKeys.groupEncKey" );
291+ assert_length (cppEncKey, 32 , " extractGroupEncKeys.groupEncKey" );
292+
293+ groupEncKeys.emplace_back (cppEncKey);
294+ }
295+
296+ return groupEncKeys;
297+ }
298+
264299class MultiEncryptWrapper : public Napi ::ObjectWrap<MultiEncryptWrapper> {
265300 public:
266301 MultiEncryptWrapper (const Napi::CallbackInfo& info) :
@@ -321,10 +356,10 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
321356 " decryptFor1o1" ,
322357 static_cast <napi_property_attributes>(
323358 napi_writable | napi_configurable)),
324- // StaticMethod<&MultiEncryptWrapper::decryptForGroup>(
325- // "decryptForGroup",
326- // static_cast<napi_property_attributes>(
327- // napi_writable | napi_configurable)),
359+ StaticMethod<&MultiEncryptWrapper::decryptForGroup>(
360+ " decryptForGroup" ,
361+ static_cast <napi_property_attributes>(
362+ napi_writable | napi_configurable)),
328363 });
329364 }
330365
@@ -549,7 +584,8 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
549584
550585 ready_to_send[i] = session::encode_for_1o1 (
551586 extractPlaintext (obj, " encryptFor1o1.obj.plaintext" ),
552- extractSenderEd25519SeedAsVector (obj, " encryptFor1o1.obj.senderEd25519Seed" ),
587+ extractSenderEd25519SeedAsVector (
588+ obj, " encryptFor1o1.obj.senderEd25519Seed" ),
553589 extractSentTimestampMs (obj, " encryptFor1o1.obj.sentTimestampMs" ),
554590 extractRecipientPubkeyAsArray (obj, " encryptFor1o1.obj.recipientPubkey" ),
555591 extractProRotatingEd25519PrivKeyAsSpan (
@@ -898,5 +934,105 @@ class MultiEncryptWrapper : public Napi::ObjectWrap<MultiEncryptWrapper> {
898934 return ret;
899935 });
900936 };
937+
938+ static Napi::Value decryptForGroup (const Napi::CallbackInfo& info) {
939+ return wrapResult (info, [&] {
940+ // we expect two arguments that match:
941+ // first: [{
942+ // "envelopePayload": Uint8Array,
943+ // "messageHash": string,
944+ // }],
945+ // second: {
946+ // "nowMs": number,
947+ // "proBackendPubkeyHex": Hexstring,
948+ // "ed25519GroupPubkeyHex": Hexstring,
949+ // "groupEncKeys": Array<Uint8Array>,
950+ // }
951+ //
952+
953+ assertInfoLength (info, 2 );
954+ assertIsArray (info[0 ], " decryptForGroup info[0]" );
955+ assertIsObject (info[1 ]);
956+
957+ auto first = info[0 ].As <Napi::Array>();
958+
959+ if (first.IsEmpty ())
960+ throw std::invalid_argument (" decryptForGroup first received empty" );
961+
962+ auto second = info[1 ].As <Napi::Object>();
963+
964+ if (second.IsEmpty ())
965+ throw std::invalid_argument (" decryptForGroup second received empty" );
966+
967+ auto nowMs = extractNowSysMs (second, " decryptForGroup.second.nowMs" );
968+ auto proBackendPubkeyHex = extractProBackendPubkeyHex (
969+ second, " decryptForGroup.second.proBackendPubkeyHex" );
970+
971+ std::vector<DecodedEnvelope> decrypted;
972+ std::vector<std::string> decryptedMessageHashes;
973+
974+ DecodeEnvelopeKey keys{};
975+ auto groupPk = extractEd25519GroupPubkeyHex (
976+ second, " decryptForGroup.second.ed25519GroupPubkeyHex" );
977+
978+ // this has to be vector and not spans, the memory gets freed by the function
979+ std::vector<std::vector<unsigned char >> groupEncKeysVec =
980+ extractGroupEncKeys (second, " decryptForGroup.second.groupEncKeys" );
981+
982+ std::vector<std::span<const unsigned char >> span_group_enc_keys;
983+ span_group_enc_keys.reserve (span_group_enc_keys.size ());
984+ for (const auto & inner : groupEncKeysVec) {
985+ span_group_enc_keys.emplace_back (inner);
986+ }
987+
988+ // Create a span of spans
989+ std::span<std::span<const unsigned char >> groupEncKeys (span_group_enc_keys);
990+
991+ keys.decrypt_keys = groupEncKeys;
992+
993+ for (uint32_t i = 0 ; i < first.Length (); i++) {
994+ auto itemValue = first.Get (i);
995+ if (!itemValue.IsObject ()) {
996+ throw std::invalid_argument (
997+ " decryptForGroup itemValue is not an "
998+ " object" );
999+ }
1000+ auto obj = itemValue.As <Napi::Object>();
1001+
1002+ try {
1003+ std::string messageHash =
1004+ extractMessageHash (obj, " decryptForGroup.obj.messageHash" );
1005+
1006+ auto envelopePayload =
1007+ extractEnvelopePayload (obj, " decryptForGroup.obj.envelopePayload" );
1008+ decrypted.push_back (
1009+ session::decode_envelope (
1010+ keys, envelopePayload, nowMs, proBackendPubkeyHex));
1011+ decryptedMessageHashes.push_back (messageHash);
1012+ } catch (const std::exception& e) {
1013+ log::warning (
1014+ cat,
1015+ " decryptForGroup: Failed to decrypt "
1016+ " message at index {}" ,
1017+ i);
1018+ }
1019+ }
1020+
1021+ auto ret = Napi::Array::New (info.Env (), decrypted.size ());
1022+ uint32_t i = 0 ;
1023+
1024+ for (auto & d : decrypted) {
1025+ auto to_insert = Napi::Object::New (info.Env ());
1026+
1027+ to_insert.Set (" decodedEnvelope" , toJs (info.Env (), d));
1028+ to_insert.Set (" messageHash" , toJs (info.Env (), decryptedMessageHashes[i]));
1029+
1030+ ret.Set (i, to_insert);
1031+ i++;
1032+ }
1033+
1034+ return ret;
1035+ });
1036+ };
9011037};
9021038}; // namespace session::nodeapi
0 commit comments