api!: remove verification methods from the FFI and JSON-RPC APIs

Core no longer tracks verification, so the API has nothing left
to report and UIs should drop their checkmark and "Introduced by" code.

BREAKING CHANGE: dc_contact_is_verified() and dc_contact_get_verifier_id() are removed.

BREAKING CHANGE: the JSON-RPC Contact object loses the `isVerified` and `verifierId` fields. A bot reading `snapshot.is_verified` now gets an `AttributeError` at runtime.

BREAKING CHANGE: the Python bindings lose `Contact.is_verified()` and `Contact.get_verifier()`.

BREAKING CHANGE: DC_STR_CONTACT_VERIFIED (35) is removed, so UIs should stop registering a translation for it. A stock id core does not know is logged and otherwise ignored, so an un-updated client keeps working.
This commit is contained in:
holger krekel
2026-08-29 23:41:13 +02:00
parent d8912a98ac
commit b1da53a56b
12 changed files with 29 additions and 233 deletions

View File

@@ -1778,8 +1778,6 @@ int dc_is_contact_in_chat (dc_context_t* context, uint32_t ch
* If the group is already _promoted_ (any message was sent to the group),
* all group members are informed by a special status message that is sent automatically by this function.
*
* If the group has group protection enabled, only verified contacts can be added to the group.
*
* Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
*
* @memberof dc_context_t
@@ -2278,7 +2276,7 @@ void dc_block_contact (dc_context_t* context, uint32_t co
/**
* Get encryption info for a contact.
* Get a multi-line encryption info, containing your fingerprint and the
* fingerprint of the contact, used e.g. to compare the fingerprints for a simple out-of-band verification.
* fingerprint of the contact, used e.g. to compare the fingerprints out-of-band.
*
* @memberof dc_context_t
* @param context The context object.
@@ -2445,7 +2443,7 @@ char* dc_imex_has_backup (dc_context_t* context, const char*
void dc_stop_ongoing_process (dc_context_t* context);
// out-of-band verification
// securejoin
#define DC_QR_ASK_VERIFYCONTACT 200 // id=contact
#define DC_QR_ASK_VERIFYGROUP 202 // text1=groupname
@@ -2480,7 +2478,7 @@ void dc_stop_ongoing_process (dc_context_t* context);
* The QR code state is returned in dc_lot_t::state as:
*
* - DC_QR_ASK_VERIFYCONTACT with dc_lot_t::id=Contact ID:
* ask whether to verify the contact;
* ask whether to start chatting with the contact;
* if so, start the protocol with dc_join_securejoin().
*
* - DC_QR_ASK_VERIFYGROUP or DC_QR_ASK_VERIFYBROADCAST
@@ -2489,7 +2487,7 @@ void dc_stop_ongoing_process (dc_context_t* context);
* if so, start the protocol with dc_join_securejoin().
*
* - DC_QR_FPR_OK with dc_lot_t::id=Contact ID:
* contact fingerprint verified,
* contact fingerprint matches,
* ask the user if they want to start chatting;
* if so, call dc_create_chat_by_contact_id().
*
@@ -2565,12 +2563,12 @@ dc_lot_t* dc_check_qr (dc_context_t* context, const char*
/**
* Get QR code text that will offer an Setup-Contact or Verified-Group invitation.
* Get QR code text that will offer a SecureJoin invitation.
*
* The scanning device will pass the scanned content to dc_check_qr() then;
* if dc_check_qr() returns
* DC_QR_ASK_VERIFYCONTACT, DC_QR_ASK_VERIFYGROUP or DC_QR_ASK_VERIFYBROADCAST
* an out-of-band-verification can be joined using dc_join_securejoin()
* the SecureJoin protocol can be started using dc_join_securejoin()
*
* The returned text will also work as a normal https:-link,
* so that the QR code is useful also without Delta Chat being installed
@@ -2581,9 +2579,8 @@ dc_lot_t* dc_check_qr (dc_context_t* context, const char*
* @memberof dc_context_t
* @param context The context object.
* @param chat_id If set to a group-chat-id,
* the Verified-Group-Invite protocol is offered in the QR code;
* works for protected groups as well as for normal groups.
* If set to 0, the Setup-Contact protocol is offered in the QR code.
* the SecureJoin QR code for the group is returned.
* If set to 0, the setup contact QR code is returned.
* See https://securejoin.delta.chat/
* for details about both protocols.
* @return The text that should go to the QR code,
@@ -2609,7 +2606,7 @@ char* dc_get_securejoin_qr (dc_context_t* context, uint32_t ch
char* dc_get_securejoin_qr_svg (dc_context_t* context, uint32_t chat_id);
/**
* Continue a Setup-Contact or Verified-Group-Invite protocol
* Continue the SecureJoin protocol
* started on another device with dc_get_securejoin_qr().
* This function is typically called when dc_check_qr() returns
* lot.state=DC_QR_ASK_VERIFYCONTACT, lot.state=DC_QR_ASK_VERIFYGROUP or lot.state=DC_QR_ASK_VERIFYBROADCAST
@@ -3575,7 +3572,6 @@ dc_lot_t* dc_chatlist_get_summary2 (dc_context_t* context, uint32_t ch
* last-message-state: @ref DC_STATE constant
* last-message-date:
* avatar-path: path-to-blobfile
* is_verified: yes/no
* @return a UTF8-encoded JSON string containing all requested info. Must be freed using dc_str_unref(). NULL is never returned.
*/
char* dc_chat_get_info_json (dc_context_t* context, size_t chat_id);
@@ -5115,19 +5111,6 @@ int dc_contact_was_seen_recently (const dc_contact_t* contact);
int dc_contact_is_blocked (const dc_contact_t* contact);
/**
* Check if the contact
* can be added to protected chats.
*
* See dc_contact_get_verifier_id() for a guidance how to display these information.
*
* @memberof dc_contact_t
* @param contact The contact object.
* @return 0: contact is not verified.
* 2: SELF and contact have verified their fingerprints in both directions.
*/
int dc_contact_is_verified (dc_contact_t* contact);
/**
* Returns whether contact is a bot.
*
@@ -5152,36 +5135,6 @@ int dc_contact_is_bot (dc_contact_t* contact);
int dc_contact_is_key_contact (dc_contact_t* contact);
/**
* Return the contact ID that verified a contact.
*
* As verifier may be unknown,
* use dc_contact_is_verified() to check if a contact can be added to a protected chat.
*
* UI should display the information in the contact's profile as follows:
*
* - If dc_contact_get_verifier_id() != 0,
* display text "Introduced by ..."
* with the name of the contact
* formatted by dc_contact_get_name().
* Prefix the text by a green checkmark.
*
* - If dc_contact_get_verifier_id() == 0 and dc_contact_is_verified() != 0,
* display "Introduced" prefixed by a green checkmark.
*
* - if dc_contact_get_verifier_id() == 0 and dc_contact_is_verified() == 0,
* display nothing
*
* @memberof dc_contact_t
* @param contact The contact object.
* @return
* The contact ID of the verifier. If it is DC_CONTACT_ID_SELF,
* we verified the contact ourself. If it is 0, we don't have verifier information or
* the contact is not verified.
*/
uint32_t dc_contact_get_verifier_id (dc_contact_t* contact);
/**
* @class dc_lot_t
*
@@ -6212,7 +6165,7 @@ void dc_event_unref(dc_event_t* event);
/**
* Contact(s) created, renamed, verified, blocked or deleted.
* Contact(s) created, renamed, blocked or deleted.
*
* @param data1 (int) contact_id of the changed contact or 0 on batch-changes or deletion.
* @param data2 0
@@ -6284,8 +6237,7 @@ void dc_event_unref(dc_event_t* event);
*
* @param data1 (int) The ID of the inviting contact.
* @param data2 (int) The progress as:
* 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
* (Bob has verified alice and waits until Alice does the same for him)
* 400=vg-/vc-request-with-auth sent, typically shown as "introducing myself."
* 1000=vg-member-added/vc-contact-confirm received
*/
#define DC_EVENT_SECUREJOIN_JOINER_PROGRESS 2061
@@ -6671,12 +6623,6 @@ void dc_event_unref(dc_event_t* event);
/// Used to build the string returned by dc_get_contact_encrinfo().
#define DC_STR_FINGERPRINTS 30
/// "%1$s verified"
///
/// Used in status messages.
/// - %1$s will be replaced by the name of the verified contact
#define DC_STR_CONTACT_VERIFIED 35
/// "Archived chats"
///
/// Used as the name for the corresponding chatlist entry.
@@ -6855,7 +6801,7 @@ void dc_event_unref(dc_event_t* event);
///
/// Added as an info-message directly after scanning a QR code for joining a group.
/// May be followed by the info-messages
/// #DC_STR_SECURE_JOIN_REPLIES, #DC_STR_CONTACT_VERIFIED and #DC_STR_MSGADDMEMBER.
/// #DC_STR_SECURE_JOIN_REPLIES and #DC_STR_MSGADDMEMBER.
///
/// `%1$s` and `%2$s` will be replaced by name of the inviter.
#define DC_STR_SECURE_JOIN_STARTED 117
@@ -6864,15 +6810,13 @@ void dc_event_unref(dc_event_t* event);
///
/// Info-message on scanning a QR code for joining a group.
/// Added after #DC_STR_SECURE_JOIN_STARTED.
/// If the handshake allows to skip a step and go for #DC_STR_CONTACT_VERIFIED directly,
/// this info-message is skipped.
///
/// `%1$s` will be replaced by the name of the inviter.
#define DC_STR_SECURE_JOIN_REPLIES 118
/// "Scan to chat with %1$s"
///
/// Subtitle for verification qrcode svg image generated by the core.
/// Subtitle for the invite qrcode svg image generated by the core.
///
/// `%1$s` will be replaced by name of the inviter.
#define DC_STR_SETUP_CONTACT_QR_DESC 119

View File

@@ -2397,7 +2397,7 @@ pub unsafe extern "C" fn dc_get_securejoin_qr_svg(
chat_id: u32,
) -> *mut libc::c_char {
if context.is_null() {
eprintln!("ignoring careless call to generate_verification_qr()");
eprintln!("ignoring careless call to dc_get_securejoin_qr_svg()");
return "".strdup();
}
let ctx = unsafe { &*context };
@@ -4049,27 +4049,6 @@ pub unsafe extern "C" fn dc_contact_is_blocked(contact: *mut dc_contact_t) -> li
ffi_contact.contact.is_blocked() as libc::c_int
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn dc_contact_is_verified(contact: *mut dc_contact_t) -> libc::c_int {
if contact.is_null() {
eprintln!("ignoring careless call to dc_contact_is_verified()");
return 0;
}
let ffi_contact = unsafe { &*contact };
if block_on(ffi_contact.contact.is_verified(&ffi_contact.context))
.context("is_verified failed")
.log_err(&ffi_contact.context)
.unwrap_or_default()
{
// Return value is essentially a boolean,
// but we return 2 for true for backwards compatibility.
2
} else {
0
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn dc_contact_is_bot(contact: *mut dc_contact_t) -> libc::c_int {
if contact.is_null() {
@@ -4088,22 +4067,6 @@ pub unsafe extern "C" fn dc_contact_is_key_contact(contact: *mut dc_contact_t) -
unsafe { (*contact).contact.is_key_contact() as libc::c_int }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn dc_contact_get_verifier_id(contact: *mut dc_contact_t) -> u32 {
if contact.is_null() {
eprintln!("ignoring careless call to dc_contact_get_verifier_id()");
return 0;
}
let ffi_contact = unsafe { &*contact };
let verifier_contact_id = block_on(ffi_contact.contact.get_verifier_id(&ffi_contact.context))
.context("failed to get verifier")
.log_err(&ffi_contact.context)
.unwrap_or_default()
.unwrap_or_default()
.unwrap_or_default();
verifier_contact_id.to_u32()
}
// dc_lot_t
pub type dc_lot_t = lot::Lot;

View File

@@ -841,20 +841,19 @@ impl CommandApi {
Ok(qr)
}
/// Get QR code (text and SVG) that will offer a Setup-Contact or Verified-Group invitation.
/// Get QR code (text and SVG) that will offer a SecureJoin invitation.
/// The QR code is compatible to the OPENPGP4FPR format
/// so that a basic fingerprint comparison also works e.g. with OpenKeychain.
///
/// The scanning device will pass the scanned content to `checkQr()` then;
/// if `checkQr()` returns `askVerifyContact` or `askVerifyGroup`
/// an out-of-band-verification can be joined using `secure_join()`
/// the securejoin protocol can be started using `secure_join()`
///
/// @deprecated as of 2026-03; use create_qr_svg(get_chat_securejoin_qr_code()) instead.
///
/// chat_id: If set to a group-chat-id,
/// the Verified-Group-Invite protocol is offered in the QR code;
/// works for protected groups as well as for normal groups.
/// If not set, the Setup-Contact protocol is offered in the QR code.
/// the SecureJoin QR code for the group is returned.
/// If not set, the setup contact QR code is returned.
/// See https://securejoin.delta.chat/ for details about both protocols.
///
/// return format: `[code, svg]`
@@ -870,7 +869,7 @@ impl CommandApi {
Ok((qr, svg))
}
/// Continue a Setup-Contact or Verified-Group-Invite protocol
/// Continue the SecureJoin protocol
/// started on another device with `get_chat_securejoin_qr_code_svg()`.
/// This function is typically called when `check_qr()` returns
/// type=AskVerifyContact or type=AskVerifyGroup.
@@ -953,8 +952,6 @@ impl CommandApi {
/// If the group is already _promoted_ (any message was sent to the group),
/// all group members are informed by a special status message that is sent automatically by this function.
///
/// If the group has group protection enabled, only verified contacts can be added to the group.
///
/// Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
async fn add_contact_to_chat(
&self,
@@ -1776,7 +1773,7 @@ impl CommandApi {
/// Get encryption info for a contact.
/// Get a multi-line encryption info, containing your fingerprint and the
/// fingerprint of the contact, used e.g. to compare the fingerprints for a simple out-of-band verification.
/// fingerprint of the contact, used e.g. to compare the fingerprints out-of-band.
async fn get_contact_encryption_info(
&self,
account_id: u32,

View File

@@ -31,37 +31,6 @@ pub struct ContactObject {
/// e.g. if we just scanned the fingerprint from a QR code.
e2ee_avail: bool,
/// True if the contact
/// can be added to protected chats
/// because SELF and contact have verified their fingerprints in both directions.
///
/// See [`Self::verifier_id`]/`Contact.verifierId` for a guidance how to display these information.
is_verified: bool,
/// The contact ID that verified a contact.
///
/// As verifier may be unknown,
/// use [`Self::is_verified`]/`Contact.isVerified` to check if a contact can be added to a protected chat.
///
/// UI should display the information in the contact's profile as follows:
///
/// - If `verifierId` != 0,
/// display text "Introduced by ..."
/// with the name of the contact.
/// Prefix the text by a green checkmark.
///
/// - If `verifierId` == 0 and `isVerified` != 0,
/// display "Introduced" prefixed by a green checkmark.
///
/// - if `verifierId` == 0 and `isVerified` == 0,
/// display nothing
///
/// This contains the contact ID of the verifier.
/// If it is `DC_CONTACT_ID_SELF`, we verified the contact ourself.
/// If it is None/Null, we don't have verifier information or
/// the contact is not verified.
verifier_id: Option<u32>,
/// the contact's last seen timestamp
last_seen: i64,
was_seen_recently: bool,
@@ -79,14 +48,6 @@ impl ContactObject {
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
None => None,
};
let is_verified = contact.is_verified(context).await?;
let verifier_id = contact
.get_verifier_id(context)
.await?
.flatten()
.map(|contact_id| contact_id.to_u32());
Ok(ContactObject {
address: contact.get_addr().to_owned(),
color: color_int_to_hex_string(contact.get_color()),
@@ -100,8 +61,6 @@ impl ContactObject {
is_blocked: contact.is_blocked(),
is_key_contact: contact.is_key_contact(),
e2ee_avail: contact.e2ee_avail(context).await?,
is_verified,
verifier_id,
last_seen: contact.last_seen(),
was_seen_recently: contact.was_seen_recently(),
is_bot: contact.is_bot(),

View File

@@ -337,8 +337,7 @@ pub enum EventType {
contact_id: u32,
/// Progress as:
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
/// (Bob has verified alice and waits until Alice does the same for him)
/// 400=vg-/vc-request-with-auth sent, typically shown as "introducing myself."
/// 1000=vg-member-added/vc-contact-confirm received
progress: u16,
},

View File

@@ -7,7 +7,7 @@ use typescript_type_def::TypeDef;
#[serde(rename = "Qr", rename_all = "camelCase")]
#[serde(tag = "kind")]
pub enum QrObject {
/// Ask the user whether to verify the contact.
/// Ask the user whether to start chatting with the contact.
///
/// If the user agrees, pass this QR code to [`crate::securejoin::join_securejoin`].
AskVerifyContact {
@@ -61,7 +61,7 @@ pub enum QrObject {
/// Whether the inviter supports the new Securejoin v3 protocol
is_v3: bool,
},
/// Contact fingerprint is verified.
/// Contact fingerprint matches.
///
/// Ask the user if they want to start chatting.
FprOk {

View File

@@ -259,19 +259,13 @@ async fn log_contactlist(context: &Context, contacts: &[ContactId]) -> Result<()
let contact = Contact::get_by_id(context, *contact_id).await?;
let name = contact.get_display_name();
let addr = contact.get_addr();
let verified_str = if contact.is_verified(context).await? {
""
} else {
""
};
let line = format!(
"{}{} <{}>",
"{} <{}>",
if !name.is_empty() {
name
} else {
"<name unset>"
},
verified_str,
if !addr.is_empty() { addr } else { "addr unset" }
);

View File

@@ -360,7 +360,7 @@ class Account:
return Chat(self, chat_id)
def secure_join(self, qrdata: str) -> Chat:
"""Continue a Setup-Contact or Verified-Group-Invite protocol started on another device.
"""Continue the SecureJoin protocol started on another device.
The function returns immediately and the handshake runs in background, sending
and receiving several messages.

View File

@@ -71,17 +71,6 @@ class Contact:
"""Unblock this contact. Messages from this contact will be retrieved (again)."""
return lib.dc_block_contact(self.account._dc_context, self.id, False)
def is_verified(self) -> bool:
"""Return True if the contact is verified."""
return lib.dc_contact_is_verified(self._dc_contact) == 2
def get_verifier(self, contact) -> Optional["Contact"]:
"""Return the address of the contact that verified the contact."""
verifier_id = lib.dc_contact_get_verifier_id(contact._dc_contact)
if verifier_id == 0:
return None
return Contact(self.account, verifier_id)
def get_profile_image(self) -> Optional[str]:
"""Get contact profile image.

View File

@@ -115,7 +115,6 @@ class TestOfflineContact:
assert contact1.addr == "some1@example.org"
assert contact1.display_name == "some1"
assert not contact1.is_blocked()
assert not contact1.is_verified()
def test_get_blocked(self, acfactory):
ac1 = acfactory.get_pseudo_configured_account()

View File

@@ -560,10 +560,10 @@ pub enum Origin {
/// To: of incoming messages of unknown sender
IncomingUnknownTo = 0x40,
/// Address scanned but not verified.
/// Address scanned from a QR code.
UnhandledQrScan = 0x80,
/// Address scanned from a SecureJoin QR code, but not verified yet.
/// Address scanned from a SecureJoin QR code.
UnhandledSecurejoinQrScan = 0x81,
/// Reply-To: of incoming message of known sender
@@ -594,14 +594,14 @@ pub enum Origin {
/// address is in our address book
AddressBook = 0x80000,
/// set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling contact_is_verified() !
/// Set on Alice's side for contacts like Bob that have scanned the QR code offered by her.
/// Only means the contact has once been established using the "securejoin" procedure.
SecurejoinInvited = 0x0100_0000,
/// Set on Bob's side for contacts scanned from a QR code.
/// Only means the contact has been scanned from the QR code,
/// but does not mean that securejoin succeeded
/// or the key has not changed since the last scan.
/// Getting the current key verification status requires calling contact_is_verified() !
SecurejoinJoined = 0x0200_0000,
/// contact added manually by create_contact(), this should be the largest origin as otherwise the user cannot modify the names
@@ -1688,50 +1688,6 @@ WHERE addr=?
Ok(self.public_key(context).await?.is_some())
}
/// Returns true if the contact
/// can be added to verified chats.
///
/// If contact is verified
/// UI should display green checkmark after the contact name
/// in contact list items and
/// in chat member list items.
///
/// Use [Self::get_verifier_id] to display the verifier contact
/// in the info section of the contact profile.
pub async fn is_verified(&self, context: &Context) -> Result<bool> {
// We're always sort of secured-verified as we could verify the key on this device any time with the key
// on this device
if self.id == ContactId::SELF {
return Ok(true);
}
Ok(self.get_verifier_id(context).await?.is_some())
}
/// Returns the `ContactId` that verified the contact.
///
/// If this returns Some(_),
/// display green checkmark in the profile and "Introduced by ..." line
/// with the name of the contact.
///
/// If this returns `Some(None)`, then the contact is verified,
/// but it's unclear by whom.
pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<Option<ContactId>>> {
let verifier_id: u32 = context
.sql
.query_get_value("SELECT verifier FROM contacts WHERE id=?", (self.id,))
.await?
.with_context(|| format!("Contact {} does not exist", self.id))?;
if verifier_id == 0 {
Ok(None)
} else if verifier_id == self.id.to_u32() {
Ok(Some(None))
} else {
Ok(Some(Some(ContactId::new(verifier_id))))
}
}
/// Returns the number of real (i.e. non-special) contacts in the database.
pub async fn get_real_cnt(context: &Context) -> Result<usize> {
if !context.sql.is_open().await {

View File

@@ -69,10 +69,6 @@ pub enum StockMessage {
#[strum(props(fallback = "Fingerprints"))]
FingerPrints = 30,
/// Unused. Was used in status messages.
#[strum(props(fallback = "%1$s verified."))]
ContactVerified = 35,
#[strum(props(fallback = "Archived chats"))]
ArchivedChats = 40,