From 6aca91ee6a8b490146324da22ab253dee71746ef Mon Sep 17 00:00:00 2001 From: Fuchun Zhang Date: Tue, 14 Nov 2023 09:40:16 +0800 Subject: [PATCH] add go wrap of this lib --- go.mod | 8 +++ go.sum | 4 ++ gomicroecc/cgo_amd64.go | 32 +++++++++ gomicroecc/cgo_arm64.go | 29 ++++++++ gomicroecc/cmd/example/main.go | 42 +++++++++++ gomicroecc/cmd/example/private_key.txt | 5 ++ gomicroecc/cmd/gen_keys/main.go | 21 ++++++ gomicroecc/ecc.go | 97 ++++++++++++++++++++++++++ gomicroecc/go_wrap.h | 36 ++++++++++ 9 files changed, 274 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 gomicroecc/cgo_amd64.go create mode 100644 gomicroecc/cgo_arm64.go create mode 100644 gomicroecc/cmd/example/main.go create mode 100644 gomicroecc/cmd/example/private_key.txt create mode 100644 gomicroecc/cmd/gen_keys/main.go create mode 100644 gomicroecc/ecc.go create mode 100644 gomicroecc/go_wrap.h diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b239dd0 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/jestan/easy-ecc + +go 1.21.3 + +require ( + github.com/petermattis/fastcgo v0.0.0-20170816164731-e33892440abb // indirect + golang.org/x/crypto v0.15.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b4589b9 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/petermattis/fastcgo v0.0.0-20170816164731-e33892440abb h1:NO9wJiBhQI0rgcuo5m4QLVe2lFDZxiI7OuQmFlScFC0= +github.com/petermattis/fastcgo v0.0.0-20170816164731-e33892440abb/go.mod h1:5CA9I6tKHPWMxtx7rxplJ5ScFukOelD7qe06IL83a7I= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= diff --git a/gomicroecc/cgo_amd64.go b/gomicroecc/cgo_amd64.go new file mode 100644 index 0000000..1fa1a7d --- /dev/null +++ b/gomicroecc/cgo_amd64.go @@ -0,0 +1,32 @@ +package gomicroecc + +/* +#define _NEED_SIGN_VERRIFY +#include "go_wrap.h" +*/ +import "C" +import ( + "unsafe" + + "github.com/petermattis/fastcgo" +) + +func Sign(out []byte, privateKey []byte, hash []byte) { + fastcgo.UnsafeCall4(C.sign, + uint64(uintptr(unsafe.Pointer(&out))), + uint64(uintptr(unsafe.Pointer(&privateKey))), + uint64(uintptr(unsafe.Pointer(&hash))), + 0, + ) +} + +func Verify(publicKey []byte, hash []byte, signature []byte) bool { + var ret int64 + fastcgo.UnsafeCall4(C.verify, + uint64(uintptr(unsafe.Pointer(&publicKey))), + uint64(uintptr(unsafe.Pointer(&hash))), + uint64(uintptr(unsafe.Pointer(&signature))), + uint64(uintptr(unsafe.Pointer(&ret))), + ) + return ret != 0 +} diff --git a/gomicroecc/cgo_arm64.go b/gomicroecc/cgo_arm64.go new file mode 100644 index 0000000..495bd12 --- /dev/null +++ b/gomicroecc/cgo_arm64.go @@ -0,0 +1,29 @@ +package gomicroecc + +/* +#define _NEED_SIGN_VERRIFY +#include "go_wrap.h" +*/ +import "C" +import ( + "unsafe" +) + +func Sign(out []byte, privateKey []byte, hash []byte) { + C.sign( + (*C.SliceHeader)(unsafe.Pointer(&out)), + (*C.SliceHeader)(unsafe.Pointer(&privateKey)), + (*C.SliceHeader)(unsafe.Pointer(&hash)), + ) +} + +func Verify(publicKey []byte, hash []byte, signature []byte) bool { + var ret C.longlong + C.verify( + (*C.SliceHeader)(unsafe.Pointer(&publicKey)), + (*C.SliceHeader)(unsafe.Pointer(&hash)), + (*C.SliceHeader)(unsafe.Pointer(&signature)), + &ret, + ) + return ret != 0 +} diff --git a/gomicroecc/cmd/example/main.go b/gomicroecc/cmd/example/main.go new file mode 100644 index 0000000..56ce40d --- /dev/null +++ b/gomicroecc/cmd/example/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "encoding/hex" + "log" + "os" + + "github.com/jestan/easy-ecc/gomicroecc" +) + +func main() { + s, err := os.ReadFile("./private_key.txt") + if err != nil { + log.Fatalln(err.Error()) + } + privateKey, err := gomicroecc.ParsePrivateKey(string(s)) + if err != nil { + log.Fatalln(err.Error()) + } + sha256Value, err := hex.DecodeString("758a18de504f9aed6e23a203cdb1d207cb86571d84c41e1f66c1deec48c01de5") + if err != nil { + log.Fatalln(err.Error()) + } + // + outSign := make([]byte, 64) + gomicroecc.Sign(outSign, privateKey.D.Bytes(), sha256Value) + golangSign, err := gomicroecc.EncodeSignature(outSign) + if err != nil { + log.Fatalln(err.Error()) + } + log.Println("sign:", hex.EncodeToString(golangSign)) + // + publicKeyBytes := make([]byte, 0, 64) + publicKeyBytes = append(publicKeyBytes, privateKey.PublicKey.X.Bytes()...) + publicKeyBytes = append(publicKeyBytes, privateKey.PublicKey.Y.Bytes()...) + ret := gomicroecc.Verify(publicKeyBytes, sha256Value, outSign) + if !ret { + log.Println("Verify fail") + return + } + log.Println("Verify ok") +} diff --git a/gomicroecc/cmd/example/private_key.txt b/gomicroecc/cmd/example/private_key.txt new file mode 100644 index 0000000..8b1fa5d --- /dev/null +++ b/gomicroecc/cmd/example/private_key.txt @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTRK9HOn+vaDmUqnQ +gBqPaavgIi7plGQmgyfZO/rN4mqhRANCAATW9d/H1AvF5Uqz9HhFvmQqi8+tdkeB +kgNcjnZpWzJ5NhSABXMi4sUJRL7wMTAWvDeg9TMqt7cPYfcpDUVOe1k5 +-----END PRIVATE KEY----- diff --git a/gomicroecc/cmd/gen_keys/main.go b/gomicroecc/cmd/gen_keys/main.go new file mode 100644 index 0000000..299cb84 --- /dev/null +++ b/gomicroecc/cmd/gen_keys/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + + "github.com/jestan/easy-ecc/gomicroecc" +) + +func main() { + privateKeyPEM, publicKeyPEM, err := gomicroecc.GenerateECDSAP256KeyPair() + if err != nil { + fmt.Println("Error:", err) + return + } + + fmt.Println("Private Key (PKCS#8 PEM):") + fmt.Println(privateKeyPEM) + + fmt.Println("\nPublic Key (PEM):") + fmt.Println(publicKeyPEM) +} diff --git a/gomicroecc/ecc.go b/gomicroecc/ecc.go new file mode 100644 index 0000000..c74377a --- /dev/null +++ b/gomicroecc/ecc.go @@ -0,0 +1,97 @@ +package gomicroecc + +/* +#define _NEED_INIT +#include "go_wrap.h" +#include "../uECC.c" +*/ +import "C" +import ( + "errors" + "fmt" + + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/pem" + + "golang.org/x/crypto/cryptobyte" + "golang.org/x/crypto/cryptobyte/asn1" +) + +func init() { + C.init_ecc() +} + +// GenerateECDSAP256KeyPair generates an ECDSA P-256 key pair and returns them as PEM-encoded strings. +func GenerateECDSAP256KeyPair() (string, string, error) { + // Generate a new ECDSA P-256 private key + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return "", "", err + } + + // Encode the private key as PKCS#8 PEM + privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return "", "", err + } + privateKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyBytes}) + + // Encode the public key as PEM + publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) + if err != nil { + return "", "", err + } + publicKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: publicKeyBytes}) + + return string(privateKeyPEM), string(publicKeyPEM), nil +} + +func ParsePrivateKey(s string) (*ecdsa.PrivateKey, error) { + privateKeyPEMBlock, _ := pem.Decode([]byte(s)) + if privateKeyPEMBlock == nil { + return nil, errors.New("decode error") + } + var err error + var privateKeyAny any + privateKeyAny, err = x509.ParsePKCS8PrivateKey(privateKeyPEMBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("x509.ParsePKCS8PrivateKey error, err=%s", err.Error()) + } + return privateKeyAny.(*ecdsa.PrivateKey), nil +} + +// addASN1IntBytes encodes in ASN.1 a positive integer represented as +// a big-endian byte slice with zero or more leading zeroes. +// copy from crypto/ecdsa +func addASN1IntBytes(b *cryptobyte.Builder, bytes []byte) { + for len(bytes) > 0 && bytes[0] == 0 { + bytes = bytes[1:] + } + if len(bytes) == 0 { + b.SetError(errors.New("invalid integer")) + return + } + b.AddASN1(asn1.INTEGER, func(c *cryptobyte.Builder) { + if bytes[0]&0x80 != 0 { + c.AddUint8(0) + } + c.AddBytes(bytes) + }) +} + +// EncodeSignature encode signature as golang format +// copy from crypto/ecdsa +func EncodeSignature(signature []byte) ([]byte, error) { + if len(signature) != 64 { + return nil, errors.New("signature len must be 64") + } + var b cryptobyte.Builder + b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) { + addASN1IntBytes(b, signature[:32]) + addASN1IntBytes(b, signature[32:]) + }) + return b.Bytes() +} diff --git a/gomicroecc/go_wrap.h b/gomicroecc/go_wrap.h new file mode 100644 index 0000000..124c2dd --- /dev/null +++ b/gomicroecc/go_wrap.h @@ -0,0 +1,36 @@ +#ifndef _GOLANG_WRAP_H_ +#define _GOLANG_WRAP_H_ + +#include "../uECC.h" + +#ifdef _NEED_INIT +const struct uECC_Curve_t *curve = NULL; + +void init_ecc() { curve = uECC_secp256r1(); } +#endif + +typedef struct { + uint64_t ptr; + uint64_t len; + uint64_t cap; +} __attribute__((packed)) SliceHeader; + +#ifdef _NEED_SIGN_VERRIFY +extern const struct uECC_Curve_t *curve; + +void sign(SliceHeader *out, const SliceHeader *private_key, + const SliceHeader *message_hash) { + uECC_sign((const uint8_t *)private_key->ptr, + (const uint8_t *)message_hash->ptr, message_hash->len, + (uint8_t *)out->ptr, curve); +} + +void verify(const SliceHeader *public_key, const SliceHeader *message_hash, + const SliceHeader *signature, int64_t *ret) { + *ret = uECC_verify((const uint8_t *)public_key->ptr, + (const uint8_t *)message_hash->ptr, message_hash->len, + (const uint8_t *)signature->ptr, curve); +} +#endif + +#endif