Skip to content

Commit

Permalink
Merge branch 'main' into gofmthook
Browse files Browse the repository at this point in the history
  • Loading branch information
Sh0g0-1758 authored Aug 12, 2024
2 parents 108f2d3 + 2ea2e10 commit 2890b49
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 53 deletions.
69 changes: 62 additions & 7 deletions docs/docs/vm-fundamentals/hints.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@
sidebar_position: 3
---

# Hints
# Cairo Zero Hints

There are several hints, explain each of them here. How they interact with the VM and affect it.
Cairo Zero relies on hints to optimize various operations. To ensure compatibility across different Cairo Virtual Machine, Starkware maintains a list of [whitelisted hints](https://github.com/starkware-libs/cairo-lang/tree/0e4dab8a6065d80d1c726394f5d9d23cb451706a/src/starkware/starknet/security/whitelists) that any Cairo VM needs to implement. These hints are essential for the proper functioning of Cairo Zero programs.

In this section, we will explore the critical role of hints in Cairo Zero, focusing on two main aspects:
- High-level operations: description of the various computations that necessitate the use of Cairo Zero hints
- Detailed hint analysis: in-depth look at all hints, explaining their purpose

## Dictionaries

In the Cairo VM, dictionaries are represented by memory segments managed by the **ZeroDictionaryManager** within a scope handled by the **ScopeManager**.

**ZeroDictionaryManager**
### ZeroDictionaryManager

A **ZeroDictionaryManager** maps a segment index to a **ZeroDictionary**. Different dictionary managers can exist in various scopes.

**ZeroDictionary**
### ZeroDictionary

A **ZeroDictionary** consists of three fields:

Expand All @@ -27,23 +31,24 @@ A dictionary segment writes data in sets of three values:
2. **Previous Value**
3. **New Value**

For example, if key k1 has a value v1 and is updated to value v2, the dictionary segment will write three values: k1, v1, and v2 in consecutive offsets. Any dictionary access operation, such as reading or writing, will similarly add data to the segment. For instance, a read operation on key k1 with value v1 will write k1, v1, and v1 to consecutive offsets.
For example, if key `k1` has a value `v1` and is updated to value `v2`, the dictionary segment will write three values: `k1`, `v1`, and `v2` in consecutive offsets. Any dictionary access operation, such as reading or writing, will similarly add data to the segment. For instance, a read operation on key `k1` with value `v1` will write `k1`, `v1`, and `v1` to consecutive offsets.

Dictionary operations in Cairo are covered in two library files:
1. [dict.cairo](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/dict.cairo)
2. [default_dict.cairo](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/default_dict.cairo)

These functions require specific hints to be implemented in the VM.

**Dict functions:**
### Dict functions

1. dict_new
2. default_dict_new
3. dict_read
4. dict_write
5. dict_update
6. dict_squash

**Hint usage in dict functions:**
### Hint usage in dict functions

| Hint | Usage |
|-----------------------------------|-----------------------|
Expand Down Expand Up @@ -94,3 +99,53 @@ Example:
Input: {(key1, 0, 2), (key1, 2, 7), (key2, 4, 1), (key1, 7, 5), (key2, 1, 2)}

Output: {(key1, 0, 5), (key2, 4, 2)}

## Usort

`usort` Cairo [function](https://github.com/starkware-libs/cairo-lang/blob/0e4dab8a6065d80d1c726394f5d9d23cb451706a/src/starkware/cairo/common/usort.cairo#L8) is used to sort an array of field elements while removing duplicates. It returns the sorted array in ascending order and its length along with the multiplicities of each unique element.

### Hint usage in `usort` functions

Overall, `usort` Cairo function requires 5 hints to be executed:

**UsortEnterScope**

Enters a new scope with `__usort_max_size` set to either:
- `0` if `__usort_max_size` variable is not found in `globals()` scope
- `1 << 20` if `__usort_max_size` variable is found `globals()` scope

This hint is used to potentially set a maximum length to the array to be sorted.

**UsortBody**

Core hint that does most of the sorting operation computation. It uses a dictionnary to group input elements and their positions. Then, it sorts the unique elements and generates the output array and multiplicities, with multiplicities being the number of times a given value appeared in the input.

After execution this hint, the Cairo code will call `verify_usort` recursive function, which ensures correctness of the sorting and multiplicity counting.

**UsortVerify**

Prepares for the verification of the multiplicity of the current value in the sorted output.

**UsortVerifyMultiplicityAssert**

Checks that the array of positions in scope doesn't contain any value. This hint actually implements the base case for the `verify_multiplicity` Cairo recursive function.

**UsortVerifyMultiplicityBody**

Extracts a specific value of the sorted array with `pop`, updating indices for the verification of the next value in the recursive call.

## Blake2s Hash Function

## Keccak Hash Function

## Sha256 Hash Function

## Uint256

## Math Operations

## Elliptic Curve Operations and Signatures

## Other Hints for Various Operations

# Cairo One Hints
59 changes: 32 additions & 27 deletions pkg/vm/builtins/ecop.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

"github.com/NethermindEth/cairo-vm-go/pkg/utils"
mem "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory"
f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
"github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
"github.com/holiman/uint256"
)

Expand All @@ -15,7 +15,7 @@ const cellsPerEcOp = 7
const inputCellsPerEcOp = 5
const instancesPerComponentEcOp = 1

var feltThree f.Element = f.Element(
var feltThree fp.Element = fp.Element(
[]uint64{
18446744073709551521,
18446744073709551615,
Expand All @@ -25,6 +25,7 @@ var feltThree f.Element = f.Element(

type EcOp struct {
ratio uint64
cache map[uint64]fp.Element
}

func (e *EcOp) String() string {
Expand All @@ -36,6 +37,12 @@ func (e *EcOp) CheckWrite(segment *mem.Segment, offset uint64, value *mem.Memory
}

func (e *EcOp) InferValue(segment *mem.Segment, offset uint64) error {
// check if the value is already in the cache
value, ok := e.cache[offset]
if ok {
mv := mem.MemoryValueFromFieldElement(&value)
return segment.Write(offset, &mv)
}
// get the current slot index and verify it is an output cell
ecopIndex := offset % cellsPerEcOp
if ecopIndex < inputCellsPerEcOp {
Expand All @@ -62,7 +69,7 @@ func (e *EcOp) InferValue(segment *mem.Segment, offset uint64) error {
}

// unwrap the values as felts
inputsFelt := [5]*f.Element{}
inputsFelt := [5]*fp.Element{}
for i := range inputs {
felt, err := inputs[i].FieldElement()
if err != nil {
Expand Down Expand Up @@ -94,15 +101,13 @@ func (e *EcOp) InferValue(segment *mem.Segment, offset uint64) error {
// store the resulting point `r`
outputOff := inputOff + inputCellsPerEcOp

rxMV := mem.MemoryValueFromFieldElement(&r.X)
err = segment.Write(outputOff, &rxMV)
if err != nil {
return err
}
// store the x and y coordinates of the resulting point
e.cache[outputOff] = r.X
e.cache[outputOff+1] = r.Y

ryMV := mem.MemoryValueFromFieldElement(&r.Y)
err = segment.Write(outputOff+1, &ryMV)
return err
value = e.cache[offset]
mv := mem.MemoryValueFromFieldElement(&value)
return segment.Write(offset, &mv)
}

func (e *EcOp) GetAllocatedSize(segmentUsedSize uint64, vmCurrentStep uint64) (uint64, error) {
Expand All @@ -111,22 +116,22 @@ func (e *EcOp) GetAllocatedSize(segmentUsedSize uint64, vmCurrentStep uint64) (u

// structure to represent a point in the elliptic curve
type point struct {
X, Y f.Element
X, Y fp.Element
}

// returns true if a point `p` belongs to the `ec` curve ruled by the params `alpha` and
// `beta`. In other words, true if y^2 = x^3 + alpha * x + beta
func (p *point) onCurve(alpha, beta *f.Element) bool {
func (p *point) onCurve(alpha, beta *fp.Element) bool {
// calculate lhs
y2 := f.Element{}
y2 := fp.Element{}
y2.Square(&p.Y)

// calculate rhs
x3 := f.Element{}
x3 := fp.Element{}
x3.Square(&p.X)
x3.Mul(&x3, &p.X)

ax := f.Element{}
ax := fp.Element{}
ax.Mul(alpha, &p.X)

x3.Add(&x3, &ax)
Expand All @@ -138,7 +143,7 @@ func (p *point) onCurve(alpha, beta *f.Element) bool {

// returns the result of the ecop operation on points `P` and `Q` with scalar
// `m` and param `alpha`. The resulting point `R` is equal to P + m * Q
func ecop(p *point, q *point, m, alpha *f.Element) (point, error) {
func ecop(p *point, q *point, m, alpha *fp.Element) (point, error) {
partialSum := *p
doublePoint := *q

Expand Down Expand Up @@ -178,20 +183,20 @@ func ecop(p *point, q *point, m, alpha *f.Element) (point, error) {
// always different
func ecadd(p *point, q *point) point {
// get the slope between the two points
slope := f.Element{}
slope := fp.Element{}
slope.Sub(&p.Y, &q.Y)
denom := f.Element{}
denom := fp.Element{}
denom.Sub(&p.X, &q.X)
slope.Div(&slope, &denom)

// get the x coordinate: x = slope^2 - p.X - q.X
x := f.Element{}
x := fp.Element{}
x.Square(&slope)
x.Sub(&x, &p.X)
x.Sub(&x, &q.X)

// get the y coordinate: y = slope * (p.X - x) - p.Y
y := f.Element{}
y := fp.Element{}
y.Sub(&p.X, &x)
y.Mul(&y, &slope)
y.Sub(&y, &p.Y)
Expand All @@ -201,28 +206,28 @@ func ecadd(p *point, q *point) point {

// performs elliptic curve doubling over a point. Assumes `y` coordinate
// is different than 0
func ecdouble(p *point, alpha *f.Element) point {
func ecdouble(p *point, alpha *fp.Element) point {
// get the double slope
doubleSlope := f.Element{}
doubleSlope := fp.Element{}
doubleSlope.Square(&p.X)
doubleSlope.Mul(
&doubleSlope,
&feltThree,
)
doubleSlope.Add(&doubleSlope, alpha)
denom := f.Element{}
denom := fp.Element{}
denom.Double(&p.Y)
doubleSlope.Div(&doubleSlope, &denom)

// get the x coordinate: x = slope^2 - 2 * p.X
x := f.Element{}
x := fp.Element{}
x.Square(&doubleSlope)
doublePx := f.Element{}
doublePx := fp.Element{}
doublePx.Double(&p.X)
x.Sub(&x, &doublePx)

// get the y coordinates: y = slope * (p.X - x) - p.Y
y := f.Element{}
y := fp.Element{}
y.Sub(&p.X, &x)
y.Mul(&y, &doubleSlope)
y.Sub(&y, &p.Y)
Expand Down
2 changes: 1 addition & 1 deletion pkg/vm/builtins/ecop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestEcOp(t *testing.T) {
r := ecadd(&point{*px, *py}, &mult)

segment := memory.EmptySegmentWithLength(cellsPerEcOp)
ecop := &EcOp{}
ecop := &EcOp{ratio: 1024, cache: make(map[uint64]fp.Element)}
segment.WithBuiltinRunner(ecop)

// write P to segment
Expand Down
16 changes: 10 additions & 6 deletions pkg/vm/builtins/keccak.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@ const instancesPerComponentKeccak = 16

type Keccak struct {
ratio uint64
cache map[uint64]fp.Element
}

func (k *Keccak) CheckWrite(segment *memory.Segment, offset uint64, value *memory.MemoryValue) error {
return nil
}

func (k *Keccak) InferValue(segment *memory.Segment, offset uint64) error {
value, ok := k.cache[offset]
if ok {
mv := memory.MemoryValueFromFieldElement(&value)
return segment.Write(offset, &mv)
}
hashIndex := offset % cellsPerKeccak
if hashIndex < inputCellsPerKeccak {
return errors.New("cannot infer value")
Expand Down Expand Up @@ -69,13 +75,11 @@ func (k *Keccak) InferValue(segment *memory.Segment, offset uint64) error {
copy(bytes[:], output[i*25:i*25+25])
//This is 25*8 bits which is smaller than max felt 252 bits so no need to check the error
v, _ := fp.LittleEndian.Element(&bytes)
mv := memory.MemoryValueFromFieldElement(&v)
err := segment.Write(startOffset+inputCellsPerKeccak+uint64(i), &mv)
if err != nil {
return err
}
k.cache[startOffset+inputCellsPerKeccak+uint64(i)] = v
}
return nil
value = k.cache[offset]
mv := memory.MemoryValueFromFieldElement(&value)
return segment.Write(offset, &mv)
}

func (k *Keccak) String() string {
Expand Down
2 changes: 1 addition & 1 deletion pkg/vm/builtins/keccak_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func TestKeccakBuiltin(t *testing.T) {
keccak := &Keccak{}
keccak := &Keccak{ratio: 2048, cache: make(map[uint64]fp.Element)}
segment := memory.EmptySegmentWithLength(9)
segment.WithBuiltinRunner(keccak)

Expand Down
7 changes: 4 additions & 3 deletions pkg/vm/builtins/layouts.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/NethermindEth/cairo-vm-go/pkg/parsers/starknet"
"github.com/NethermindEth/cairo-vm-go/pkg/vm/memory"
"github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
)

type LayoutBuiltin struct {
Expand Down Expand Up @@ -43,9 +44,9 @@ func getStarknetWithKeccakLayout() Layout {
{Runner: &RangeCheck{ratio: 16}, Builtin: starknet.RangeCheck},
{Runner: &ECDSA{ratio: 2048}, Builtin: starknet.ECDSA},
{Runner: &Bitwise{ratio: 64}, Builtin: starknet.Bitwise},
{Runner: &EcOp{ratio: 1024}, Builtin: starknet.ECOP},
{Runner: &Keccak{ratio: 2048}, Builtin: starknet.Keccak},
{Runner: &Poseidon{ratio: 32}, Builtin: starknet.Poseidon},
{Runner: &EcOp{ratio: 1024, cache: make(map[uint64]fp.Element)}, Builtin: starknet.ECOP},
{Runner: &Keccak{ratio: 2048, cache: make(map[uint64]fp.Element)}, Builtin: starknet.Keccak},
{Runner: &Poseidon{ratio: 32, cache: make(map[uint64]fp.Element)}, Builtin: starknet.Poseidon},
}}
}

Expand Down
17 changes: 10 additions & 7 deletions pkg/vm/builtins/poseidon.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ const instancesPerComponentPoseidon = 1

type Poseidon struct {
ratio uint64
cache map[uint64]fp.Element
}

func (p *Poseidon) CheckWrite(segment *mem.Segment, offset uint64, value *mem.MemoryValue) error {
return nil
}

func (p *Poseidon) InferValue(segment *mem.Segment, offset uint64) error {
value, ok := p.cache[offset]
if ok {
mv := mem.MemoryValueFromFieldElement(&value)
return segment.Write(offset, &mv)
}
poseidonIndex := offset % cellsPerPoseidon
if poseidonIndex < inputCellsPerPoseidon {
return errors.New("cannot infer value")
Expand All @@ -42,14 +48,11 @@ func (p *Poseidon) InferValue(segment *mem.Segment, offset uint64) error {
// poseidon hash calculation
hash := PoseidonPerm(poseidonInputValues[0], poseidonInputValues[1], poseidonInputValues[2])
for i := 0; i < 3; i++ {
hashValue := mem.MemoryValueFromFieldElement(&hash[i])
err := segment.Write(baseOffset+uint64(i+3), &hashValue)
if err != nil {
return err
}

p.cache[offset+uint64(i)] = hash[i]
}
return nil
value = p.cache[offset]
mv := mem.MemoryValueFromFieldElement(&value)
return segment.Write(offset, &mv)
}

func (p *Poseidon) GetAllocatedSize(segmentUsedSize uint64, vmCurrentStep uint64) (uint64, error) {
Expand Down
Loading

0 comments on commit 2890b49

Please sign in to comment.