Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agent/flow-trace/00_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
→ Broadcast to aggregator

13. AGGREGATE Aggregator combines M+1 shares → plaintext
C7a/C7b proofs (proves reconstruction correct)
C7 proof (proves reconstruction correct)

14. COMPLETE publishPlaintextOutput() → rewards distributed
→ Each active committee member gets fee / N
Expand Down
26 changes: 16 additions & 10 deletions agent/flow-trace/04_DKG_AND_COMPUTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@ EnclaveSolReader decodes CiphertextOutputPublished event
│ │ → Circuit: ThresholdShareDecryption (C6)
│ │ → Proves decryption share was correctly computed from
│ │ sk_poly_sum, es_poly_sum, and ciphertext
│ │ → Fiat-Shamir transcript absorbs full `d` (all coefficients per CRT limb)
│ ├─ ZkActor generates proof via bb binary
│ ├─ Signs proof
│ └─ Publishes signed C6 proof
Expand Down Expand Up @@ -663,14 +664,13 @@ ThresholdPlaintextAggregator receives DecryptionshareCreated events
│ │ ├─ Dispatches ComputeRequest::zk(
│ │ │ ZkRequest::DecryptedSharesAggregation {...}
│ │ │ )
│ │ │ → Circuit: DecryptedSharesAggregation (C7a/b)
│ │ │ → Two variants: BN (bn254 field) and Mod (modular arithmetic)
│ │ │ → Circuit: DecryptedSharesAggregation (C7)
│ │ │ → Proves plaintext was correctly reconstructed from M+1 shares
│ │ ├─ ZkActor generates proof(s) via bb binary
│ │ ├─ Signs each proof variant
│ │ ├─ Signs each C7 proof (one per ciphertext index)
│ │ └─ Publishes AggregationProofSigned {
│ │ e3_id, party_id, signed_proof(C7)
│ │ } × (per variant)
│ │ }
│ │
│ └─ Publish PlaintextAggregated { e3_id, decrypted_output }
Expand Down Expand Up @@ -748,13 +748,19 @@ ThresholdPlaintextAggregator receives DecryptionshareCreated events
│ │ │ │ computed from all pk_shares │
├──────┼────────────────────────────┼───────────────────┼──────────────────────────────┤
│ C6 │ Threshold Share Decryption │ Decryption │ Decryption share correctly │
│ │ (T5) │ │ derived from sk + ciphertext │
│ │ (T5) │ │ derived from sk + ciphertext;│
│ │ │ │ public output: commitment to │
│ │ │ │ first MAX_MSG_NON_ZERO_COEFFS│
│ │ │ │ coeffs of d per CRT limb │
├──────┼────────────────────────────┼───────────────────┼──────────────────────────────┤
│ C7a │ Decrypted Shares Agg. (BN) │ Final Aggregation │ Plaintext correctly │
│ │ │ │ reconstructed (bn254 field) │
├──────┼────────────────────────────┼───────────────────┼──────────────────────────────┤
│ C7b │ Decrypted Shares Agg. (Mod)│ Final Aggregation │ Plaintext correctly │
│ │ │ │ reconstructed (modular) │
Comment thread
cedoor marked this conversation as resolved.
│ C7 │ Decrypted Shares Agg. │ Final Aggregation │ Plaintext correctly │
│ │ │ │ reconstructed from shares │
│ │ │ │ (modular decode over t); │
│ │ │ │ public inputs: C6 `d` │
│ │ │ │ commitments + party IDs + msg;│
│ │ │ │ in-circuit equality vs │
│ │ │ │ commitments from witness │
│ │ │ │ decryption shares │
└──────┴────────────────────────────┴───────────────────┴──────────────────────────────┘

Slash Reasons by Proof Type:
Expand Down
2 changes: 1 addition & 1 deletion agent/flow-trace/05_FAILURE_REFUND_SLASHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,7 @@ Slash Reasons (derived from ProofType for Lane A):
│ C0, C1-C4 │ E3_BAD_DKG_PROOF │
│ C5 │ E3_BAD_PK_AGGREGATION │
│ C6 │ E3_BAD_DECRYPTION_PROOF │
C7a, C7b │ E3_BAD_AGGREGATION_PROOF │
C7 │ E3_BAD_AGGREGATION_PROOF │
└─────────────────┴──────────────────────────┘
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@

use bb_proof_verification::{UltraHonkVerificationKey, UltraHonkZKProof, verify_honk_proof};
use lib::{
configs::default::{MAX_MSG_NON_ZERO_COEFFS, T, threshold::L},
configs::default::{MAX_MSG_NON_ZERO_COEFFS, T},
math::commitments::compute_recursive_aggregation_commitment,
};

// Number of proofs.
pub global N_PROOFS: u32 = 1;
/// Number of public inputs/outputs per proof.
pub global N_PUBLIC_INPUTS: u32 =
((T + 1) * L * MAX_MSG_NON_ZERO_COEFFS) + (T + 1 + MAX_MSG_NON_ZERO_COEFFS);
pub global N_PUBLIC_INPUTS: u32 = (T + 1) + MAX_MSG_NON_ZERO_COEFFS + (T + 1);

fn main(
verification_key: UltraHonkVerificationKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use lib::math::commitments::compute_recursive_aggregation_commitment;
// Number of proofs.
pub global N_PROOFS: u32 = 1;
/// Number of public inputs/outputs per proof.
pub global N_PUBLIC_INPUTS: u32 = 2 + 2 * L * N;
pub global N_PUBLIC_INPUTS: u32 = 2 + 2 * L * N + 1;

fn main(
verification_key: UltraHonkVerificationKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@

use lib::configs::default::{MAX_MSG_NON_ZERO_COEFFS, T};
use lib::configs::default::threshold::{
DECRYPTED_SHARES_AGGREGATION_BIT_NOISE, DECRYPTED_SHARES_AGGREGATION_CONFIGS, L,
DECRYPTED_SHARES_AGGREGATION_BIT_D, DECRYPTED_SHARES_AGGREGATION_BIT_NOISE,
DECRYPTED_SHARES_AGGREGATION_CONFIGS, L,
};
use lib::core::threshold::decrypted_shares_aggregation::DecryptedSharesAggregation;
use lib::math::polynomial::Polynomial;

fn main(
decryption_shares: pub [[Polynomial<MAX_MSG_NON_ZERO_COEFFS>; L]; T + 1],
expected_d_commitments: pub [Field; T + 1],
party_ids: pub [Field; T + 1],
message: pub Polynomial<MAX_MSG_NON_ZERO_COEFFS>,
decryption_shares: [[Polynomial<MAX_MSG_NON_ZERO_COEFFS>; L]; T + 1],
u_global: Polynomial<MAX_MSG_NON_ZERO_COEFFS>,
crt_quotients: [Polynomial<MAX_MSG_NON_ZERO_COEFFS>; L],
) {
let decrypted_shares_aggregation: DecryptedSharesAggregation<MAX_MSG_NON_ZERO_COEFFS, L, T, DECRYPTED_SHARES_AGGREGATION_BIT_NOISE> = DecryptedSharesAggregation::new(
let decrypted_shares_aggregation: DecryptedSharesAggregation<MAX_MSG_NON_ZERO_COEFFS, L, T, DECRYPTED_SHARES_AGGREGATION_BIT_NOISE, DECRYPTED_SHARES_AGGREGATION_BIT_D> = DecryptedSharesAggregation::new(
DECRYPTED_SHARES_AGGREGATION_CONFIGS,
expected_d_commitments,
decryption_shares,
party_ids,
message,
Expand Down
5 changes: 3 additions & 2 deletions circuits/bin/threshold/share_decryption/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

use lib::configs::default::MAX_MSG_NON_ZERO_COEFFS;
use lib::configs::default::threshold::{
L, N, THRESHOLD_SHARE_DECRYPTION_BIT_CT, THRESHOLD_SHARE_DECRYPTION_BIT_D,
THRESHOLD_SHARE_DECRYPTION_BIT_E_SM, THRESHOLD_SHARE_DECRYPTION_BIT_R1,
Expand All @@ -23,8 +24,8 @@ fn main(
r1: [Polynomial<(2 * N) - 1>; L],
r2: [Polynomial<N - 1>; L],
d: [Polynomial<N>; L],
) {
let share_decryption: ShareDecryption<N, L, THRESHOLD_SHARE_DECRYPTION_BIT_CT, THRESHOLD_SHARE_DECRYPTION_BIT_SK, THRESHOLD_SHARE_DECRYPTION_BIT_E_SM, THRESHOLD_SHARE_DECRYPTION_BIT_R1, THRESHOLD_SHARE_DECRYPTION_BIT_R2, THRESHOLD_SHARE_DECRYPTION_BIT_D> = ShareDecryption::new(
) -> pub Field {
let share_decryption: ShareDecryption<N, MAX_MSG_NON_ZERO_COEFFS, L, THRESHOLD_SHARE_DECRYPTION_BIT_CT, THRESHOLD_SHARE_DECRYPTION_BIT_SK, THRESHOLD_SHARE_DECRYPTION_BIT_E_SM, THRESHOLD_SHARE_DECRYPTION_BIT_R1, THRESHOLD_SHARE_DECRYPTION_BIT_R2, THRESHOLD_SHARE_DECRYPTION_BIT_D> = ShareDecryption::new(
THRESHOLD_SHARE_DECRYPTION_CONFIGS,
expected_sk_commitment,
expected_e_sm_commitment,
Expand Down
1 change: 1 addition & 0 deletions circuits/lib/src/configs/insecure/threshold.nr
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,7 @@ decrypted_shares_aggregation (CIRCUIT 7)
************************************/

pub global DECRYPTED_SHARES_AGGREGATION_BIT_NOISE: u32 = 65;
pub global DECRYPTED_SHARES_AGGREGATION_BIT_D: u32 = 35;

pub global DECRYPTED_SHARES_AGGREGATION_CONFIGS: DecryptedSharesAggregationConfigs<L> =
DecryptedSharesAggregationConfigs::new(QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T);
1 change: 1 addition & 0 deletions circuits/lib/src/configs/secure/threshold.nr
Original file line number Diff line number Diff line change
Expand Up @@ -32933,6 +32933,7 @@ decrypted_shares_aggregation (CIRCUIT 7)
************************************/

pub global DECRYPTED_SHARES_AGGREGATION_BIT_NOISE: u32 = 200;
pub global DECRYPTED_SHARES_AGGREGATION_BIT_D: u32 = 52;

pub global DECRYPTED_SHARES_AGGREGATION_CONFIGS: DecryptedSharesAggregationConfigs<L> =
DecryptedSharesAggregationConfigs::new(QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T);
22 changes: 18 additions & 4 deletions circuits/lib/src/core/threshold/decrypted_shares_aggregation.nr
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

use crate::math::commitments::compute_threshold_decryption_share_commitment;
use crate::math::modulo::U128::ModU128;
use crate::math::polynomial::Polynomial;
use dep::bignum::BigNum;
Expand All @@ -30,14 +31,18 @@ impl<let L: u32> Configs<L> {
/// Uses BigNum for Q values (works for both insecure and secure parameter sets).
///
/// Verifies:
/// 0. Each party's decryption share matches the corresponding C6 public `d` commitment
/// 1. Lagrange interpolation to compute u^{(l)} for each CRT basis
/// 2. CRT reconstruction: u^{(l)} + r^{(l)} * q_l = u_global
/// 3. Decoding verification: message = -Q^{-1} * (t * u_global)_Q mod t
pub struct DecryptedSharesAggregation<let MAX_MSG_NON_ZERO_COEFFS: u32, let L: u32, let T: u32, let BIT_NOISE: u32> {
pub struct DecryptedSharesAggregation<let MAX_MSG_NON_ZERO_COEFFS: u32, let L: u32, let T: u32, let BIT_NOISE: u32, let BIT_D: u32> {
/// Circuit parameters including crypto constants
configs: Configs<L>,

/// Decryption shares from t+1 parties (public witnesses)
/// Public `d` commitments from circuit 6 (one per reconstructing party), same order as `decryption_shares`
expected_d_commitments: [Field; T + 1],

/// Decryption shares from t+1 parties (secret witnesses)
decryption_shares: [[Polynomial<MAX_MSG_NON_ZERO_COEFFS>; L]; T + 1],

/// Party IDs (x-coordinates) for interpolation (public witnesses)
Expand All @@ -54,9 +59,10 @@ pub struct DecryptedSharesAggregation<let MAX_MSG_NON_ZERO_COEFFS: u32, let L: u
crt_quotients: [Polynomial<MAX_MSG_NON_ZERO_COEFFS>; L],
}

impl<let MAX_MSG_NON_ZERO_COEFFS: u32, let L: u32, let T: u32, let BIT_NOISE: u32> DecryptedSharesAggregation<MAX_MSG_NON_ZERO_COEFFS, L, T, BIT_NOISE> {
impl<let MAX_MSG_NON_ZERO_COEFFS: u32, let L: u32, let T: u32, let BIT_NOISE: u32, let BIT_D: u32> DecryptedSharesAggregation<MAX_MSG_NON_ZERO_COEFFS, L, T, BIT_NOISE, BIT_D> {
pub fn new(
configs: Configs<L>,
expected_d_commitments: [Field; T + 1],
decryption_shares: [[Polynomial<MAX_MSG_NON_ZERO_COEFFS>; L]; T + 1],
party_ids: [Field; T + 1],
message: Polynomial<MAX_MSG_NON_ZERO_COEFFS>,
Expand All @@ -65,6 +71,7 @@ impl<let MAX_MSG_NON_ZERO_COEFFS: u32, let L: u32, let T: u32, let BIT_NOISE: u3
) -> Self {
DecryptedSharesAggregation {
configs,
expected_d_commitments,
decryption_shares,
party_ids,
message,
Expand All @@ -73,8 +80,15 @@ impl<let MAX_MSG_NON_ZERO_COEFFS: u32, let L: u32, let T: u32, let BIT_NOISE: u3
}
}

/// Main verification function
/// Main verification function.
pub fn execute(self) {
for party_idx in 0..(T + 1) {
let computed = compute_threshold_decryption_share_commitment::<MAX_MSG_NON_ZERO_COEFFS, L, BIT_D>(
self.decryption_shares[party_idx],
);
assert(computed == self.expected_d_commitments[party_idx], "d commitment mismatch");
}

// Step 1: Compute Lagrange coefficients in-circuit
let lagrange_coeffs = compute_all_lagrange_coeffs::<T, L>(self.configs.qis, self.party_ids);

Expand Down
47 changes: 36 additions & 11 deletions circuits/lib/src/core/threshold/share_decryption.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
// or FITNESS FOR A PARTICULAR PURPOSE.

use crate::math::commitments::{
compute_aggregated_shares_commitment, compute_threshold_share_decryption_challenge,
compute_aggregated_shares_commitment, compute_threshold_decryption_share_commitment,
compute_threshold_share_decryption_challenge,
};
use crate::math::helpers::flatten;
use crate::math::polynomial::Polynomial;
Expand All @@ -32,7 +33,7 @@ impl<let L: u32> Configs<L> {
/// 1. Commitment to sk matches expected (from DKG decryption circuit)
/// 2. Commitment to e_sm matches expected (from DKG decryption circuit)
/// 3. Correct computation: d_i = c_0i + c_1i * s_i + e_i + r_2i * (X^N + 1) + r_1i * q_i
pub struct ShareDecryption<let N: u32, let L: u32, let BIT_CT: u32, let BIT_SK: u32, let BIT_E_SM: u32, let BIT_R1: u32, let BIT_R2: u32, let BIT_D: u32> {
pub struct ShareDecryption<let N: u32, let MAX_MSG_NON_ZERO_COEFFS: u32, let L: u32, let BIT_CT: u32, let BIT_SK: u32, let BIT_E_SM: u32, let BIT_R1: u32, let BIT_R2: u32, let BIT_D: u32> {
/// Circuit parameters including bounds and cryptographic constants
configs: Configs<L>,

Expand Down Expand Up @@ -61,12 +62,11 @@ pub struct ShareDecryption<let N: u32, let L: u32, let BIT_CT: u32, let BIT_SK:
r1: [Polynomial<2 * N - 1>; L],
r2: [Polynomial<N - 1>; L],

/// Party's computed decryption share
/// (public witnesses)
/// Party's computed decryption share (secret witness)
d: [Polynomial<N>; L],
}

impl<let N: u32, let L: u32, let BIT_CT: u32, let BIT_SK: u32, let BIT_E_SM: u32, let BIT_R1: u32, let BIT_R2: u32, let BIT_D: u32> ShareDecryption<N, L, BIT_CT, BIT_SK, BIT_E_SM, BIT_R1, BIT_R2, BIT_D> {
impl<let N: u32, let MAX_MSG_NON_ZERO_COEFFS: u32, let L: u32, let BIT_CT: u32, let BIT_SK: u32, let BIT_E_SM: u32, let BIT_R1: u32, let BIT_R2: u32, let BIT_D: u32> ShareDecryption<N, MAX_MSG_NON_ZERO_COEFFS, L, BIT_CT, BIT_SK, BIT_E_SM, BIT_R1, BIT_R2, BIT_D> {
pub fn new(
configs: Configs<L>,
expected_sk_commitment: Field,
Expand All @@ -79,6 +79,7 @@ impl<let N: u32, let L: u32, let BIT_CT: u32, let BIT_SK: u32, let BIT_E_SM: u32
r2: [Polynomial<N - 1>; L],
d: [Polynomial<N>; L],
) -> Self {
assert(MAX_MSG_NON_ZERO_COEFFS <= N);
ShareDecryption {
configs,
expected_sk_commitment,
Expand Down Expand Up @@ -111,6 +112,22 @@ impl<let N: u32, let L: u32, let BIT_CT: u32, let BIT_SK: u32, let BIT_E_SM: u32
);
}

/// Truncates each CRT limb of `d` to `MAX_MSG_NON_ZERO_COEFFS` coefficients.
/// Used only to build the C7 public commitment (`compute_threshold_decryption_share_commitment`);
/// the Fiat-Shamir transcript separately absorbs the full `d` witness via [`Self::payload`].
fn get_truncated_decryption_share(self) -> [Polynomial<MAX_MSG_NON_ZERO_COEFFS>; L] {
let mut out: [Polynomial<MAX_MSG_NON_ZERO_COEFFS>; L] =
[Polynomial::new([0; MAX_MSG_NON_ZERO_COEFFS]); L];
for i in 0..L {
let mut coeffs = [0; MAX_MSG_NON_ZERO_COEFFS];
for j in 0..MAX_MSG_NON_ZERO_COEFFS {
coeffs[j] = self.d[i].coefficients[j];
}
out[i] = Polynomial::new(coeffs);
}
out
}

/// Flattens all witness data into a single array for Fiat-Shamir challenge generation.
///
/// This function serializes all polynomial coefficients (both public inputs and
Expand All @@ -124,7 +141,7 @@ impl<let N: u32, let L: u32, let BIT_CT: u32, let BIT_SK: u32, let BIT_E_SM: u32
/// 4. Ciphertext components `c_1` for each CRT basis (serialized coefficients)
/// 5. Quotient polynomials `r_1` for each CRT basis (serialized coefficients)
/// 6. Quotient polynomials `r_2` for each CRT basis (serialized coefficients)
/// 7. Decryption shares `d` for each CRT basis (serialized coefficients)
/// 7. Full decryption share polynomials `d` for each CRT basis (all N coefficients per limb)
///
/// Note: Aggregated secret shares `sk` and noise shares `e_sm` are represented by their
/// commitments rather than serialized coefficients. This saves constraints while
Expand All @@ -148,14 +165,15 @@ impl<let N: u32, let L: u32, let BIT_CT: u32, let BIT_SK: u32, let BIT_E_SM: u32
inputs = flatten::<_, _, BIT_R1>(inputs, self.r1);
inputs = flatten::<_, _, BIT_R2>(inputs, self.r2);

// Flatten decryption shares (public outputs)
inputs = flatten::<_, _, BIT_D>(inputs, self.d);
// Full `d` fixes the C7 prefix commitment (computed separately as the public output)
inputs = flatten::<N, L, BIT_D>(inputs, self.d);

inputs
}

/// Main verification function
pub fn execute(self) {
/// Main verification function. Returns the public commitment to the first
/// `MAX_MSG_NON_ZERO_COEFFS` coefficients of `d` per CRT limb.
pub fn execute(self) -> Field {
// Step 1: Verify sk commitment matches expected
self.verify_agg_sk_commitment();

Expand All @@ -167,13 +185,20 @@ impl<let N: u32, let L: u32, let BIT_CT: u32, let BIT_SK: u32, let BIT_E_SM: u32
// (the DKG decryption circuit already performed range checks on these values)
self.check_range_bounds();

let d_truncated = self.get_truncated_decryption_share();
let d_commitment = compute_threshold_decryption_share_commitment::<MAX_MSG_NON_ZERO_COEFFS, L, BIT_D>(
d_truncated,
);

// Step 4: Generate Fiat-Shamir challenge from the transcript
let gamma = self.generate_challenge();

// Step 5: Verify decryption share computation for each CRT basis
for i in 0..L {
self.verify_decryption_share_computation(i, gamma);
}

d_commitment
}

/// Performs range checks on quotient polynomial witnesses.
Expand Down Expand Up @@ -210,7 +235,7 @@ impl<let N: u32, let L: u32, let BIT_CT: u32, let BIT_SK: u32, let BIT_E_SM: u32
/// Generates Fiat-Shamir challenge value using the SAFE cryptographic sponge.
///
/// This function implements the Fiat-Shamir transform for the decryption share circuit:
/// 1. Flattens all witness data (commitments for sk/e_sm, ciphertexts c_0/c_1, quotients r_1/r_2, decryption shares d) into a single array
/// 1. Flattens all witness data (commitments for sk/e_sm, ciphertexts c_0/c_1, quotients r_1/r_2, full d) into a single array
/// 2. Absorbs the flattened data into the SAFE sponge
/// 3. Squeezes a single challenge value
///
Expand Down
Loading
Loading