Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Module 0x1::sigma_protocol_key_rotation

The key rotation NP relation ($\mathcal{R}_\mathsf{keyrot}$)

$\def\old#1{{\color{red}{\dot{#1}}}}\def\new#1{{\color{teal}{\widetilde{#1}}}}$

A ZKPoK of having rotated an encryption key to a new one and re-encrypted (part of) a Twisted ElGamal ciphertext.

Notation

  • $\old{x}$ denotes a stale/old ciphertext component; $\new{x}$ denotes a fresh/new one.
  • $\ell$: number of available balance chunks.

The relation

$$ \mathcal{R}\mathsf{keyrot}^\ell\left(\begin{array}{l} H, \mathsf{ek}, \new{\mathsf{ek}}, \old{\mathbf{R}}, \new{\mathbf{R}} \textbf{;}\ \mathsf{dk}, \delta, \delta\mathsf{inv} \end{array}\right) = 1 \Leftrightarrow \left{\begin{array}{r@{,,}l@{\quad}l} H &= \mathsf{dk} \cdot \mathsf{ek}\ \new{\mathsf{ek}} &= \delta \cdot \mathsf{ek}\ \mathsf{ek} &= \delta_\mathsf{inv} \cdot \new{\mathsf{ek}}\ \new{R}_i &= \delta \cdot \old{R}_i, &\forall i \in [\ell]\ \end{array}\right. $$

Homomorphism

This can be framed as a homomorphism check $\psi(\mathbf{w}) = f(\mathbf{X})$ where $\mathbf{w} = (\mathsf{dk}, \delta, \delta_\mathsf{inv})$ is the witness and $\mathbf{X} = (H, \mathsf{ek}, \new{\mathsf{ek}}, \old{\mathbf{R}}, \new{\mathbf{R}})$ is the statement.

  1. The homomorphism $\psi$ is:

$$ \psi(\mathsf{dk}, \delta, \delta_\mathsf{inv}) = \begin{pmatrix} \mathsf{dk} \cdot \mathsf{ek}\ \delta \cdot \mathsf{ek}\ \delta_\mathsf{inv} \cdot \new{\mathsf{ek}}\ \delta \cdot \old{R}_i, &\forall i \in [\ell]\ \end{pmatrix} $$

  1. The transformation function $f$ is:

$$ f(\mathbf{X}) = \begin{pmatrix} H\ \new{\mathsf{ek}}\ \mathsf{ek}\ \new{R}_i, &\forall i \in [\ell]\ \end{pmatrix} $$

use 0x1::bcs;
use 0x1::chain_id;
use 0x1::confidential_balance;
use 0x1::error;
use 0x1::fungible_asset;
use 0x1::object;
use 0x1::ristretto255;
use 0x1::sigma_protocol_fiat_shamir;
use 0x1::sigma_protocol_proof;
use 0x1::sigma_protocol_representation;
use 0x1::sigma_protocol_representation_vec;
use 0x1::sigma_protocol_statement;
use 0x1::sigma_protocol_statement_builder;
use 0x1::sigma_protocol_utils;
use 0x1::sigma_protocol_witness;
use 0x1::signer;
use 0x1::vector;

Struct KeyRotation

Phantom marker type for key rotation statements.

struct KeyRotation has drop
Fields
dummy_field: bool

Struct KeyRotationSession

Used for domain separation

struct KeyRotationSession has drop
Fields
sender: address
token_type: object::Object<fungible_asset::Metadata>
num_chunks: u64

Constants

The homomorphism or transformation function implementation is not inserting points at the expected positions.

const E_STATEMENT_BUILDER_INCONSISTENCY: u64 = 6;

Index of $\mathsf{dk}$ (old decryption key) in the witness’s scalars vector.

const IDX_DK: u64 = 0;

Index of $\mathsf{ek}$ (old encryption key) in the statement’s points vector.

const IDX_EK: u64 = 1;

Index of $H$ in the statement’s points vector.

const IDX_H: u64 = 0;

Protocol ID used for domain separation

const PROTOCOL_ID: vector<u8> = [65, 112, 116, 111, 115, 67, 111, 110, 102, 105, 100, 101, 110, 116, 105, 97, 108, 65, 115, 115, 101, 116, 47, 75, 101, 121, 82, 111, 116, 97, 116, 105, 111, 110, 86, 49];

The key rotation proof was invalid

const E_INVALID_KEY_ROTATION_PROOF: u64 = 5;

Index of $\delta$ in the witness’s scalars vector.

const IDX_DELTA: u64 = 1;

Index of $\delta_\mathsf{inv}$ in the witness’s scalars vector.

const IDX_DELTA_INV: u64 = 2;

Index of $\widetilde{\mathsf{ek}}$ (new encryption key) in the statement’s points vector.

const IDX_EK_NEW: u64 = 2;

The old R values ($\dot{R}_i$ ) occupy indices 3 to 3 + (num_chunks - 1), inclusive.

Note: The new R values ($\widetilde{R}_i$) occupy indices 3 + num_chunks to 3 + (2*num_chunks - 1), inclusive. A get_start_idx_for_new_R(num_chunks) function can be used to fetch the 3 + num_chunks starting index.

const START_IDX_OLD_R: u64 = 3;

Function get_start_idx_for_new_R

Returns the starting index of new_R values.

fun get_start_idx_for_new_R(): u64
Implementation
inline fun get_start_idx_for_new_R(): u64 {
    START_IDX_OLD_R + get_num_available_chunks()
}

Function assert_key_rotation_statement_is_well_formed

Ensures the statement is of the form: $\left( H, \mathsf{ek}, \widetilde{\mathsf{ek}}, (\dot{R}i){i \in [\ell]}), (\widetilde{R}i){i \in [\ell]} \right)$

fun assert_key_rotation_statement_is_well_formed(stmt: &sigma_protocol_statement::Statement<sigma_protocol_key_rotation::KeyRotation>)
Implementation
fun assert_key_rotation_statement_is_well_formed(
    stmt: &Statement<KeyRotation>,
) {
    assert!(stmt.get_points().length() == 3 + 2 * get_num_available_chunks(), e_wrong_num_points());
    assert!(stmt.get_scalars().length() == 0, e_wrong_num_scalars());
}

Function new_session

public(friend) fun new_session(sender: &signer, token_type: object::Object<fungible_asset::Metadata>): sigma_protocol_key_rotation::KeyRotationSession
Implementation
public(friend) fun new_session(sender: &signer, token_type: Object<Metadata>): KeyRotationSession {
    KeyRotationSession {
        sender: signer::address_of(sender),
        token_type,
        num_chunks: get_num_available_chunks(),
    }
}

Function new_key_rotation_statement

Creates a new key rotation statement. The order matches the NP relation: $(H, \mathsf{ek}, \widetilde{\mathsf{ek}}, \dot{\mathbf{R}}, \widetilde{\mathbf{R}})$. Note that the # of chunks is inferred from the sizes of the old and new balance ciphertexts.

All points are decompressed internally from their compressed forms by the StatementBuilder.

@param compressed_ek: Compressed form of the old encryption key @param compressed_new_ek: Compressed form of the new encryption key @param compressed_old_R: Compressed forms of old_R (by reference; num_chunks elements) @param compressed_new_R: Compressed forms of new_R (by reference; num_chunks elements)

public(friend) fun new_key_rotation_statement(compressed_ek: ristretto255::CompressedRistretto, compressed_new_ek: ristretto255::CompressedRistretto, compressed_old_R: &vector<ristretto255::CompressedRistretto>, compressed_new_R: &vector<ristretto255::CompressedRistretto>): sigma_protocol_statement::Statement<sigma_protocol_key_rotation::KeyRotation>
Implementation
public(friend) fun new_key_rotation_statement(
    compressed_ek: CompressedRistretto,
    compressed_new_ek: CompressedRistretto,
    compressed_old_R: &vector<CompressedRistretto>,
    compressed_new_R: &vector<CompressedRistretto>,
): Statement<KeyRotation> {
    let err = error::internal(E_STATEMENT_BUILDER_INCONSISTENCY);
    let b = new_builder();
    assert!(b.add_point(get_encryption_key_basepoint_compressed()) == IDX_H, err);                  // H
    assert!(b.add_point(compressed_ek) == IDX_EK, err);                                                // ek
    assert!(b.add_point(compressed_new_ek) == IDX_EK_NEW, err);                                        // new_ek
    assert!(b.add_points(compressed_old_R) == START_IDX_OLD_R, err);                                   // old_R
    assert!(b.add_points(compressed_new_R) == START_IDX_OLD_R + get_num_available_chunks(), err);      // new_R
    let stmt = b.build();
    assert_key_rotation_statement_is_well_formed(&stmt);
    stmt
}

Function psi

The homomorphism $\psi$ for the key rotation relation.

Given witness $(dk, \delta, \delta_{inv})$, outputs:

[
dk * ek,           // should equal H
delta * ek,        // should equal new_ek
delta_inv * new_ek, // should equal ek
delta * old_R_i,   // should equal new_R_i, for i in [1..num_chunks]
]
fun psi(_stmt: &sigma_protocol_statement::Statement<sigma_protocol_key_rotation::KeyRotation>, w: &sigma_protocol_witness::Witness): sigma_protocol_representation_vec::RepresentationVec
Implementation
fun psi(_stmt: &Statement<KeyRotation>, w: &Witness): RepresentationVec {
    // WARNING: Crucial for security
    assert_key_rotation_statement_is_well_formed(_stmt);
    // WARNING: Crucial for security
    assert!(w.length() == 3, e_wrong_witness_len());

    let dk = *w.get(IDX_DK);
    let delta = *w.get(IDX_DELTA);
    let delta_inv = *w.get(IDX_DELTA_INV);

    // Build the representation vector
    let reprs = vector[
        // dk * ek
        repr_scaled(IDX_EK, dk),
        // delta * ek
        repr_scaled(IDX_EK, delta),
        // delta_inv * new_ek
        repr_scaled(IDX_EK_NEW, delta_inv),
    ];

    // delta * old_R_i for each chunk
    let ell = get_num_available_chunks();
    reprs.append(vector::range(0, ell).map(|i|
        repr_scaled(START_IDX_OLD_R + i, delta)
    ));

    // WARNING: Crucial for security
    assert!(reprs.length() == 3 + ell, e_wrong_output_len());
    new_representation_vec(reprs)
}

Function f

The transformation function $f$ for the key rotation relation.

Given the statement, outputs:

[
H,
new_ek,
ek,
new_R_i for i in [1..num_chunks]
]
fun f(_stmt: &sigma_protocol_statement::Statement<sigma_protocol_key_rotation::KeyRotation>): sigma_protocol_representation_vec::RepresentationVec
Implementation
fun f(_stmt: &Statement<KeyRotation>): RepresentationVec {
    // WARNING: We do not re-assert the stmt is well-formed anymore here, since wherever the transformation function
    // is called, so is the homomorphism, so the check will be done.

    let ell = get_num_available_chunks();
    let idx_r_new_start = get_start_idx_for_new_R();

    let reprs = vector[
        // H
        repr_point(IDX_H),
        // new_ek
        repr_point(IDX_EK_NEW),
        // ek
        repr_point(IDX_EK),
    ];

    // new_R_i for each chunk
    reprs.append(vector::range(0, ell).map(|i|
        repr_point(idx_r_new_start + i)
    ));

    // Note: Not needed for security, since a mismatched f(X) length will be caught in the verifier. But good practice
    // for catching mistakes *early* when implementing your f(X).
    assert!(reprs.length() == 3 + ell, e_wrong_output_len());
    new_representation_vec(reprs)
}

Function assert_verifies

Asserts that a key rotation proof verifies

public(friend) fun assert_verifies(self: &sigma_protocol_key_rotation::KeyRotationSession, stmt: &sigma_protocol_statement::Statement<sigma_protocol_key_rotation::KeyRotation>, proof: &sigma_protocol_proof::Proof)
Implementation
public(friend) fun assert_verifies(self: &KeyRotationSession, stmt: &Statement<KeyRotation>, proof: &Proof) {
    assert_key_rotation_statement_is_well_formed(stmt);

    let success = sigma_protocol::verify(
        new_domain_separator(@aptos_framework, chain_id::get(), PROTOCOL_ID, bcs::to_bytes(self)),
        |_X, w| psi(_X, w),
        |_X| f(_X),
        stmt,
        proof
    );

    assert!(success, error::invalid_argument(E_INVALID_KEY_ROTATION_PROOF));
}