sigma_protocol_withdraw - [devnet]
此内容尚不支持你的语言。
The withdrawal NP relation ()
A ZKPoK of a correct balance update when publicly withdrawing amount from an old available balance. Also used for normalization (where ).
Notation
- denotes a stale/old ciphertext component; denotes a fresh/new one.
- 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_auditorflag. - denotes the inner product.
- where is the positional weight vector for chunk encoding.
- : 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: is a public scalar in the statement (not in the witness). It appears in but not in .
Homomorphism
This can be framed as a homomorphism check where is the witness and is the statement (including public scalar ).
- The homomorphism is:
- The transformation function is:
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 in the witness.
const IDX_DK: u64 = 0;Index of (the sender’s encryption key) in the statement’s points.
const IDX_EK: u64 = 2;Index of (the Ristretto255 basepoint) in the statement’s points.
const IDX_G: u64 = 0;Index of (the encryption key basepoint) in the statement’s points.
const IDX_H: u64 = 1;Index of (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 dropFields
-
dummy_field: bool
WithdrawSession
Used for domain separation in the Fiat-Shamir transform.
struct WithdrawSession has dropFields
-
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(): u64Implementation
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::WithdrawSessionImplementation
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 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ℓ):
- dk · ek
- new_a[i] · G + new_r[i] · H, for i ∈ [1..ℓ]
- new_r[i] · ek, for i ∈ [1..ℓ]
- 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::RepresentationVecImplementation
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): u64Implementation
fun expected_output_len(ell: u64, has_auditor: bool): u64 { if (has_auditor) { 2 + 3 * ell } else { 2 + 2 * ell }}f
The transformation function for the withdrawal relation.
Outputs (auditorless, m = 2 + 2ℓ):
- H
- new_P[i], for i ∈ [1..ℓ]
- new_R[i], for i ∈ [1..ℓ]
- ⟨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::RepresentationVecImplementation
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));}