sigma_protocol_transfer - [devnet]
此内容尚不支持你的语言。
The transfer NP relation ()
A ZKPoK of a correct confidential transfer from sender to recipient. This is a composition of (the sender’s balance update with SECRET amount ) and (the transfer amount encrypted identically under all parties’ keys).
Notation
- denotes a stale/old ciphertext component; denotes a fresh/new one.
- denotes components present only when has_effective_auditor is true.
- denotes the inner product.
- where is the positional weight vector for chunk encoding.
- : number of available balance chunks; : number of transfer (pending balance) chunks.
- : number of voluntary auditors ().
- The effective auditor (if present) sees the sender’s new balance AND the transfer amount. Extra auditors see only the transfer amount.
The relation
\mathcal{R}^{-}_\mathsf{txfer}\left(\begin{array}{l} G, H, \mathsf{ek}^\mathsf{sid}, \mathsf{ek}^\mathsf{rid}, \old{\mathbf{P}}, \old{\mathbf{R}}, \new{\mathbf{P}}, \new{\mathbf{R}}, \mathbf{P}, \mathbf{R}^\mathsf{sid}, \mathbf{R}^\mathsf{rid},\\ \opt{\mathsf{ek}^\mathsf{eff}, \new{\mathbf{R}}^\mathsf{eff}, \mathbf{R}^\mathsf{eff}},\; (\mathsf{ek}^\mathsf{ex}_i, \mathbf{R}^\mathsf{ex}_i)_{i \in [T]} \textbf{;}\\ \mathsf{dk}, \new{\mathbf{a}}, \new{\mathbf{r}}, \mathbf{v}, \mathbf{r} \end{array}\right) = 1 \Leftrightarrow \left\{\begin{array}{r@{\,\,}l@{\quad}l} H &= \mathsf{dk} \cdot \mathsf{ek}^\mathsf{sid}\\ \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}^\mathsf{sid}, &\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 &= \mathsf{dk} \cdot \langle \mathbf{B}, \old{\mathbf{R}} \rangle + (\langle \mathbf{B}, \new{\mathbf{a}} \rangle + \langle \mathbf{B}, \mathbf{v} \rangle) \cdot G\\ P_j &= v_j \cdot G + r_j \cdot H, &\forall j \in [n]\\ R^\mathsf{sid}_j &= r_j \cdot \mathsf{ek}^\mathsf{sid}, &\forall j \in [n]\\ R^\mathsf{rid}_j &= r_j \cdot \mathsf{ek}^\mathsf{rid}, &\forall j \in [n]\\ \opt{R^\mathsf{eff}_j} &\opt{= r_j \cdot \mathsf{ek}^\mathsf{eff},} &\opt{\forall j \in [n]}\\ R^\mathsf{ex}_{i,j} &= r_j \cdot \mathsf{ek}^\mathsf{ex}_i, &\forall j \in [n],\; \forall i \in [T]\\ \end{array}\right.Homomorphism
This can be framed as a homomorphism check where is the witness and is the statement.
- The homomorphism is:
- The transformation function is:
use 0x1::bcs;use 0x1::chain_id;use 0x1::confidential_amount;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;The homomorphism or transformation function implementation is not inserting points at the expected positions.
const E_STATEMENT_BUILDER_INCONSISTENCY: u64 = 7;Index of dk (sender’s decryption key).
const IDX_DK: u64 = 0;Index of (the Ristretto255 basepoint).
const IDX_G: u64 = 0;Index of (the encryption key basepoint).
const IDX_H: u64 = 1;old_P starts at index 4. Layout: old_P[1..ℓ], old_R[1..ℓ], new_P[1..ℓ], new_R[1..ℓ], amount_P[1..n], amount_R_sender[1..n], amount_R_recip[1..n] With effective auditor: …, ek_eff_aud, new_R_eff_aud[1..ℓ], amount_R_eff_aud[1..n] For each voluntary auditor t: …, ek_volun_auds[t], amount_R_volun_auds[t][1..n]
const START_IDX_OLD_P: u64 = 4;new_a[0..ℓ-1] at 1..ℓ. new_r[0..ℓ-1] at 1+ℓ..2ℓ. v[0..n-1] at 1+2ℓ..1+2ℓ+n-1. r[0..n-1] at 1+2ℓ+n..1+2ℓ+2n-1. The transfer proof was invalid.
const E_INVALID_TRANSFER_PROOF: u64 = 5;Index of (the recipient’s encryption key).
const IDX_EK_RECIP: u64 = 3;Index of (the sender’s encryption key).
const IDX_EK_SENDER: u64 = 2;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, 84, 114, 97, 110, 115, 102, 101, 114, 86, 49];Structs
Transfer
Phantom marker type for transfer statements.
struct Transfer has dropFields
-
dummy_field: bool
TransferSession
Used for domain separation in the Fiat-Shamir transform.
struct TransferSession has dropFields
-
sender: address -
recipient: address -
asset_type: object::Object<fungible_asset::Metadata> -
num_avail_chunks: u64 -
num_transfer_chunks: u64 -
has_effective_auditor: bool -
num_volun_auditors: u64
Functions
get_ell
Returns the fixed number of available balance chunks ℓ.
fun get_ell(): u64Implementation
inline fun get_ell(): u64 { get_num_available_chunks() }get_n
Returns the fixed number of transfer (pending) balance chunks n.
fun get_n(): u64Implementation
inline fun get_n(): u64 { get_num_pending_chunks() }assert_transfer_statement_is_well_formed
Validates the statement structure.
Expected point count: 4 + 4ℓ + 3n + (has_eff ? 1+ℓ+n : 0) + num_volun*(1+n)
fun assert_transfer_statement_is_well_formed(stmt: &sigma_protocol_statement::Statement<sigma_protocol_transfer::Transfer>, has_effective_auditor: bool, num_volun_auditors: u64)Implementation
fun assert_transfer_statement_is_well_formed( stmt: &Statement<Transfer>, has_effective_auditor: bool, num_volun_auditors: u64,) { let ell = get_ell(); let n = get_n(); let num_points = stmt.get_points().length();
let expected_num_points = 4 + 4 * ell + 3 * n + if (has_effective_auditor) { 1 + ell + n } else { 0 } + num_volun_auditors * (1 + n); assert!(num_points == expected_num_points, e_wrong_num_points()); assert!(stmt.get_scalars().length() == 0, e_wrong_num_scalars());}new_session
public(friend) fun new_session(sender: &signer, recipient: address, asset_type: object::Object<fungible_asset::Metadata>, has_effective_auditor: bool, num_volun_auditors: u64): sigma_protocol_transfer::TransferSessionImplementation
public(friend) fun new_session( sender: &signer, recipient: address, asset_type: Object<Metadata>, has_effective_auditor: bool, num_volun_auditors: u64,): TransferSession { TransferSession { sender: signer::address_of(sender), recipient, asset_type, num_avail_chunks: get_num_available_chunks(), num_transfer_chunks: get_num_pending_chunks(), has_effective_auditor, num_volun_auditors, }}new_transfer_statement
Creates a transfer statement, optionally including effective and voluntary auditor components.
Points (base): [G, H, ek_sender, ek_recip, old_P[1..ℓ], old_R[1..ℓ], new_P[1..ℓ], new_R[1..ℓ], amount_P[1..n], amount_R_sender[1..n], amount_R_recip[1..n]] If effective: + [ek_eff_aud, new_R_eff_aud[1..ℓ], amount_R_eff_aud[1..n]] For each voluntary auditor t: + [ek_volun_auds[t], amount_R_volun_auds[t][1..n]]
For no effective auditor, pass option::none() for compressed_ek_eff_aud
and ensure amount / new_balance have empty effective-auditor R components.
For no voluntary auditors, pass an empty vector for compressed_ek_volun_auds.
public(friend) fun new_transfer_statement(compressed_ek_sender: ristretto255::CompressedRistretto, compressed_ek_recip: ristretto255::CompressedRistretto, compressed_old_balance: &confidential_balance::CompressedBalance<confidential_balance::Available>, compressed_new_balance: &confidential_balance::CompressedBalance<confidential_balance::Available>, compressed_amount: &confidential_amount::CompressedAmount, compressed_ek_eff_aud: &option::Option<ristretto255::CompressedRistretto>, compressed_ek_volun_auds: &vector<ristretto255::CompressedRistretto>): (sigma_protocol_statement::Statement<sigma_protocol_transfer::Transfer>, vector<ristretto255::RistrettoPoint>, confidential_balance::Balance<confidential_balance::Pending>)Implementation
public(friend) fun new_transfer_statement( compressed_ek_sender: CompressedRistretto, compressed_ek_recip: CompressedRistretto, compressed_old_balance: &CompressedBalance<Available>, compressed_new_balance: &CompressedBalance<Available>, compressed_amount: &CompressedAmount, compressed_ek_eff_aud: &Option<CompressedRistretto>, compressed_ek_volun_auds: &vector<CompressedRistretto>,): (Statement<Transfer>, vector<RistrettoPoint>, Balance<Pending>) { let has_eff = compressed_ek_eff_aud.is_some(); let num_volun = compressed_ek_volun_auds.length();
// Validate auditor counts before expensive statement construction assert!( compressed_amount.num_volun_auditors_compressed() == num_volun, error::invalid_argument(E_AUDITOR_COUNT_MISMATCH) ); assert!( compressed_amount.has_effective_auditor_compressed() == has_eff, error::invalid_argument(E_AUDITOR_COUNT_MISMATCH) ); assert!( compressed_new_balance.get_compressed_R_aud().length() == if (has_eff) { get_num_available_chunks() } else { 0 }, error::invalid_argument(E_AUDITOR_COUNT_MISMATCH) ); assert!( compressed_amount.get_compressed_R_volun_auds().all(|r| r.length() == get_n()), error::invalid_argument(E_AUDITOR_COUNT_MISMATCH) );
let ell = get_ell(); let n = get_n(); let e = error::internal(E_STATEMENT_BUILDER_INCONSISTENCY);
let b = new_builder(); assert!(b.add_point(ristretto255::basepoint_compressed()) == IDX_G, e); // G assert!(b.add_point(confidential_balance::get_encryption_key_basepoint_compressed()) == IDX_H, e); // H assert!(b.add_point(compressed_ek_sender) == IDX_EK_SENDER, e); // ek_sender assert!(b.add_point(compressed_ek_recip) == IDX_EK_RECIP, e); // ek_recip assert!(b.add_points(compressed_old_balance.get_compressed_P()) == START_IDX_OLD_P, e); // old_P assert!(b.add_points(compressed_old_balance.get_compressed_R()) == START_IDX_OLD_P + ell, e); // old_R let (idx, new_balance_P) = b.add_points_cloned(compressed_new_balance.get_compressed_P()); // new_P assert!(idx == START_IDX_OLD_P + 2 * ell, e); assert!(b.add_points(compressed_new_balance.get_compressed_R()) == START_IDX_OLD_P + 3 * ell, e); // new_R let (idx, amount_P) = b.add_points_cloned(compressed_amount.get_compressed_P()); // amount_P assert!(idx == START_IDX_OLD_P + 4 * ell, e); assert!(b.add_points(compressed_amount.get_compressed_R_sender()) == START_IDX_OLD_P + 4 * ell + n, e); // amount_R_sender let (idx, recip_R) = b.add_points_cloned(compressed_amount.get_compressed_R_recip()); // amount_R_recip assert!(idx == START_IDX_OLD_P + 4 * ell + 2 * n, e);
// Effective auditor: ek, new_R[1..ℓ], amount_R[1..n] let idx_eff_start = START_IDX_OLD_P + 4 * ell + 3 * n; if (has_eff) { let ek_eff = *compressed_ek_eff_aud.borrow(); assert!(b.add_point(ek_eff) == idx_eff_start, e); // ek_eff_aud assert!(b.add_points(compressed_new_balance.get_compressed_R_aud()) == idx_eff_start + 1, e); // new_R_eff_aud assert!(b.add_points(compressed_amount.get_compressed_R_eff_aud()) == idx_eff_start + 1 + ell, e); // amount_R_eff_aud };
// Voluntary auditors: for each, append [ek_volun_aud, amount_R_volun_aud[1..n]] let idx_volun_start = idx_eff_start + if (has_eff) { 1 + ell + n } else { 0 }; let compressed_R_volun_auds = compressed_amount.get_compressed_R_volun_auds(); vector::range(0, num_volun).for_each(|i| { let expected_idx = idx_volun_start + i * (1 + n); let ek_volun = compressed_ek_volun_auds[i]; assert!(b.add_point(ek_volun) == expected_idx, e); // ek_volun_aud assert!(b.add_points(&compressed_R_volun_auds[i]) == expected_idx + 1, e); // amount_R_volun_aud });
let stmt = b.build(); assert_transfer_statement_is_well_formed(&stmt, has_eff, num_volun); let recip_pending = new_pending_from_p_and_r(amount_P, recip_R); (stmt, new_balance_P, recip_pending)}psi
The combined homomorphism for the transfer relation (see module-level doc for full definition).
fun psi(stmt: &sigma_protocol_statement::Statement<sigma_protocol_transfer::Transfer>, w: &sigma_protocol_witness::Witness, has_effective_auditor: bool, num_volun_auditors: u64): sigma_protocol_representation_vec::RepresentationVecImplementation
fun psi( stmt: &Statement<Transfer>, w: &Witness, has_effective_auditor: bool, num_volun_auditors: u64,): RepresentationVec { // WARNING: Crucial for security assert_transfer_statement_is_well_formed(stmt, has_effective_auditor, num_volun_auditors);
let ell = get_ell(); let n = get_n();
// WARNING: Crucial for security let expected_witness_len = 1 + 2 * ell + 2 * n; assert!(w.length() == expected_witness_len, e_wrong_witness_len());
let b_powers_ell = get_b_powers(ell); let b_powers_n = get_b_powers(n);
let dk = *w.get(IDX_DK);
let reprs = vector[];
// === R^veiled_withdraw part ===
// 1. dk · ek_sender reprs.push_back(repr_scaled(IDX_EK_SENDER, dk));
// 2. new_a[i] · G + new_r[i] · H 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_sender vector::range(0, ell).for_each(|i| { let new_r_i = *w.get(1 + ell + i); reprs.push_back(repr_scaled(IDX_EK_SENDER, new_r_i)); });
// 3b. (effective auditor only) new_r[i] · ek_eff_aud if (has_effective_auditor) { let idx_ek_eff_aud = START_IDX_OLD_P + 4 * ell + 3 * n; vector::range(0, ell).for_each(|i| { let new_r_i = *w.get(1 + ell + i); reprs.push_back(repr_scaled(idx_ek_eff_aud, new_r_i)); }); };
// 4. Balance equation: dk · ⟨B, old_R⟩ + (⟨B, new_a⟩ + ⟨B, v⟩) · 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_ell[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_ell[i])); });
// v[j] · B^j · G (the secret transfer amount) vector::range(0, n).for_each(|j| { let v_j = *w.get(1 + 2 * ell + j); point_idxs.push_back(IDX_G); scalars.push_back(v_j.scalar_mul(&b_powers_n[j])); });
reprs.push_back(new_representation(point_idxs, scalars));
// === R_eq part ===
let idx_v_start = 1 + 2 * ell; let idx_r_start = 1 + 2 * ell + n;
// 5. v[j] · G + r[j] · H vector::range(0, n).for_each(|j| { let v_j = *w.get(idx_v_start + j); let r_j = *w.get(idx_r_start + j); reprs.push_back(new_representation(vector[IDX_G, IDX_H], vector[v_j, r_j])); });
// 6. r[j] · ek_sender vector::range(0, n).for_each(|j| { let r_j = *w.get(idx_r_start + j); reprs.push_back(repr_scaled(IDX_EK_SENDER, r_j)); });
// 7. r[j] · ek_recip vector::range(0, n).for_each(|j| { let r_j = *w.get(idx_r_start + j); reprs.push_back(repr_scaled(IDX_EK_RECIP, r_j)); });
// 7b. (effective auditor only) r[j] · ek_eff_aud if (has_effective_auditor) { let idx_ek_eff_aud = START_IDX_OLD_P + 4 * ell + 3 * n; vector::range(0, n).for_each(|j| { let r_j = *w.get(idx_r_start + j); reprs.push_back(repr_scaled(idx_ek_eff_aud, r_j)); }); };
// 7c. (voluntary auditors) r[j] · ek_volun_aud_t, for each voluntary auditor t let idx_volun_auds_start = START_IDX_OLD_P + 4 * ell + 3 * n + if (has_effective_auditor) { 1 + ell + n } else { 0 }; vector::range(0, num_volun_auditors).for_each(|i| { let idx_ek_volun_aud = idx_volun_auds_start + i * (1 + n); vector::range(0, n).for_each(|j| { let r_j = *w.get(idx_r_start + j); reprs.push_back(repr_scaled(idx_ek_volun_aud, r_j)); }); });
// WARNING: Crucial for security assert!(reprs.length() == expected_output_len(ell, n, has_effective_auditor, num_volun_auditors), e_wrong_output_len()); new_representation_vec(reprs)}expected_output_len
fun expected_output_len(ell: u64, n: u64, has_effective_auditor: bool, num_volun_auditors: u64): u64Implementation
fun expected_output_len(ell: u64, n: u64, has_effective_auditor: bool, num_volun_auditors: u64): u64 { 2 + 2 * ell + 3 * n + (if (has_effective_auditor) { ell + n } else { 0 }) + num_volun_auditors * n}f
The transformation function for the transfer relation (see module-level doc for full definition).
fun f(_stmt: &sigma_protocol_statement::Statement<sigma_protocol_transfer::Transfer>, has_effective_auditor: bool, num_volun_auditors: u64): sigma_protocol_representation_vec::RepresentationVecImplementation
fun f( _stmt: &Statement<Transfer>, has_effective_auditor: bool, num_volun_auditors: u64,): RepresentationVec { let ell = get_ell(); let n = get_n(); let b_powers_ell = get_b_powers(ell);
let idx_new_P_start = START_IDX_OLD_P + 2 * ell; let idx_new_R_start = START_IDX_OLD_P + 3 * ell; let idx_amount_P_start = START_IDX_OLD_P + 4 * ell; let idx_amount_R_sender_start = idx_amount_P_start + n; let idx_amount_R_recip_start = idx_amount_R_sender_start + n;
let reprs = vector[];
// === R^veiled_withdraw part ===
// 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. (effective auditor only) new_R_eff_aud[i] if (has_effective_auditor) { let idx_ek_eff_aud = START_IDX_OLD_P + 4 * ell + 3 * n; let idx_new_R_eff_aud_start = idx_ek_eff_aud + 1; vector::range(0, ell).for_each(|i| { reprs.push_back(repr_point(idx_new_R_eff_aud_start + i)); }); };
// 4. ⟨B, old_P⟩ (no -v·G because v is secret) 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_ell[i]); }); reprs.push_back(new_representation(point_idxs, scalars));
// === R_eq part ===
// 5. amount_P[j] vector::range(0, n).for_each(|j| { reprs.push_back(repr_point(idx_amount_P_start + j)); });
// 6. amount_R_sender[j] vector::range(0, n).for_each(|j| { reprs.push_back(repr_point(idx_amount_R_sender_start + j)); });
// 7. amount_R_recip[j] vector::range(0, n).for_each(|j| { reprs.push_back(repr_point(idx_amount_R_recip_start + j)); });
// 7b. (effective auditor only) amount_R_eff_aud[j] if (has_effective_auditor) { let idx_amount_R_eff_aud_start = START_IDX_OLD_P + 4 * ell + 3 * n + 1 + ell; vector::range(0, n).for_each(|j| { reprs.push_back(repr_point(idx_amount_R_eff_aud_start + j)); }); };
// 7c. (voluntary auditors) amount_R_volun_aud_t[j], for each voluntary auditor t let idx_volun_auds_start = START_IDX_OLD_P + 4 * ell + 3 * n + if (has_effective_auditor) { 1 + ell + n } else { 0 }; vector::range(0, num_volun_auditors).for_each(|i| { let idx_amount_R_volun_aud_start = idx_volun_auds_start + i * (1 + n) + 1; vector::range(0, n).for_each(|j| { reprs.push_back(repr_point(idx_amount_R_volun_aud_start + j)); }); });
// 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, n, has_effective_auditor, num_volun_auditors), e_wrong_output_len()); new_representation_vec(reprs)}assert_verifies
Asserts that a transfer proof verifies.
public(friend) fun assert_verifies(self: &sigma_protocol_transfer::TransferSession, stmt: &sigma_protocol_statement::Statement<sigma_protocol_transfer::Transfer>, proof: &sigma_protocol_proof::Proof)Implementation
public(friend) fun assert_verifies( self: &TransferSession, stmt: &Statement<Transfer>, proof: &Proof,) { let has_eff = self.has_effective_auditor; let num_volun = self.num_volun_auditors; assert_transfer_statement_is_well_formed(stmt, has_eff, num_volun);
let success = sigma_protocol::verify( new_domain_separator(@aptos_framework, chain_id::get(), PROTOCOL_ID, bcs::to_bytes(self)), |_X, w| psi(_X, w, has_eff, num_volun), |_X| f(_X, has_eff, num_volun), stmt, proof );
assert!(success, error::invalid_argument(E_INVALID_TRANSFER_PROOF));}