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.
- 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} $$
- 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} $$
- The key rotation NP relation ($\mathcal{R}_\mathsf{keyrot}$)
- Struct
KeyRotation - Struct
KeyRotationSession - Constants
- Function
get_start_idx_for_new_R - Function
assert_key_rotation_statement_is_well_formed - Function
new_session - Function
new_key_rotation_statement - Function
psi - Function
f - Function
assert_verifies
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));
}