pub struct ExternalCommitPolicyV1 {
pub allow_external_commit: bool,
pub expires_at_ns: u64,
pub expire_in_ns: u64,
pub symmetric_key: Vec<u8>,
pub external_group_id: Vec<u8>,
}Expand description
v1 external-commit-policy payload. Field-coupling invariants enforced by libxmtp when applying an AppDataUpdate(EXTERNAL_COMMIT_POLICY) proposal:
-
When
allow_external_committransitions to true:symmetric_keyandexternal_group_idMUST be populated (non-empty, meeting their length requirements) in the same proposal. The two transitions are atomic — there is no window where the bit is on but the invite coordinates are unset. -
When
allow_external_committransitions to false (revoke):symmetric_keyandexternal_group_idMUST be cleared (set to empty bytes) in the same proposal. Leaving stale coordinates in the group state after revoke would let a future re-enable accidentally revive a previously-distributed key. -
On re-enable (false → true after a prior revoke): the new
symmetric_keyMUST differ from every previously-used value for this group, and the newexternal_group_idSHOULD differ as well. Reusing a revoked key would re-validate every QR ever printed under that key, defeating the revocation. Admin clients are responsible for generating fresh material on each enable.
Fields§
§allow_external_commit: boolMaster switch for MLS External Commits adding new members. Required for the QR-invite flow. Defaults to false; admins (super-admin by default) opt in via AppDataUpdate(EXTERNAL_COMMIT_POLICY).
See the field-coupling invariants in the message-level comment above: enabling MUST populate symmetric_key + external_group_id; revoking (true → false) MUST clear them.
expires_at_ns: u64Wall-clock auto-disable timestamp (ns since UNIX epoch).
0 = no automatic expiry. After this timestamp the validator
rejects all external commits regardless of allow_external_commit.
Lets admins issue time-bounded invite campaigns without having to
come back and flip the bit manually.
expire_in_ns: u64Maximum staleness of the GroupInfo referenced by an external
commit, in nanoseconds since GroupInfo export. 0 = no staleness
limit. External commits whose referenced GroupInfo was exported
more than expire_in_ns ago are rejected. Narrows the replay
window for stolen-blob attacks and forces re-export frequency.
symmetric_key: Vec<u8>32-byte ChaCha20Poly1305 key used to wrap the EncryptedGroupInfoBlob for the currently-active invite. Carried in the group state so any member (especially a just-joined external committer) can re-export GroupInfo and re-upload a refreshed blob under the same key after a join — without this, a printed QR / link would die the moment the issuing admin went offline.
The QR carries the same key bytes. Rotation = admin sets a new value here in a single AppDataUpdate(EXTERNAL_COMMIT_POLICY) proposal AND issues a new QR carrying the matching key; old QR holders’ keys no longer decrypt blobs the service serves under the rotated slot.
Length MUST be exactly 32 bytes when populated. Empty (zero-length)
means no active invite — and MUST coincide with
allow_external_commit == false (see the field-coupling invariants
at the top of this message). Revoking the invite MUST clear this
field; re-enabling MUST populate it with a freshly-generated value
distinct from any previously-used key for this group.
Note: the service_pointer (where the blob lives) is intentionally NOT stored in the group. It is per-QR application-defined opaque bytes; different invites for the same group may point at different services. Joiners use the service_pointer from the QR they scanned.
external_group_id: Vec<u8>Identifier for the service slot holding the active invite’s encrypted blob. Application-defined opaque bytes (UUID, snowflake, short slot key, etc.); decoupled from the MLS group_id. Admins MAY rotate the symmetric_key while keeping this stable (overwrite the same slot on the service) or change both together (new slot, leaves the old slot orphaned for application-side GC).
The QR carries the same value. The joiner verifies that the QR’s
external_group_id equals this field after joining, as
defense-in-depth against a stale or swapped QR. Mismatch indicates
the admin rotated to a new slot after the QR was minted; the
joining client SHOULD treat the just-published commit as orphaned
(it validates fine, but the refreshed blob the joiner would upload
to the old slot will not be reachable by holders of the new QR).
MUST be at least 4 bytes when populated (collision-avoidance floor
for tiny services). RECOMMENDED: 16 random bytes when no
application-specific scheme is in use. Empty (zero-length) means
no active invite — and MUST coincide with
allow_external_commit == false (see the field-coupling
invariants at the top of this message). Revoking the invite MUST
clear this field; re-enabling SHOULD use a freshly-generated value
(reusing a prior external_group_id is permitted only when the
admin intends to overwrite the old service slot — typically the
admin generates a new value to leave the prior slot orphaned).
Trait Implementations§
Source§impl Clone for ExternalCommitPolicyV1
impl Clone for ExternalCommitPolicyV1
Source§fn clone(&self) -> ExternalCommitPolicyV1
fn clone(&self) -> ExternalCommitPolicyV1
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for ExternalCommitPolicyV1
impl Debug for ExternalCommitPolicyV1
Source§impl Default for ExternalCommitPolicyV1
impl Default for ExternalCommitPolicyV1
§impl<'de> Deserialize<'de> for ExternalCommitPolicyV1
impl<'de> Deserialize<'de> for ExternalCommitPolicyV1
§fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
Source§impl Hash for ExternalCommitPolicyV1
impl Hash for ExternalCommitPolicyV1
Source§impl Message for ExternalCommitPolicyV1
impl Message for ExternalCommitPolicyV1
Source§fn encoded_len(&self) -> usize
fn encoded_len(&self) -> usize
Source§fn encode(&self, buf: &mut impl BufMut) -> Result<(), EncodeError>where
Self: Sized,
fn encode(&self, buf: &mut impl BufMut) -> Result<(), EncodeError>where
Self: Sized,
Source§fn encode_to_vec(&self) -> Vec<u8> ⓘwhere
Self: Sized,
fn encode_to_vec(&self) -> Vec<u8> ⓘwhere
Self: Sized,
Source§fn encode_length_delimited(
&self,
buf: &mut impl BufMut,
) -> Result<(), EncodeError>where
Self: Sized,
fn encode_length_delimited(
&self,
buf: &mut impl BufMut,
) -> Result<(), EncodeError>where
Self: Sized,
Source§fn encode_length_delimited_to_vec(&self) -> Vec<u8> ⓘwhere
Self: Sized,
fn encode_length_delimited_to_vec(&self) -> Vec<u8> ⓘwhere
Self: Sized,
Source§fn decode(buf: impl Buf) -> Result<Self, DecodeError>where
Self: Default,
fn decode(buf: impl Buf) -> Result<Self, DecodeError>where
Self: Default,
Source§fn decode_length_delimited(buf: impl Buf) -> Result<Self, DecodeError>where
Self: Default,
fn decode_length_delimited(buf: impl Buf) -> Result<Self, DecodeError>where
Self: Default,
Source§fn merge(&mut self, buf: impl Buf) -> Result<(), DecodeError>where
Self: Sized,
fn merge(&mut self, buf: impl Buf) -> Result<(), DecodeError>where
Self: Sized,
self. Read moreSource§fn merge_length_delimited(&mut self, buf: impl Buf) -> Result<(), DecodeError>where
Self: Sized,
fn merge_length_delimited(&mut self, buf: impl Buf) -> Result<(), DecodeError>where
Self: Sized,
self.Source§impl Name for ExternalCommitPolicyV1
impl Name for ExternalCommitPolicyV1
Source§const NAME: &'static str = "ExternalCommitPolicyV1"
const NAME: &'static str = "ExternalCommitPolicyV1"
Message.
This name is the same as it appears in the source .proto file, e.g. FooBar.Source§const PACKAGE: &'static str = "xmtp.mls.message_contents"
const PACKAGE: &'static str = "xmtp.mls.message_contents"
., e.g. google.protobuf.Source§fn full_name() -> String
fn full_name() -> String
Message.
It’s prefixed with the package name and names of any parent messages,
e.g. google.rpc.BadRequest.FieldViolation.
By default, this is the package name followed by the message name.
Fully-qualified names must be unique within a domain of Type URLs.Source§impl PartialEq for ExternalCommitPolicyV1
impl PartialEq for ExternalCommitPolicyV1
Source§fn eq(&self, other: &ExternalCommitPolicyV1) -> bool
fn eq(&self, other: &ExternalCommitPolicyV1) -> bool
self and other values to be equal, and is used by ==.§impl Serialize for ExternalCommitPolicyV1
impl Serialize for ExternalCommitPolicyV1
impl Eq for ExternalCommitPolicyV1
impl StructuralPartialEq for ExternalCommitPolicyV1
Auto Trait Implementations§
impl Freeze for ExternalCommitPolicyV1
impl RefUnwindSafe for ExternalCommitPolicyV1
impl Send for ExternalCommitPolicyV1
impl Sync for ExternalCommitPolicyV1
impl Unpin for ExternalCommitPolicyV1
impl UnsafeUnpin for ExternalCommitPolicyV1
impl UnwindSafe for ExternalCommitPolicyV1
Blanket Implementations§
§impl<T> AggregateExpressionMethods for T
impl<T> AggregateExpressionMethods for T
§fn aggregate_distinct(self) -> Self::Outputwhere
Self: DistinctDsl,
fn aggregate_distinct(self) -> Self::Outputwhere
Self: DistinctDsl,
DISTINCT modifier for aggregate functions Read more§fn aggregate_all(self) -> Self::Outputwhere
Self: AllDsl,
fn aggregate_all(self) -> Self::Outputwhere
Self: AllDsl,
ALL modifier for aggregate functions Read more§fn aggregate_filter<P>(self, f: P) -> Self::Outputwhere
P: AsExpression<Bool>,
Self: FilterDsl<<P as AsExpression<Bool>>::Expression>,
fn aggregate_filter<P>(self, f: P) -> Self::Outputwhere
P: AsExpression<Bool>,
Self: FilterDsl<<P as AsExpression<Bool>>::Expression>,
§fn aggregate_order<O>(self, o: O) -> Self::Outputwhere
Self: OrderAggregateDsl<O>,
fn aggregate_order<O>(self, o: O) -> Self::Outputwhere
Self: OrderAggregateDsl<O>,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Classify for T
impl<T> Classify for T
type Classified = T
fn classify(self) -> T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Declassify for T
impl<T> Declassify for T
type Declassified = T
fn declassify(self) -> T
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.§impl<T> DowncastSend for T
impl<T> DowncastSend for T
§impl<T> DowncastSync for T
impl<T> DowncastSync for T
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<T> Identity for Twhere
T: ?Sized,
impl<T> Identity for Twhere
T: ?Sized,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request§impl<T> IntoSql for T
impl<T> IntoSql for T
§impl<L> LayerExt<L> for L
impl<L> LayerExt<L> for L
§fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>where
L: Layer<S>,
fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>where
L: Layer<S>,
Layered].