Exemple #1
0
def test_nonpure_mul():
    code = """
    [ap] = [ap - 1] * 2; ap++
    """

    with pytest.raises(VmException, match='Could not complete computation *'):
        run_single(code, 1, ap=102, extra_mem={101: RelocatableValue(1, 0)})
Exemple #2
0
def run_single(code: str,
               steps: int,
               *,
               pc=RelocatableValue(0, 10),
               ap=100,
               fp=100,
               extra_mem={}):
    program = compile_cairo(code, PRIME, debug_info=True)

    # Set memory[fp - 1] to an arbitrary value, since [fp - 1] is assumed to be set.
    memory: Dict[MaybeRelocatable, MaybeRelocatable] = {
        **{pc + i: v
           for i, v in enumerate(program.data)}, fp - 1: 1234,
        **extra_mem
    }
    context = RunContext(
        pc=pc,
        ap=ap,
        fp=fp,
        memory=MemoryDict(memory),
        prime=PRIME,
    )

    vm = VirtualMachine(program, context, {})
    for _ in range(steps):
        vm.step()
    return vm
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()
Exemple #4
0
def test_nonpure_jmp_rel():
    code = """
    jmp rel [ap - 1]
    """

    with pytest.raises(VmException,
                       match='Could not complete computation jmp rel'):
        run_single(code, 1, ap=102, extra_mem={101: RelocatableValue(1, 0)})
def test_cairo_pie_memory_invalid_address(cairo_pie: CairoPie):
    # Write to an invalid address.
    cairo_pie.memory.unfreeze_for_testing()
    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()
Exemple #6
0
def test_memory_dict_get():
    md = MemoryDict({14: 15})
    assert md.get(14, 'default') == 15
    assert md.get(1234, 'default') == 'default'
    with pytest.raises(ValueError, match='must be nonnegative'):
        md.get(-10, 'default')
    # Attempting to read address with a negative offset is ok, it simply returns None.
    assert md.get(RelocatableValue(segment_index=10, offset=-2)) is None
def test_validated_memory_dict():
    memory = MemoryDict()
    memory_validator = ValidatedMemoryDict(memory=memory)

    def rule_identical_pairs(mem, addr):
        """
        Validates that the values in address pairs (i, i+1), where i is even, are identical.
        """
        offset_diff = (-1) ** (addr.offset % 2)
        other_addr = RelocatableValue.from_tuple((addr.segment_index, addr.offset + offset_diff))
        if other_addr in mem:
            assert mem[addr] == mem[other_addr]
            return {addr, other_addr}
        return set()

    def rule_constant_value(mem, addr, constant):
        assert mem[addr] == constant, \
            f'Expected value in address {addr} to be {constant}, got {mem[addr]}.'
        return {addr}

    memory_validator.add_validation_rule(1, lambda memory, addr: set())
    memory_validator.add_validation_rule(2, lambda memory, addr: {addr})
    memory_validator.add_validation_rule(3, rule_identical_pairs)
    memory_validator.add_validation_rule(4, rule_constant_value, 0)

    addr0 = RelocatableValue.from_tuple((1, 0))
    addr1 = RelocatableValue.from_tuple((2, 0))
    addr2 = RelocatableValue.from_tuple((3, 0))
    addr3 = RelocatableValue.from_tuple((3, 1))
    addr4 = RelocatableValue.from_tuple((4, 0))

    # Test validated_addresses update.
    memory_validator[addr0] = 0
    assert memory_validator._ValidatedMemoryDict__validated_addresses == set()
    memory_validator[addr1] = 0
    assert memory_validator._ValidatedMemoryDict__validated_addresses == {addr1}
    # Test validation rule application.
    memory_validator[addr2] = 1
    assert memory_validator._ValidatedMemoryDict__validated_addresses == {addr1}
    memory_validator[addr3] = 1
    assert memory_validator._ValidatedMemoryDict__validated_addresses == {addr1, addr2, addr3}

    with pytest.raises(
            AssertionError, match='Expected value in address 4:0 to be 0, got 1.'):
        memory_validator[addr4] = 1
 def rule_identical_pairs(mem, addr):
     """
     Validates that the values in address pairs (i, i+1), where i is even, are identical.
     """
     offset_diff = (-1) ** (addr.offset % 2)
     other_addr = RelocatableValue.from_tuple((addr.segment_index, addr.offset + offset_diff))
     if other_addr in mem:
         assert mem[addr] == mem[other_addr]
         return {addr, other_addr}
     return set()
def test_segment_relocation_failures():
    memory = MemoryDict()

    relocation_target = RelocatableValue(segment_index=4, offset=25)
    with pytest.raises(AssertionError, match='src_ptr.segment_index must be < 0, src_ptr=1:2.'):
        memory.add_relocation_rule(src_ptr=RelocatableValue(
            segment_index=1, offset=2), dest_ptr=relocation_target)

    with pytest.raises(AssertionError, match='src_ptr.offset must be 0, src_ptr=-3:2.'):
        memory.add_relocation_rule(src_ptr=RelocatableValue(
            segment_index=-3, offset=2), dest_ptr=relocation_target)

    memory.add_relocation_rule(src_ptr=RelocatableValue(
        segment_index=-3, offset=0), dest_ptr=relocation_target)

    with pytest.raises(
            AssertionError, match='The segment with index -3 already has a relocation rule.'):
        memory.add_relocation_rule(src_ptr=RelocatableValue(
            segment_index=-3, offset=0), dest_ptr=relocation_target)
Exemple #10
0
    def add(self, size: Optional[int] = None) -> RelocatableValue:
        """
        Adds a new segment and returns its starting location as a RelocatableValue.

        If size is not None the segment is finalized with the given size.
        """
        segment_index = self.n_segments
        self.n_segments += 1
        if size is not None:
            self.finalize(segment_index, size)
        return RelocatableValue(segment_index=segment_index, offset=0)
Exemple #11
0
def test_negative_address():
    runner = run_code_in_runner("""
main:
[ap] = 0; ap++
ret
""")
    # Access negative offset manually, so it is not taken modulo prime.
    runner.vm_memory.set_without_checks(
        RelocatableValue(segment_index=0, offset=-17), 0)
    with pytest.raises(SecurityError,
                       match='Accessed address 0:-17 has negative offset.'):
        verify_secure_runner(runner)
Exemple #12
0
def test_pages(runner_and_output_runner):
    """
    Tests the add_page() functionality.
    """
    runner, output_builtin_runner, base = runner_and_output_runner
    for i in range(15):
        runner.vm_memory[base + i] = i
    # Add two pages, with page_id 1 and 3.
    output_builtin_runner.add_page(page_id=1, page_start=base + 3, page_size=4)
    output_builtin_runner.add_page(page_id=3, page_start=base + 9, page_size=3)

    # page_start must be in the output segment (base).
    with pytest.raises(AssertionError,
                       match='page_start must be in the output segment'):
        output_builtin_runner.add_page(page_id=4,
                                       page_start=RelocatableValue(999, 999),
                                       page_size=3)

    runner.end_run()
    runner.finalize_segments()

    # A list of output cells and their page id.
    offset_page_pairs = [
        (0, 0),
        (1, 0),
        (2, 0),
        (3, 1),
        (4, 1),
        (5, 1),
        (6, 1),
        (7, 0),
        (8, 0),
        (9, 3),
        (10, 3),
        (11, 3),
        (12, 0),
        (13, 0),
        (14, 0),
    ]

    assert runner.segments.public_memory_offsets[base.segment_index] == \
        offset_page_pairs

    # Check that get_public_memory_addresses() returns the correct page_id for each value.
    # The program and execution segments are always in page 0.
    segment_offsets = {0: 0, 1: 10, 2: 100}
    assert runner.segments.get_public_memory_addresses(
        segment_offsets=segment_offsets) == (
            [(i, 0)
             for i in range(len(runner.program.data))] +  # Program segment.
            [(10, 0), (11, 0), (12, 0)] +  # Execution segment.
            [(100 + offset, page_id)
             for offset, page_id in offset_page_pairs])  # Output segment.
def test_memory_dict_setdefault():
    memory = MemoryDict({14: 15})
    memory.setdefault(14, 0)
    assert memory[14] == 15
    memory.setdefault(123, 456)
    assert memory[123] == 456
    with pytest.raises(ValueError, match='must be an int'):
        memory.setdefault(10, 'default')
    with pytest.raises(KeyError, match='must be nonnegative'):
        memory.setdefault(-10, 123)
    with pytest.raises(ValueError, match='The offset of a relocatable value must be nonnegative'):
        memory[RelocatableValue(segment_index=10, offset=-2)] = 13
Exemple #14
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
    def add_temp_segment(self) -> RelocatableValue:
        """
        Adds a new temporary segment and returns its starting location as a RelocatableValue.

        A temporary segment is a segment that is relocated using memory.add_relocation_rule()
        before the Cairo PIE is produced.
        """

        self.n_temp_segments += 1
        # Temporary segments have negative segment indices that start from -1.
        segment_index = -self.n_temp_segments

        return RelocatableValue(segment_index=segment_index, offset=0)
Exemple #16
0
def test_auto_deduction_rules():
    code = """
[fp + 1] = [fp] + [ap]
"""

    program = compile_cairo(code=code, prime=PRIME, debug_info=True)
    memory = {i: v for i, v in enumerate(program.data)}
    initial_ap = RelocatableValue(segment_index=1, offset=200)
    initial_fp = RelocatableValue(segment_index=2, offset=100)

    context = RunContext(
        pc=0,
        ap=initial_ap,
        fp=initial_fp,
        memory=MemoryDict(memory),
        prime=PRIME,
    )

    vm = VirtualMachine(program, context, {})

    def rule_ap_segment(vm, addr, val):
        return val

    vm.add_auto_deduction_rule(1, rule_ap_segment, 100)
    vm.add_auto_deduction_rule(2, lambda vm, addr: None)
    vm.add_auto_deduction_rule(
        2, lambda vm, addr: 200 if addr == initial_fp else None)
    vm.add_auto_deduction_rule(2, lambda vm, addr: 456)

    vm.step()

    assert vm.run_context.memory[initial_ap] == 100
    assert vm.run_context.memory[initial_fp] == 200
    assert vm.run_context.memory[initial_fp + 1] == 300

    with pytest.raises(InconsistentAutoDeductionError,
                       match='at address 2:100. 200 != 456'):
        vm.verify_auto_deductions()
Exemple #17
0
def test_jnz_relocatables(offset: int):
    code = """
    jmp body if [ap - 1] != 0
    [ap] = 0; ap++
    body:
    [ap] = 1; ap++
    """
    relocatable_value = RelocatableValue(segment_index=5, offset=offset)
    error_message = \
        None if relocatable_value.offset >= 0 else \
        f'Could not complete computation jmp != 0 of non pure value {relocatable_value}'
    with maybe_raises(expected_exception=VmException,
                      error_message=error_message):
        vm = run_single(code, 2, ap=102, extra_mem={101: relocatable_value})
        assert vm.run_context.memory[102] == 1
Exemple #18
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()
Exemple #19
0
def test_get_segment_used_size():
    memory = MemoryDict({
        RelocatableValue(0, 0): 0,
        RelocatableValue(0, 2): 0,
        RelocatableValue(1, 5): 0,
        RelocatableValue(1, 7): 0,
        RelocatableValue(3, 0): 0,
        RelocatableValue(4, 1): 0,
    })
    assert [get_segment_used_size(i, memory) for i in range(5)] == [3, 8, 0, 1, 2]
Exemple #20
0
def get_program_output(cairo_pie: CairoPie) -> List[int]:
    """
    Returns the program output.
    """
    assert 'output' in cairo_pie.metadata.builtin_segments, 'The output builtin must be used.'
    output = cairo_pie.metadata.builtin_segments['output']

    def verify_int(x: MaybeRelocatable) -> int:
        assert isinstance(x, int), \
            f'Expected program output to contain absolute values, found: {x}.'
        return x

    return [
        verify_int(cairo_pie.memory[RelocatableValue(
            segment_index=output.index, offset=i)]) for i in range(output.size)
    ]
def test_relocatable_operations():
    x = RelocatableValue(1, 2)
    y = 3
    assert x + y == RelocatableValue(1, 5)
    assert x - y == RelocatableValue(1, -1)
    assert (x + y) - x == y
    assert RelocatableValue(1, 101) % 10 == RelocatableValue(1, 1)

    with pytest.raises(TypeError):
        x * y
    with pytest.raises(AssertionError):
        x + x
    with pytest.raises(AssertionError):
        RelocatableValue(1, 2) - RelocatableValue(2, 2)
def test_get_segment_used_size():
    memory = MemoryDict({
        RelocatableValue(0, 0): 0,
        RelocatableValue(0, 2): 0,
        RelocatableValue(1, 5): 0,
        RelocatableValue(1, 7): 0,
        RelocatableValue(3, 0): 0,
        RelocatableValue(4, 1): 0,
    })
    segments = MemorySegmentManager(memory=memory, prime=PRIME)
    segments.n_segments = 5
    memory.freeze()
    segments.compute_effective_sizes()
    assert [segments.get_segment_used_size(i)
            for i in range(5)] == [3, 8, 0, 1, 2]
Exemple #23
0
def test_relocate_segments():
    segments = MemorySegmentManager(memory={}, prime=PRIME)

    for i in range(5):
        assert segments.add() == RelocatableValue(segment_index=i, offset=0)

    segment_sizes = [3, 8, 0, 1, 2]
    public_memory_offsets = [
        [(0, 0), (1, 1)],
        [(i, 0) for i in range(8)],
        [],
        [],
        [(1, 2)],
    ]
    for i, (size, public_memory) in enumerate(zip(segment_sizes, public_memory_offsets)):
        segments.finalize(i, size=size, public_memory=public_memory)

    segment_offsets = segments.relocate_segments()
    assert segment_offsets == {0: 0, 1: 3, 2: 11, 3: 11, 4: 12}
    assert segments.get_public_memory_addresses(segment_offsets) == [
        (0, 0), (1, 1), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0), (8, 0), (9, 0), (10, 0), (13, 2)]
Exemple #24
0
def test_memory_validation_in_hints():
    code = """
%{ memory[ap] = 0 %}
[ap] = [ap]; ap++
%{ memory[ap] = 0 %}
[ap] = [ap]; ap++
"""

    program = compile_cairo(code=code, prime=PRIME, debug_info=True)
    initial_ap_and_fp = RelocatableValue(segment_index=1, offset=200)
    memory = {i: v for i, v in enumerate(program.data)}
    # Set memory[fp - 1] to an arbitrary value, since [fp - 1] is assumed to be set.
    memory[initial_ap_and_fp - 1] = 1234

    context = RunContext(
        pc=0,
        ap=initial_ap_and_fp,
        fp=initial_ap_and_fp,
        memory=MemoryDict(memory),
        prime=PRIME,
    )

    vm = VirtualMachine(program, context, {})

    vm.add_validation_rule(1, lambda memory, addr: {addr})
    assert vm.validated_memory._ValidatedMemoryDict__validated_addresses == set(
    )
    vm.step()
    assert vm.validated_memory._ValidatedMemoryDict__validated_addresses == {
        initial_ap_and_fp
    }

    def fail_validation(memory, addr):
        raise Exception('Validation failed.')

    vm.add_validation_rule(1, fail_validation)
    with pytest.raises(VmException, match='Exception: Validation failed.'):
        vm.step()
Exemple #25
0
 def get_additional_data(self):
     return [[list(RelocatableValue.to_tuple(addr)), signature]
             for addr, signature in sorted(self.signatures.items())]
def test_relocatable_value_frozen():
    x = RelocatableValue(1, 2)
    with pytest.raises(dataclasses.FrozenInstanceError,
                       match="cannot assign to field 'no_such_field'"):
        x.no_such_field = 5
def test_relocatable_value_serialization(byte_order, n_bytes):
    for num in [19, RelocatableValue(2, 5)]:
        assert RelocatableValue.from_bytes(
            RelocatableValue.to_bytes(num, n_bytes, byte_order),
            byte_order) == num
 def get_segment(self, segment_info: SegmentInfo):
     return self.memory.get_range(RelocatableValue(
         segment_index=segment_info.index, offset=0),
                                  size=segment_info.size)
 def extend_additional_data(self, data, relocate_callback):
     for addr, signature in data:
         self.signatures[relocate_callback(RelocatableValue.from_tuple(addr))] = signature
Exemple #30
0
 def serialize(self, field_bytes):
     return b''.join(
         RelocatableValue.to_bytes(addr, ADDR_SIZE_IN_BYTES, 'little') +
         RelocatableValue.to_bytes(value, field_bytes, 'little')
         for addr, value in self.items())