Adds support for new Huobi authorised requests and a tool for ecdsa keys

Fixes: https://github.com/thrasher-/gocryptotrader/issues/150
This commit is contained in:
Adrian Gallagher
2018-07-13 15:54:31 +10:00
parent e5b3ce8de8
commit 4fadc6ff48
7 changed files with 268 additions and 36 deletions

View File

@@ -117,6 +117,7 @@ type ExchangeConfig struct {
AuthenticatedAPISupport bool
APIKey string
APISecret string
APIAuthPEMKey string `json:",omitempty"`
ClientID string `json:",omitempty"`
AvailablePairs string
EnabledPairs string

File diff suppressed because one or more lines are too long

View File

@@ -85,27 +85,27 @@ type FundHistory struct {
// Base stores the individual exchange information
type Base struct {
Name string
Enabled bool
Verbose bool
Websocket bool
RESTPollingDelay time.Duration
AuthenticatedAPISupport bool
APISecret, APIKey, ClientID string
Nonce nonce.Nonce
TakerFee, MakerFee, Fee float64
BaseCurrencies []string
AvailablePairs []string
EnabledPairs []string
AssetTypes []string
PairsLastUpdated int64
SupportsAutoPairUpdating bool
SupportsRESTTickerBatching bool
HTTPTimeout time.Duration
WebsocketURL string
APIUrl string
RequestCurrencyPairFormat config.CurrencyPairFormatConfig
ConfigCurrencyPairFormat config.CurrencyPairFormatConfig
Name string
Enabled bool
Verbose bool
Websocket bool
RESTPollingDelay time.Duration
AuthenticatedAPISupport bool
APISecret, APIKey, APIAuthPEMKey, ClientID string
Nonce nonce.Nonce
TakerFee, MakerFee, Fee float64
BaseCurrencies []string
AvailablePairs []string
EnabledPairs []string
AssetTypes []string
PairsLastUpdated int64
SupportsAutoPairUpdating bool
SupportsRESTTickerBatching bool
HTTPTimeout time.Duration
WebsocketURL string
APIUrl string
RequestCurrencyPairFormat config.CurrencyPairFormatConfig
ConfigCurrencyPairFormat config.CurrencyPairFormatConfig
*request.Requester
}

View File

@@ -2,11 +2,17 @@ package huobi
import (
"bytes"
"crypto/ecdsa"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"log"
"net/url"
"strconv"
"strings"
"time"
"github.com/thrasher-/gocryptotrader/common"
@@ -82,6 +88,7 @@ func (h *HUOBI) Setup(exch config.ExchangeConfig) {
h.Enabled = true
h.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
h.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
h.APIAuthPEMKey = exch.APIAuthPEMKey
h.SetHTTPClientTimeout(exch.HTTPTimeout)
h.RESTPollingDelay = exch.RESTPollingDelay
h.Verbose = exch.Verbose
@@ -723,7 +730,34 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url
headers["Content-Type"] = "application/x-www-form-urlencoded"
hmac := common.GetHMAC(common.HashSHA256, []byte(payload), []byte(h.APISecret))
values.Set("Signature", common.Base64Encode(hmac))
signature := common.Base64Encode(hmac)
values.Set("Signature", signature)
pemKey := strings.NewReader(h.APIAuthPEMKey)
pemBytes, err := ioutil.ReadAll(pemKey)
if err != nil {
return fmt.Errorf("Huobi unable to ioutil.ReadAll PEM key: %s", err)
}
block, _ := pem.Decode(pemBytes)
if block == nil {
return fmt.Errorf("Huobi block is nil")
}
x509Encoded := block.Bytes
privKey, err := x509.ParseECPrivateKey(x509Encoded)
if err != nil {
return fmt.Errorf("Huobi unable to ParseECPrivKey: %s", err)
}
r, s, err := ecdsa.Sign(rand.Reader, privKey, common.GetSHA256([]byte(signature)))
if err != nil {
return fmt.Errorf("Huobi unable to sign: %s", err)
}
privSig := r.Bytes()
privSig = append(privSig, s.Bytes()...)
values.Set("PrivateSignature", common.Base64Encode(privSig))
url := fmt.Sprintf("%s%s", huobiAPIURL, endpoint)
url = common.EncodeURLValues(url, values)

View File

@@ -1,9 +1,16 @@
package huobi
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"strconv"
"strings"
"testing"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
)
@@ -251,3 +258,29 @@ func TestCancelWithdraw(t *testing.T) {
t.Error("Test failed - Huobi TestCancelWithdraw: Invalid withdraw-ID was valid")
}
}
func TestPEMLoadAndSign(t *testing.T) {
t.Parallel()
pemKey := strings.NewReader(h.APIAuthPEMKey)
pemBytes, err := ioutil.ReadAll(pemKey)
if err != nil {
t.Fatalf("Test Failed. TestPEMLoadAndSign Unable to ioutil.ReadAll PEM key: %s", err)
}
block, _ := pem.Decode(pemBytes)
if block == nil {
t.Fatalf("Test Failed. TestPEMLoadAndSign Block is nil")
}
x509Encoded := block.Bytes
privKey, err := x509.ParseECPrivateKey(x509Encoded)
if err != nil {
t.Fatalf("Test Failed. TestPEMLoadAndSign Unable to ParseECPrivKey: %s", err)
}
_, _, err = ecdsa.Sign(rand.Reader, privKey, common.GetSHA256([]byte("test")))
if err != nil {
t.Fatalf("Test Failed. TestPEMLoadAndSign Unable to sign: %s", err)
}
}

File diff suppressed because one or more lines are too long

162
tools/huobi_auth/main.go Normal file
View File

@@ -0,0 +1,162 @@
package main
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"log"
"github.com/thrasher-/gocryptotrader/common"
)
func encodePEM(privKey *ecdsa.PrivateKey, pubKey bool) ([]byte, error) {
if !pubKey {
x509Enc, err := x509.MarshalECPrivateKey(privKey)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(
&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: x509Enc,
},
), nil
}
publicKey := &privKey.PublicKey
x509EncPub, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(
&pem.Block{
Type: "EC PUBLIC KEY",
Bytes: x509EncPub,
},
), nil
}
func decodePEM(PEMPrivKey, PEMPubKey []byte) (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) {
block, _ := pem.Decode([]byte(PEMPrivKey))
if block == nil {
return nil, nil, errors.New("priv block data is nil")
}
x509Enc := block.Bytes
privateKey, err := x509.ParseECPrivateKey(x509Enc)
if err != nil {
return nil, nil, err
}
blockPub, _ := pem.Decode([]byte(PEMPubKey))
if block == nil {
return nil, nil, errors.New("pub block data is nil")
}
x509EncPub := blockPub.Bytes
genPubkey, err := x509.ParsePKIXPublicKey(x509EncPub)
if err != nil {
return nil, nil, err
}
publicKey := genPubkey.(*ecdsa.PublicKey)
return privateKey, publicKey, nil
}
func writeFile(file string, data []byte) error {
return common.WriteFile(file, data)
}
func main() {
genKeys := false
privKeyData, err := common.ReadFile("privatekey.pem")
if err != nil {
genKeys = true
}
log.Printf("Generating keys: %v.", genKeys)
var privKey, pubKey []byte
PEMEncoder := func(p *ecdsa.PrivateKey) {
privKey, err = encodePEM(p, false)
if err != nil {
log.Fatal(err)
}
pubKey, err = encodePEM(p, true)
if err != nil {
log.Fatal(err)
}
}
if genKeys {
pKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
PEMEncoder(pKey)
err = writeFile("privatekey.pem", privKey)
if err != nil {
log.Fatal(err)
}
err = writeFile("publickey.pem", pubKey)
if err != nil {
log.Fatal(err)
}
} else {
pubKeyData, err := common.ReadFile("publickey.pem")
if err != nil {
log.Fatal(err)
}
log.Println("Successfully read PEM files.")
priv, _, err := decodePEM(privKeyData, pubKeyData)
if err != nil {
log.Fatal(err)
}
PEMEncoder(priv)
if !bytes.Equal(privKey, privKeyData) {
log.Fatalf("PEM privkey mismatch")
}
if !bytes.Equal(pubKey, pubKeyData) {
log.Fatalf("PEM pubkey mismatch")
}
log.Println("PEM data matches.")
}
fmt.Println()
fmt.Println(string(privKey))
fmt.Println(string(pubKey))
type JSONGeneration struct {
APIAuthPEMKey string
}
r := JSONGeneration{
APIAuthPEMKey: string(privKey),
}
resultk, err := common.JSONEncode(r)
if err != nil {
log.Fatal(err)
}
log.Println("Please visit https://github.com/huobiapi/API_Docs_en/wiki/Signing_API_Requests and follow from step 2 onwards.")
log.Printf("After completing the above instructions, please copy and paste the below key (including the following ',') into your Huobi exchange config file:\n\n")
fmt.Println(string(resultk[1:len(resultk)-1]) + ",")
}