From 53f1c73e52dff5b88500d8368b6f9116cbaf069e Mon Sep 17 00:00:00 2001
From: adbenitez
Date: Wed, 26 Feb 2025 20:17:10 +0100
Subject: [PATCH 01/20] cleanup ContactSelectionListAdapter
---
.../contacts/ContactSelectionListAdapter.java | 23 +++++++++----------
1 file changed, 11 insertions(+), 12 deletions(-)
diff --git a/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java b/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
index b6d551130..2dd098c71 100644
--- a/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
+++ b/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
@@ -47,7 +47,7 @@ import java.util.Set;
*
* @author Jake McGinty
*/
-public class ContactSelectionListAdapter extends RecyclerView.Adapter
+public class ContactSelectionListAdapter extends RecyclerView.Adapter
{
private static final int VIEW_TYPE_CONTACT = 0;
private static final int MAX_CACHE_SIZE = 100;
@@ -157,7 +157,7 @@ public class ContactSelectionListAdapter extends RecyclerView.Adapter
if (!longPressSelect) {
return;
}
- int adapterPosition = getAdapterPosition();
+ int adapterPosition = getBindingAdapterPosition();
if (adapterPosition < 0) return;
int contactId = getContactId(adapterPosition);
boolean enabled = actionModeSelection.indexOfKey(adapterPosition) > -1;
@@ -244,8 +244,9 @@ public class ContactSelectionListAdapter extends RecyclerView.Adapter
this.longPressSelect = longPressSelect;
}
+ @NonNull
@Override
- public ContactSelectionListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ public ContactSelectionListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_CONTACT) {
return new ContactViewHolder(li.inflate(R.layout.contact_selection_list_item, parent, false), clickListener);
} else {
@@ -254,11 +255,10 @@ public class ContactSelectionListAdapter extends RecyclerView.Adapter
}
@Override
- public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
+ public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
int id = dcContactList[i];
DcContact dcContact = null;
- String label = null;
String name;
String addr = null;
boolean itemMultiSelect = multiSelect;
@@ -278,22 +278,21 @@ public class ContactSelectionListAdapter extends RecyclerView.Adapter
addr = dcContact.getAddr();
}
- ViewHolder holder = (ViewHolder) viewHolder;
- holder.unbind(glideRequests);
+ viewHolder.unbind(glideRequests);
boolean enabled = true;
if (dcContact == null) {
- holder.setSelected(false);
- holder.setEnabled(!isActionModeEnabled());
+ viewHolder.setSelected(false);
+ viewHolder.setEnabled(!isActionModeEnabled());
if (isActionModeEnabled()) {
enabled = false;
}
} else {
boolean selected = actionModeSelection.indexOfValue(id) > -1;
- holder.setSelected(selected);
+ viewHolder.setSelected(selected);
enabled = !(dcContact.getId() == DcContact.DC_CONTACT_ID_SELF && itemMultiSelect);
}
- holder.bind(glideRequests, id, dcContact, name, addr, label, itemMultiSelect, enabled);
- holder.setChecked(selectedContacts.contains(addr));
+ viewHolder.bind(glideRequests, id, dcContact, name, addr, null, itemMultiSelect, enabled);
+ viewHolder.setChecked(selectedContacts.contains(addr));
}
@Override
From 2e2aeed12703cff13e3bd3c198237f2b69c22f17 Mon Sep 17 00:00:00 2001
From: "B. Petersen"
Date: Thu, 27 Feb 2025 17:24:19 +0100
Subject: [PATCH 02/20] update deltachat-core-rust to 'chore(release): prepare
for 1.156.0' of 'v1.156.0'
---
jni/deltachat-core-rust | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/jni/deltachat-core-rust b/jni/deltachat-core-rust
index 302aa5a5f..08be98f69 160000
--- a/jni/deltachat-core-rust
+++ b/jni/deltachat-core-rust
@@ -1 +1 @@
-Subproject commit 302aa5a5f750d2d24211bff8e61eb2610470fca3
+Subproject commit 08be98f69373f121f0b2c017e81366180f73eaea
From c94f9371fed2983143a097268cd1c4925f66c856 Mon Sep 17 00:00:00 2001
From: adbenitez
Date: Wed, 19 Feb 2025 21:39:04 +0100
Subject: [PATCH 03/20] add C bindings
---
jni/dc_wrapper.c | 14 ++++++++++++++
src/main/java/com/b44t/messenger/DcMsg.java | 2 ++
2 files changed, 16 insertions(+)
diff --git a/jni/dc_wrapper.c b/jni/dc_wrapper.c
index bc2c5c25d..5a976863c 100644
--- a/jni/dc_wrapper.c
+++ b/jni/dc_wrapper.c
@@ -1623,6 +1623,20 @@ JNIEXPORT void Java_com_b44t_messenger_DcMsg_setText(JNIEnv *env, jobject obj, j
}
+JNIEXPORT void Java_com_b44t_messenger_DcMsg_sendEditRequest(JNIEnv *env, jobject obj, jstring text)
+{
+ CHAR_REF(text);
+ dc_send_edit_request(get_dc_msg(env, obj), textPtr);
+ CHAR_UNREF(text);
+}
+
+
+JNIEXPORT jboolean Java_com_b44t_messenger_DcMsg_isEdited(JNIEnv *env, jobject obj)
+{
+ return dc_msg_is_edited(get_dc_msg(env, obj));
+}
+
+
JNIEXPORT void Java_com_b44t_messenger_DcMsg_setFileAndDeduplicate(JNIEnv *env, jobject obj, jstring file, jstring name, jstring filemime)
{
CHAR_REF(file);
diff --git a/src/main/java/com/b44t/messenger/DcMsg.java b/src/main/java/com/b44t/messenger/DcMsg.java
index ed628eea7..6f594ab62 100644
--- a/src/main/java/com/b44t/messenger/DcMsg.java
+++ b/src/main/java/com/b44t/messenger/DcMsg.java
@@ -164,6 +164,8 @@ public class DcMsg {
public native String getQuotedText ();
public native String getError ();
public native String getOverrideSenderName();
+ public native void sendEditRequest (String text);
+ public native boolean isEdited ();
public String getSenderName(DcContact dcContact) {
String overrideName = getOverrideSenderName();
From d1737cb69fa253e530b526138d1e1cafbed51df7 Mon Sep 17 00:00:00 2001
From: adbenitez
Date: Thu, 20 Feb 2025 01:07:23 +0100
Subject: [PATCH 04/20] allow to edit outgoing text messages
---
.../securesms/ConversationActivity.java | 55 +++++++++++++++----
.../securesms/ConversationFragment.java | 22 ++++++++
.../securesms/components/InputPanel.java | 3 +
src/main/res/menu/conversation.xml | 3 -
src/main/res/menu/conversation_context.xml | 4 ++
5 files changed, 72 insertions(+), 15 deletions(-)
diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java
index 09fd2f180..41c8e86af 100644
--- a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java
@@ -189,6 +189,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private boolean isDefaultSms = true;
private boolean isSecurityInitialized = false;
private boolean successfulForwardingAttempt = false;
+ private boolean isEditing = false;
@Override
protected void onCreate(Bundle state, boolean ready) {
@@ -489,13 +490,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
Log.e(TAG, "cannot set up in-chat-search: ", e);
}
- if (!dcChat.canSend()) {
- MenuItem attachItem = menu.findItem(R.id.menu_add_attachment);
- if (attachItem!=null) {
- attachItem.setVisible(false);
- }
- }
-
super.onPrepareOptionsMenu(menu);
return true;
}
@@ -504,10 +498,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
int itemId = item.getItemId();
- if (itemId == R.id.menu_add_attachment) {
- handleAddAttachment();
- return true;
- } else if (itemId == R.id.menu_leave) {
+ if (itemId == R.id.menu_leave) {
handleLeaveGroup();
return true;
} else if (itemId == R.id.menu_archive_chat) {
@@ -731,6 +722,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
* @return
*/
private ListenableFuture initializeDraft() {
+ isEditing = false;
final SettableFuture future = new SettableFuture<>();
DcMsg draft = dcContext.getDraft(chatId);
final String sharedText = RelayUtil.getSharedText(this);
@@ -1016,6 +1008,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
DcMsg msg = null;
Optional quote = inputPanel.getQuote();
Integer recompress = 0;
+ boolean editing = isEditing;
// for a quick ui feedback, we clear the related controls immediately on sending messages.
// for drafts, however, we do not change the controls, the activity may be resumed.
@@ -1024,6 +1017,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
inputPanel.clearQuote();
}
+ if (editing) {
+ int msgId = quote.get().getQuotedMsg().getId();
+ Util.runOnAnyBackgroundThread(() -> {
+ if (action == ACTION_SEND_OUT) {
+ dcContext.sendEditRequest(msgId, body);
+ } else {
+ dcContext.setDraft(chatId, null);
+ }
+ });
+ return future;
+ }
+
if(slideDeck!=null) {
if (action==ACTION_SEND_OUT) {
@@ -1162,7 +1167,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
return;
}
- if (composeText.getText().length() == 0 && !attachmentManager.isAttachmentPresent()) {
+ if (!isEditing && composeText.getText().length() == 0 && !attachmentManager.isAttachmentPresent()) {
buttonToggle.display(attachButton);
quickAttachmentToggle.show();
} else {
@@ -1280,9 +1285,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
+ @Override
+ public void onQuoteDismissed() {
+ isEditing = false;
+ composeText.setText("");
+ }
+
// media selected by the system keyboard
@Override
public void onMediaSelected(@NonNull Uri uri, String contentType) {
+ if (isEditing) return;
if (MediaUtil.isImageType(contentType)) {
sendSticker(uri, contentType);
} else if (MediaUtil.isVideoType(contentType)) {
@@ -1433,6 +1445,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
inputPanel.clickOnComposeInput();
}
+ @Override
+ public void handleEditMessage(DcMsg msg) {
+ isEditing = true;
+ Recipient author = new Recipient(this, dcContext.getContact(msg.getFromId()));
+
+ SlideDeck slideDeck = new SlideDeck();
+ String text = msg.getSummarytext(500);
+
+ inputPanel.setQuote(GlideApp.with(this),
+ msg,
+ msg.getTimestamp(),
+ author,
+ text,
+ slideDeck);
+
+ setDraftText(msg.getText());
+ inputPanel.clickOnComposeInput();
+ }
+
@Override
public void onAttachmentChanged() {
handleSecurityChange(isSecureText, isDefaultSms);
diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java b/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java
index 01409ba96..f00c7aeb6 100644
--- a/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java
+++ b/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java
@@ -324,6 +324,7 @@ public class ConversationFragment extends MessageSelectorFragment
menu.findItem(R.id.menu_context_details).setVisible(false);
menu.findItem(R.id.menu_context_share).setVisible(false);
menu.findItem(R.id.menu_context_reply).setVisible(false);
+ menu.findItem(R.id.menu_context_edit).setVisible(false);
menu.findItem(R.id.menu_context_reply_privately).setVisible(false);
menu.findItem(R.id.menu_add_to_home_screen).setVisible(false);
} else {
@@ -333,6 +334,8 @@ public class ConversationFragment extends MessageSelectorFragment
menu.findItem(R.id.menu_context_share).setVisible(messageRecord.hasFile());
boolean canReply = canReplyToMsg(messageRecord);
menu.findItem(R.id.menu_context_reply).setVisible(chat.canSend() && canReply);
+ boolean canEdit = canEditMsg(messageRecord);
+ menu.findItem(R.id.menu_context_edit).setVisible(chat.canSend() && canEdit);
boolean showReplyPrivately = chat.isMultiUser() && !messageRecord.isOutgoing() && canReply;
menu.findItem(R.id.menu_context_reply_privately).setVisible(showReplyPrivately);
menu.findItem(R.id.menu_add_to_home_screen).setVisible(messageRecord.getType() == DcMsg.DC_MSG_WEBXDC);
@@ -361,6 +364,10 @@ public class ConversationFragment extends MessageSelectorFragment
return !dcMsg.isInfo() && dcMsg.getType() != DcMsg.DC_MSG_VIDEOCHAT_INVITATION;
}
+ static boolean canEditMsg(DcMsg dcMsg) {
+ return dcMsg.isOutgoing() && !dcMsg.isInfo() && dcMsg.getType() == DcMsg.DC_MSG_TEXT;
+ }
+
public void handleClearChat() {
handleDeleteMessages((int) chatId, getListAdapter().getMessageIds());
}
@@ -461,6 +468,16 @@ public class ConversationFragment extends MessageSelectorFragment
listener.handleReplyMessage(message);
}
+ @SuppressLint("RestrictedApi")
+ private void handleEditMessage(final DcMsg message) {
+ if (getActivity() != null) {
+ //noinspection ConstantConditions
+ ((AppCompatActivity) getActivity()).getSupportActionBar().collapseActionView();
+ }
+
+ listener.handleEditMessage(message);
+ }
+
private void handleReplyMessagePrivately(final DcMsg msg) {
if (getActivity() != null) {
@@ -591,6 +608,7 @@ public class ConversationFragment extends MessageSelectorFragment
public interface ConversationFragmentListener {
void handleReplyMessage(DcMsg messageRecord);
+ void handleEditMessage(DcMsg messageRecord);
}
private class ConversationScrollListener extends OnScrollListener {
@@ -958,6 +976,10 @@ public class ConversationFragment extends MessageSelectorFragment
handleReplyMessage(getSelectedMessageRecord(getListAdapter().getSelectedItems()));
actionMode.finish();
return true;
+ } else if (itemId == R.id.menu_context_edit) {
+ handleEditMessage(getSelectedMessageRecord(getListAdapter().getSelectedItems()));
+ actionMode.finish();
+ return true;
} else if (itemId == R.id.menu_context_reply_privately) {
handleReplyMessagePrivately(getSelectedMessageRecord(getListAdapter().getSelectedItems()));
return true;
diff --git a/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java b/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java
index c8c0c22b9..c307c55dd 100644
--- a/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java
+++ b/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java
@@ -141,6 +141,7 @@ public class InputPanel extends ConstraintLayout
public void clearQuoteWithoutAnimation() {
quoteView.dismiss();
+ if (listener != null) listener.onQuoteDismissed();
}
public void clearQuote() {
@@ -152,6 +153,7 @@ public class InputPanel extends ConstraintLayout
@Override
public void onAnimationEnd(@NonNull Animator animation) {
quoteView.dismiss();
+ if (listener != null) listener.onQuoteDismissed();
}
});
@@ -329,6 +331,7 @@ public class InputPanel extends ConstraintLayout
void onRecorderCanceled();
void onRecorderPermissionRequired();
void onEmojiToggle();
+ void onQuoteDismissed();
}
private static class SlideToCancel {
diff --git a/src/main/res/menu/conversation.xml b/src/main/res/menu/conversation.xml
index 46be4a3a6..3a036cf23 100644
--- a/src/main/res/menu/conversation.xml
+++ b/src/main/res/menu/conversation.xml
@@ -41,9 +41,6 @@
OpenPGP non è insicuro di per sé.
-I problemi di sicurezza di OpenPGP più discussi pubblicamente
-in realtà derivano da una cattiva usabilità o da cattive implementazioni di strumenti o app (o entrambi).
+La maggior parte dei problemi di sicurezza di OpenPGP discussi pubblicamentederivano in realtà da una cattiva usabilità o da cattive implementazioni di strumenti o app (o entrambi).
È particolarmente importante distinguere tra OpenPGP, lo standard di crittografia IETF,
e GnuPG (GPG), uno strumento da riga di comando che implementa OpenPGP.
Molte critiche pubbliche di OpenPGP in realtà discutono di GnuPG che Delta Chat non ha mai utilizzato.
-Delta Chat utilizza piuttosto l’implementazione OpenPGP Rust rPGP,
+Delta Chat utilizza piuttosto l’implementazione Rust di OpenPGP rPGP,
disponibile come un pacchetto “pgp” indipendente,
-e controllato sulla sicurezza nel 2019.
Puntiamo, insieme ad altri implementatori di OpenPGP,
per migliorare ulteriormente le caratteristiche di sicurezza implementando il
@@ -1190,24 +1189,25 @@ Per altri programmi, dovresti essere in grado di trovare una soluzione online.
-
Yes, multiple times.
-The Delta Chat project continuously undergoes independent security audits and analysis,
-from most recent to older:
+
Sì, più volte.
+Il progetto Delta Chat è sottoposto costantemente a verifiche e analisi di sicurezza indipendenti,
+dal più recente al più vecchio:
@@ -669,7 +669,7 @@ nadal będziesz mógł pisać, ale nie będziesz już powiadamiany o żadnych no
Tak, Delta Chat korzysta z bezpiecznego podzbioru OpenPGP i wyświetla wskaźnik bezpieczeństwa kłódki na wiadomości tylko wtedy, gdy cała wiadomość jest prawidłowo zaszyfrowana i podpisana. Na przykład „Odłączone podpisy” nie są traktowane jako bezpieczne.
-
OpenPGP samo w sobie nie jest niebezpieczne. Większość publicznie omawianych problemów związanych z bezpieczeństwem OpenPGP tak naprawdę wynika ze złej użyteczności lub złej implementacji narzędzi, lub aplikacji (lub obu). Szczególnie ważne jest rozróżnienie pomiędzy OpenPGP, standardem szyfrowania IETF, a GnuPG (GPG), narzędziem wiersza poleceń implementującym OpenPGP. Wiele publicznych komentarzy krytycznych na temat OpenPGP tak naprawdę omawia GnuPG, którego Delta Chat nigdy nie używał. Delta Chat korzysta raczej z implementacji OpenPGP Rust rPGP, dostępnej jako niezależny pakiet „pgp” i poddanej audytowi bezpieczeństwa w 2019 roku.
+
OpenPGP samo w sobie nie jest niebezpieczne. Większość publicznie omawianych problemów związanych z bezpieczeństwem OpenPGP tak naprawdę wynika ze złej użyteczności lub złej implementacji narzędzi, lub aplikacji (lub obu). Szczególnie ważne jest rozróżnienie pomiędzy OpenPGP, standardem szyfrowania IETF, a GnuPG (GPG), narzędziem wiersza poleceń implementującym OpenPGP. Wiele publicznych komentarzy krytycznych na temat OpenPGP tak naprawdę omawia GnuPG, którego Delta Chat nigdy nie używał. Delta Chat korzysta raczej z implementacji OpenPGP Rust rPGP, dostępnej jako niezależny pakiet „pgp” i poddanej audytowi bezpieczeństwa w 2019 i 2024 roku.
Naszym celem, wraz z innymi wdrażającymi OpenPGP, jest dalsza poprawa parametrów bezpieczeństwa poprzez wdrożenie nowego IETF OpenPGP Crypto-Refresh, który na szczęście został przyjęty latem 2023 roku.
@@ -868,25 +868,20 @@ W przypadku innych programów można znaleźć rozwiązanie online.
-
Yes, multiple times.
-The Delta Chat project continuously undergoes independent security audits and analysis,
-from most recent to older:
+
Tak, wielokrotnie.
+Projekt Delta Chat stale przechodzi niezależne audyty bezpieczeństwa i analizy,
+od najnowszych do najstarszych:
+
+
W grudniu 2024 r. NLNET wykonała ocenę rPGP zleconą przez Radically Open Security. rPGP służy jako kompleksowy silnik szyfrowania OpenPGP w Delta Chat. Wydano dwa ostrzeżenia związane z wynikami tego audytu:
Problemy opisane w tych ostrzeżeniach zostały naprawione i są częścią wydań Delta Chat we wszystkich sklepach z aplikacjami od grudnia 2024 r.
-
The issues outlined in these advisories have been fixed and are part of Delta Chat
-releases on all appstores since December 2024.
-
+
W marcu 2024 r. otrzymaliśmy dogłębną analizę bezpieczeństwa od grupy badawczej ds. kryptografii stosowanej w ETH Zuerich i zajęliśmy się wszystkimi poruszonymi kwestiami. Więcej szczegółowych informacji można znaleźć na naszym blogu na temat szyfrowania typu End-to-End z gwarancją Hardening oraz opublikowanym później artykule badawczym Cryptographic Analysis of Delta Chat.
Вместе с другими разработчиками OpenPGP мы стремимся,
повысить характеристики безопасности путём реализации
diff --git a/src/main/assets/help/sk/help.html b/src/main/assets/help/sk/help.html
index 35bf361b6..e87c27334 100644
--- a/src/main/assets/help/sk/help.html
+++ b/src/main/assets/help/sk/help.html
@@ -1,5 +1,5 @@
-
OpenPGP сам по собі не є небезпечним. Більшість публічно обговорюваних проблем безпеки OpenPGP насправді виникають через недостатню зручність використання або погану реалізацію інструментів чи програм (або обох).
+Особливо важливо розрізняти OpenPGP, стандарт шифрування IETF, і GnuPG (GPG), інструмент командного рядка, що реалізує OpenPGP.
+Багато публічних критиків OpenPGP насправді обговорюють GnuPG, який Delta Chat ніколи не використовував.
+Delta Chat скоріше використовує реалізацію OpenPGP Rust rPGP, доступну як незалежний пакет «pgp» і перевірку безпеки в 2019 і 2024 роках.
Ми прагнемо, разом з іншими розробниками OpenPGP, подальше покращення характеристик безпеки шляхом впровадження нового IETF OpenPGP Crypto-Refresh який, на щастя, був прийнятий влітку 2023 року.
@@ -872,25 +875,19 @@ Look for something like Start Autocrypt Setup Transfer in the s
-
Yes, multiple times.
-The Delta Chat project continuously undergoes independent security audits and analysis,
-from most recent to older:
+
Так, багаторазово. Проект Delta Chat постійно проходить незалежні перевірки та аналіз безпеки, а саме, від останніх і закінчуючи старішими:
Проблеми, описані в цих рекомендаціях, виправлено, і вони є частиною випусків Delta Chat у всіх магазинах додатків з грудня 2024 року.
+
+
2024 березня ми отримали глибокий аналіз безпеки від дослідницької групи Applied Cryptography дослідницької групи з прикладної криптографії в ETH Zuerich і вирішили всі порушені питання. Більш детальну інформацію можна знайти в нашому блозі в статті Hardening Guaranteed End-to-end encryption, а також в дослідженні Cryptographic Analysis of Delta Chat, опублікованому пізніше.
OpenPGP is not insecure by itself.
+Most publically discussed OpenPGP security problems
+actually stem from bad usability or bad implementations of tools or apps (or both).
+It is particularly important to distinguish between OpenPGP, the IETF encryption standard,
+and GnuPG (GPG), a command line tool implementing OpenPGP.
+Many public critiques of OpenPGP actually discuss GnuPG which Delta Chat has never used.
+Delta Chat rather uses the OpenPGP Rust implementation rPGP,
+available as an independent “pgp” package,
+and security-audited in 2019 and 2024.