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

Implement ECDSA builtin #151

Merged
merged 18 commits into from
Nov 14, 2023
13 changes: 13 additions & 0 deletions integration_tests/builtin_tests/ecdsa_test.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
%builtins ecdsa
from starkware.cairo.common.cairo_builtins import SignatureBuiltin
from starkware.cairo.common.signature import verify_ecdsa_signature

func main{ ecdsa_ptr: SignatureBuiltin*}() {
verify_ecdsa_signature(
2718,
1735102664668487605176656616876767369909409133946409161569774794110049207117,
3086480810278599376317923499561306189851900463386393948998357832163236918254,
598673427589502599949712887611119751108407514580626464031881322743364689811,
);
return ();
}
13 changes: 13 additions & 0 deletions integration_tests/cairozero_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,19 @@ func TestPedersen(t *testing.T) {
clean("./builtin_tests/")
}

func TestECDSA(t *testing.T) {
compiledOutput, err := compileZeroCode("./builtin_tests/ecdsa_test.cairo")
require.NoError(t, err)

_, _, output, err := runVm(compiledOutput)
//Note: This fails because no addSiganture hint
fmt.Println(output)
require.Error(t, err)

clean("./builtin_tests/")
}


func TestKeccak(t *testing.T) {
compiledOutput, err := compileZeroCode("./builtin_tests/keccak_test.cairo")
require.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/vm/builtins/builtin_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func Runner(name starknetParser.Builtin) memory.BuiltinRunner {
case starknetParser.Pedersen:
return &Pedersen{}
case starknetParser.ECDSA:
panic("Not implemented")
return &ECDSA{}
case starknetParser.Keccak:
return &Keccak{}
case starknetParser.Bitwise:
Expand Down
160 changes: 160 additions & 0 deletions pkg/vm/builtins/ecdsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package builtins

import (
"fmt"

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

const ECDSAName = "ecdsa"
const cellsPerECDSA = 2

type ECDSA struct {
signatures map[uint64]ecdsa.Signature
rodrigo-pino marked this conversation as resolved.
Show resolved Hide resolved
}

// verify_ecdsa_signature(message_hash, public_key, sig_r, sig_s)
func (e *ECDSA) CheckWrite(segment *memory.Segment, offset uint64, value *memory.MemoryValue) error {
ecdsaIndex := offset % cellsPerECDSA
pubOffset := offset - ecdsaIndex
msg_offset := pubOffset + 1
jmjac marked this conversation as resolved.
Show resolved Hide resolved

pub := segment.Peek(pubOffset)
if !pub.Known() {
//Not sure if this is the right approach. It seems the msg and pub key can be passed in either order.
return nil
//return fmt.Errorf("cannot infer value: input value at offset %d is unknown", pubOffset)
}

pubX, err := pub.FieldElement() //X element of the sig
if err != nil {
return err
}

msg := segment.Peek(msg_offset)
if !msg.Known() {
return nil
//return fmt.Errorf("cannot infer value: input value at offset %d is unknown", msg_offset)
}
msgField, err := msg.FieldElement()
if err != nil {
return err
}
rodrigo-pino marked this conversation as resolved.
Show resolved Hide resolved

//Recover Y part of the public key
posY, negY, err := recoverY(pubX)
if err != nil {
return err
}

//Try first with positive y
key := starkcurve.G1Affine{X: *pubX, Y: *posY}
if !key.IsOnCurve() {
return fmt.Errorf("Key is not on curve")
jmjac marked this conversation as resolved.
Show resolved Hide resolved
}

pubKey := &ecdsa.PublicKey{A: key}
sig, ok := e.signatures[pubOffset]
if !ok {
return fmt.Errorf("Signature is missing form ECDA builtin")
jmjac marked this conversation as resolved.
Show resolved Hide resolved
}

msgBytes := msgField.Bytes()
valid, err := pubKey.Verify(sig.Bytes(), msgBytes[:], nil)
if err != nil {
return err
}

if !valid {
// Now try with Neg Y. Already know the point is on the curve so no need to check again
key = starkcurve.G1Affine{X: *pubX, Y: *negY}
pubKey = &ecdsa.PublicKey{A: key}
valid, err := pubKey.Verify(sig.Bytes(), msgBytes[:], nil)
if err != nil {
return err
}
if !valid {
return fmt.Errorf("Signature is not valid")
jmjac marked this conversation as resolved.
Show resolved Hide resolved
}
}
fmt.Println("VALID")
jmjac marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

func (e *ECDSA) InferValue(segment *memory.Segment, offset uint64) error {
return fmt.Errorf("Can't infer value")
rodrigo-pino marked this conversation as resolved.
Show resolved Hide resolved
}

/*
Hint that will call this function looks like this:

"hints": {
"6": [
{
"accessible_scopes": [
"starkware.cairo.common.signature",
"starkware.cairo.common.signature.verify_ecdsa_signature"
],
"code": "ecdsa_builtin.add_signature(ids.ecdsa_ptr.address_, (ids.signature_r, ids.signature_s))",
"flow_tracking_data": {
"ap_tracking": {
"group": 2,
"offset": 0
},
"reference_ids": {
"starkware.cairo.common.signature.verify_ecdsa_signature.ecdsa_ptr": 4,
"starkware.cairo.common.signature.verify_ecdsa_signature.message": 0,
"starkware.cairo.common.signature.verify_ecdsa_signature.public_key": 1,
"starkware.cairo.common.signature.verify_ecdsa_signature.signature_r": 2,
"starkware.cairo.common.signature.verify_ecdsa_signature.signature_s": 3
}
}
}
]
},
*/
func (e *ECDSA) AddSignature(pubOffset uint64, r, s fp.Element) error {
jmjac marked this conversation as resolved.
Show resolved Hide resolved
if e.signatures == nil {
e.signatures = make(map[uint64]ecdsa.Signature)
}
bytes := make([]byte, 0, 64)
rBytes := r.Bytes()
bytes = append(bytes, rBytes[:]...)
sBytes := s.Bytes()
bytes = append(bytes, sBytes[:]...)

sig := ecdsa.Signature{}
_, err := sig.SetBytes(bytes)
if err != nil {
return err
}

e.signatures[pubOffset] = sig
return nil
}

func (e *ECDSA) String() string {
return ECDSAName
}

// recoverY recovers the y and -y coordinate of x. True y can be either y or -y
func recoverY(x *fp.Element) (*fp.Element, *fp.Element, error) {
rodrigo-pino marked this conversation as resolved.
Show resolved Hide resolved
ALPHA := fp.NewElement(1)
BETA := fp.Element{}
_, _ = BETA.SetString("3141592653589793238462643383279502884197169399375105820974944592307816406665")
jmjac marked this conversation as resolved.
Show resolved Hide resolved
// y_squared = (x * x * x + ALPHA * x + BETA) % FIELD_PRIME
x2 := new(fp.Element).Mul(x, x)
x3 := x2.Mul(x2, x)
rodrigo-pino marked this conversation as resolved.
Show resolved Hide resolved
a := new(fp.Element).Mul(&ALPHA, x)
jmjac marked this conversation as resolved.
Show resolved Hide resolved
x3.Add(x3, a)
x3.Add(x3, &BETA)
y := x3.Sqrt(x3)
rodrigo-pino marked this conversation as resolved.
Show resolved Hide resolved
if y == nil {
return nil, nil, fmt.Errorf("Invalid Public key")
}
//TODO: Figure out if we need to check both
jmjac marked this conversation as resolved.
Show resolved Hide resolved
return y, new(fp.Element).Neg(y), nil
}
47 changes: 47 additions & 0 deletions pkg/vm/builtins/ecdsa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package builtins

import (
"testing"

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

func TestECDSA(t *testing.T) {
ecdsa := &ECDSA{}
segment := memory.EmptySegmentWithLength(5)
segment.WithBuiltinRunner(ecdsa)

pubkey, _ := new(fp.Element).SetString("1735102664668487605176656616876767369909409133946409161569774794110049207117")
msg, _ := new(fp.Element).SetString("2718")
r, _ := new(fp.Element).SetString("3086480810278599376317923499561306189851900463386393948998357832163236918254")
s, _ := new(fp.Element).SetString("598673427589502599949712887611119751108407514580626464031881322743364689811")

pubkeyValue := memory.MemoryValueFromFieldElement(pubkey)
msgValue := memory.MemoryValueFromFieldElement(msg)

require.NoError(t, ecdsa.AddSignature(0, *r, *s))
require.NoError(t, segment.Write(1, &msgValue))
require.NoError(t, segment.Write(0, &pubkeyValue))

}
func TestECDSAInvalidSig(t *testing.T) {
ecdsa := &ECDSA{}
segment := memory.EmptySegmentWithLength(5)
segment.WithBuiltinRunner(ecdsa)

pubkey, _ := new(fp.Element).SetString("1735102664668487605176656616876767369909409133946409161569774794110049207117")
msg, _ := new(fp.Element).SetString("999999999999999")
r, _ := new(fp.Element).SetString("4123123123213")
s, _ := new(fp.Element).SetString("31231231313")

pubkeyValue := memory.MemoryValueFromFieldElement(pubkey)
msgValue := memory.MemoryValueFromFieldElement(msg)

require.NoError(t, ecdsa.AddSignature(0, *r, *s))
require.NoError(t, segment.Write(0, &pubkeyValue))
err := segment.Write(1, &msgValue)
require.ErrorContains(t, err, "Signature is not valid")

}