Signal struct provides direct access to Signal protocol operations including message encryption/decryption for both 1:1 and group conversations, session management, and participant node creation.
Access
Access Signal protocol operations through the client:Methods
encrypt_message
Encrypt plaintext for a single recipient using the Signal protocol.jid- Recipient JID. PN JIDs are resolved to LID andHostedJIDs toHostedLidwhen a mapping exists, matching WA Web’sSignalAddress.toString()and the internal send path. See Signal address resolution.plaintext- Raw bytes to encrypt. The caller is responsible for padding if needed.
(EncType, Vec<u8>)- The encryption type and ciphertext bytes
EncType::PreKeyMessage- Session was just established (includes prekey bundle)EncType::Message- Standard encrypted message
decrypt_message
Decrypt a Signal protocol message from a sender.jid- Sender JID. PN JIDs are resolved to LID andHostedJIDs toHostedLidwhen a mapping exists.enc_type- The encryption type (EncType::PreKeyMessageorEncType::Message)ciphertext- Encrypted bytes to decrypt
Vec<u8>- Raw padded plaintext. UseMessageUtils::unpad_message_refwith the stanza’svattribute if WhatsApp message unpadding is needed.
Passing
EncType::SenderKey returns an error — use decrypt_group_message for sender-key encrypted group messages.encrypt_group_message
Encrypt plaintext for a group using sender keys.group_jid- Group JID (@g.us)plaintext- Raw bytes to encrypt
(Option<Vec<u8>>, Vec<u8>)- A tuple of optional SKDM bytes and ciphertext bytes. The SKDM isSomeonly when a new sender key was created (first encrypt for this group or after key rotation). You must distribute the SKDM to all group participants when present.
Concurrent calls are serialized on a per-
(group_jid, sender_jid) chain lock (sender_key_lock), keyed here by your own JID as the sender. Two overlapping encrypt_group_message calls for the same group safely queue behind one another. A concurrent decrypt_group_message call only shares this lock when its sender_jid is your own JID (an unusual case) — decrypting messages from other participants uses a different chain and runs fully in parallel. See sender-key chain locking.decrypt_group_message
Decrypt a group (sender-key) message.group_jid- Group JIDsender_jid- Sender’s JID within the groupciphertext- Encrypted bytes to decrypt
Vec<u8>- Raw padded plaintext. UseMessageUtils::unpad_message_refwith the stanza’svattribute if WhatsApp message unpadding is needed.
Concurrent calls are serialized on a per-
(group_jid, sender_jid) chain lock (sender_key_lock), so two overlapping decrypt_group_message calls for the same sender safely queue behind one another. Calls for different senders in the same group — or a concurrent encrypt_group_message call, which uses your own JID as the chain identity — touch a different chain and run in parallel. See sender-key chain lock (group receive).sender_key_distribution
Create (or lazily initialize) and serialize the current outgoing sender-key distribution message for a group.group_jid- Group JIDsender_jid- Your own JID as it should appear to other group members (the sender key chain owner)
Vec<u8>- SerializedSenderKeyDistributionMessagebytes, ready to send to a new or existing group member (e.g. when adding a participant who needs to decrypt future messages)
encrypt_group_message returns automatically on first use — call it directly when you need to (re)distribute a sender key out of band, such as when a new member joins and needs the current chain without waiting for the next group message. The distribution is durably persisted before this method returns.
Example:
process_sender_key_distribution
Process an incoming sender-key distribution message for a group, installing the sender’s chain so futureskmsg stanzas from them can be decrypted.
group_jid- Group JIDsender_jid- JID of the participant who distributed the sender keydistribution- SerializedSenderKeyDistributionMessagebytes received from the sender (typically extracted from an incoming SKDM node)
has_sender_key
Check whether sender-key state already exists for a group and sender.group_jid- Group JIDsender_jid- Sender’s JID within the group
bool-trueif a sender-key chain is already stored for this(group_jid, sender_jid)pair
delete_sender_key
Durably delete a sender-key chain for a group and sender, e.g. on group exit or key rotation.group_jid- Group JIDsender_jid- Sender’s JID within the group
encrypt_group_message ratchet advance) to finish before removing the chain, and is flushed to the persistent backend before returning.
Example:
validate_session
Check whether a Signal session exists for a JID.jid- JID to check. PN JIDs are resolved to LID andHostedJIDs toHostedLidwhen a mapping exists.
bool-trueif a session exists,falseotherwise
session_info
Inspect an existing pairwise Signal session, migrating legacy PN-addressed state to its resolved LID namespace when needed.jid- JID to inspect. PN JIDs are resolved to LID andHostedJIDs toHostedLidwhen a mapping exists.
Option<SignalSessionInfo>-Somewith the session’s base key and remote registration id if a session exists,Noneotherwise
If only a legacy PN-addressed session exists and the resolved address is a LID, this method migrates it first (moving session and identity state to the LID namespace) and then reports on the migrated session — mirroring the on-the-fly migration used by the decrypt path. This means
session_info is not a purely read-only probe: it can itself perform the migration, so a subsequent migrate_sessions call on the same pair may find there is nothing left to move. See SignalSessionInfo and PN→LID session migration.delete_sessions
Delete Signal sessions and identity keys for the given JIDs.jids- JIDs whose sessions and identity keys should be deleted. PN JIDs are resolved to LID andHostedJIDs toHostedLidwhen a mapping exists.
deleteRemoteSession behavior, which removes both the session and identity key as a paired operation. Changes are flushed to the persistent backend before returning.
Example:
install_prekey_bundle
Durably install a supplied pre-key bundle for a JID, establishing (or replacing) a pairwise session from it.jid- JID to install the session for. Resolved the same way asencrypt_message.bundle- APreKeyBundleobtained out of band — e.g. from a manual/custom prekey fetch — rather than throughassert_sessions’s normal usync + fetch flow
IdentityChange-IdentityChange::NewOrUnchangedif the peer had no identity key or it matched, orIdentityChange::ReplacedExistingif this bundle’s identity key replaced a previously trusted one
migrate_sessions
Move pairwise session and identity state from one JID namespace to another for the same underlying account (PN→LID, or Hosted→HostedLid).from- Source JID namespace (must bePnorHosted)to- Destination JID namespace (must beLidfor aPnsource, orHostedLidfor aHostedsource)
SignalSessionMigration- Counts of sessions and identities moved, discarded, or skipped. SeeSignalSessionMigration.
from, moving each pairwise session and identity to to when the destination doesn’t already have one, and discarding the stale source entry when it does. This is the same logic the client runs automatically on LID discovery and on-the-fly during decryption — exposed here for callers that want to trigger it manually. See PN→LID session migration.
Mismatched namespace pairs (e.g. a Pn source with a HostedLid destination, or two otherwise unrelated JIDs) are rejected with SignalError::InvalidInput.
Example:
create_participant_nodes
Create encrypted participant<to> nodes for the given recipient JIDs.
recipient_jids- JIDs to encrypt formessage- Protobuf message to encrypt
(Vec<Node>, bool)- The encrypted participant XML nodes and a boolean indicating whether a device identity node should be included in the stanza (true when any participant received a PreKey message).
session_mutexes_for() (bare recipient JID for the recipient, per-device for own companion devices).
Example:
assert_sessions
Ensure E2E sessions exist for the given JIDs.jids- JIDs to ensure sessions for
get_user_devices
Get all known device JIDs for the given user JIDs via usync.jids- User JIDs to query
Vec<Jid>- All device JIDs for the given users
EncType
TheEncType enum represents the Signal protocol encryption type used for a message:
EncType exposes two predicate helpers: is_session() (true for Message / PreKeyMessage, excludes MessageSecret) and is_bot_secret() (true only for MessageSecret).
EncType is re-exported from the crate root as whatsapp_rust::EncType — prefer that over the internal wacore::message_processing::EncType path shown in older examples.SignalSessionInfo
Read-only information from a currently open pairwise session, returned bysession_info:
SignalSessionInfo is re-exported from the crate root, so it’s also available as whatsapp_rust::SignalSessionInfo.
SignalSessionMigration
Result of moving pairwise session state between address namespaces, returned bymigrate_sessions:
has_state_changes(self) -> bool-trueif any source state was moved or removed (migrated != 0 || migrated_identities != 0 || discarded_identities != 0). Useful for deciding whether a migration was a meaningful no-op (nothing to move) versus one that changed persisted state.
SignalSessionMigration is #[non_exhaustive] and re-exported from the crate root as whatsapp_rust::SignalSessionMigration.
Bot message decryption (msmsg)
When you message Meta AI or another@bot account, the bot’s replies arrive as <enc type="msmsg"> stanzas. These are not Signal-session encrypted — they use a dual-HKDF derivation over the 32-byte messageSecret from the prompt you sent, then AES-256-GCM.
The client handles this end to end and transparently:
- On send to a bot, the outbound
MessageContextInfo.messageSecretis persisted (keyed by(chat, sender, msg_id)) so the reply can be decrypted later. - On receive, an
msmsgstanza is decrypted and decoded into awa::Message, then dispatched as a normalEvent::Messages— there is no separate bot event. The sender is the bot JID (e.g.…@bot) andMsgMetaInfo.target_idpoints back at your original prompt. - On failure (missing secret, GCM tag mismatch, malformed proto) the client nacks with reason
495(MissingMessageSecret) instead of silently dropping, and group bot replies are acked with a bare<ack class="message">matching WA Web.
wacore::bot_message::decrypt_bot_message(message_secret, enc_iv, enc_payload, ctx), and persistence is backed by the MsgSecretStore trait.
Usage examples
Manual 1:1 encryption round-trip
Check session before sending
Group encryption with SKDM handling
Add a participant to an existing sender-key group
Reset a broken session
Manually migrate a session to LID addressing
Error types
SignalError
All signal methods return Result<T, SignalError>:
Protocol— Signal protocol error (session mismatch, decode failure, etc.)Unsupported— Operation not supported for the given parametersInvalidInput— The operation is supported but one of its inputs is malformed — e.g. a sender-key distribution message that fails to decode, or amigrate_sessionscall with a source/destination pair that isn’t a valid PN→LID or Hosted→HostedLid namespace matchInternal— Catch-all for other errors
See also
- Signal Protocol implementation - Deep dive into the protocol internals
- Client - Core client API
- Send - High-level message sending (handles encryption automatically)