跳转到内容

sigma_protocol_withdraw - [devnet]

此内容尚不支持你的语言。

The withdrawal NP relation (Rwithdraw\mathcal{R}^{-}_\mathsf{withdraw})

\def\old#1{{\color{red}{\dot{#1}}}}\def\new#1{{\color{teal}{\widetilde{#1}}}}\def\opt#1{{\color{orange}{\boldsymbol{[}}} #1 {\color{orange}{\boldsymbol{]}}}}

A ZKPoK of a correct balance update when publicly withdrawing amount vv from an old available balance. Also used for normalization (where v=0v = 0).

Notation

  • \oldx\old{x} denotes a stale/old ciphertext component; \newx\new{x} denotes a fresh/new one.
  • \opt\opt{\cdot} denotes components present only when an auditor is set. Auditor components are placed at the end of the statement so that the common prefix is identical in both cases. psi/f receive auditor presence via an explicit has_auditor flag.
  • x,y=ixiyi\langle \mathbf{x}, \mathbf{y} \rangle = \sum_i x_i \cdot y_i denotes the inner product.
  • B=(B0,B1,)\mathbf{B} = (B^0, B^1, \ldots) where B=216B = 2^{16} is the positional weight vector for chunk encoding.
  • \ell: number of available balance chunks.

The relation

\mathcal{R}^{-}_\mathsf{withdraw}\left(\begin{array}{l} G, H, \mathsf{ek}, \old{\mathbf{P}}, \old{\mathbf{R}}, \new{\mathbf{P}}, \new{\mathbf{R}}, \opt{\mathsf{ek}^\mathsf{eff}, \new{\mathbf{R}}^\mathsf{eff}} \textbf{;}\\ \mathsf{dk}, \new{\mathbf{a}}, \new{\mathbf{r}} \textbf{;}\; v \end{array}\right) = 1 \Leftrightarrow \left\{\begin{array}{r@{\,\,}l@{\quad}l} H &= \mathsf{dk} \cdot \mathsf{ek}\\ \new{P}_i &= \new{a}_i \cdot G + \new{r}_i \cdot H, &\forall i \in [\ell]\\ \new{R}_i &= \new{r}_i \cdot \mathsf{ek}, &\forall i \in [\ell]\\ \opt{\new{R}^\mathsf{eff}_i} &\opt{= \new{r}_i \cdot \mathsf{ek}^\mathsf{eff},} &\opt{\forall i \in [\ell]}\\ \langle \mathbf{B}, \old{\mathbf{P}} \rangle - v \cdot G &= \mathsf{dk} \cdot \langle \mathbf{B}, \old{\mathbf{R}} \rangle + \langle \mathbf{B}, \new{\mathbf{a}} \rangle \cdot G\\ \end{array}\right.

Note: vv is a public scalar in the statement (not in the witness). It appears in ff but not in ψ\psi.

Homomorphism

This can be framed as a homomorphism check ψ(w)=f(X)\psi(\mathbf{w}) = f(\mathbf{X}) where w=(dk,\newa,\newr)\mathbf{w} = (\mathsf{dk}, \new{\mathbf{a}}, \new{\mathbf{r}}) is the witness and X\mathbf{X} is the statement (including public scalar vv).

  1. The homomorphism ψ\psi is:
ψ(dk,\newa,\newr)=(dkek\newaiG+\newriH,i[]\newriek,i[]\opt\newriekeff,  i[]dkB,\oldR+B,\newaG)\psi(\mathsf{dk}, \new{\mathbf{a}}, \new{\mathbf{r}}) = \begin{pmatrix} \mathsf{dk} \cdot \mathsf{ek}\\ \new{a}_i \cdot G + \new{r}_i \cdot H, &\forall i \in [\ell]\\ \new{r}_i \cdot \mathsf{ek}, &\forall i \in [\ell]\\ \opt{\new{r}_i \cdot \mathsf{ek}^\mathsf{eff}, \;\forall i \in [\ell]}\\ \mathsf{dk} \cdot \langle \mathbf{B}, \old{\mathbf{R}} \rangle + \langle \mathbf{B}, \new{\mathbf{a}} \rangle \cdot G\\ \end{pmatrix}
  1. The transformation function ff is:
f(X)=(H\newPi,i[]\newRi,i[]\opt\newRieff,  i[]B,\oldPvG)f(\mathbf{X}) = \begin{pmatrix} H\\ \new{P}_i, &\forall i \in [\ell]\\ \new{R}_i, &\forall i \in [\ell]\\ \opt{\new{R}^\mathsf{eff}_i, \;\forall i \in [\ell]}\\ \langle \mathbf{B}, \old{\mathbf{P}} \rangle - v \cdot G\\ \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::option;
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;

Constants

The number of auditor R components does not match the expected auditor count.

const E_AUDITOR_COUNT_MISMATCH: u64 = 6;

new_a[0..ℓ-1] starts at index 1. new_r[0..ℓ-1] starts at 1 + ℓ. The withdrawal proof was invalid.

const E_INVALID_PROOF: u64 = 5;

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

const E_STATEMENT_BUILDER_INCONSISTENCY: u64 = 7;

An error occurred in one of our tests.

const E_TEST_INTERNAL: u64 = 1000;

Index of dk\mathsf{dk} in the witness.

const IDX_DK: u64 = 0;

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

const IDX_EK: u64 = 2;

Index of GG (the Ristretto255 basepoint) in the statement’s points.

const IDX_G: u64 = 0;

Index of HH (the encryption key basepoint) in the statement’s points.

const IDX_H: u64 = 1;

Index of vv (the withdrawn value) in the statement’s scalars.

const IDX_V: u64 = 0;

old_P values start at index 3. old_R starts at 3 + ℓ. new_P at 3 + 2ℓ. new_R at 3 + 3ℓ. If auditor present: ek_aud at 3 + 4ℓ, then new_R_aud at 3 + 4ℓ + 1.

const START_IDX_OLD_P: u64 = 3;

Protocol ID for withdrawal proofs (also used for normalization, which is withdrawal with v = 0)

const WITHDRAWAL_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, 87, 105, 116, 104, 100, 114, 97, 119, 97, 108, 86, 49];

Structs

Withdrawal

Phantom marker type for withdrawal statements.

struct Withdrawal has drop
Fields
dummy_field: bool

WithdrawSession

Used for domain separation in the Fiat-Shamir transform.

struct WithdrawSession has drop
Fields
sender: address
asset_type: object::Object<fungible_asset::Metadata>
num_chunks: u64
has_auditor: bool

Functions

get_num_chunks

Returns the fixed number of balance chunks ℓ (= AVAILABLE_BALANCE_CHUNKS).

fun get_num_chunks(): u64
Implementation
inline fun get_num_chunks(): u64 {
get_num_available_chunks()
}

assert_withdraw_statement_is_well_formed

Validates that the statement has the correct structure for the given auditor flag.

fun assert_withdraw_statement_is_well_formed(stmt: &sigma_protocol_statement::Statement<sigma_protocol_withdraw::Withdrawal>, has_auditor: bool)
Implementation
fun assert_withdraw_statement_is_well_formed(stmt: &Statement<Withdrawal>, has_auditor: bool) {
let ell = get_num_chunks();
let expected = 3 + 4 * ell + if (has_auditor) { 1 + ell } else { 0 };
assert!(stmt.get_points().length() == expected,e_wrong_num_points());
// i.e., the transferred amount v
assert!(stmt.get_scalars().length() == 1, e_wrong_num_scalars());
}

new_session

public(friend) fun new_session(sender: &signer, asset_type: object::Object<fungible_asset::Metadata>, has_auditor: bool): sigma_protocol_withdraw::WithdrawSession
Implementation
public(friend) fun new_session(sender: &signer, asset_type: Object<Metadata>, has_auditor: bool): WithdrawSession {
WithdrawSession {
sender: signer::address_of(sender),
asset_type,
num_chunks: get_num_chunks(),
has_auditor,
}
}

new_withdrawal_statement

Creates a withdrawal statement, optionally including auditor components.

Points (auditorless): [ G, H, ek, old_P[0..ℓ-1], old_R[0..ℓ-1], new_P[0..ℓ-1], new_R[0..ℓ-1] ] Points (w/ auditor): [ ---------------------------- as above ------------------------------, ek_aud, new_R_aud] Scalars: [ v ]

For the auditorless case, pass option::none() for compressed_ek_aud and ensure new_balance / compressed_new_balance have empty R_aud.

public(friend) fun new_withdrawal_statement(compressed_ek: ristretto255::CompressedRistretto, compressed_old_balance: &confidential_balance::CompressedBalance<confidential_balance::Available>, compressed_new_balance: &confidential_balance::CompressedBalance<confidential_balance::Available>, compressed_ek_aud: &option::Option<ristretto255::CompressedRistretto>, v: ristretto255::Scalar): (sigma_protocol_statement::Statement<sigma_protocol_withdraw::Withdrawal>, vector<ristretto255::RistrettoPoint>)
Implementation
public(friend) fun new_withdrawal_statement(
compressed_ek: CompressedRistretto,
compressed_old_balance: &CompressedBalance<Available>,
compressed_new_balance: &CompressedBalance<Available>,
compressed_ek_aud: &Option<CompressedRistretto>,
v: Scalar,
): (Statement<Withdrawal>, vector<RistrettoPoint>) {
assert!(
compressed_new_balance.get_compressed_R_aud().length() == if (compressed_ek_aud.is_some()) { get_num_chunks() } else { 0 },
error::invalid_argument(E_AUDITOR_COUNT_MISMATCH)
);
let ell = get_num_chunks();
let err = error::internal(E_STATEMENT_BUILDER_INCONSISTENCY);
let b = new_builder();
assert!(b.add_point(ristretto255::basepoint_compressed()) == IDX_G, err); // G
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_points(compressed_old_balance.get_compressed_P()) == START_IDX_OLD_P, err); // old_P
assert!(b.add_points(compressed_old_balance.get_compressed_R()) == START_IDX_OLD_P + ell, err); // old_R
let (idx, new_P) = b.add_points_cloned(compressed_new_balance.get_compressed_P()); // new_P
assert!(idx == START_IDX_OLD_P + 2 * ell, err);
assert!(b.add_points(compressed_new_balance.get_compressed_R()) == START_IDX_OLD_P + 3 * ell, err); // new_R
if (compressed_ek_aud.is_some()) {
assert!(b.add_point(*compressed_ek_aud.borrow()) == START_IDX_OLD_P + 4 * ell, err); // ek_aud
assert!(b.add_points(compressed_new_balance.get_compressed_R_aud()) == START_IDX_OLD_P + 4 * ell + 1, err); // new_R_aud
};
assert!(b.add_scalar(v) == IDX_V, err);
let stmt = b.build();
assert_withdraw_statement_is_well_formed(&stmt, compressed_ek_aud.is_some());
(stmt, new_P)
}

psi

The homomorphism ψ\psi for the withdrawal relation.

Here, B = (B^0, B^1, …, B^{ℓ-1}) with B = 2^16 is the chunk weight vector (see module doc).

Outputs (auditorless, m = 2 + 2ℓ):

  1. dk · ek
  2. new_a[i] · G + new_r[i] · H, for i ∈ [1..ℓ]
  3. new_r[i] · ek, for i ∈ [1..ℓ]
  4. dk · ⟨B, old_R⟩ + ⟨B, new_a⟩ · G

With auditor (m = 2 + 3ℓ), inserts between 3 and 4: 3b. new_r[i] · ek_aud, for i ∈ [1..ℓ]

fun psi(stmt: &sigma_protocol_statement::Statement<sigma_protocol_withdraw::Withdrawal>, w: &sigma_protocol_witness::Witness, has_auditor: bool): sigma_protocol_representation_vec::RepresentationVec
Implementation
fun psi(stmt: &Statement<Withdrawal>, w: &Witness, has_auditor: bool): RepresentationVec {
// WARNING: Crucial for security
assert_withdraw_statement_is_well_formed(stmt, has_auditor);
let ell = get_num_chunks();
let b_powers = get_b_powers(ell);
// WARNING: Crucial for security
let expected_witness_len = 1 + 2 * ell;
assert!(w.length() == expected_witness_len, e_wrong_witness_len());
let dk = *w.get(IDX_DK);
let reprs = vector[];
// 1. dk · ek
reprs.push_back(repr_scaled(IDX_EK, dk));
// 2. new_a[i] · G + new_r[i] · H, for i ∈ [1..ℓ]
vector::range(0, ell).for_each(|i| {
let new_a_i = *w.get(1 + i);
let new_r_i = *w.get(1 + ell + i);
reprs.push_back(new_representation(vector[IDX_G, IDX_H], vector[new_a_i, new_r_i]));
});
// 3. new_r[i] · ek, for i ∈ [1..ℓ]
vector::range(0, ell).for_each(|i| {
let new_r_i = *w.get(1 + ell + i);
reprs.push_back(repr_scaled(IDX_EK, new_r_i));
});
// 3b. (auditor only) new_r[i] · ek_aud, for i ∈ [1..ℓ]
if (has_auditor) {
let idx_ek_aud = START_IDX_OLD_P + 4 * ell;
vector::range(0, ell).for_each(|i| {
let new_r_i = *w.get(1 + ell + i);
reprs.push_back(repr_scaled(idx_ek_aud, new_r_i));
});
};
// 4. Balance equation: dk · ⟨B, old_R⟩ + ⟨B, new_a⟩ · G
let idx_old_R_start = START_IDX_OLD_P + ell;
let point_idxs = vector[];
let scalars = vector[];
// dk · B^i · old_R[i]
vector::range(0, ell).for_each(|i| {
point_idxs.push_back(idx_old_R_start + i);
scalars.push_back(dk.scalar_mul(&b_powers[i]));
});
// new_a[i] · B^i · G
vector::range(0, ell).for_each(|i| {
let new_a_i = *w.get(1 + i);
point_idxs.push_back(IDX_G);
scalars.push_back(new_a_i.scalar_mul(&b_powers[i]));
});
reprs.push_back(new_representation(point_idxs, scalars));
// WARNING: Crucial for security
assert!(reprs.length() == expected_output_len(ell, has_auditor), e_wrong_output_len());
new_representation_vec(reprs)
}

expected_output_len

fun expected_output_len(ell: u64, has_auditor: bool): u64
Implementation
fun expected_output_len(ell: u64, has_auditor: bool): u64 {
if (has_auditor) { 2 + 3 * ell } else { 2 + 2 * ell }
}

f

The transformation function ff for the withdrawal relation.

Outputs (auditorless, m = 2 + 2ℓ):

  1. H
  2. new_P[i], for i ∈ [1..ℓ]
  3. new_R[i], for i ∈ [1..ℓ]
  4. ⟨B, old_P⟩ − v · G

With auditor (m = 2 + 3ℓ), inserts between 3 and 4: 3b. new_R_aud[i], for i ∈ [1..ℓ]

fun f(stmt: &sigma_protocol_statement::Statement<sigma_protocol_withdraw::Withdrawal>, has_auditor: bool): sigma_protocol_representation_vec::RepresentationVec
Implementation
fun f(stmt: &Statement<Withdrawal>, has_auditor: bool): RepresentationVec {
let ell = get_num_chunks();
let b_powers = get_b_powers(ell);
let v = stmt.get_scalars()[0];
let idx_new_P_start = START_IDX_OLD_P + 2 * ell;
let idx_new_R_start = START_IDX_OLD_P + 3 * ell;
let reprs = vector[];
// 1. H
reprs.push_back(repr_point(IDX_H));
// 2. new_P[i]
vector::range(0, ell).for_each(|i| {
reprs.push_back(repr_point(idx_new_P_start + i));
});
// 3. new_R[i]
vector::range(0, ell).for_each(|i| {
reprs.push_back(repr_point(idx_new_R_start + i));
});
// 3b. (auditor only) new_R_aud[i]
if (has_auditor) {
let idx_new_R_aud_start = START_IDX_OLD_P + 4 * ell + 1; // +1 for ek_aud
vector::range(0, ell).for_each(|i| {
reprs.push_back(repr_point(idx_new_R_aud_start + i));
});
};
// 4. ⟨B, old_P⟩ − v · G
let point_idxs = vector[];
let scalars = vector[];
vector::range(0, ell).for_each(|i| {
point_idxs.push_back(START_IDX_OLD_P + i);
scalars.push_back(b_powers[i]);
});
point_idxs.push_back(IDX_G);
scalars.push_back(v.scalar_neg());
reprs.push_back(new_representation(point_idxs, scalars));
// 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() == expected_output_len(ell, has_auditor), e_wrong_output_len());
new_representation_vec(reprs)
}

assert_verifies

Asserts that a withdrawal proof verifies.

public(friend) fun assert_verifies(self: &sigma_protocol_withdraw::WithdrawSession, stmt: &sigma_protocol_statement::Statement<sigma_protocol_withdraw::Withdrawal>, proof: &sigma_protocol_proof::Proof)
Implementation
public(friend) fun assert_verifies(self: &WithdrawSession, stmt: &Statement<Withdrawal>, proof: &Proof) {
assert_withdraw_statement_is_well_formed(stmt, self.has_auditor);
let success = sigma_protocol::verify(
new_domain_separator(@aptos_framework, chain_id::get(), WITHDRAWAL_PROTOCOL_ID, bcs::to_bytes(self)),
|_X, w| psi(_X, w, self.has_auditor),
|_X| f(_X, self.has_auditor),
stmt,
proof
);
assert!(success, error::invalid_argument(E_INVALID_PROOF));
}