ccxt-go/exchange_crypto.go

545 lines
13 KiB
Go
Raw Permalink Normal View History

2025-02-28 10:33:20 +08:00
package ccxt
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/hmac"
md5Hash "crypto/md5"
"crypto/rand"
rsaHash "crypto/rsa"
sha1Hash "crypto/sha1"
sha256Hash "crypto/sha256"
sha512Hash "crypto/sha512"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"hash/crc32"
"math/big"
"strings"
"golang.org/x/crypto/sha3"
// "golang.org/x/crypto/sha3"
)
func Sha1() string { return "sha1" }
func Sha256() string { return "sha256" }
func sha256() string { return "sha256" }
func Sha384() string { return "sha384" }
func sha384() string { return "sha384" }
func Sha512() string { return "sha512" }
func sha512() string { return "sha512" }
func Md5() string { return "md5" }
func md5() string { return "md5" }
func Ed25519() string { return "ed25519" }
func ed25519() string { return "ed25519" }
func Keccak() string { return "keccak" }
func Secp256k1() string { return "secp256k1" }
func P256() string { return "p256" }
func keccak() string { return "keccak" }
func secp256k1() string { return "secp256k1" }
func (this *Exchange) Hmac(request2 interface{}, secret2 interface{}, algorithm2 func() string, args ...interface{}) string {
digest := GetArg(args, 0, "hex").(string)
return Hmac(request2, secret2, algorithm2, digest)
}
func Hmac(request2 interface{}, secret2 interface{}, algorithm2 func() string, digest string) string {
var request []byte
switch v := request2.(type) {
case string:
request = []byte(v)
case []byte:
request = v
}
var secretBytes []byte
switch v := secret2.(type) {
case string:
secretBytes = []byte(v)
case []byte:
secretBytes = v
}
algorithm := "md5"
if algorithm2 != nil {
algorithm = algorithm2()
}
var signature []byte
switch algorithm {
case "sha256":
signature = signHMACSHA256(request, secretBytes)
case "sha512":
signature = signHMACSHA512(request, secretBytes)
case "sha384":
signature = signHMACSHA384(request, secretBytes)
case "md5":
signature = signHMACMD5(request, secretBytes)
}
if digest == "hex" {
return hex.EncodeToString(signature)
}
return base64.StdEncoding.EncodeToString(signature)
}
func signHMACSHA256(data, secret []byte) []byte {
h := hmac.New(sha256Hash.New, secret)
h.Write([]byte(data))
return h.Sum(nil)
}
func signHMACSHA512(data, secret []byte) []byte {
h := hmac.New(sha512Hash.New, secret)
h.Write([]byte(data))
return h.Sum(nil)
}
func signHMACSHA384(data, secret []byte) []byte {
h := hmac.New(sha512Hash.New, secret)
h.Write([]byte(data))
return h.Sum(nil)
}
func signHMACMD5(data, secret []byte) []byte {
h := hmac.New(md5Hash.New, secret)
h.Write(data)
return h.Sum(nil)
}
func (this *Exchange) Hash(request2 interface{}, hash func() string, args ...interface{}) interface{} {
digest2 := GetArg(args, 0, "hex")
return Hash(request2, hash, digest2)
}
func Hash(request2 interface{}, hash func() string, digest2 interface{}) interface{} {
var request string
switch v := request2.(type) {
case string:
request = v
}
algorithm := hash()
digest := "hex"
if digest2 != nil {
digest = digest2.(string)
}
var signature []byte
switch algorithm {
case "sha256":
signature = signSHA256(request)
case "sha512":
signature = signSHA512(request)
case "sha384":
signature = signSHA384(request)
case "sha1":
signature = signSHA1(request)
case "md5":
signature = signMD5(request)
case "keccak":
signature = signKeccak(request2)
case "sha3":
signature = signKeccak(request)
}
if digest == "binary" {
return signature
}
if digest == "hex" {
return hex.EncodeToString(signature)
}
return base64.StdEncoding.EncodeToString(signature)
}
func (this *Exchange) Axolotl(a interface{}, b interface{}, c interface{}) string {
return ""
}
func signSHA256(data string) []byte {
h := sha256Hash.New()
h.Write([]byte(data))
return h.Sum(nil)
}
func signSHA512(data string) []byte {
h := sha512Hash.New()
h.Write([]byte(data))
return h.Sum(nil)
}
func signSHA384(data string) []byte {
h := sha512Hash.New384()
h.Write([]byte(data))
return h.Sum(nil)
}
func signSHA1(data string) []byte {
h := sha1Hash.New()
h.Write([]byte(data))
return h.Sum(nil)
}
func signMD5(data string) []byte {
h := md5Hash.New()
h.Write([]byte(data))
return h.Sum(nil)
}
func signKeccak(data interface{}) []byte {
var input []byte
switch v := data.(type) {
case string:
input = []byte(v)
case []byte:
input = v
default:
return []byte{}
}
hash := sha3.NewLegacyKeccak256()
hash.Write(input)
return hash.Sum(nil)
}
func Jwt(data interface{}, secret interface{}, hash func() string, optionalArgs ...interface{}) string {
isRsa := GetArg(optionalArgs, 0, false).(bool)
params := GetArg(optionalArgs, 2, map[string]interface{}{}).(map[string]interface{})
return JwtFull(data, secret, hash, isRsa, params)
}
func JwtFull(data interface{}, secret interface{}, hash func() string, isRsa bool, options map[string]interface{}) string {
if options == nil {
options = make(map[string]interface{})
}
algorithm := hash()
algPrefix := "HS"
if isRsa {
algPrefix = "RS"
}
alg := algPrefix + strings.ToUpper(algorithm[3:])
if algOpt, ok := options["alg"]; ok {
alg = algOpt.(string)
}
header := map[string]interface{}{
"alg": alg,
"typ": "JWT",
}
for k, v := range options {
header[k] = v
}
if iat, ok := header["iat"]; ok {
if dataMap, ok := data.(map[string]interface{}); ok {
dataMap["iat"] = iat
}
delete(header, "iat")
}
encodedHeader := base64.RawURLEncoding.EncodeToString([]byte(Json(header)))
encodedData := base64.RawURLEncoding.EncodeToString([]byte(Json(data)))
token := encodedHeader + "." + encodedData
var signature string
if isRsa {
signature = Rsa(token, secret, hash)
} else if alg[:2] == "ES" {
// Ecdsa signing logic here (omitted for simplicity)
} else {
signature = base64.RawURLEncoding.EncodeToString(signHMACSHA256([]byte(token), []byte(secret.(string))))
}
// dirty quicky
signature = strings.Replace(signature, "+", "-", -1)
signature = strings.Replace(signature, "/", "_", -1)
signature = strings.TrimRight(signature, "==")
return token + "." + signature
}
func Rsa(data2 interface{}, privateKey2 interface{}, algorithm2 func() string) string {
data := data2.(string)
publicKey := privateKey2.(string)
// hashAlgorithm := hashAlgorithm2.(string)
hashAlgorithm := algorithm2()
// Remove PEM headers
// pkParts := strings.Split(publicKey, "\n")
// pkParts = pkParts[1 : len(pkParts)-1]
// newPk := strings.Join(pkParts, "")
// Decode base64 public key
// privateKey, err := base64.StdEncoding.DecodeString(newPk)
// if err != nil {
// panic(err)
// }
privateKey := []byte(publicKey)
// Parse the private key
block, _ := pem.Decode(privateKey)
if block == nil || block.Type != "RSA PRIVATE KEY" {
panic("RSA PRIVATE KEY")
}
parsedKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
// Hash the data
var hashedData []byte
var hash crypto.Hash
switch hashAlgorithm {
case "sha1":
hash = crypto.SHA1
h := sha1Hash.New()
h.Write([]byte(data))
hashedData = h.Sum(nil)
case "sha256":
hash = crypto.SHA256
h := sha256Hash.New()
h.Write([]byte(data))
hashedData = h.Sum(nil)
case "sha384":
hash = crypto.SHA384
h := sha512Hash.New384()
h.Write([]byte(data))
hashedData = h.Sum(nil)
case "sha512":
hash = crypto.SHA512
h := sha512Hash.New()
h.Write([]byte(data))
hashedData = h.Sum(nil)
case "md5":
hash = crypto.MD5
h := md5Hash.New()
h.Write([]byte(data))
hashedData = h.Sum(nil)
default:
return ""
}
// Sign the data
signData, err := rsaHash.SignPKCS1v15(rand.Reader, parsedKey, hash, hashedData)
if err != nil {
return ""
}
// Return base64 encoded signature
return base64.StdEncoding.EncodeToString(signData)
}
func Eddsa(data2 interface{}, publicKey2 interface{}, hashAlgorithm2 interface{}) string {
return "" // to do
}
// func Ecdsa(request interface{}, secret interface{}, alg interface{}, hash interface{}) string {
// return "" // to do
// }
func stringToPrivateKey(privKeyStr string) *ecdsa.PrivateKey {
// Decode PEM formatted private key
block, _ := pem.Decode([]byte(privKeyStr))
if block == nil {
return nil
}
// Parse the ECDSA private key
privKey, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil
}
return privKey
}
var secp256k1Curve = &elliptic.CurveParams{
Name: "secp256k1",
BitSize: 256,
P: fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"),
N: fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"),
B: fromHex("07"),
Gx: fromHex("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"),
Gy: fromHex("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8"),
}
// Helper function to convert hex string to big.Int
func fromHex(s string) *big.Int {
b, _ := new(big.Int).SetString(s, 16)
return b
}
// Helper function to convert a string hex to byte array
func hexToBytes(hexStr string) ([]byte, bool) {
bytes, err := hex.DecodeString(hexStr)
if err != nil {
return nil, false
}
return bytes, true
}
// Helper function to convert byte array to hex string
func toHex(bytes []byte) string {
return hex.EncodeToString(bytes)
}
func enforceLowS(s *big.Int) *big.Int {
// secp256k1 curve order
curveOrder := fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141")
// If s is greater than curveOrder / 2, calculate the new s as curveOrder - s
halfOrder := new(big.Int).Div(curveOrder, big.NewInt(2))
if s.Cmp(halfOrder) > 0 {
s.Sub(curveOrder, s)
}
return s
}
// Sign the message with secp256k1 curve using Go's native ecdsa package
// func signSecp256k1(message []byte, seckey []byte) ([]byte, int, bool) {
// // Sign the message with the secp256k1 private key
// // return nil, 0, false
// signature, err := secp256k1Hash.Sign(message, seckey)
// if err != nil {
// return nil, 0, false
// }
// recoveryID := int(signature[64])
// // // Split the signature into r and s components
// r := new(big.Int).SetBytes(signature[:32])
// s := new(big.Int).SetBytes(signature[32:64])
// // // Enforce low-s rule on the 's' value
// s = enforceLowS(s)
// // // Convert r and s back to byte slices
// rBytes := r.FillBytes(make([]byte, 32))
// sBytes := s.FillBytes(make([]byte, 32))
// // // Reconstruct the signature with the adjusted low-s value
// signature = append(rBytes, sBytes...)
// // The recovery ID is the last byte in the original signature
// return signature, recoveryID, true
// }
// Helper function to sign with P256 (Go's native implementation)
func signP256(message []byte, seckey []byte) ([]byte, int, bool) {
curve := elliptic.P256()
privKey := new(ecdsa.PrivateKey)
privKey.PublicKey.Curve = curve
privKey.D = new(big.Int).SetBytes(seckey)
r, s, err := ecdsa.Sign(rand.Reader, privKey, message)
if err != nil {
return nil, 0, false
}
rBytes := r.Bytes()
sBytes := s.Bytes()
signature := append(rBytes, sBytes...)
return signature, 0, true // P256 does not need a recovery ID
}
// Main Ecdsa function
func Ecdsa(request interface{}, secret interface{}, curveFunc func() string, hashFunc func() string) map[string]interface{} {
// Initialize return structure
result := map[string]interface{}{
"r": "",
"s": "",
"v": 0,
}
// Determine the curve
curveName := "secp256k1"
if curveFunc != nil {
curveName = curveFunc()
}
if curveName != "secp256k1" && curveName != "p256" {
return result
}
// Hash the message if needed
var messageHash []byte
requestStr, ok := request.(string)
if !ok {
return result
}
if hashFunc != nil {
hashName := hashFunc()
if hashName == "sha256" {
h := sha256Hash.New()
h.Write([]byte(requestStr))
messageHash = h.Sum(nil)
} else {
return result
}
} else {
messageHash, ok = hexToBytes(requestStr)
if !ok {
return result
}
}
// Convert secret key to bytes
secretStr, ok := secret.(string)
if !ok {
return result
}
secretKeyBytes, ok := hexToBytes(secretStr)
if !ok {
return result
}
// Sign the message depending on the curve
var signature []byte
var recoveryId int
success := false
if curveName == "secp256k1" {
signature, recoveryId, success = signSecp256k1(messageHash, secretKeyBytes)
} else {
signature, recoveryId, success = signP256(messageHash, secretKeyBytes)
}
if !success {
return result
}
// Extract r and s components
rBytes := signature[:32]
sBytes := signature[32:]
rHex := toHex(rBytes)
sHex := toHex(sBytes)
// Update the result map with signature components
result["r"] = rHex
result["s"] = sHex
result["v"] = recoveryId
return result
}
func Crc32(str string, signed2 ...bool) int64 {
signed := false
if len(signed2) > 0 {
signed = signed2[0]
}
// Compute the CRC32 checksum using IEEE polynomial
crc := crc32.ChecksumIEEE([]byte(str))
if signed {
// Convert unsigned CRC32 to signed 32-bit integer
return int64(int32(crc))
}
// Return unsigned 32-bit integer as int64
return int64(crc)
}