Example #1
0
def test_cairo_pie_memory_invalid_address(cairo_pie: CairoPie):
    # Write to an invalid address.
    cairo_pie.memory[RelocatableValue(
        segment_index=cairo_pie.metadata.ret_pc_segment.index, offset=0)] = 0
    with pytest.raises(
            AssertionError, match='Invalid memory cell address.'):
        cairo_pie.run_validity_checks()
Example #2
0
def test_cairo_pie_memory_negative_address(cairo_pie: CairoPie):
    # Write to a negative address.
    cairo_pie.memory.set_without_checks(RelocatableValue(
        segment_index=cairo_pie.metadata.program_segment.index, offset=-5), 0)
    with pytest.raises(
            AssertionError, match='Invalid memory cell address.'):
        cairo_pie.run_validity_checks()
Example #3
0
def test_cairo_pie_memory_invalid_value(cairo_pie: CairoPie):
    # Write a value after the execution segment.
    output_end = RelocatableValue(
        segment_index=cairo_pie.metadata.execution_segment.index,
        offset=cairo_pie.metadata.execution_segment.size)
    cairo_pie.memory[output_end] = output_end + 2
    # It should fail because the address is outside the segment expected size.
    with pytest.raises(
            AssertionError, match='Invalid memory cell address.'):
        cairo_pie.run_validity_checks()
    # Increase the size.
    cairo_pie.metadata.execution_segment.size += 1
    # Now it should fail because of the value.
    with pytest.raises(AssertionError, match='Invalid memory cell value.'):
        cairo_pie.run_validity_checks()
Example #4
0
    def add_job(self, cairo_pie: CairoPie) -> str:
        """
        Sends a job to the SHARP.
        cairo_pie is the product of running the corresponding Cairo program locally
        (using cairo-run --cairo_pie_output).
        Returns job_key - a unique id of the job in the SHARP system.
        """

        res = self._send(
            'add_job', {'cairo_pie': base64.b64encode(cairo_pie.serialize()).decode('ascii')})
        assert 'cairo_job_key' in res, f'Error when sending job to SHARP: {res}.'
        return res['cairo_job_key']
Example #5
0
def test_cairo_pie_serialize_deserialize():
    program = compile_cairo(
        code=[('%builtins output pedersen range_check ecdsa\nmain:\n[ap] = [ap]\n', '')],
        prime=DEFAULT_PRIME)
    metadata = CairoPieMetadata(
        program=program.stripped(),
        program_segment=SegmentInfo(0, 10),
        execution_segment=SegmentInfo(1, 20),
        ret_fp_segment=SegmentInfo(6, 12),
        ret_pc_segment=SegmentInfo(7, 21),
        builtin_segments={
            'a': SegmentInfo(4, 15),
        },
        extra_segments=[],
    )
    memory = MemoryDict({
        1: 2,
        RelocatableValue(3, 4): RelocatableValue(6, 7),
    })
    additional_data = {'c': ['d', 3]}
    execution_resources = ExecutionResources(
        n_steps=10,
        n_memory_holes=7,
        builtin_instance_counter={
            'output': 6,
            'pedersen': 3,
        }
    )
    cairo_pie = CairoPie(
        metadata=metadata,
        memory=memory,
        additional_data=additional_data,
        execution_resources=execution_resources
    )

    fileobj = io.BytesIO()
    cairo_pie.to_file(fileobj)
    actual_cairo_pie = CairoPie.from_file(fileobj)

    assert cairo_pie == actual_cairo_pie
Example #6
0
    def get_cairo_pie(self) -> CairoPie:
        """
        Constructs and returns a CairoPie representing the current VM run.
        """
        builtin_segments = self.get_builtin_segments_info()
        known_segment_indices = WriteOnceDict()
        for segment_info in builtin_segments.values():
            known_segment_indices[segment_info.index] = None

        # Note that n_used_builtins might be smaller then len(builtin_segments).
        n_used_builtins = len(self.program.builtins)
        ret_fp, ret_pc = (self.vm_memory[self.execution_base +
                                         n_used_builtins + i]
                          for i in range(2))

        assert isinstance(
            ret_fp,
            RelocatableValue), f'Expecting a relocatable value got {ret_fp}.'
        assert isinstance(
            ret_pc,
            RelocatableValue), f'Expecting a relocatable value got {ret_pc}.'

        assert self.segments.segment_sizes[ret_fp.segment_index] == 0, \
            f'Unexpected ret_fp_segment size {self.segments.segment_sizes[ret_fp.segment_index]}'
        assert self.segments.segment_sizes[ret_pc.segment_index] == 0, \
            f'Unexpected ret_pc_segment size {self.segments.segment_sizes[ret_pc.segment_index]}'

        for addr in self.program_base, self.execution_base, ret_fp, ret_pc:
            assert addr.offset == 0, 'Expecting a 0 offset.'
            known_segment_indices[addr.segment_index] = None

        # Put all the remaining segments in extra_segments.
        extra_segments = [
            SegmentInfo(idx, size)
            for idx, size in sorted(self.segments.segment_sizes.items())
            if idx not in known_segment_indices
        ]

        execution_size = self.vm.run_context.ap - self.execution_base
        cairo_pie_metadata = CairoPieMetadata(
            program=self.program.stripped(),
            program_segment=SegmentInfo(index=self.program_base.segment_index,
                                        size=len(self.program.data)),
            execution_segment=SegmentInfo(
                index=self.execution_base.segment_index, size=execution_size),
            ret_fp_segment=SegmentInfo(ret_fp.segment_index, 0),
            ret_pc_segment=SegmentInfo(ret_pc.segment_index, 0),
            builtin_segments=builtin_segments,
            extra_segments=extra_segments,
        )

        execution_resources = self.get_execution_resources()

        return CairoPie(
            metadata=cairo_pie_metadata,
            memory=self.vm.run_context.memory,
            additional_data={
                name: builtin.get_additional_data()
                for name, builtin in self.builtin_runners.items()
            },
            execution_resources=execution_resources,
        )
Example #7
0
def test_cairo_pie_validity_invalid_builtin_list_execution_resources(cairo_pie: CairoPie):
    cairo_pie.execution_resources.builtin_instance_counter['tmp_builtin'] = \
        cairo_pie.execution_resources.builtin_instance_counter['output_builtin']
    with pytest.raises(
            AssertionError, match='Builtin list mismatch in execution_resources.'):
        cairo_pie.run_validity_checks()
Example #8
0
def test_cairo_pie_validity_invalid_builtin_segments(cairo_pie: CairoPie):
    cairo_pie.metadata.builtin_segments['tmp'] = cairo_pie.metadata.builtin_segments['output']
    with pytest.raises(
            AssertionError, match='Builtin list mismatch in builtin_segments.'):
        cairo_pie.run_validity_checks()
Example #9
0
def test_cairo_pie_validity_invalid_builtin_list(cairo_pie: CairoPie):
    cairo_pie.program.builtins.append('output')
    with pytest.raises(
            AssertionError, match='Invalid builtin list.'):
        cairo_pie.run_validity_checks()
Example #10
0
def test_cairo_pie_validity_invalid_program_size(cairo_pie: CairoPie):
    cairo_pie.metadata.program_segment.size += 1
    with pytest.raises(
            AssertionError, match='Program length does not match the program segment size.'):
        cairo_pie.run_validity_checks()
Example #11
0
def cairo_run(args):
    trace_needed = args.tracer or args.profile_output is not None
    trace_file = args.trace_file
    if trace_file is None and trace_needed:
        # If --tracer or --profile_output is used, use a temporary file as trace_file.
        trace_file = tempfile.NamedTemporaryFile(mode='wb')

    memory_file = args.memory_file
    if memory_file is None and trace_needed:
        # If --tracer or --profile_output is used, use a temporary file as memory_file.
        memory_file = tempfile.NamedTemporaryFile(mode='wb')

    debug_info_file = args.debug_info_file
    if debug_info_file is None and trace_needed:
        # If --tracer or --profile_output is used, use a temporary file as debug_info_file.
        debug_info_file = tempfile.NamedTemporaryFile(mode='w')

    ret_code = 0
    cairo_pie_input = None
    if args.program is not None:
        program: ProgramBase = load_program(args.program)
        initial_memory = MemoryDict()
        steps_input = args.steps
    else:
        assert args.run_from_cairo_pie is not None
        assert args.steps is None and args.min_steps is None, \
            '--steps and --min_steps cannot be specified in --run_from_cairo_pie mode.'
        cairo_pie_input = CairoPie.from_file(args.run_from_cairo_pie)
        try:
            cairo_pie_input.run_validity_checks()
        except Exception as exc:
            # Trim error message in case it's too long.
            msg = str(exc)[:10000]
            raise CairoRunError(
                f'Security check for the CairoPIE input failed: {msg}')
        program = cairo_pie_input.program
        initial_memory = cairo_pie_input.memory
        steps_input = cairo_pie_input.execution_resources.n_steps

    runner = CairoRunner(program=program,
                         layout=args.layout,
                         memory=initial_memory,
                         proof_mode=args.proof_mode)

    runner.initialize_segments()
    end = runner.initialize_main_entrypoint()

    if args.run_from_cairo_pie is not None:
        # Add extra_segments.
        for segment_info in cairo_pie_input.metadata.extra_segments:
            runner.segments.add(size=segment_info.size)
        # Update the builtin runners' additional_data.
        for name, builtin_runner in runner.builtin_runners.items():
            if name in cairo_pie_input.additional_data:
                builtin_runner.extend_additional_data(
                    data=cairo_pie_input.additional_data[name],
                    relocate_callback=lambda x: x,
                    data_is_trusted=not args.secure_run)

    program_input = json.load(args.program_input) if args.program_input else {}
    runner.initialize_vm(hint_locals={'program_input': program_input})

    try:
        if args.no_end:
            assert args.steps is not None, '--steps must be specified when running with --no-end.'
        else:
            additional_steps = 1 if args.proof_mode else 0
            max_steps = steps_input - additional_steps if steps_input is not None else None
            runner.run_until_pc(end, max_steps=max_steps)
            if args.proof_mode:
                # Run one more step to make sure the last pc that was executed (rather than the pc
                # after it) is __end__.
                runner.run_for_steps(1)
            runner.original_steps = runner.vm.current_step

        if args.min_steps:
            runner.run_until_steps(args.min_steps)

        disable_trace_padding = False
        if steps_input is not None:
            runner.run_until_steps(steps_input)
            disable_trace_padding = True

        runner.end_run(disable_trace_padding=disable_trace_padding)
    except (VmException, AssertionError) as exc:
        if args.debug_error:
            print(f'Got an error:\n{exc}')
            ret_code = 1
        else:
            raise exc

    if not args.no_end:
        runner.read_return_values()

    if not args.no_end and args.proof_mode:
        # Finalize important segments by correct size.
        runner.finalize_segments()

    if args.secure_run:
        verify_secure_runner(runner)
        if args.run_from_cairo_pie is not None:
            assert cairo_pie_input is not None
            assert cairo_pie_input == runner.get_cairo_pie(), \
                'The Cairo PIE input is not identical to the resulting Cairo PIE. ' \
                'This may indicate that the Cairo PIE was not generated by cairo_run.'

    if args.cairo_pie_output:
        runner.get_cairo_pie().to_file(args.cairo_pie_output)

    runner.relocate()

    if args.print_memory:
        runner.print_memory(relocated=args.relocate_prints)

    if args.print_output:
        runner.print_output()

    if args.print_info:
        runner.print_info(relocated=args.relocate_prints)
        # Skip builtin usage calculation if the execution stopped before reaching the end symbol.
        # Trying to calculate the builtin usage is likely to raise an exception and prevent the user
        # from opening the tracer.
        if args.proof_mode and not args.no_end:
            runner.print_builtin_usage()

    if trace_file is not None:
        field_bytes = math.ceil(program.prime.bit_length() / 8)
        write_binary_trace(trace_file, runner.relocated_trace)

    if memory_file is not None:
        field_bytes = math.ceil(program.prime.bit_length() / 8)
        write_binary_memory(memory_file, runner.relocated_memory, field_bytes)

    if args.air_public_input is not None:
        rc_min, rc_max = runner.get_perm_range_check_limits()
        write_air_public_input(
            layout=args.layout,
            public_input_file=args.air_public_input,
            memory=runner.relocated_memory,
            public_memory_addresses=runner.segments.
            get_public_memory_addresses(runner.segment_offsets),
            memory_segment_addresses=runner.get_memory_segment_addresses(),
            trace=runner.relocated_trace,
            rc_min=rc_min,
            rc_max=rc_max)

    if args.air_private_input is not None:
        assert args.trace_file is not None, \
            '--trace_file must be set when --air_private_input is set.'
        assert args.memory_file is not None, \
            '--memory_file must be set when --air_private_input is set.'
        json.dump(
            {
                'trace_path': f'{os.path.abspath(trace_file.name)}',
                'memory_path': f'{os.path.abspath(memory_file.name)}',
                **runner.get_air_private_input(),
            },
            args.air_private_input,
            indent=4)
        print(file=args.air_private_input)
        args.air_private_input.flush()

    if debug_info_file is not None:
        json.dump(DebugInfo.Schema().dump(runner.get_relocated_debug_info()),
                  debug_info_file)
        debug_info_file.flush()

    if args.tracer:
        CAIRO_TRACER = 'starkware.cairo.lang.tracer.tracer'
        subprocess.call(
            list(
                filter(None, [
                    sys.executable,
                    '-m',
                    CAIRO_TRACER,
                    f'--program={args.program.name}',
                    f'--trace={trace_file.name}',
                    f'--memory={memory_file.name}',
                    f'--air_public_input={args.air_public_input.name}'
                    if args.air_public_input else None,
                    f'--debug_info={debug_info_file.name}',
                ])))

    if args.profile_output is not None:
        CAIRO_PROFILER = 'starkware.cairo.lang.tracer.profiler'
        subprocess.call(
            list(
                filter(None, [
                    sys.executable,
                    '-m',
                    CAIRO_PROFILER,
                    f'--program={args.program.name}',
                    f'--trace={trace_file.name}',
                    f'--memory={memory_file.name}',
                    f'--air_public_input={args.air_public_input.name}'
                    if args.air_public_input else None,
                    f'--debug_info={debug_info_file.name}',
                    f'--profile_output={args.profile_output}',
                ])))

    return ret_code