Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/openmls/openmls/llms.txt

Use this file to discover all available pages before exploring further.

A ParentNode represents an internal node in the MLS ratchet tree. Parent nodes are located at odd indices and contain the shared encryption key for a subtree of members, along with metadata for tree synchronization.

Overview

Parent nodes:
  • Are located at odd indices in the tree array
  • Contain HPKE public keys for path encryption
  • Track parent hash for tree validation
  • Maintain lists of unmerged leaves
  • Do not have signatures (unlike leaf nodes)

Structure

pub struct ParentNode {
    encryption_key: EncryptionKey,
    parent_hash: VLBytes,
    unmerged_leaves: UnmergedLeaves,
}
encryption_key
EncryptionKey
HPKE public key for this parent node
parent_hash
VLBytes
Hash of the parent node’s parent for validation
unmerged_leaves
UnmergedLeaves
Sorted list of leaf indices that haven’t been merged into this node’s path

Creating Parent Nodes

Parent nodes are typically created internally during path derivation:
let parent_node = ParentNode::from(encryption_key);
This creates a parent node with:
  • The given encryption key
  • Empty parent hash
  • Empty unmerged leaves list

Accessing Parent Node Data

public_key()

Returns the HPKE public key:
pub fn public_key(&self) -> &HpkePublicKey
return
&HpkePublicKey
Reference to the public key bytes

encryption_key()

Returns the encryption key structure:
pub fn encryption_key(&self) -> &EncryptionKey
return
&EncryptionKey
Reference to the encryption key

parent_hash()

Returns the parent hash:
pub fn parent_hash(&self) -> &[u8]
return
&[u8]
Parent hash bytes

unmerged_leaves()

Returns the list of unmerged leaf indices:
pub fn unmerged_leaves(&self) -> &[LeafNodeIndex]
return
&[LeafNodeIndex]
Sorted slice of unmerged leaf indices

Path Derivation

Parent nodes are created as part of update path derivation:
let (path, update_path_nodes, keypairs, commit_secret) = 
    ParentNode::derive_path(
        crypto,
        ciphersuite,
        path_secret,
        path_indices,
    )?;
crypto
&impl OpenMlsCrypto
required
Crypto provider for key derivation
ciphersuite
Ciphersuite
required
Ciphersuite to use
path_secret
PathSecret
required
Initial path secret for derivation
path_indices
Vec<ParentNodeIndex>
required
Indices of parent nodes in the path
return
PathDerivationResult
Tuple of (parent nodes with indices, plain update nodes, keypairs, commit secret)

Parent Hash Operations

Computing Parent Hash

Compute the parent hash for this node:
let hash = parent_node.compute_parent_hash(
    crypto,
    ciphersuite,
    original_child_resolution,
)?;
crypto
&impl OpenMlsCrypto
required
Crypto provider
ciphersuite
Ciphersuite
required
Ciphersuite for hashing
original_child_resolution
&[u8]
required
Serialized original child resolution
return
Vec<u8>
Computed parent hash

Setting Parent Hash

parent_node.set_parent_hash(hash);
hash
Vec<u8>
required
New parent hash value

Unmerged Leaves Management

Unmerged leaves track which members don’t have the private key for this parent node.

Adding Unmerged Leaf

parent_node.add_unmerged_leaf(leaf_index);
leaf_index
LeafNodeIndex
required
Leaf index to add to unmerged list
The leaf is inserted in sorted order automatically.

Setting Unmerged Leaves

parent_node.set_unmerged_leaves(vec![leaf0, leaf1, leaf2]);
leaves
Vec<LeafNodeIndex>
required
Complete list of unmerged leaves (must be sorted)

UpdatePathNode (Encrypted)

When sending updates, parent nodes are encrypted into UpdatePathNode structures:
pub struct UpdatePathNode {
    pub public_key: EncryptionKey,
    pub encrypted_path_secrets: Vec<HpkeCiphertext>,
}
This is created from PlainUpdatePathNode:
let update_path_node = plain_node.encrypt(
    crypto,
    ciphersuite,
    public_keys,
    group_context,
)?;
crypto
&impl OpenMlsCrypto
required
Crypto provider
ciphersuite
Ciphersuite
required
Ciphersuite to use
public_keys
&[EncryptionKey]
required
Public keys to encrypt to (resolution of the copath)
group_context
&[u8]
required
Serialized group context for AEAD
return
UpdatePathNode
Encrypted update path node

Example: Inspecting Parent Node

use openmls::prelude::*;

fn inspect_parent_node(parent: &ParentNode, index: ParentNodeIndex) {
    println!("Parent node at index {}", index.u32());
    
    // Show encryption key
    let key_bytes = parent.public_key();
    println!("Public key: {} bytes", key_bytes.len());
    
    // Show parent hash
    let parent_hash = parent.parent_hash();
    println!("Parent hash: {} bytes", parent_hash.len());
    
    // Show unmerged leaves
    let unmerged = parent.unmerged_leaves();
    if !unmerged.is_empty() {
        println!("Unmerged leaves: {:?}", 
                 unmerged.iter()
                        .map(|idx| idx.u32())
                        .collect::<Vec<_>>());
    } else {
        println!("All leaves merged");
    }
}

Example: Processing Update Path

use openmls::prelude::*;

// Derive a path from a path secret
let path_secret = PathSecret::from(secret);

let path_indices = vec![
    ParentNodeIndex::new(1),
    ParentNodeIndex::new(3),
    ParentNodeIndex::new(7),
];

let (parent_nodes, plain_nodes, keypairs, commit_secret) = 
    ParentNode::derive_path(
        provider.crypto(),
        ciphersuite,
        path_secret,
        path_indices,
    )?;

// parent_nodes: Vec<(ParentNodeIndex, ParentNode)>
for (index, parent) in parent_nodes {
    println!("Derived parent at {}", index.u32());
}

// plain_nodes: Vec<PlainUpdatePathNode> - can be encrypted
// keypairs: Vec<EncryptionKeyPair> - private keys to store
// commit_secret: CommitSecret - used in key schedule

UnmergedLeaves Structure

The unmerged leaves list is maintained as a sorted vector:
pub struct UnmergedLeaves {
    list: Vec<LeafNodeIndex>,
}

Properties

  • Always kept in sorted order
  • Binary search used for insertion
  • Duplicate prevention during insertion

Creating UnmergedLeaves

let unmerged = UnmergedLeaves::new(); // Empty list

// From vector (must be sorted)
let unmerged = UnmergedLeaves::try_from(vec![leaf0, leaf1])?;

Blank Parent Nodes

Blank parent nodes represent empty positions:
let blank_parent = TreeSyncParentNode::blank();
Blank nodes:
  • Have no encryption key
  • Have no parent hash
  • Have empty unmerged leaves
  • Are represented as None in exported trees

Wire Format

Parent nodes are serialized as:
struct {
    HPKEPublicKey encryption_key;
    opaque parent_hash<V>;
    uint32 unmerged_leaves<V>;
} ParentNode;

Tree Navigation

Parent nodes are accessed by index:
// Get parent by index
if let Some(parent) = tree_sync.parent(parent_index) {
    // Process parent node
}

// Iterate all parents
for (index, parent) in tree_sync.full_parents() {
    println!("Parent {}: {:?}", index.u32(), parent.public_key());
}

Security Considerations

  1. Parent Hash Validation: Always validate parent hashes when importing trees
  2. Unmerged Leaves: Track carefully to ensure proper key distribution
  3. Path Secrets: Never expose path secrets; only use through secure derivation
  4. Blank Nodes: Handle blank nodes correctly in path operations

See Also