org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement Maven / Gradle / Ivy
Show all versions of libsignal-client Show documentation
//
// Copyright 2024 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.libsignal.zkgroup.groupsend;
import static org.signal.libsignal.internal.FilterExceptions.filterExceptions;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.Collection;
import org.signal.libsignal.internal.Native;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.internal.ByteArray;
/**
* An endorsement for a user or set of users in a group.
*
* GroupSendEndorsements provide a form of authorization by demonstrating that the holder of the
* endorsement is in a group with a particular user or set of users. They can be {@link #combine
* combined} and {@link #byRemoving removed} in a set-like fashion.
*
*
The endorsement "flow" starts with receiving a {@link GroupSendEndorsementsResponse} from the
* group server, which contains endorsements for all members in a group (including the local user).
* The response object provides the single expiration for all the endorsements. From there, the
* {@code receive} method produces a {@link GroupSendEndorsementsResponse.ReceivedEndorsements},
* which exposes the individual endorsements as well as a combined endorsement for everyone but the
* local user. Clients should save these endorsements and the expiration with the group state.
*
*
When it comes time to send a message to an individual user, clients should check to see if
* they have a {@link GroupSendEndorsement.Token} for that user, and generate and cache one using
* {@link #toToken} if not. The token should then be converted to a full token using {@link
* GroupSendEndorsement.Token#toFullToken}, providing the expiration saved previously. Finally, the
* serialized full token can be used as authorization in a request to the chat server.
*
*
Similarly, when it comes time to send a message to the group, clients should start by {@link
* #byRemoving removing} the endorsements of any users they are excluding (say, because they need a
* Sender Key Distribution Message first), and then converting the resulting endorsement to a token.
* From there, the token can be converted to a full token and serialized as for an individual send.
* (Saving the repeated work of converting to a token is left to the clients here; worst case, it's
* still cheaper than a usual zkgroup presentation.)
*/
public final class GroupSendEndorsement extends ByteArray {
public GroupSendEndorsement(byte[] contents) throws InvalidInputException {
super(contents);
filterExceptions(
InvalidInputException.class,
() -> Native.GroupSendEndorsement_CheckValidContents(contents));
}
GroupSendEndorsement(byte[] contents, UncheckedAndUncloned marker) {
super(contents, marker);
}
/**
* Combines several endorsements into one.
*
*
For example, if you have endorsements to send to Meredith and Aruna individually, then you
* can combine them to produce an endorsement to send a multi-recipient message to the two of
* them.
*/
public static GroupSendEndorsement combine(Collection endorsements) {
ByteBuffer[] buffers = new ByteBuffer[endorsements.size()];
int nextOffset = 0;
for (GroupSendEndorsement next : endorsements) {
byte[] nextEndorsementRaw = next.getInternalContentsForJNI();
buffers[nextOffset] = ByteBuffer.allocateDirect(nextEndorsementRaw.length);
buffers[nextOffset].put(nextEndorsementRaw);
++nextOffset;
}
byte[] rawCombinedEndorsement = Native.GroupSendEndorsement_Combine(buffers);
return filterExceptions(() -> new GroupSendEndorsement(rawCombinedEndorsement));
}
/**
* Removes an endorsement (individual or combined) from this combined endorsement.
*
* If {@code this} is not a combined endorsement, or {@code toRemove} includes
* endorsements that were not combined into {@code this}, the result will not generate valid
* tokens.
*/
public GroupSendEndorsement byRemoving(GroupSendEndorsement toRemove) {
byte[] rawResult =
Native.GroupSendEndorsement_Remove(
getInternalContentsForJNI(), toRemove.getInternalContentsForJNI());
return filterExceptions(() -> new GroupSendEndorsement(rawResult));
}
/**
* A minimal cacheable representation of an endorsement.
*
*
This contains the minimal information needed to represent this specific endorsement; it must
* be converted to a {@link GroupSendFullToken} before sending to the chat server. (It is valid to
* do this immediately; it just uses up extra space.)
*
*
Generated by {@link GroupSendEndorsement#toToken}.
*/
public static class Token extends ByteArray {
public Token(byte[] contents) throws InvalidInputException {
super(contents);
filterExceptions(
InvalidInputException.class, () -> Native.GroupSendToken_CheckValidContents(contents));
}
/**
* Converts this token to a "full token", which can be sent to the chat server as
* authentication.
*
*
{@code expiration} must be the same expiration that was in the original {@link
* GroupSendEndorsementsResponse}, or the resulting token will fail to verify.
*/
public GroupSendFullToken toFullToken(Instant expiration) {
byte[] rawResult =
Native.GroupSendToken_ToFullToken(
getInternalContentsForJNI(), expiration.getEpochSecond());
return filterExceptions(() -> new GroupSendFullToken(rawResult));
}
}
/**
* Generates a cacheable token used to authenticate sends.
*
*
The token is no longer associated with the group; it merely identifies the user or set of
* users referenced by this endorsement. (Of course, a set of users is a pretty good stand-in for
* a group.)
*
* @see Token
*/
public Token toToken(GroupSecretParams groupParams) {
byte[] rawResult =
Native.GroupSendEndorsement_ToToken(
getInternalContentsForJNI(), groupParams.getInternalContentsForJNI());
return filterExceptions(() -> new Token(rawResult));
}
/**
* Generates a token used to authenticate sends, ready to put in an auth header.
*
*
{@code expiration} must be the same expiration that was in the original {@link
* GroupSendEndorsementsResponse}, or the resulting token will fail to verify.
*
*
Equivalent to {@link #toToken} followed by {@link Token#toFullToken}.
*/
public GroupSendFullToken toFullToken(GroupSecretParams groupParams, Instant expiration) {
return toToken(groupParams).toFullToken(expiration);
}
}