Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add memory trace prove entrypoint #71

Merged
merged 18 commits into from
Nov 20, 2024

Conversation

zmalatrax
Copy link
Collaborator

Closes #55

The write_trace function is in the table.rs file to access the private fields of MemoryTable and MemoryTableRow, but it might be better to refactor it into its own file?

The conversion from MemoryTable to Vec<BaseColumn> and the evaluation on the circle domain could be refactored into two separate functions, to be used in the write_trace function.

The name write_trace might be misleading as we're not writing the trace to a tree but simply returning the Trace evaluation and the Claim on its size. (We could add the tree to which the trace evaluation should be written to as parameter and do the write part in this function, it is currently in the prove_brainfuck as tree_builder.extend_evals(trace)

I'm assuming that trying to convert an empty MemoryTable to a Trace for the Memory component should not be possible, it means that the execution trace is empty (compared to the I/O tables where it would simply mean no inputs nor output instruction)

About the log_size, when using the SimdBackend, the LOG_N_LANES be appended to the size of the trace, but I'm not so sure, to be confirmed.
The Claim should be on the log_size of the MemoryTable, or the log_size appended with LOG_N_LANES?
I think that it is the MemoryTable one, but still unsure.

@zmalatrax zmalatrax marked this pull request as draft November 20, 2024 09:00
@zmalatrax zmalatrax marked this pull request as ready for review November 20, 2024 12:13
crates/brainfuck_prover/src/brainfuck_air/mod.rs Outdated Show resolved Hide resolved
crates/brainfuck_prover/src/components/mod.rs Outdated Show resolved Hide resolved
crates/brainfuck_prover/src/components/memory/table.rs Outdated Show resolved Hide resolved
crates/brainfuck_prover/src/components/memory/component.rs Outdated Show resolved Hide resolved
Comment on lines 5 to 19
/// The claim for the Memory component
#[derive(Debug, Eq, PartialEq)]
pub struct Claim {
pub log_size: u32,
}
impl Claim {
pub fn log_sizes(&self) -> TreeVec<Vec<u32>> {
let trace_log_sizes = vec![self.log_size; N_COLS_MEMORY_TABLE];
TreeVec::new(vec![trace_log_sizes])
}

pub fn mix_into(&self, channel: &mut impl Channel) {
channel.mix_u64(u64::from(self.log_size));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have header documentation for these functions to know what is their purpose?

crates/brainfuck_prover/src/components/memory/component.rs Outdated Show resolved Hide resolved
Comment on lines +202 to +211
/// Number of columns in the memory table
pub const N_COLS_MEMORY_TABLE: usize = 4;
/// Index of the `clk` register column in the Memory trace.
const CLK_COL_INDEX: usize = 0;
/// Index of the `mp` register column in the Memory trace.
const MP_COL_INDEX: usize = 1;
/// Index of the `mv` register column in the Memory trace.
const MV_COL_INDEX: usize = 2;
/// Index of the `d` register column in the Memory trace.
const D_COL_INDEX: usize = 3;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe (just a suggestion, totally fine to do in a follow up or not if you don't like it) to avoid the number of constants into the code, can't we transform that into an enum?

/// Enum representing the column indices in the Memory trace
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemoryColumn {
    Clk,
    Mp,
    Mv,
    D,
}

impl MemoryColumn {
    /// Returns the index of the column in the Memory table
    pub fn index(self) -> usize {
        match self {
            MemoryColumn::Clk => 0,
            MemoryColumn::Mp => 1,
            MemoryColumn::Mv => 2,
            MemoryColumn::D => 3,
        }
    }

    /// Returns the total number of columns in the Memory table
    pub fn count() -> usize {
        std::mem::variant_count::<Self>()
    }
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not opinionated on const/enum, the goal is to reduce the number of public variables: having a single enum rather than 5 constants, + the number of columns in the table is not a magic number but calculated from the number of columns defined in the enum

From this angle, having an enum seems better

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let us do in a follow up if you want so that we don't block things here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, i'll open an issue then

Comment on lines 213 to 247
/// Transforms the [`MemoryTable`] into [`super::super::TraceEval`], to be commited when generating
/// a STARK proof.
///
/// The [`MemoryTable`] is transformed from an array of rows (one element = one step of all
/// registers) to an array of columns (one element = all steps of one register).
/// It is then evaluated on the circle domain.
///
/// # Arguments
/// * memory - The [`MemoryTable`] containing the sorted and padded trace as an array of rows.
pub fn get_trace_evaluation(memory: &MemoryTable) -> Result<(TraceEval, Claim), TraceError> {
let table = memory.table();
let n_rows = table.len() as u32;
if n_rows == 0 {
return Err(TraceError::EmptyTraceError);
}
let log_n_rows = n_rows.ilog2();
// TODO: Confirm that the log_size used for evaluation on Circle domain is the log_size of the
// table plus the SIMD lanes
let log_size = log_n_rows + LOG_N_LANES;
let mut trace: Vec<BaseColumn> =
(0..N_COLS_MEMORY_TABLE).map(|_| BaseColumn::zeros(1 << log_size)).collect();

for (vec_row, row) in table.iter().enumerate().take(1 << log_n_rows) {
trace[CLK_COL_INDEX].data[vec_row] = row.clk().into();
trace[MP_COL_INDEX].data[vec_row] = row.mp().into();
trace[MV_COL_INDEX].data[vec_row] = row.mv().into();
trace[D_COL_INDEX].data[vec_row] = row.d().into();
}

let domain = CanonicCoset::new(log_size).circle_domain();
let trace = trace.into_iter().map(|col| CircleEvaluation::new(domain, col)).collect();

// TODO: Confirm that the log_size in `Claim` is `log_size`, including the SIMD lanes
Ok((trace, Claim { log_size }))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we integrate this directy into impl MemoryTable in order to have:

pub fn get_trace_evaluation(&self) -> Result<(TraceEval, Claim), TraceError> {
... 
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effectively, refactoring it to MemoryTable

@tcoratger tcoratger merged commit fd3a238 into main Nov 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat: add Memory trace generation to the prove_brainfuck entrypoint
2 participants