diff --git a/go/zkscrypto/example/main.go b/go/zkscrypto/example/main.go new file mode 100644 index 0000000..05a361b --- /dev/null +++ b/go/zkscrypto/example/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "encoding/hex" + "log" + + "github.com/matter-labs/zksync-sdk/go/zkscrypto" +) + +func main() { + seed := make([]byte, 32) + message := []byte("hello") + + privateKey, err := zkscrypto.NewPrivateKey(seed) + if err != nil { + log.Fatalf("error creating private key: %s", err.Error()) + } + publicKey, err := privateKey.PublicKey() + if err != nil { + log.Fatalf("error creating public key: %s", err.Error()) + } + publicKeyHash, err := publicKey.Hash() + if err != nil { + log.Fatalf("error creating public key hash: %s", err.Error()) + } + signature, err := privateKey.Sign(message) + if err != nil { + log.Fatalf("error signing message: %s", err.Error()) + } + log.Printf("Seed: %s\n", hex.EncodeToString(seed)) + log.Printf("Private key: %s\n", privateKey.HexString()) + log.Printf("Public key: %s\n", publicKey.HexString()) + log.Printf("Public key hash: %s\n", publicKeyHash.HexString()) + log.Printf("Signature: %s\n", signature.HexString()) +} diff --git a/go/zkscrypto/go.mod b/go/zkscrypto/go.mod new file mode 100644 index 0000000..2fe6b00 --- /dev/null +++ b/go/zkscrypto/go.mod @@ -0,0 +1,3 @@ +module github.com/matter-labs/zksync-sdk/go/zkscrypto + +go 1.15 diff --git a/go/zkscrypto/types.go b/go/zkscrypto/types.go new file mode 100644 index 0000000..0f8280f --- /dev/null +++ b/go/zkscrypto/types.go @@ -0,0 +1,21 @@ +package zkscrypto + +// PrivateKey represents a private key. +type PrivateKey struct { + data []byte +} + +// PublicKey represents a public key +type PublicKey struct { + data []byte +} + +// PublicKeyHash represents a public key hash +type PublicKeyHash struct { + data []byte +} + +// Signature represents a multi-signature +type Signature struct { + data []byte +} diff --git a/go/zkscrypto/zks_crypto.h b/go/zkscrypto/zks_crypto.h new file mode 100644 index 0000000..cbd38c3 --- /dev/null +++ b/go/zkscrypto/zks_crypto.h @@ -0,0 +1,106 @@ +#ifndef ZKS_CRYPTO_H +#define ZKS_CRYPTO_H + +/* Generated with cbindgen:0.14.3 */ + +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#include +#include +#include +#include + + +/** + * Maximum byte length of the message that can be signed. + */ +#define MAX_SIGNED_MESSAGE_LEN 92 + +/** + * Byte length of the signature. Signature contains r and s points. + */ +#define PACKED_SIGNATURE_LEN 64 + +/** + * Byte length of the private key + */ +#define PRIVATE_KEY_LEN 32 + +/** + * Byte length of the public key hash + */ +#define PUBKEY_HASH_LEN 20 + +/** + * Byte length of the public key + */ +#define PUBLIC_KEY_LEN 32 + +typedef enum MUSIG_SIGN_RES { + MUSIG_SIGN_OK = 0, + MUSIG_SIGN_MSG_TOO_LONG, +} MUSIG_SIGN_RES; + +typedef enum PRIVATE_KEY_FROM_SEED_RES { + PRIVATE_KEY_FROM_SEED_OK = 0, + /** + * Seed should be at least 32 bytes long + */ + PRIVATE_KEY_FROM_SEED_SEED_TOO_SHORT, +} PRIVATE_KEY_FROM_SEED_RES; + +typedef enum PUBKEY_HASH_FROM_PUBKEY_RES { + PUBKEY_HASH_FROM_PUBKEY_OK = 0, +} PUBKEY_HASH_FROM_PUBKEY_RES; + +typedef enum PUBLIC_KEY_FROM_PRIVATE_RES { + PUBLIC_KEY_FROM_PRIVATE_OK = 0, +} PUBLIC_KEY_FROM_PRIVATE_RES; + +typedef struct ZksPrivateKey { + uint8_t data[PRIVATE_KEY_LEN]; +} ZksPrivateKey; + +typedef struct ZksPackedPublicKey { + uint8_t data[PUBLIC_KEY_LEN]; +} ZksPackedPublicKey; + +typedef struct ZksPubkeyHash { + uint8_t data[PUBKEY_HASH_LEN]; +} ZksPubkeyHash; + +typedef struct ZksSignature { + uint8_t data[PACKED_SIGNATURE_LEN]; +} ZksSignature; + +/** + * Initializes thread local storage of the parameters used for calculations. + * Calling this before other calls is optional since parameters will be initialized when needed. + * Can save time for the first call of other functions in the thread + * since it takes time to init parameters. + */ +void zks_crypto_init(void); + +PRIVATE_KEY_FROM_SEED_RES zks_crypto_private_key_from_seed(const uint8_t *seed, + size_t seed_len, + ZksPrivateKey *private_key); + +PUBLIC_KEY_FROM_PRIVATE_RES zks_crypto_private_key_to_public_key(const ZksPrivateKey *private_key, + ZksPackedPublicKey *public_key); + +PUBKEY_HASH_FROM_PUBKEY_RES zks_crypto_public_key_to_pubkey_hash(const ZksPackedPublicKey *public_key, + ZksPubkeyHash *pubkey_hash); + +/** + * We use musig Schnorr signature scheme. + * It is impossible to restore signer for signature, that is why we provide public key of the signer + * along with signature. + * [0..32] - packed r point of the signature. + * [32..64] - s point of the signature. + */ +MUSIG_SIGN_RES zks_crypto_sign_musig(const ZksPrivateKey *private_key, + const uint8_t *msg, + size_t msg_len, + ZksSignature *signature_output); + +#endif /* ZKS_CRYPTO_H */ diff --git a/go/zkscrypto/zkscrypto.go b/go/zkscrypto/zkscrypto.go new file mode 100644 index 0000000..6b620e8 --- /dev/null +++ b/go/zkscrypto/zkscrypto.go @@ -0,0 +1,150 @@ +package zkscrypto + +/* +#cgo LDFLAGS: -L${SRCDIR}/libs -lzks_crypto + +#include "zks_crypto.h" +*/ +import "C" +import ( + "encoding/hex" + "errors" + "unsafe" +) + +var ( + errSeedLen = errors.New("given seed is too short, length must be greater than 32") + errPrivateKey = errors.New("error on private key generation") + errSignedMsgLen = errors.New("musig message length must not be larger than 92") + errSign = errors.New("error on sign message") +) + +func init() { + C.zks_crypto_init() +} + +/* +************************************************************************************************ +Private key implementation +************************************************************************************************ +*/ + +// NewPrivateKey generates private key from seed +func NewPrivateKey(seed []byte) (*PrivateKey, error) { + pointer := C.struct_ZksPrivateKey{} + rawSeed := C.CBytes(seed) + defer C.free(rawSeed) + result := C.zks_crypto_private_key_from_seed((*C.uint8_t)(rawSeed), C.ulong(len(seed)), &pointer) + if result != 0 { + switch result { + case 1: + return nil, errSeedLen + default: + return nil, errPrivateKey + } + } + data := unsafe.Pointer(&pointer.data) + return &PrivateKey{data: C.GoBytes(data, C.PRIVATE_KEY_LEN)}, nil +} + +// Sign message with musig Schnorr signature scheme +func (pk *PrivateKey) Sign(message []byte) (*Signature, error) { + privateKeyC := C.struct_ZksPrivateKey{} + rawMessage := C.CBytes(message) + defer C.free(rawMessage) + for i := range pk.data { + privateKeyC.data[i] = C.uint8_t(pk.data[i]) + } + signatureC := C.struct_ZksSignature{} + result := C.zks_crypto_sign_musig(&privateKeyC, (*C.uint8_t)(rawMessage), C.ulong(len(message)), &signatureC) + if result != 0 { + switch result { + case 1: + return nil, errSignedMsgLen + default: + return nil, errSign + } + } + data := unsafe.Pointer(&signatureC.data) + return &Signature{data: C.GoBytes(data, C.PACKED_SIGNATURE_LEN)}, nil +} + +// PublicKey generates public key from private key +func (pk *PrivateKey) PublicKey() (*PublicKey, error) { + privateKeyC := C.struct_ZksPrivateKey{} + for i := range pk.data { + privateKeyC.data[i] = C.uint8_t(pk.data[i]) + } + pointer := C.struct_ZksPackedPublicKey{} + result := C.zks_crypto_private_key_to_public_key(&privateKeyC, &pointer) + if result != 0 { + return nil, errors.New("error on public key generation") + } + data := unsafe.Pointer(&pointer.data) + return &PublicKey{data: C.GoBytes(data, C.PUBLIC_KEY_LEN)}, nil +} + +// HexString creates a hex string representation of a private key +func (pk *PrivateKey) HexString() string { + if pk.data == nil || len(pk.data) == 0 { + return "0x" + } + return hex.EncodeToString(pk.data) +} + +/* +************************************************************************************************ +Public key implementation +************************************************************************************************ +*/ + +// Hash generates hash from public key +func (pk *PublicKey) Hash() (*PublicKeyHash, error) { + publicKeyC := C.struct_ZksPackedPublicKey{} + for i := range pk.data { + publicKeyC.data[i] = C.uint8_t(pk.data[i]) + } + pointer := C.struct_ZksPubkeyHash{} + result := C.zks_crypto_public_key_to_pubkey_hash(&publicKeyC, &pointer) + if result != 0 { + return nil, errors.New("Error on public key hash generation") + } + data := unsafe.Pointer(&pointer.data) + return &PublicKeyHash{data: C.GoBytes(data, C.PUBKEY_HASH_LEN)}, nil +} + +// HexString creates a hex string representation of a public key +func (pk *PublicKey) HexString() string { + if pk.data == nil || len(pk.data) == 0 { + return "0x" + } + return hex.EncodeToString(pk.data) +} + +/* +************************************************************************************************ +Private key Hash implementation +************************************************************************************************ +*/ + +// HexString creates a hex string representation of a public key hash +func (pk *PublicKeyHash) HexString() string { + if pk.data == nil || len(pk.data) == 0 { + return "0x" + } + return hex.EncodeToString(pk.data) +} + +/* +************************************************************************************************ +Signature implementation +************************************************************************************************ +*/ + +// HexString creates a hex string representation of a signature +func (pk *Signature) HexString() string { + if pk.data == nil || len(pk.data) == 0 { + return "0x" + } + return hex.EncodeToString(pk.data) +} diff --git a/go/zkscrypto/zkscrypto_test.go b/go/zkscrypto/zkscrypto_test.go new file mode 100644 index 0000000..4ea1cf9 --- /dev/null +++ b/go/zkscrypto/zkscrypto_test.go @@ -0,0 +1,61 @@ +package zkscrypto + +import ( + "bytes" + "testing" +) + +func TestPrivateKeyGeneration(t *testing.T) { + seed := make([]byte, 32) + expected := []byte{1, 31, 91, 153, 8, 76, 92, 46, 45, 94, 99, 72, 142, 15, 113, 104, 213, 153, 165, 192, 31, 233, 254, 196, 201, 150, 5, 116, 61, 165, 232, 92} + privateKey, err := NewPrivateKey(seed) + if err != nil { + t.Fatalf("%s", err.Error()) + } + if !bytes.Equal(privateKey.data, expected) { + t.Fatalf("%s,%v,%v must be equal to %v", "Unexpected private key", seed, privateKey.data, expected) + } +} + +func TestPublicKeyGenerationFromPrivateKey(t *testing.T) { + privateKeyRaw := []byte{1, 31, 91, 153, 8, 76, 92, 46, 45, 94, 99, 72, 142, 15, 113, 104, 213, 153, 165, 192, 31, 233, 254, 196, 201, 150, 5, 116, 61, 165, 232, 92} + expected := []byte{23, 156, 58, 89, 20, 125, 48, 49, 108, 136, 102, 40, 133, 35, 72, 201, 180, 42, 24, 184, 33, 8, 74, 201, 239, 121, 189, 115, 233, 185, 78, 141} + pk := PrivateKey{data: privateKeyRaw} + + publicKey, err := pk.PublicKey() + if err != nil { + t.Fatalf("%s", err.Error()) + } + if !bytes.Equal(publicKey.data, expected) { + t.Fatalf("%s,%v must be equal to %v", "Unexpected public key", publicKey.data, expected) + } +} + +func TestHashGenerationFromPublicKey(t *testing.T) { + publicKeyRaw := []byte{23, 156, 58, 89, 20, 125, 48, 49, 108, 136, 102, 40, 133, 35, 72, 201, 180, 42, 24, 184, 33, 8, 74, 201, 239, 121, 189, 115, 233, 185, 78, 141} + expected := []byte{199, 113, 39, 22, 185, 239, 107, 210, 23, 83, 196, 233, 29, 236, 195, 81, 177, 17, 192, 109} + pk := PublicKey{data: publicKeyRaw} + + publicKeyHash, err := pk.Hash() + if err != nil { + t.Fatalf("%s", err.Error()) + } + if !bytes.Equal(publicKeyHash.data, expected) { + t.Fatalf("%s,%v must be equal to %v", "Unexpected public key hash", publicKeyHash.data, expected) + } +} + +func TestSigningMessageUsingPrivateKey(t *testing.T) { + privateKeyRaw := []byte{1, 31, 91, 153, 8, 76, 92, 46, 45, 94, 99, 72, 142, 15, 113, 104, 213, 153, 165, 192, 31, 233, 254, 196, 201, 150, 5, 116, 61, 165, 232, 92} + message := []byte("hello") + expected := []byte{66, 111, 115, 126, 202, 53, 46, 252, 88, 149, 33, 63, 156, 220, 202, 144, 162, 98, 68, 248, 76, 194, 149, 192, 31, 0, 20, 92, 6, 200, 13, 37, 62, 28, 185, 253, 66, 183, 96, 128, 196, 211, 32, 85, 182, 137, 234, 62, 1, 229, 111, 152, 128, 227, 145, 47, 155, 27, 153, 193, 228, 91, 80, 4} + pk := PrivateKey{data: privateKeyRaw} + + signature, err := pk.Sign(message) + if err != nil { + t.Fatalf("%s", err.Error()) + } + if !bytes.Equal(signature.data, expected) { + t.Fatalf("%s,%v must be equal to %v", "Unexpected signature", signature.data, expected) + } +}