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

Add support for Ed25519 API keys #28

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ package binance_connector
import (
"bytes"
"context"
"crypto/ed25519"
"crypto/hmac"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"log"
Expand Down Expand Up @@ -117,13 +122,56 @@ func (c *Client) parseRequest(r *request, opts ...RequestOption) (err error) {

if r.secType == secTypeSigned {
raw := fmt.Sprintf("%s%s", queryString, bodyString)
mac := hmac.New(sha256.New, []byte(c.SecretKey))
_, err = mac.Write([]byte(raw))
if err != nil {
return err
var signatureStr string
// Refer to this for more information about the key types supported by Binance:
// https://github.com/binance/binance-spot-api-docs/blob/master/faqs/api_key_types.md
// Note that the format of the signature may vary depending on the key type.
switch r.keyType {
case KeyTypeHMACSHA256:
mac := hmac.New(sha256.New, []byte(c.SecretKey))
_, err = mac.Write([]byte(raw))
if err != nil {
return err
}
// Hex format expected
signatureStr = fmt.Sprintf("%x", mac.Sum(nil))

case KeyTypeEd25519PEM:
privateKeyBlock, _ := pem.Decode([]byte(c.SecretKey))
if privateKeyBlock == nil {
return fmt.Errorf("failed to parse PEM block containing the private key")
}
privateKeyRaw, err := x509.ParsePKCS8PrivateKey(privateKeyBlock.Bytes)
if err != nil {
return fmt.Errorf("failed to parse private key: %w", err)
}
privateKey, ok := privateKeyRaw.(ed25519.PrivateKey)
if !ok {
return fmt.Errorf("invalid private key: %w", err)
}
// Base64 format expected
signatureStr = base64.StdEncoding.EncodeToString(ed25519.Sign(privateKey, []byte(raw)))

case KeyTypeEd25519Base64:
ed25519key, err := base64.StdEncoding.DecodeString(c.SecretKey)
if err != nil {
return fmt.Errorf("failed to decode private key from base64: %w", err)
}
// Base64 format expected
signatureStr = base64.StdEncoding.EncodeToString(ed25519.Sign(ed25519key, []byte(raw)))

case KeyTypeEd25519Hex:
ed25519key, err := hex.DecodeString(c.SecretKey)
if err != nil {
return fmt.Errorf("failed to decode private key from hex: %w", err)
}
// Base64 format expected
signatureStr = base64.StdEncoding.EncodeToString(ed25519.Sign(ed25519key, []byte(raw)))
default:
return fmt.Errorf("invalid key type")
}
v := url.Values{}
v.Set(signatureKey, fmt.Sprintf("%x", (mac.Sum(nil))))
v.Set(signatureKey, signatureStr)
if queryString == "" {
queryString = v.Encode()
} else {
Expand Down
17 changes: 17 additions & 0 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ const (
secTypeSigned // if the 'timestamp' parameter is required
)

type KeyType int

const (
KeyTypeHMACSHA256 KeyType = iota
KeyTypeEd25519PEM // Ed25519 key in PEM format
KeyTypeEd25519Base64 // Ed25519 key in base64 format
KeyTypeEd25519Hex // Ed25519 key in hex format
)

type params map[string]interface{}

// request define an API request
Expand All @@ -25,6 +34,7 @@ type request struct {
form url.Values
recvWindow int64
secType secType
keyType KeyType // Only used when secType is secTypeSigned
header http.Header
body io.Reader
fullURL string
Expand Down Expand Up @@ -75,3 +85,10 @@ func WithRecvWindow(recvWindow int64) RequestOption {

// RequestOption define option type for request
type RequestOption func(*request)

// RequestOption to set secret/private key type for request
func ReqOptWithKeyType(keyType KeyType) RequestOption {
return func(r *request) {
r.keyType = keyType
}
}