Skip to content

sigma_protocol_fiat_shamir - [devnet]

use 0x1::aptos_hash;
use 0x1::bcs;
use 0x1::option;
use 0x1::ristretto255;
use 0x1::sigma_protocol_statement;
use 0x1::string;
use 0x1::type_info;
use 0x1::vector;

Enum DomainSeparator

A domain separator prevents replay attacks in Σ\Sigma protocols and consists of 5 things.

  1. The contract address (defense in depth: binds the proof to a specific deployed contract)

  2. The chain ID (defense in depth: binds the proof to a specific Aptos network)

  3. A protocol identifier, which is typically split up into two things:

  • A higher-level protocol: “Confidential Assets v1 on Aptos”
  • A lower-level relation identifier (e.g., “PedEq”, “Schnorr”, “DLEQ”, etc.)
  1. Statement (i.e., the public statement in the NP relation being proved)
  • This is captured implicitly in our prove and verify functions ==> it is not part of this struct.
  1. Session identifier
  • Chosen by user
  • specifies the “context” in which this proof is valid
  • e.g., “Alice (0x1) is paying Bob (0x2) at time t
  • together with the protocol identifier, prevents replay attacks across the same protocol or different protocols

Note: The session identifier can be tricky, since in some settings the “session” accumulates implicitly in the statement being proven. For confidential assets, it does not AFAICT: the “session” is represented at least by the confidential balances of the users & their addresses.

enum DomainSeparator has copy, drop
Variants
V1
Fields
contract_address: address
chain_id: u8
protocol_id: vector<u8>
session_id: vector<u8>

Constants

One of our internal invariants was broken. There is likely a logical error in the code.

const E_INTERNAL_INVARIANT_FAILED: u64 = 2;

The length of the A field in Proof should NOT be zero

const E_PROOF_COMMITMENT_EMPTY: u64 = 1;

Structs

FiatShamirInputs

Unfortunately, we cannot directly use the Statement struct here because its vector<RistrettoPoint> will not serialize correctly via bcs::to_bytes, since a RistrettoPoint stores a Move VM “handle” rather than an actual point.

struct FiatShamirInputs has drop
Fields
dst: sigma_protocol_fiat_shamir::DomainSeparator
type_name: string::String
The fully-qualified type name of the phantom marker type P in Statement<P>. E.g., "0x7::sigma_protocol_registration::Registration". This binds the Fiat-Shamir challenge to the specific protocol type for defense in depth.
k: u64
stmt_X: vector<ristretto255::CompressedRistretto>
stmt_x: vector<ristretto255::Scalar>
proof_A: vector<ristretto255::CompressedRistretto>

Functions

new_domain_separator

public(friend) fun new_domain_separator(contract_address: address, chain_id: u8, protocol_id: vector<u8>, session_id: vector<u8>): sigma_protocol_fiat_shamir::DomainSeparator
Implementation
public(friend) fun new_domain_separator(contract_address: address, chain_id: u8, protocol_id: vector<u8>, session_id: vector<u8>): DomainSeparator {
DomainSeparator::V1 {
contract_address,
chain_id,
protocol_id,
session_id
}
}

fiat_shamir

Returns the Sigma protocol challenge ee and 1,β,β2,,βm11,\beta,\beta^2,\ldots, \beta^{m-1}

public(friend) fun fiat_shamir<P>(dst: sigma_protocol_fiat_shamir::DomainSeparator, stmt: &sigma_protocol_statement::Statement<P>, compressed_A: &vector<ristretto255::CompressedRistretto>, k: u64): (ristretto255::Scalar, vector<ristretto255::Scalar>)
Implementation
public(friend) fun fiat_shamir<P>(
dst: DomainSeparator,
stmt: &Statement<P>,
compressed_A: &vector<CompressedRistretto>,
k: u64): (Scalar, vector<Scalar>)
{
let m = compressed_A.length();
assert!(m != 0, error::invalid_argument(E_PROOF_COMMITMENT_EMPTY));
// We will hash an application-specific domain separator and the (full) public statement,
// which will include any public parameters like group generators $G$, $H$.
// Note: A hardcodes $m$, the statement hardcodes $n_1$ and $n_2$, and $k$ is specified manually!
let fs_inputs = FiatShamirInputs {
dst,
type_name: type_info::type_name<P>(),
k,
stmt_X: *stmt.get_compressed_points(),
stmt_x: *stmt.get_scalars(),
proof_A: *compressed_A
};
let seed = sha2_512_value(&fs_inputs);
// Note:A more principled `Merlin` / `spongefish`-like approach would have been preferred, but... more code.
//
// e = SHA2-512(SHA2-512(fs_inputs.to_bcs_bytes()) || 0x00)
seed.push_back(0u8);
assert!(*seed.last() == 0, error::internal(E_INTERNAL_INVARIANT_FAILED));
let e_hash = sha2_512(seed);
// beta = SHA2-512(SHA2-512(fs_inputs.to_bcs_bytes()) || 0x01)
*seed.last_mut() += 1;
assert!(*seed.last() == 1, error::internal(E_INTERNAL_INVARIANT_FAILED));
let beta_hash = sha2_512(seed);
let e = new_scalar_uniform_from_64_bytes(e_hash).extract();
let beta = new_scalar_uniform_from_64_bytes(beta_hash).extract();
let betas = vector[];
let prev_beta = scalar_one();
betas.push_back(prev_beta);
for (_i in 1..m) {
prev_beta = prev_beta.scalar_mul(&beta);
betas.push_back(prev_beta);
};
// This will only fail when our logic above for generating the `\beta_i`'s is broken
assert!(betas.length() == m, error::internal(E_INTERNAL_INVARIANT_FAILED));
(e, betas)
}