Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

PI refactor #138

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open

PI refactor #138

wants to merge 43 commits into from

Conversation

CeciliaZ030
Copy link

Description

Refactor the PI circuit in circuit-tool style.

Comment on lines 303 to 304
meta.create_gate("keccak(rpi)", |meta| {
cb.restart();

Choose a reason for hiding this comment

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

Much cleaner to just move everything in a single gate like the MPT circuit, then also no restart necessary (and I guess not always restarted now, so constraints are added multiple times now per gate because they accumulate)?

Copy link
Author

Choose a reason for hiding this comment

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

Glad that you like it :) No restart needed.
What do you mean accumulate tho? If you're talking about block_acc it should be applied per row within the left field bytes section.

Choose a reason for hiding this comment

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

I meant that the multiple gates introduces extra things people can forget and make errors with. Because each gate does build_constraints, if you don't call restart between the different gates, constraints will keep getting added to the list in the constrain builder and those will keep being added when calling build_constraints, so that will also add the constraints in the previous gates again.

Conclusion: gates bad! haha

require!(a!(rpi_field_bytes_acc) => a!(rpi_field_bytes));
});
});
cb.build_constraints(None)

Choose a reason for hiding this comment

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

Why did you add an option for an additional selector here? People should just use ifx instead no?

Copy link
Author

Choose a reason for hiding this comment

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

Because I don't like too much indentation with lots of ifx 😅 (aesthetically unpleasing given macro's ! and parentheses) and I feel like if there's must be a fixed selector enabling of circuits, why don't we generate the constraint "anchoring" this gate.

Choose a reason for hiding this comment

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

If you write normal rust code, would you introduce a gate like construct to make it cleaner? If the answer is no, why would you do it here? Does normal rust code have too many nested if's? I don't think so, you just split things up over multiple functions in a way that makes sense. So why not do exactly the same with circuits? The whole of MPT is written using a single gate, I don't think there was any problem there with this because it's split up mostly like you would split up a normal rust program.

why don't we generate the constraint "anchoring" this gate.

Because then the flow control is split into multiple parts and gates don't allow if/else statements, so I find it harder to read and it's not something normal programmers are used to.

It's also not possible to do gates inside gates. So even if you like using gates, it's impossible to use it except on the top level of the program. So it's a strange concept to rely on because if you like to use it to split up your program, it only works for tiny programs that have only 1 level complexity, anything more complex does require using if/else statement.

It's also not directly compatible with regions/context management, so this would require a different path to make it behave like the normal control flow using ifs/matches.

@CeciliaZ030
Copy link
Author

CeciliaZ030 commented Aug 21, 2023

I have one concern tho... @Brechtpd do you think for bytes: [Cell<F>; N] within FieldBytesGadget I should directly fill in in increment with r?
Remember in the original accumulation column increment all the way and it was just one constraint like next = cur * r + byte which has low degree, but mine has block_accc[i+1] = ((byte0 * r) + byte1 * r) * ... + byte31 which causes high degree. I know we can reduce it with split_expression but that's also some amount of overhead.
For simple circuit like this refactoring with circuit-tools i just worry about performance degrade in general 😭 and whether it's worthy to tradeoff performance over readability.

@Brechtpd
Copy link

I have one concern tho... @Brechtpd do you think for bytes: [Cell<F>; N] within FieldBytesGadget I should directly fill in in increment with r? Remember in the original accumulation column increment all the way and it was just one constraint like next = cur * r + byte which has low degree, but mine has block_accc[i+1] = ((byte0 * r) + byte1 * r) * ... + byte31 which causes high degree. I know we can reduce it with split_expression but that's also some amount of overhead. For simple circuit like this refactoring with circuit-tools i just worry about performance degrade in general 😭 and whether it's worthy to tradeoff performance over readability.

  • Challenges behave like constants so they do not increase the expression degree when multiplying with them, so I think the expression degree here should still be low.
  • Yeah only use the cell manager when it makes sense. If something is just column based and easy to understand, no reason to force the cell manager into it.

Copy link

@Brechtpd Brechtpd left a comment

Choose a reason for hiding this comment

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

The version in taiko_pi_circuit_.rs I think could also potentially work, but yeah the cost of the current version will be much higher because of many more columns and A LOT more lookups, so the performance will be much worse. But to solve that it just needs to be possible to make the CellManager be a single column (or maybe two columns), which seems to be possible to me.

On the other hand, this will be very close to just using the taiko_pi_circuit.rs version where everything is still assumed to be on a single row, and just put every cell used on that row in a cellmanager. The cellmanager doesn't do much in this case, but it still does away with having to assign things to columns instead of allocated cells. I currently lean towards this approach because it will be easier.

To print the most important info for the circuit performance:

println!("max expression degree: {}", meta.degree());
println!("num lookups: {}", meta.lookups().len());
println!("num advices: {}", meta.num_advice_columns());
println!("num fixed: {}", meta.num_fixed_columns());

Comment on lines 286 to 288
cb.set_cell_manager(state_cm.clone());
let block_number = [();2].map(|_| cb.query_one(PiCellType::Storage1));
let keccak_output = [();2].map(|_| cb.query_one(PiCellType::Storage2));

Choose a reason for hiding this comment

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

Doe these need to be stored in a separate CellManager? Seems like these could just be stored somewhere in the main CellManager and it should still be okay? keccak_output requires a copy constraint I think which should be possible I think, block_number doesn't seem to require anything special, just some storage cell, unless I miss something.

Comment on lines 471 to 473
is_field: Cell<F>,
is_first: Cell<F>,
is_last: Cell<F>,

Choose a reason for hiding this comment

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

These are advice cells, but seems like they should be fixed cells? (should not be possible for the prover to set these values).

);
let q_enable = meta.complex_selector();
let public_input = meta.instance_column();
let block_acc = meta.advice_column_in(SecondPhase);

Choose a reason for hiding this comment

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

Why not also put it in the CellManager and just use rot with the height?

(PiCellType::Storage1, 3, 1, false)
],
0,
1,

Choose a reason for hiding this comment

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

I think with a single CellManager it will be easier to just make this height be whatever, and then the other code should be able to use the same height in rot to check between the different states, and in the witness assignment.

@CeciliaZ030 CeciliaZ030 marked this pull request as ready for review August 24, 2023 13:12
@smtmfft
Copy link
Collaborator

smtmfft commented Sep 1, 2023

For A5 integration, after functionality test, please merge this PR to feat/a5-testnet branch.

const SIGNAL_ROOT: usize = 3;
const GRAFFITI: usize = 4;
const PROVER: usize = 5;
const S1: PiCellType = PiCellType::Storage1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

StoragePhase1

Copy link
Author

Choose a reason for hiding this comment

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

Sure of you like :)


/// PublicData contains all the values that the PiCircuit receives as input
const PADDING_LEN: usize = 32;
const META_HASH: usize = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

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

enum?

Copy link
Author

Choose a reason for hiding this comment

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

I think const is better cuz enum is wordy, you have to type somethingPiField::MetaHash.

self.field.iter().map(|f| f.expr()).collect()
}

fn acc(&self, r: Expression<F>) -> Expression<F> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

rlc_acc or rlc?


#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum PiCellType {
Storage1,
Copy link
Collaborator

Choose a reason for hiding this comment

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

StoragePhase1/2

fn mock_public_data() -> PublicData<Fr> {
let mut evidence = PublicData::default();
evidence.set_field(PARENT_HASH, OMMERS_HASH.to_fixed_bytes().to_vec());
evidence.set_field(BLOCK_HASH, OMMERS_HASH.to_fixed_bytes().to_vec());
Copy link
Collaborator

Choose a reason for hiding this comment

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

init address once as it has different data format

Copy link
Author

Choose a reason for hiding this comment

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

You mean mock address for prover?

Copy link
Collaborator

Choose a reason for hiding this comment

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

yes, at least test different type once

assign!(region, self.block_hash.0, 0 => (evidence.block_context.number).as_u64().scalar());
assign!(region, self.block_hash.2, 0 => evidence.assignment_acc(BLOCK_HASH, evm_word));

let _acc = F::ZERO;
Copy link
Collaborator

Choose a reason for hiding this comment

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

useless variable??

field10,
impl<F: Field> PublicData<F> {
fn new(block: &witness::Block<F>) -> Self {
let meta_hash = Token::FixedBytes(block.protocol_instance.meta_hash.hash().to_word().to_be_bytes().to_vec());
Copy link
Collaborator

Choose a reason for hiding this comment

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

hash.hash() for what??

Copy link
Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

meta_hash here is already a hash, no need hash again.

Copy link
Author

@CeciliaZ030 CeciliaZ030 Sep 4, 2023

Choose a reason for hiding this comment

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

In the old version meta_hash is a struct MetaHash which returns the actual hash when you call hash() on itself, but yea now it's already a hash.

@CeciliaZ030 CeciliaZ030 mentioned this pull request Sep 4, 2023
1 task
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants