Skip to content

Commit

Permalink
Add prime256v1 identities.
Browse files Browse the repository at this point in the history
  • Loading branch information
q-uint committed Mar 23, 2024
1 parent f40de9f commit 9353151
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 13 deletions.
37 changes: 32 additions & 5 deletions agent_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package agent_test

import (
"crypto/ed25519"
"crypto/rand"
"encoding/json"
"fmt"
"github.com/aviate-labs/agent-go"
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions identity/anonymous.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions identity/ed25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions identity/ed25519_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
4 changes: 4 additions & 0 deletions identity/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
98 changes: 98 additions & 0 deletions identity/prime256v1.go
Original file line number Diff line number Diff line change
@@ -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
}
54 changes: 54 additions & 0 deletions identity/prime256v1_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
23 changes: 17 additions & 6 deletions identity/secp256k1.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"github.com/aviate-labs/agent-go/principal"
"github.com/aviate-labs/secp256k1"
"math/big"
"slices"
)

Expand All @@ -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),
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions identity/secp256k1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package identity

import (
"bytes"
"encoding/hex"
"testing"
)

Expand All @@ -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")
}
}

0 comments on commit 9353151

Please sign in to comment.