VAC-DECENTRALIZED-MESSAGING-ETHEREUM
- Status: raw
- Category: informational
- Editor: Ramses Fernandez-Valencia <ramses@status.im>
Abstract
This document introduces a decentralized group messaging protocol using Ethereum adresses as identifiers. It is based in the proposal DCGKA by Weidner et al. It includes also approximations to overcome limitations related to using PKI and the multi-device setting.
Motivation
The need for secure communications has become paramount. Traditional centralized messaging protocols are susceptible to various security threats, including unauthorized access, data breaches, and single points of failure. Therefore a decentralized approach to secure communication becomes increasingly relevant, offering a robust solution to address these challenges.
Secure messaging protocols used should have the following key features:
Asynchronous Messaging: Users can send messages even if the recipients are not online at the moment.
Resilience to Compromise: If a user's security is compromised, the protocol ensures that previous messages remain secure through forward secrecy (FS). This means that messages sent before the compromise cannot be decrypted by adversaries. Additionally, the protocol maintains post-compromise security (PCS) by regularly updating keys, making it difficult for adversaries to decrypt future communication.
Dynamic Group Management: Users can easily add or remove group members at any time, reflecting the flexible nature of communication within the app.
In this field, there exists a trilemma, similar to what one observes in blockchain, involving three key aspects:
- security,
- scalability, and
- decentralization.
For instance, protocols like the MLS perform well in terms of scalability and security. However, they falls short in decentralization.
Newer studies such as CoCoa improve features related to security and scalability, but they still rely on servers, which may not be fully trusted though they are necessary.
On the other hand, older studies like Causal TreeKEM exhibit decent scalability (logarithmic) but lack forward secrecy and have weak post-compromise security (PCS).
The creators of DCGKA introduce a decentralized, asynchronous secure group messaging protocol that supports dynamic groups. This protocol operates effectively on various underlying networks without strict requirements on message ordering or latency. It can be implemented in peer-to-peer or anonymity networks, accommodating network partitions, high latency links, and disconnected operation seamlessly. Notably, the protocol doesn't rely on servers or a consensus protocol for its functionality.
This proposal provides end-to-end encryption with forward secrecy and post-compromise security, even when multiple users concurrently modify the group state.
Theory
Protocol overview
This protocol makes use of ratchets to provide FS by encrypting each message with a different key.
In the figure one can see the ratchet for encrypting a sequence of messages.
The sender requires an initial update secret I_1
, which is introduced in a PRG.
The PRG will produce two outputs, namely a symmetric key for AEAD encryption, and
a seed for the next ratchet state.
The associated data needed in the AEAD encryption includes the message index i
.
The ciphertext c_i
associated to message m_i
is then broadcasted to all group members.
The next step requires deleting I_1
, k_i
and any old ratchet state.
After a period of time the sender may replace the ratchet state with new update secrets
I_2
, I_3
, and so on.
To start a post-compromise security update,
a user creates a new random value known as a seed secret and
shares it with every other group member through a secure two-party channel.
Upon receiving the seed secret,
each group member uses it to calculate an update secret for both the sender's ratchet
and their own.
Additionally, the recipient sends an unencrypted acknowledgment to the group
confirming the update.
Every member who receives the acknowledgment updates
not only the ratchet for the original sender but
also the ratchet for the sender of the acknowledgment.
Consequently, after sharing the seed secret through n - 1
two-party messages and
confirming it with n - 1
broadcast acknowledgments,
every group member has derived an update secret and updated their ratchet accordingly.
When removing a group member, the user who initiates the removal conducts a post-compromise security update by sending the update secret to all group members except the one being removed. To add a new group member, each existing group member shares the necessary state with the new user, enabling them to derive their future update secrets.
Since group members may receive messages in various orders, it's important to ensure that each sender's ratchet is updated consistently with the same sequence of update secrets at each group member.
The network protocol used in this scheme ensures that messages from the same sender are processed in the order they were sent.
Components of the protocol
This protocol relies in 3 components: authenticated causal broadcast (ACB), decentralized group membership (DGM) and 2-party secure messaging (2SM).
Authenticated causal broadcast
A causal order is a partial order relation <
on messages.
Two messages m_1
and m_2
are causally ordered, or
m_1
causally precedes m_2
(denoted by m_1 < m_2
), if one of the following contiditions hold:
m_1
andm_2
were sent by the same group member, andm_1
was sent beforem_2
.m_2
was sent by a group member U, andm_1
was received and processed byU
before sendingm_2
.- There exists
m_3
such thatm_1 < m_3
andm_3 < m_2
.
Causal broadcast requires that before processing m
, a group member must
process all preceding messages {m' | m' < m}
.
The causal broadcast module used in this protocol authenticates the sender of each message, as well as its causal ordering metadata, using a digital signature under the sender’s identity key. This prevents a passive adversary from impersonating users or affecting causally ordered delivery.
Decentralized group membership
This protocol assumes the existence of a decentralized group membership function (denoted as DGM) that takes a set of membership change messages and their causal order relantionships, and returns the current set of group members’ IDs. It needs to be deterministic and depend only on causal order, and not exact order.
2-party secure messaging (2SM)
This protocol makes use of bidirectional 2-party secure messaging schemes,
which consist of 3 algorithms: 2SM-Init
, 2SM-Send
and 2SM-Receive
.
Function 2SM-Init
This function takes two IDs as inputs:
ID1
representing the local user and ID2
representing the other party.
It returns an initial protocol state sigma
.
The 2SM protocol relies on a Public Key Infrastructure (PKI) or
a key server to map these IDs to their corresponding public keys.
In practice, the PKI should incorporate ephemeral prekeys.
This allows users to send messages to a new group member,
even if that member is currently offline.
Function 2SM-Send
This function takes a state sigma
and a plaintext m
as inputs, and returns
a new state sigma’
and a ciphertext c
.
Function 2SM-Receive
This function takes a state sigma
and a ciphertext c
, and
returns a new state sigma’
and a plaintext m
.
This function takes a state sigma
and a ciphertext c
, and returns a new
state sigma’
and a plaintext m
.
Function 2SM Syntax
The variable sigma
denotes the state consisting in the variables below:
sigma.mySks[0] = sk
sigma.nextIndex = 1
sigma.receivedSk = empty_string
sigma.otherPk = pk`<br/>
sigma.otherPksender = “other”
sigma.otherPkIndex = 0
2SM-Init
On input a key pair (sk, pk)
, this functions otuputs a state sigma
.
2SM-Send
This function encrypts the message m
using sigma.otherPk
, which represents
the other party’s current public key.
This key is determined based on the last public key generated for the other
party or the last public key received from the other party,
whichever is more recent. sigma.otherPkSender
is set to me
in the former
case and other
in the latter case.
Metadata including otherPkSender
and otherPkIndex
are included in the
message to indicate which of the recipient’s public keys is being utilized.
Additionally, this function generates a new key pair for the local user,
storing the secret key in sigma.mySks
and sending the public key.
Similarly, it generates a new key pair for the other party,
sending the secret key (encrypted) and storing the public key in
sigma.otherPk
.
sigma.mySks[sigma.nextIndex], myNewPk) = PKE-Gen()
(otherNewSk, otherNewPk) = PKE-Gen()
plaintext = (m, otherNewSk, sigma`.nextIndex, myNewPk)
msg = (PKE-Enc(sigma.otherPk, plaintext), sigma.otherPkSender, sigma.otherPkIndex)
sigma.nextIndex++
(sigma.otherPk, sigma.otherPkSender, sigma.otherPkIndex) = (otherNewPk, "me", empty_string)
return (sigma`, msg)
2SM-Receive
This function utilizes the metadata of the message c
to determine which
secret key to utilize for decryption, assigning it to sk
.
If the secret key corresponds to one generated by ourselves,
that secret key along with all keys with lower index are deleted.
This deletion is indicated by sigma.mySks[≤ keyIndex] = empty_string
.
Subsequently, the new public and secret keys contained in the message are
stored.
(ciphertext, keySender, keyIndex) = c
if keySender = "other" then
sk = sigma.mySks[keyIndex]
sigma.mySks[≤ keyIndex] = empty_string
else sk = sigma.receivedSk
(m, sigma.receivedSk, sigma.otherPkIndex, sigma.otherPk) = PKE-Dec(sk, ciphertext)
sigma.otherPkSender = "other"
return (sigma, m)
PKE Syntax
The required PKE that MUST be used is ElGamal with a 2048-bit modulus p
.
Parameters
The following parameters must be used:
p = 308920927247127345254346920820166145569
g = 2
PKE-KGen
Each user u
MUST do the following:
PKE-KGen():
a = randint(2, p-2)
pk = (p, g, g^a)
sk = a
return (pk, sk)
PKE-Enc
A user v
encrypting a message m
for u
MUST follow these steps:
PKE-Enc(pk):
k = randint(2, p-2)
eta = g^k % p
delta = m * (g^a)^k % p
return ((eta, delta))
PKE-Dec
The user u
recovers a message m
from a ciphertext c
by performing the following operations:
PKE-Dec(sk):
mu = eta^(p-1-sk) % p
return ((mu * delta) % p)
DCGKA Syntax
Auxiliary functions
There exist 6 functions that are auxiliary for the rest of components of the protocol, namely:
init
This function takes an ID
as input and returns its associated initial state,
denoted by gamma
:
gamma.myId = ID
gamma.mySeq = 0
gamma.history = empty
gamma.nextSeed = empty_string
gamma.2sm[·] = empty_string
gamma.memberSecret[·, ·, ·] = empty_string
gamma.ratchet[·] = empty_string
return (gamma)
encrypt-to
Upon reception of the recipient’s ID
and a plaintext, it encrypts a direct
message for another group member.
Should it be the first message for a particular ID
,
then the 2SM
protocol state is initialized and stored in
gamma.2sm[recipient.ID]
.
One then uses 2SM_Send
to encrypt the message and store the updated protocol
in gamma
.
if gamma.2sm[recipient_ID] = empty_string then
gamma.2sm[recipient_ID] = 2SM_Init(gamma.myID, recipient_ID)
(gamma.2sm[recipient_ID], ciphertext) = 2SM_Send(gamma.2sm[recipient_ID], plaintext)
return (gamma, ciphertext)
decrypt-from
After receiving the sender’s ID
and a ciphertext, it behaves as the reverse
function of encrypt-to
and has a similar initialization:
if gamma.2sm[sender_ID] = empty_string then
gamma.2sm[sender_ID] = 2SM_Init(gamma.myID, sender_ID)
(gamma.2sm[sender_ID], plaintext) = 2SM_Receive(gamma.2sm[sender_ID], ciphertext)
return (gamma, plaintext)
update-ratchet
This function generates the next update secret I_update
for the group member
ID
.
The ratchet state is stored in gamma.ratchet[ID]
.
It is required to use a HMAC-based key derivation function HKDF to combine the
ratchet state with an input, returning an update secret and a new ratchet
state.
(updateSecret, gamma.ratchet[ID]) = HKDF(gamma.ratchet[ID], input)
return (gamma, updateSecret)
member-view
This function calculates the set of group members
based on the most recent control message sent by the specified user ID
.
It filters the group membership operations
to include only those observed by the specified ID
, and
then invokes the DGM function to generate the group membership.
ops = {m in gamma.history st. m was sent or acknowledged by ID}
return DGM(ops)
generate-seed
This functions generates a random bit string and
sends it encrypted to each member of the group using the 2SM
mechanism.
It returns the updated protocol state and
the set of direct messages (denoted as dmsgs
) to send.
gamma.nextSeed = random.randbytes()
dmsgs = empty
for each ID in recipients:
(gamma, msg) = encrypt-to(gamma, ID, gamma.nextSeed)
dmsgs = dmsgs + (ID, msg)
return (gamma, dmsgs)
Creation of a group
A group is generated in a 3 steps procedure:
- A user calls the
create
function and broadcasts a control message of type create. - Each receiver of the message processes the message and broadcasts an ack control message.
- Each member processes the ack message received.
create
This function generates a create control message and calls generate-seed
to
define the set of direct messages that need to be sent.
Then it calls process-create
to process the control message for this user.
The function process-create
returns a tuple including an updated state gamma
and an update secret I
.
control = (“create”, gamma.mySeq, IDs)
(gamma, dmsgs) = generate-seed(gamma, IDs)
(gamma, _, _, I, _) = process-create(gamma, gamma.myId, gamma.mySeq, IDs, empty_string)
return (gamma, control, dmsgs, I)
process-seed
This function initially employs member-view
to identify the users who were
part of the group when the control message was dispatched.
Then, it attempts to acquire the seed secret through the following steps:
- If the control message was dispatched by the local user, it uses the most
recent invocation of
generate-seed
stored the seed secret ingamma.nextSeed
. - If the
control
message was dispatched by another user, and the local user is among its recipients, the function utilizesdecrypt-from
to decrypt the direct message that includes the seed secret. - Otherwise, it returns an
ack
message without deriving an update secret.
Afterwards, process-seed
generates separate member secrets for each group
member from the seed secret by combining the seed secret and
each user ID using HKDF.
The secret for the sender of the message is stored in senderSecret
, while
those for the other group members are stored in gamma.memberSecret
.
The sender's member secret is immediately utilized to update their KDF ratchet
and compute their update secret I_sender
using update-ratchet
.
If the local user is the sender of the control message, the process is
completed, and the update secret is returned.
However, if the seed secret is received from another user, an ack
control
message is constructed for broadcast, including the sender ID and sequence
number of the message being acknowledged.
The final step computes an update secret I_me
for the local user invoking the
process-ack
function.
recipients = member-view(gamma, sender) - {sender}
if sender = gamma.myId then seed = gamma.nextSeed; gamma.nextSeed =
empty_string
else if gamma.myId in recipients then (gamma, seed) = decrypt-from(gamma,
sender, dmsg)
else
return (gamma, (ack, ++gamma.mySeq, (sender, seq)), empty_string ,
empty_string , empty_string)
for ID in recipients do gamma.memberSecret[sender, seq, ID] = HKDF(seed, ID)
senderSecret = HKDF(seed, sender)
(gamma, I_sender) = update-ratchet(gamma, sender, senderSecret)
if sender = gamma.myId then return (gamma, empty_string , empty_string ,
I_sender, empty_string)
control = (ack, ++gamma.mySeq, (sender, seq))
members = member-view(gamma, gamma.myId)
forward = empty
for ID in {members - (recipients + {sender})}
s = gamma.memberSecret[sender, seq, gamma.myId]
(gamma, msg) = encrypt-to(gamma, ID, s)
forward = forward + {(ID, msg)}
(gamma, _, _, I_me, _) = process-ack(gamma, gamma.myId, gamma.mySeq,
(sender, seq), empty_string)
return (gamma, control, forward, I_sender, I_me)
process-create
This function is called by the sender and each of the receivers of the create
control message.
First, it records the information from the create message in the
gamma.history+ {op}
, which is used to track group membership changes. Then,
it proceeds to call process-seed
.
op = (”create”, sender, seq, IDs)
gamma.history = gamma.history + {op}
return (process-seed(gamma, sender, seq, dmsg))
process-ack
This function is called by those group members once they receive an ack
message.
In process-ack
, ackID
and ackSeq
are the sender and sequence number of
the acknowledged message.
Firstly, if the acknowledged message is a group membership operation, it
records the acknowledgement in gamma.history
.
Following this, the function retrieves the relevant member secret from
gamma.memberSecret
, which was previously obtained from the seed secret
contained in the acknowledged message.
Finally, it updates the ratchet for the sender of the ack
and returns the
resulting update secret.
if (ackID, ackSeq) was a create / add / remove then
op = ("ack", sender, seq, ackID, ackSeq)
gamma.history = gamma.history + {op}`
s = gamma.memberSecret[ackID, ackSeq, sender]
gamma.memberSecret[ackID, ackSeq, sender] = empty_string
if (s = empty_string) & (dmsg = empty_string) then return (gamma, empty_string,
empty_string, empty_string, empty_string)
if (s = empty_string) then (gamma, s) = decrypt-from(gamma, sender, dmsg)
(gamma, I) = update-ratchet(gamma, sender, s)
return (gamma, empty_string, empty_string, I, empty_string)
The HKDF function MUST follow RFC 5869 using the hash function SHA256.
Post-compromise security updates and group member removal
The functions update
and remove
share similarities with create
:
they both call the function generate-seed
to encrypt a new seed secret for
each group member.
The distinction lies in the determination of the group members using member
view
.
In the case of remove
, the user being removed is excluded from the recipients
of the seed secret.
Additionally, the control message they construct is designated with type
update
or remove
respectively.
Likewise, process-update
and process-remove
are akin to process-create
.
The function process-update
skips the update of gamma.history
,
whereas process-remove
includes a removal operation in the history.
update
control = ("update", ++gamma.mySeq, empty_string)
recipients = member-view(gamma, gamma.myId) - {gamma.myId}
(gamma, dmsgs) = generate-seed(gamma, recipients)
(gamma, _, _, I , _) = process-update(gamma, gamma.myId, gamma.mySeq,
empty_string, empty_string)
return (gamma, control, dmsgs, I)
remove
control = ("remove", ++gamma.mySeq, empty)
recipients = member-view(gamma, gamma.myId) - {ID, gamma.myId}
(gamma, dmsgs) = generate-seed(gamma, recipients)
(gamma, _, _, I , _) = process-update(gamma, gamma.myId, gamma.mySeq, ID,
empty_string)
return (gamma, control, dmsgs, I)
process-update
return process-seed(gamma, sender, seq, dmsg)
process-remove
op = ("remove", sender, seq, removed)
gamma.history = gamma.history + {op}
return process-seed(gamma, sender, seq, dmsg)
Group member addition
add
When adding a new group member, an existing member initiates the process by
invoking the add
function and providing the ID of the user to be added.
This function prepares a control message marked as add
for broadcast to the
group. Simultaneously, it creates a welcome message intended for the new member
as a direct message.
This welcome
message includes the current state of the sender's KDF ratchet,
encrypted using 2SM
, along with the history of group membership operations
conducted so far.
control = ("add", ++gamma.mySeq, ID)
(gamma, c) = encrypt-to(gamma, ID, gamma.ratchet[gamma.myId])
op = ("add", gamma.myId, gamma.mySeq, ID)
welcome = (gamma.history + {op}, c)
(gamma, _, _, I, _) = process-add(gamma, gamma.myId, gamma.mySeq, ID, empty_string)
return (gamma, control, (ID, welcome), I)
process-add
This function is invoked by both the sender and each recipient of an add
message, which includes the new group member. If the local user is the newly
added member, the function proceeds to call process-welcome
and then exits.
Otherwise, it extends gamma.history
with the add
operation.
Line 5 determines whether the local user was already a group member at the time
the add
message was sent; this condition is typically true but may be false
if multiple users were added concurrently.
On lines 6 to 8, the ratchet for the sender of the add message is updated
twice. In both calls to update-ratchet
, a constant string is used as the
ratchet input instead of a random seed secret.
The value returned by the first ratchet update is stored in
gamma.memberSecret
as the added user’s initial member secret. The result of
the second ratchet update becomes I_sender
, the update secret for the sender
of the add
message. On line 10, if the local user is the sender, the update
secret is returned.
If the local user is not the sender, an acknowledgment for the add
message is
required.
Therefore, on line 11, a control message of type add-ack
is constructed for
broadcast.
Subsequently, in line 12 the current ratchet state is encrypted using 2SM
to
generate a direct message intended for the added user, allowing them to decrypt
subsequent messages sent by the sender.
Finally, in lines 13 to 15, process-add-ack
is called to calculate the local
user’s update secret (I_me
), which is then returned along with I_sender
.
if added = gamma.myId then return process-welcome(gamma, sender, seq, dmsg)
op = ("add", sender, seq, added)
gamma.history = gamma.history + {op}
if gamma.myId in member-view(gamma, sender) then
(gamma, s) = update-ratchet(gamma, sender, "welcome")
gamma.memberSecret[sender, seq, added] = s
(gamma, I_sender) = update-ratchet(gamma, sender, "add")
else I_sender = empty_string
if sender = gamma.myId then return (gamma, empty_string, empty_string,
I_sender, empty_string)
control = ("add-ack", ++gamma.mySeq, (sender, seq))
(gamma, c) = encrypt-to(gamma, added, ratchet[gamma.myId])
(gamma, _, _, I_me, _) = process-add-ack(gamma, gamma.myId, gamma.mySeq,
(sender, seq), empty_string)
return (gamma, control, {(added, c)}, I_sender, I_me)
process-add-ack
This function is invoked by both the sender and each recipient of an add-ack
message, including the new group member. Upon lines 1–2, the acknowledgment is
added to gamma.history
, mirroring the process in process-ack
.
If the current user is the new group member, the add-ack
message includes the
direct message constructed in process-add
; this direct message contains the
encrypted ratchet state of the sender of the add-ack
, then it is decrypted on
lines 3–5.
Upon line 6, a check is performed to check if the local user was already a
group member at the time the add-ack
was sent. If affirmative, a new update
secret I
for the sender of the add-ack
is computed on line 7 by invoking
update-ratchet
with the constant string add
.
In the scenario involving the new member, the ratchet state was recently initialized on line 5. This ratchet update facilitates all group members, including the new addition, to derive each member’s update by obtaining any update secret from before their inclusion.
op = ("ack", sender, seq, ackID, ackSeq)
gamma$.history = gamma.history + {op}
if dmsg != empty_string then
(gamma, s) = decrypt-from(gamma, sender, dmsg)
gamma.ratchet[sender] = s
if gamma.myId in member-view(gamma, sender) then
(gamma, I) = update-ratchet(gamma, sender, "add")
return (gamma, empty_string, empty_string, I, empty_string)
else return (gamma, empty_string, empty_string, empty_string, empty_string)
process-welcome
This function serves as the second step called by a newly added group member.
In this context, adderHistory
represents the adding user’s copy of
gamma.history
sent in their welcome message, which is utilized to initialize
the added user’s history.
Here, c
denotes the ciphertext of the adding user’s ratchet state, which is
decrypted on line 2 using decrypt-from
.
Once gamma.ratchet[sender]
is initialized, update-ratchet
is invoked twice
on lines 3 to 5 with the constant strings welcome
and add
respectively.
These operations mirror the ratchet operations performed by every other group
member in process-add
.
The outcome of the first update-ratchet
call becomes the first member secret
for the added user,
while the second call returns I_sender
, the update secret for the sender of
the add operation.
Subsequently, the new group member constructs an ack control message to
broadcast on line 6 and calls process-ack
to compute their initial update
secret I_me. The function process-ack
reads from gamma.memberSecret
and
passes it to update-ratchet
. The previous ratchet state for the new member is
the empty string empty
, as established by init
, thereby initializing the
new member’s ratchet.
Upon receiving the new member’s ack
, every other group member initializes
their copy of the new member’s ratchet in a similar manner.
By the conclusion of process-welcome
, the new group member has acquired
update secrets for themselves and the user who added them.
The ratchets for other group members are initialized by process-add-ack
.
gamma.history = adderHistory
(gamma, gamma.ratchet[sender]) = decrypt-from(gamma, sender, c)
(gamma, s) = update-ratchet(gamma, sender, "welcome")
gamma.memberSecret[sender, seq, gamma.myId] = s
(gamma, I_sender) = update-ratchet(gamma, sender, "add")
control = ("ack", ++gamma.mySeq, (sender, seq))
(gamma, _, _, I_me, _) = process-ack(gamma, gamma.myId, gamma.mySeq, (sender,
seq), empty_string)
return (gamma, control, empty_string , I_sender, I_me)
Privacy Considerations
Dependency on PKI
The DCGKA proposal presents some limitations highlighted by the authors. Among these limitations one finds the requirement of a PKI (or a key server) mapping IDs to public keys.
One method to overcome this limitation is adapting the protocol SIWE (Sign in
with Ethereum) so a user u_1
who wants to start a communication with a user
u_2
can interact with latter’s wallet to request a public key using an
Ethereum address as ID
.
SIWE
The SIWE (Sign In With Ethereum) proposal was a suggested standard for leveraging Ethereum to authenticate and authorize users on web3 applications. Its goal is to establish a standardized method for users to sign in to web3 applications using their Ethereum address and private key, mirroring the process by which users currently sign in to web2 applications using their email and password. Below follows the required steps:
- A server generates a unique Nonce for each user intending to sign in.
- A user initiates a request to connect to a website using their wallet.
- The user is presented with a distinctive message that includes the Nonce and details about the website.
- The user authenticates their identity by signing in with their wallet.
- Upon successful authentication, the user's identity is confirmed or approved.
- The website grants access to data specific to the authenticated user.
Our approach
The idea in the DCGKA setting closely resembles the procedure outlined in SIWE. Here:
- The server corresponds to user D1,who initiates a request (instead of generating a nonce) to obtain the public key of user D2.
- Upon receiving the request, the wallet of D2 send the request to the user,
- User D2 receives the request from the wallet, and decides whether accepts or rejects.
- The wallet and responds with a message containing the requested public key in case of acceptance by D2.
This message may be signed, allowing D1 to verify that the owner of the received public key is indeed D2.
Multi-device setting
One may see the set of devices as a group and create a group key for internal
communications.
One may use treeKEM for instance, since it provides interesting properties like
forward secrecy and post-compromise security.
All devices share the same ID
, which is held by one of them, and from other
user’s point of view, they would look as a single user.
Using servers, like in the paper Multi-Device for Signal, should be avoided; but this would imply using a particular device as receiver and broadcaster within the group. There is an obvious drawback which is having a single device working as a “server”. Should this device be attacked or without connection, there should be a mechanism for its revocation and replacement.
Another approach for communications between devices could be using the keypair of each device. This could open the door to use UPKE, since keypairs should be regenerated frequently.
Each time a device sends a message, either an internal message or an external message, it needs to replicate and broadcast it to all devices in the group.
The mechanism for the substitution of misbehaving leader devices follows:
- Each device within a group knows the details of other leader devices. This information may come from metadata in received messages, and is replicated by the leader device.
- To replace a leader, the user should select any other device within its group and use it to send a signed message to all other users.
- To get the ability to sign messages, this new leader should request the keypair associated to the ID to the wallet.
- Once the leader has been changed, it revocates access from DCGKA to the former leader using the DCGKA protocol.
- The new leader starts a key update in DCGKA.
Not all devices in a group should be able to send messages to other users. Only
the leader device should be in charge of sending and receiving messages.
To prevent other devices from sending messages outside their group, a
requirement should be signing each message. The keys associated to the ID
should only be in control of the leader device.
The leader device is in charge of setting the keys involved in the DCGKA. This information must be replicated within the group to make sure it is updated.
To detect missing messages or potential misbehavior, messages must include a counter.
Using UPKE
Managing the group of devices of a user can be done either using a group key protocol such as treeKEM or using the keypair of each device. Setting a common key for a group of devices under the control of the same actor might be excessive, furthermore it may imply some of the problems one can find in the usual setting of a group of different users; for example: one of the devices may not participate in the required updating processes, representing a threat for the group.
The other approach to managing the group of devices is using each device’s keypair, but it would require each device updating these materia frequently, something that may not happens.
UPKE is a form of asymetric cryptography where any user can update any other user’s key pair by running an update algorithm with (high-entropy) private coins. Any sender can initiate a key update by sending a special update ciphertext. This ciphertext updates the receiver’s public key and also, once processed by the receiver, will update their secret key.
To the best of my knowledge, there exists several efficient constructions both UPKE from ElGamal (based in the DH assumption) and UPKE from Lattices (based in lattices). None of them have been implemented in a secure messaging protocol, and this opens the door to some novel research.
Copyright
Copyright and related rights waived via CC0.