From 9353151f5b8d44c3f556894694de7c8aad7dc890 Mon Sep 17 00:00:00 2001 From: Quint Daenen Date: Sat, 23 Mar 2024 09:34:49 +0100 Subject: [PATCH] Add prime256v1 identities. --- agent_test.go | 37 ++++++++++++-- identity/anonymous.go | 8 +++ identity/ed25519.go | 9 +++- identity/ed25519_test.go | 11 +++++ identity/identity.go | 4 ++ identity/prime256v1.go | 98 +++++++++++++++++++++++++++++++++++++ identity/prime256v1_test.go | 54 ++++++++++++++++++++ identity/secp256k1.go | 23 ++++++--- identity/secp256k1_test.go | 33 +++++++++++++ 9 files changed, 264 insertions(+), 13 deletions(-) create mode 100644 identity/prime256v1.go create mode 100644 identity/prime256v1_test.go diff --git a/agent_test.go b/agent_test.go index 409b74d..67fd07c 100644 --- a/agent_test.go +++ b/agent_test.go @@ -1,8 +1,6 @@ package agent_test import ( - "crypto/ed25519" - "crypto/rand" "encoding/json" "fmt" "github.com/aviate-labs/agent-go" @@ -51,9 +49,38 @@ func Example_json() { // {"e8s":0} } -func Example_query() { - publicKey, privateKey, _ := ed25519.GenerateKey(rand.Reader) - id, _ := identity.NewEd25519Identity(publicKey, privateKey) +func Example_query_prime256v1() { + id, _ := identity.NewRandomPrime256v1Identity() + ledgerID, _ := principal.Decode("ryjl3-tyaaa-aaaaa-aaaba-cai") + a, _ := agent.New(agent.Config{Identity: id}) + var balance struct { + E8S uint64 `ic:"e8s"` + } + _ = a.Query(ledgerID, "account_balance_dfx", []any{map[string]any{ + "account": "9523dc824aa062dcd9c91b98f4594ff9c6af661ac96747daef2090b7fe87037d", + }}, []any{&balance}) + fmt.Println(balance.E8S) + // Output: + // 0 +} + +func Example_query_ed25519() { + id, _ := identity.NewRandomEd25519Identity() + ledgerID, _ := principal.Decode("ryjl3-tyaaa-aaaaa-aaaba-cai") + a, _ := agent.New(agent.Config{Identity: id}) + var balance struct { + E8S uint64 `ic:"e8s"` + } + _ = a.Query(ledgerID, "account_balance_dfx", []any{map[string]any{ + "account": "9523dc824aa062dcd9c91b98f4594ff9c6af661ac96747daef2090b7fe87037d", + }}, []any{&balance}) + fmt.Println(balance.E8S) + // Output: + // 0 +} + +func Example_query_secp256k1() { + id, _ := identity.NewRandomSecp256k1Identity() ledgerID, _ := principal.Decode("ryjl3-tyaaa-aaaaa-aaaba-cai") a, _ := agent.New(agent.Config{Identity: id}) var balance struct { diff --git a/identity/anonymous.go b/identity/anonymous.go index 62fc621..1b57bd4 100644 --- a/identity/anonymous.go +++ b/identity/anonymous.go @@ -7,6 +7,14 @@ import ( // AnonymousIdentity is an identity that does not sign messages. type AnonymousIdentity struct{} +func (id AnonymousIdentity) Verify(_, _ []byte) bool { + return true +} + +func (id AnonymousIdentity) ToPEM() ([]byte, error) { + return nil, nil +} + // PublicKey returns the public key of the identity. func (id AnonymousIdentity) PublicKey() []byte { return nil diff --git a/identity/ed25519.go b/identity/ed25519.go index 13021c1..9813bc4 100644 --- a/identity/ed25519.go +++ b/identity/ed25519.go @@ -45,8 +45,8 @@ func NewEd25519Identity(publicKey ed25519.PublicKey, privateKey ed25519.PrivateK // NewEd25519IdentityFromPEM creates a new identity from the given PEM file. func NewEd25519IdentityFromPEM(data []byte) (*Ed25519Identity, error) { - block, _ := pem.Decode(data) - if block.Type != "PRIVATE KEY" { + block, remainder := pem.Decode(data) + if block == nil || len(remainder) != 0 || block.Type != "PRIVATE KEY" { return nil, fmt.Errorf("invalid pem file") } privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) @@ -85,6 +85,11 @@ func (id Ed25519Identity) Sign(data []byte) []byte { return ed25519.Sign(id.privateKey, data) } +// Verify verifies the given signature. +func (id Ed25519Identity) Verify(data, signature []byte) bool { + return ed25519.Verify(id.publicKey, data, signature) +} + // ToPEM returns the PEM representation of the identity. func (id Ed25519Identity) ToPEM() ([]byte, error) { data, err := x509.MarshalPKCS8PrivateKey(id.privateKey) diff --git a/identity/ed25519_test.go b/identity/ed25519_test.go index 5a93e7d..cf315f3 100644 --- a/identity/ed25519_test.go +++ b/identity/ed25519_test.go @@ -22,3 +22,14 @@ func TestNewEd25519Identity(t *testing.T) { t.Error() } } + +func TestEd25519Identity_Sign(t *testing.T) { + id, err := NewRandomEd25519Identity() + if err != nil { + t.Fatal(err) + } + data := []byte("hello") + if !id.Verify(data, id.Sign(data)) { + t.Error() + } +} diff --git a/identity/identity.go b/identity/identity.go index 206418a..669a563 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -23,4 +23,8 @@ type Identity interface { Sign(msg []byte) []byte // PublicKey returns the public key of the identity. PublicKey() []byte + // Verify verifies the signature of the given message. + Verify(msg, sig []byte) bool + // ToPEM returns the PEM representation of the identity. + ToPEM() ([]byte, error) } diff --git a/identity/prime256v1.go b/identity/prime256v1.go new file mode 100644 index 0000000..9b78072 --- /dev/null +++ b/identity/prime256v1.go @@ -0,0 +1,98 @@ +package identity + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/asn1" + "encoding/pem" + "fmt" + "github.com/aviate-labs/agent-go/principal" + "math/big" +) + +var prime256v1OID = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} + +type Prime256v1Identity struct { + privateKey *ecdsa.PrivateKey + publicKey *ecdsa.PublicKey +} + +func NewRandomPrime256v1Identity() (*Prime256v1Identity, error) { + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + return NewPrime256v1Identity(privateKey), nil +} + +func (id Prime256v1Identity) Sender() principal.Principal { + return principal.NewSelfAuthenticating(id.PublicKey()) +} + +func (id Prime256v1Identity) Sign(msg []byte) []byte { + hashData := sha256.Sum256(msg) + sigR, sigS, _ := ecdsa.Sign(rand.Reader, id.privateKey, hashData[:]) + var buffer [64]byte + r := sigR.Bytes() + s := sigS.Bytes() + copy(buffer[(32-len(r)):], r) + copy(buffer[(64-len(s)):], s) + return buffer[:] +} + +func (id Prime256v1Identity) Verify(msg, sig []byte) bool { + r := new(big.Int).SetBytes(sig[:32]) + s := new(big.Int).SetBytes(sig[32:]) + hashData := sha256.Sum256(msg) + return ecdsa.Verify(id.publicKey, hashData[:], r, s) +} + +func NewPrime256v1Identity(privateKey *ecdsa.PrivateKey) *Prime256v1Identity { + return &Prime256v1Identity{ + privateKey: privateKey, + publicKey: privateKey.Public().(*ecdsa.PublicKey), + } +} + +func NewPrime256v1IdentityFromPEM(data []byte) (*Prime256v1Identity, error) { + block, remainder := pem.Decode(data) + if block == nil || block.Type != "EC PRIVATE KEY" || len(remainder) != 0 { + return nil, fmt.Errorf("invalid pem file") + } + privateKey, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return NewPrime256v1Identity(privateKey), nil +} + +func derEncodePrime256v1PublicKey(key *ecdsa.PublicKey) ([]byte, error) { + return asn1.Marshal(ecPublicKey{ + Metadata: []asn1.ObjectIdentifier{ + ecPublicKeyOID, + prime256v1OID, + }, + PublicKey: asn1.BitString{ + Bytes: marshal(elliptic.P256(), key.X, key.Y), + }, + }) +} + +func (id Prime256v1Identity) PublicKey() []byte { + der, _ := derEncodePrime256v1PublicKey(id.publicKey) + return der +} + +func (id Prime256v1Identity) ToPEM() ([]byte, error) { + data, err := x509.MarshalECPrivateKey(id.privateKey) + if err != nil { + return nil, err + } + return pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: data, + }), nil +} diff --git a/identity/prime256v1_test.go b/identity/prime256v1_test.go new file mode 100644 index 0000000..88f5942 --- /dev/null +++ b/identity/prime256v1_test.go @@ -0,0 +1,54 @@ +package identity + +import ( + "bytes" + "encoding/hex" + "testing" +) + +func TestNewPrime256v1Identity(t *testing.T) { + id, _ := NewRandomPrime256v1Identity() + data, err := id.ToPEM() + if err != nil { + t.Fatal(err) + } + id_, err := NewPrime256v1IdentityFromPEM(data) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(id.privateKey.D.Bytes(), id_.privateKey.D.Bytes()) { + t.Error() + } + if !bytes.Equal(id.PublicKey(), id_.PublicKey()) { + t.Error() + } +} + +func TestPrime256v1Identity_Sign(t *testing.T) { + id, err := NewRandomPrime256v1Identity() + if err != nil { + t.Fatal(err) + } + data := []byte("hello") + if !id.Verify(data, id.Sign(data)) { + t.Error() + } +} + +func TestNewPrime256v1IdentityFromPEM(t *testing.T) { + pem := ` +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIL1ybmbwx+uKYsscOZcv71MmKhrNqfPP0ke1unET5AY4oAoGCCqGSM49 +AwEHoUQDQgAEUbbZV4NerZTPWfbQ749/GNLu8TaH8BUS/I7/+ipsu+MPywfnBFIZ +Sks4xGbA/ZbazsrMl4v446U5UIVxCGGaKw== +-----END EC PRIVATE KEY----- +` + der, _ := hex.DecodeString("3059301306072a8648ce3d020106082a8648ce3d0301070342000451b6d957835ead94cf59f6d0ef8f7f18d2eef13687f01512fc8efffa2a6cbbe30fcb07e70452194a4b38c466c0fd96dacecacc978bf8e3a53950857108619a2b") + id, err := NewPrime256v1IdentityFromPEM([]byte(pem)) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(id.PublicKey(), der) { + t.Fatal("public key mismatch") + } +} diff --git a/identity/secp256k1.go b/identity/secp256k1.go index 64310f5..3f90384 100644 --- a/identity/secp256k1.go +++ b/identity/secp256k1.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/aviate-labs/agent-go/principal" "github.com/aviate-labs/secp256k1" + "math/big" "slices" ) @@ -17,8 +18,8 @@ func derEncodeSecp256k1PublicKey(key *secp256k1.PublicKey) ([]byte, error) { point := key.ToECDSA() return asn1.Marshal(ecPublicKey{ Metadata: []asn1.ObjectIdentifier{ - {1, 2, 840, 10045, 2, 1}, // ec.PublicKey - secp256k1OID, // Secp256k1 + ecPublicKeyOID, + secp256k1OID, }, PublicKey: asn1.BitString{ Bytes: marshal(secp256k1.S256(), point.X, point.Y), @@ -80,10 +81,8 @@ func (id Secp256k1Identity) Sender() principal.Principal { } func (id Secp256k1Identity) Sign(msg []byte) []byte { - hash := sha256.New() - hash.Write(msg) - hashData := hash.Sum(nil) - sig, _ := id.privateKey.Sign(hashData) + hashData := sha256.Sum256(msg) + sig, _ := id.privateKey.Sign(hashData[:]) var buffer [64]byte r := sig.R.Bytes() s := sig.S.Bytes() @@ -121,6 +120,18 @@ func (id Secp256k1Identity) ToPEM() ([]byte, error) { ), nil } +// Verify verifies the signature of the given message. +func (id Secp256k1Identity) Verify(msg, sig []byte) bool { + signature := secp256k1.Signature{ + R: new(big.Int).SetBytes(sig[:32]), + S: new(big.Int).SetBytes(sig[32:]), + } + hashData := sha256.Sum256(msg) + return signature.Verify(hashData[:], id.publicKey) +} + +var ecPublicKeyOID = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} + type ecPrivateKey struct { Version int PrivateKey []byte diff --git a/identity/secp256k1_test.go b/identity/secp256k1_test.go index a4ae5d2..5ac4a6a 100644 --- a/identity/secp256k1_test.go +++ b/identity/secp256k1_test.go @@ -2,6 +2,7 @@ package identity import ( "bytes" + "encoding/hex" "testing" ) @@ -22,3 +23,35 @@ func TestNewSecp256k1Identity(t *testing.T) { t.Error() } } + +func TestSecp256k1Identity_Sign(t *testing.T) { + id, err := NewRandomSecp256k1Identity() + if err != nil { + t.Fatal(err) + } + data := []byte("hello") + if !id.Verify(data, id.Sign(data)) { + t.Error() + } +} + +func TestNewSecp256k1IdentityFromPEM(t *testing.T) { + pem := ` +-----BEGIN EC PARAMETERS----- +BgUrgQQACg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIAgy7nZEcVHkQ4Z1Kdqby8SwyAiyKDQmtbEHTIM+WNeBoAcGBSuBBAAK +oUQDQgAEgO87rJ1ozzdMvJyZQ+GABDqUxGLvgnAnTlcInV3NuhuPv4O3VGzMGzeB +N3d26cRxD99TPtm8uo2OuzKhSiq6EQ== +-----END EC PRIVATE KEY----- +` + der, _ := hex.DecodeString("3056301006072a8648ce3d020106052b8104000a0342000480ef3bac9d68cf374cbc9c9943e180043a94c462ef8270274e57089d5dcdba1b8fbf83b7546ccc1b3781377776e9c4710fdf533ed9bcba8d8ebb32a14a2aba11") + id, err := NewSecp256k1IdentityFromPEM([]byte(pem)) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(id.PublicKey(), der) { + t.Fatal("public key mismatch") + } +}