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 ();
}
15 changes: 13 additions & 2 deletions integration_tests/cairozero_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,26 @@ 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)

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

clean("./builtin_tests/")
}

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

_, _, _, err = runVm(compiledOutput)
// todo(rodro): This test is failing due to the lack of hint processing. It should be address soon
require.Error(t, err)
clean("./builtin_tests/")

clean("./builtin_tests/")
}

func TestKeccak(t *testing.T) {
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
156 changes: 156 additions & 0 deletions pkg/vm/builtins/ecdsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package builtins

import (
"fmt"

"github.com/NethermindEth/cairo-vm-go/pkg/utils"
"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
msgOffset := pubOffset + 1

pub := segment.Peek(pubOffset)
msg := segment.Peek(msgOffset)

//Both must be known to check the signature
if !msg.Known() || !pub.Known() {
return nil
}

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

msgField, err := msg.FieldElement()
if err != nil {
return err
}

//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")
}

pubKey := &ecdsa.PublicKey{A: key}
sig, ok := e.signatures[pubOffset]
if !ok {
return fmt.Errorf("signature is missing form ECDA builtin")
}

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")
}
}
return nil
}

func (e *ECDSA) InferValue(segment *memory.Segment, offset uint64) error {
return fmt.Errorf("can't infer value")
}

/*
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 {
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) {
// y_squared = (x * x * x + ALPHA * x + BETA) % FIELD_PRIME
ax := &fp.Element{}
ax.Mul(&utils.Alpha, x)
x2 := &fp.Element{}
x2.Mul(x, x)
x2.Mul(x2, x)
x2.Add(x2, ax)
x2.Add(x2, &utils.Beta)
y := x2.Sqrt(x2)
if y == nil {
return fp.Element{}, fp.Element{}, fmt.Errorf("Invalid Public key")
}
negY := fp.Element{}
negY.Neg(y)
return *y, negY, 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")

}