From d22f633f74809161fbcb3bdcc3733b11f8de62da Mon Sep 17 00:00:00 2001 From: wuyingfengsui Date: Thu, 31 Mar 2022 17:17:57 +0800 Subject: [PATCH 01/20] business api --- app.go | 10 ++- business.go | 68 +++++++++++++++++++++ cmd/ctl/main.go | 18 +++++- company.go | 4 ++ crypto.go | 157 ++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 + kyc.go | 4 ++ session.go | 102 +++++++++++++++++++++++++++++++ util.go | 1 + 9 files changed, 361 insertions(+), 5 deletions(-) create mode 100644 business.go diff --git a/app.go b/app.go index 9fe43cd..3540415 100644 --- a/app.go +++ b/app.go @@ -120,7 +120,7 @@ func (a *App) GetOrder(id string) (*Result, error) { // UpdateOrder update order's note. func (a *App) UpdateOrder(id string, note string) (*Result, error) { - return a.session.put("/api/v1/app/order/" + id, map[string]interface{}{ + return a.session.put("/api/v1/app/order/"+id, map[string]interface{}{ "note": note, }) } @@ -151,8 +151,8 @@ func (a *App) Transfer(to, coinType, value string) (*Result, error) { } return a.session.post("/api/v1/app/"+coinType+"/transfer", map[string]interface{}{ - "to": to, - "value": value, + "to": to, + "value": value, "note": "", "message": "", }) @@ -344,6 +344,10 @@ func (a *App) getSecret() string { return a.Secret } +func (a *App) getPubKey() string { + return "" +} + func (a *App) getAddr() string { return a.Addr } diff --git a/business.go b/business.go new file mode 100644 index 0000000..ca232be --- /dev/null +++ b/business.go @@ -0,0 +1,68 @@ +package jadepoolsaas + +import ( + "encoding/base64" + "os" +) + +// NewBusinessWithAddr creates a new business instance with server addr, business key, the private key pem file of your service and the public key pem file of xpert. +func NewBusinessWithAddr(addr, businessKey, pemFilePath, pubPemFilePath string) (*Business, error) { + data, err := os.ReadFile(pemFilePath) + if err != nil { + return nil, err + } + + var pubData []byte + if len(pubPemFilePath) > 0 { + pubData, err = os.ReadFile(pubPemFilePath) + if err != nil { + return nil, err + } + } + + a := &Business{ + Addr: addr, + Key: businessKey, + Secret: base64.StdEncoding.EncodeToString(data), + } + if len(pubData) > 0 { + a.PubKey = base64.StdEncoding.EncodeToString(pubData) + } + a.session = &session{client: a} + return a, nil +} + +// AssetsGet fetch all assets in the wallet. +func (b *Business) AssetsGet() (*BusinessResult, error) { + return b.session.businessGet("/api/v1/business/assets") +} + +// Business represents a business instance. +type Business struct { + Addr string + Key string + Secret string + PubKey string + + session *session +} + +func (b *Business) getKey() string { + return b.Key +} + +func (b *Business) getKeyHeaderName() string { + return "X-BUSINESS-KEY" +} + +func (b *Business) getSecret() string { + return b.Secret +} + +func (b *Business) getPubKey() string { + return b.PubKey +} + +func (b *Business) getAddr() string { + return b.Addr +} diff --git a/cmd/ctl/main.go b/cmd/ctl/main.go index 06edd22..8175723 100644 --- a/cmd/ctl/main.go +++ b/cmd/ctl/main.go @@ -17,6 +17,7 @@ func runCommand(arguments docopt.Opts) (*sdk.Result, error) { action, _ := arguments.String("") params := arguments[""].([]string) addr, _ := arguments.String("--address") + pubKey, _ := arguments.String("--pubkey") switch action { case "CreateAddress": @@ -364,6 +365,12 @@ func runCommand(arguments docopt.Opts) (*sdk.Result, error) { } return getKYC(addr, key, secret).ApplicationSubmit(params[0]) + case "BusinessAssetsGet": + result, err := getBusiness(addr, key, secret, pubKey).AssetsGet() + if err != nil { + return nil, err + } + return result.ToResult(), nil default: return nil, errors.New("unknown action: " + action) } @@ -387,16 +394,22 @@ func getKYC(addr, key, secret string) *sdk.KYC { return sdk.NewKYCWithAddr(addr, key, secret) } +func getBusiness(addr, key, secret, pubKey string) *sdk.Business { + b, _ := sdk.NewBusinessWithAddr(addr, key, secret, pubKey) + return b +} + func main() { usage := `JadePool SAAS control tool. Usage: - ctl [...] [-a ] + ctl [...] [-a ] [-p ] ctl -h | --help Options: -h --help Show this screen. - -a , --address Use custom SAAS server, e.g., http://127.0.0.1:8092` + -a , --address Use custom SAAS server, e.g., http://127.0.0.1:8092 + -p , --pubkey Use the public key pem file for verifying response` arguments, _ := docopt.ParseDoc(usage) @@ -408,6 +421,7 @@ Options: fmt.Println("code:", result.Code) fmt.Println("message:", result.Message) + fmt.Println("sign:", result.Sign) fmt.Println("data:") printMap(result.Data) } diff --git a/company.go b/company.go index 1e0fa90..51d0e20 100644 --- a/company.go +++ b/company.go @@ -207,6 +207,10 @@ func (c *Company) getSecret() string { return c.Secret } +func (c *Company) getPubKey() string { + return "" +} + func (c *Company) getAddr() string { return c.Addr } diff --git a/crypto.go b/crypto.go index 93fe43e..011b24f 100644 --- a/crypto.go +++ b/crypto.go @@ -6,10 +6,17 @@ import ( "crypto/cipher" "crypto/hmac" "crypto/sha256" + "crypto/x509/pkix" + "encoding/asn1" "encoding/base64" "encoding/hex" "encoding/json" + "encoding/pem" + "errors" "fmt" + "github.com/btcsuite/btcd/btcec" + "golang.org/x/crypto/sha3" + "math/big" "reflect" "sort" "strconv" @@ -157,8 +164,158 @@ func aesDecrypt(src []byte, key []byte, iv []byte) ([]byte, error) { return src, nil } +func parseEcdsaPrivateKeyFromPem(pemContent []byte) (*btcec.PrivateKey, error) { + block, _ := pem.Decode(pemContent) + if block == nil { + return nil, errors.New("invalid pem") + } + + var ecp ecPrivateKey + _, err := asn1.Unmarshal(block.Bytes, &ecp) + if err != nil { + return nil, err + } + + priKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), ecp.PrivateKey) + return priKey, nil +} + +func signECCDataStr(priKeyPemBase64 string, msgStr string, sigEncode string) (*eccSig, error) { + priKeyPem, err := base64.StdEncoding.DecodeString(priKeyPemBase64) + if err != nil { + return nil, err + } + priKey, err := parseEcdsaPrivateKeyFromPem(priKeyPem) + if err != nil { + return nil, err + } + + sha3Hash := sha3.NewLegacyKeccak256() + _, err = sha3Hash.Write([]byte(msgStr)) + if err != nil { + return nil, err + } + msgBuf := sha3Hash.Sum(nil) + sig, err := priKey.Sign(msgBuf) + if err != nil { + return nil, err + } + + r := sig.R.Bytes() + s := sig.S.Bytes() + if len(r) < 32 { + preArr := []byte{} + for i := len(r) + 1; i <= 32; i++ { + preArr = append(preArr, 0) + } + r = append(preArr, r...) + } + if len(s) < 32 { + preArr := []byte{} + for i := len(s) + 1; i <= 32; i++ { + preArr = append(preArr, 0) + } + s = append(preArr, s...) + } + _sig := &eccSig{} + if sigEncode == "hex" { + _sig.R = hex.EncodeToString(r) + _sig.S = hex.EncodeToString(s) + } else if sigEncode == "base64" { + _sig.R = base64.StdEncoding.EncodeToString(r) + _sig.S = base64.StdEncoding.EncodeToString(s) + } + return _sig, nil +} + +func parseEcdsaPubKeyFromPem(pemContent []byte) (*btcec.PublicKey, error) { + block, _ := pem.Decode(pemContent) + if block == nil { + return nil, errors.New("invalid pem") + } + + var ecp ecPublicKey + _, err := asn1.Unmarshal(block.Bytes, &ecp) + if err != nil { + return nil, err + } + + return btcec.ParsePubKey(ecp.PublicKey.RightAlign(), btcec.S256()) +} + +func verifyECCSign(pubKeyPemBase64 string, obj map[string]interface{}, sign *eccSig, sigEncode string) (bool, error) { + pubKeyPem, err := base64.StdEncoding.DecodeString(pubKeyPemBase64) + if err != nil { + return false, err + } + + pubKey, err := parseEcdsaPubKeyFromPem(pubKeyPem) + if err != nil { + return false, err + } + + msgStr := buildMsg(obj, "", "") + return verifyECCSignStr(msgStr, sign, pubKey, sigEncode) +} + +func verifyECCSignStr(msgStr string, sign *eccSig, pubKey *btcec.PublicKey, sigEncode string) (bool, error) { + sha3Hash := sha3.NewLegacyKeccak256() + _, err := sha3Hash.Write([]byte(msgStr)) + if err != nil { + return false, err + } + msgBuf := sha3Hash.Sum(nil) + + var decodedR, decodedS []byte + if sigEncode == "hex" { + decodedR, err = hex.DecodeString(sign.R) + if err != nil { + return false, err + } + decodedS, err = hex.DecodeString(sign.S) + if err != nil { + return false, err + } + } else if sigEncode == "base64" { + decodedR, err = base64.StdEncoding.DecodeString(sign.R) + if err != nil { + return false, err + } + decodedS, err = base64.StdEncoding.DecodeString(sign.S) + if err != nil { + return false, err + } + } + + signature := btcec.Signature{ + R: new(big.Int).SetBytes(decodedR), + S: new(big.Int).SetBytes(decodedS), + } + return signature.Verify(msgBuf, pubKey), nil +} + func unpadding(src []byte) []byte { n := len(src) unPadNum := int(src[n-1]) return src[:n-unPadNum] } + +//This type provides compatibility with the btcec package +type ecPrivateKey struct { + Version int + PrivateKey []byte + NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"` + PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"` +} + +//This type provides compatibility with the btcec package +type ecPublicKey struct { + Raw asn1.RawContent + Algorithm pkix.AlgorithmIdentifier + PublicKey asn1.BitString +} + +type eccSig struct { + R string `json:"r"` + S string `json:"s"` +} diff --git a/go.mod b/go.mod index a0403d3..2916ef6 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/nbltrust/hashkey-custody-sdk-go go 1.13 require ( + github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 github.com/imroc/req v0.2.4 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 ) diff --git a/kyc.go b/kyc.go index 92d9188..c7d221a 100644 --- a/kyc.go +++ b/kyc.go @@ -129,6 +129,10 @@ func (k *KYC) getSecret() string { return k.Secret } +func (k *KYC) getPubKey() string { + return "" +} + func (k *KYC) getAddr() string { return k.Addr } diff --git a/session.go b/session.go index 4047361..03295fe 100644 --- a/session.go +++ b/session.go @@ -6,12 +6,15 @@ import ( "fmt" "io/ioutil" "math/rand" + "net/http" + "strconv" "time" "github.com/imroc/req" ) type params req.Param +type businessParams req.Param // Result request result. type Result struct { @@ -21,6 +24,18 @@ type Result struct { Sign string } +// BusinessResult request result. +type BusinessResult struct { + Code int + Data map[string]interface{} + Message string + Sign struct { + R string + S string + } + signVerified bool +} + type session struct { client client nonceCount int @@ -30,6 +45,10 @@ func (session *session) get(path string) (*Result, error) { return session.getWithParams(path, map[string]interface{}{}) } +func (session *session) businessGet(path string) (*BusinessResult, error) { + return session.businessGetWithParams(path, map[string]interface{}{}) +} + func (session *session) getWithParams(path string, params params) (*Result, error) { url := session.getURL(path) err := session.prepareParams(params) @@ -58,6 +77,34 @@ func (session *session) getWithParams(path string, params params) (*Result, erro return &result, err } +func (session *session) businessGetWithParams(path string, params businessParams) (*BusinessResult, error) { + url := session.getURL(path) + err := session.businessPrepareParams(http.MethodGet, path, params) + if err != nil { + return nil, err + } + + r, err := req.Get(url, session.commonHeaders(), req.Param(params)) + if err != nil { + return nil, err + } + if r.Response().StatusCode != 200 { + return nil, fmt.Errorf("http error code:%d", r.Response().StatusCode) + } + + var result BusinessResult + err = r.ToJSON(&result) + if err != nil { + return nil, fmt.Errorf("parse body to json failed: %v", err) + } + + if err = result.error(session.client.getPubKey()); err != nil { + return &result, err + } + + return &result, err +} + func (session *session) getFile(path string, filePath string) (*Result, error) { params := params{} @@ -298,6 +345,12 @@ func (session *session) prepareParams(params params) error { return params.sign(session.client.getSecret()) } +func (session *session) businessPrepareParams(method, path string, params businessParams) error { + timestamp := time.Now().Unix() + params["timestamp"] = timestamp + return params.sign(method, path, session.client.getSecret()) +} + func (session *session) commonHeaders() req.Header { keyName := session.client.getKeyHeaderName() return req.Header{ @@ -320,6 +373,55 @@ func (params *params) sign(secret string) error { return nil } +func (params *businessParams) sign(method, path string, priKeyPemBase64 string) error { + msgStr := method + path + msgStr += buildMsg(*params, "", "") + sign, err := signECCDataStr(priKeyPemBase64, msgStr, "base64") + if err != nil { + return err + } + (*params)["sigR"] = sign.R + (*params)["sigS"] = sign.S + return nil +} + +func (result *BusinessResult) error(pubKey string) error { + if !result.success() { + return errors.New(result.Message) + } + + if len(pubKey) > 0 && !result.checkSign(pubKey) { + return errors.New("check sign failed") + } + return nil +} + +func (result *BusinessResult) success() bool { + return result.Code == 0 +} + +func (result *BusinessResult) checkSign(pubKeyPemBase64 string) bool { + verified, err := verifyECCSign(pubKeyPemBase64, result.Data, &eccSig{ + R: result.Sign.R, + S: result.Sign.S, + }, "base64") + if err != nil { + return false + } + + result.signVerified = true + return verified +} + +func (result *BusinessResult) ToResult() *Result { + return &Result{ + Code: result.Code, + Data: result.Data, + Message: result.Message, + Sign: strconv.FormatBool(result.signVerified), + } +} + func (result *Result) error(secret string) error { if !result.success() { return errors.New(result.Message) diff --git a/util.go b/util.go index 79eb41c..3dd3d98 100644 --- a/util.go +++ b/util.go @@ -8,5 +8,6 @@ type client interface { getKey() string getKeyHeaderName() string getSecret() string + getPubKey() string getAddr() string } From 1f93a0b50094e5230e4717680f8c53681c133e25 Mon Sep 17 00:00:00 2001 From: wuyingfengsui Date: Fri, 1 Apr 2022 17:16:51 +0800 Subject: [PATCH 02/20] business balances --- business.go | 15 +++++++++++++++ cmd/ctl/main.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/business.go b/business.go index ca232be..64129a6 100644 --- a/business.go +++ b/business.go @@ -32,11 +32,26 @@ func NewBusinessWithAddr(addr, businessKey, pemFilePath, pubPemFilePath string) return a, nil } +// ClientGet fetch client info. +func (b *Business) ClientGet(userID uint) (*BusinessResult, error) { + return b.session.businessGetWithParams("/api/v1/business/client", map[string]interface{}{ + "userID": userID, + }) +} + // AssetsGet fetch all assets in the wallet. func (b *Business) AssetsGet() (*BusinessResult, error) { return b.session.businessGet("/api/v1/business/assets") } +// WalletBalancesGet fetch the asset balance in the wallet. +func (b *Business) WalletBalancesGet(userID, assetID uint) (*BusinessResult, error) { + return b.session.businessGetWithParams("/api/v1/business/wallet/balances", map[string]interface{}{ + "userID": userID, + "assetID": assetID, + }) +} + // Business represents a business instance. type Business struct { Addr string diff --git a/cmd/ctl/main.go b/cmd/ctl/main.go index 8175723..0db529d 100644 --- a/cmd/ctl/main.go +++ b/cmd/ctl/main.go @@ -371,6 +371,41 @@ func runCommand(arguments docopt.Opts) (*sdk.Result, error) { return nil, err } return result.ToResult(), nil + case "BusinessClientGet": + if len(params) != 1 { + return nil, errors.New("invalid params") + } + + uid, err := strconv.ParseUint(params[0], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + result, err := getBusiness(addr, key, secret, pubKey).ClientGet(uint(uid)) + if err != nil { + return nil, err + } + return result.ToResult(), nil + case "BusinessWalletBalancesGet": + if len(params) != 2 { + return nil, errors.New("invalid params") + } + + uid, err := strconv.ParseUint(params[0], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + aid, err := strconv.ParseUint(params[1], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + result, err := getBusiness(addr, key, secret, pubKey).WalletBalancesGet(uint(uid), uint(aid)) + if err != nil { + return nil, err + } + return result.ToResult(), nil default: return nil, errors.New("unknown action: " + action) } From 376e271621b8782a889151ba49384a7e858c8fe4 Mon Sep 17 00:00:00 2001 From: wuyingfengsui Date: Fri, 8 Apr 2022 18:56:05 +0800 Subject: [PATCH 03/20] business lock --- business.go | 20 ++++++++++++++++++++ cmd/ctl/main.go | 40 ++++++++++++++++++++++++++++++++++++++++ session.go | 39 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/business.go b/business.go index 64129a6..8670952 100644 --- a/business.go +++ b/business.go @@ -52,6 +52,26 @@ func (b *Business) WalletBalancesGet(userID, assetID uint) (*BusinessResult, err }) } +// BalanceLock lock the balance. +func (b *Business) BalanceLock(userID, assetID uint, sequence, amount string) (*BusinessResult, error) { + return b.session.businessPut("/api/v1/business/balance/lock", map[string]interface{}{ + "userID": userID, + "assetID": assetID, + "sequence": sequence, + "amount": amount, + }) +} + +// BalanceUnlock unlock the balance. +func (b *Business) BalanceUnlock(userID, assetID uint, sequence, amount string) (*BusinessResult, error) { + return b.session.businessPut("/api/v1/business/balance/unlock", map[string]interface{}{ + "userID": userID, + "assetID": assetID, + "sequence": sequence, + "amount": amount, + }) +} + // Business represents a business instance. type Business struct { Addr string diff --git a/cmd/ctl/main.go b/cmd/ctl/main.go index 0db529d..6783365 100644 --- a/cmd/ctl/main.go +++ b/cmd/ctl/main.go @@ -406,6 +406,46 @@ func runCommand(arguments docopt.Opts) (*sdk.Result, error) { return nil, err } return result.ToResult(), nil + case "BusinessBalanceLock": + if len(params) != 4 { + return nil, errors.New("invalid params") + } + + uid, err := strconv.ParseUint(params[0], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + aid, err := strconv.ParseUint(params[1], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + result, err := getBusiness(addr, key, secret, pubKey).BalanceLock(uint(uid), uint(aid), params[2], params[3]) + if err != nil { + return nil, err + } + return result.ToResult(), nil + case "BusinessBalanceUnlock": + if len(params) != 4 { + return nil, errors.New("invalid params") + } + + uid, err := strconv.ParseUint(params[0], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + aid, err := strconv.ParseUint(params[1], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + result, err := getBusiness(addr, key, secret, pubKey).BalanceUnlock(uint(uid), uint(aid), params[2], params[3]) + if err != nil { + return nil, err + } + return result.ToResult(), nil default: return nil, errors.New("unknown action: " + action) } diff --git a/session.go b/session.go index 03295fe..e2d832d 100644 --- a/session.go +++ b/session.go @@ -302,6 +302,34 @@ func (session *session) put(path string, params params) (*Result, error) { return &result, err } +func (session *session) businessPut(path string, params businessParams) (*BusinessResult, error) { + url := session.getURL(path) + err := session.businessPrepareParams(http.MethodPut, path, params) + if err != nil { + return nil, err + } + + r, err := req.Put(url, session.commonHeaders(), req.BodyJSON(¶ms)) + if err != nil { + return nil, err + } + if r.Response().StatusCode != 200 { + return nil, fmt.Errorf("http error code:%d", r.Response().StatusCode) + } + + var result BusinessResult + err = r.ToJSON(&result) + if err != nil { + return nil, fmt.Errorf("parse body to json failed: %v", err) + } + + if err = result.error(session.client.getPubKey()); err != nil { + return nil, err + } + + return &result, err +} + func (session *session) delete(path string) (*Result, error) { return session.deleteWithParams(path, map[string]interface{}{}) } @@ -380,8 +408,15 @@ func (params *businessParams) sign(method, path string, priKeyPemBase64 string) if err != nil { return err } - (*params)["sigR"] = sign.R - (*params)["sigS"] = sign.S + if method == http.MethodGet { + (*params)["sigR"] = sign.R + (*params)["sigS"] = sign.S + } else { + (*params)["sig"] = map[string]interface{}{ + "r": sign.R, + "s": sign.S, + } + } return nil } From ad943c9eae038c8bf4b0046584c23fcba56e491a Mon Sep 17 00:00:00 2001 From: wuyingfengsui Date: Tue, 12 Apr 2022 11:42:00 +0800 Subject: [PATCH 04/20] swap --- business.go | 13 +++++++++++++ cmd/ctl/main.go | 37 +++++++++++++++++++++++++++++++------ session.go | 28 ++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/business.go b/business.go index 8670952..9232f22 100644 --- a/business.go +++ b/business.go @@ -72,6 +72,19 @@ func (b *Business) BalanceUnlock(userID, assetID uint, sequence, amount string) }) } +// Swap swap. +func (b *Business) Swap(from, fromAssetID, officialAssetID uint, sequence, fromAmount, officialAmount, note string) (*BusinessResult, error) { + return b.session.businessPost("/api/v1/business/swap", map[string]interface{}{ + "from": from, + "fromAssetID": fromAssetID, + "officialAssetID": officialAssetID, + "sequence": sequence, + "fromAmount": fromAmount, + "officialAmount": officialAmount, + "note": note, + }) +} + // Business represents a business instance. type Business struct { Addr string diff --git a/cmd/ctl/main.go b/cmd/ctl/main.go index 6783365..a4822b3 100644 --- a/cmd/ctl/main.go +++ b/cmd/ctl/main.go @@ -411,17 +411,17 @@ func runCommand(arguments docopt.Opts) (*sdk.Result, error) { return nil, errors.New("invalid params") } - uid, err := strconv.ParseUint(params[0], 10, 64) + uid, err := strconv.ParseUint(params[1], 10, 64) if err != nil { return nil, errors.New("invalid params") } - aid, err := strconv.ParseUint(params[1], 10, 64) + aid, err := strconv.ParseUint(params[2], 10, 64) if err != nil { return nil, errors.New("invalid params") } - result, err := getBusiness(addr, key, secret, pubKey).BalanceLock(uint(uid), uint(aid), params[2], params[3]) + result, err := getBusiness(addr, key, secret, pubKey).BalanceLock(uint(uid), uint(aid), params[0], params[3]) if err != nil { return nil, err } @@ -431,17 +431,42 @@ func runCommand(arguments docopt.Opts) (*sdk.Result, error) { return nil, errors.New("invalid params") } - uid, err := strconv.ParseUint(params[0], 10, 64) + uid, err := strconv.ParseUint(params[1], 10, 64) if err != nil { return nil, errors.New("invalid params") } - aid, err := strconv.ParseUint(params[1], 10, 64) + aid, err := strconv.ParseUint(params[2], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + result, err := getBusiness(addr, key, secret, pubKey).BalanceUnlock(uint(uid), uint(aid), params[0], params[3]) + if err != nil { + return nil, err + } + return result.ToResult(), nil + case "BusinessSwap": + if len(params) != 6 { + return nil, errors.New("invalid params") + } + + uid, err := strconv.ParseUint(params[1], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + aid, err := strconv.ParseUint(params[2], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + oaid, err := strconv.ParseUint(params[4], 10, 64) if err != nil { return nil, errors.New("invalid params") } - result, err := getBusiness(addr, key, secret, pubKey).BalanceUnlock(uint(uid), uint(aid), params[2], params[3]) + result, err := getBusiness(addr, key, secret, pubKey).Swap(uint(uid), uint(aid), uint(oaid), params[0], params[3], params[5], "") if err != nil { return nil, err } diff --git a/session.go b/session.go index e2d832d..e7d329c 100644 --- a/session.go +++ b/session.go @@ -182,6 +182,34 @@ func (session *session) post(path string, params params) (*Result, error) { return &result, err } +func (session *session) businessPost(path string, params businessParams) (*BusinessResult, error) { + url := session.getURL(path) + err := session.businessPrepareParams(http.MethodPost, path, params) + if err != nil { + return nil, err + } + + r, err := req.Post(url, session.commonHeaders(), req.BodyJSON(¶ms)) + if err != nil { + return nil, err + } + if r.Response().StatusCode != 200 { + return nil, fmt.Errorf("http error code:%d", r.Response().StatusCode) + } + + var result BusinessResult + err = r.ToJSON(&result) + if err != nil { + return nil, fmt.Errorf("parse body to json failed: %v", err) + } + + if err = result.error(session.client.getPubKey()); err != nil { + return nil, err + } + + return &result, err +} + func (session *session) patch(path string, params params) (*Result, error) { url := session.getURL(path) err := session.prepareParams(params) From b166ff27087ae22deaacfd2dd09637bc02ffa5df Mon Sep 17 00:00:00 2001 From: wuyingfengsui Date: Tue, 12 Apr 2022 16:37:21 +0800 Subject: [PATCH 05/20] batch --- business.go | 18 ++++++++++++++++++ cmd/ctl/main.go | 25 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/business.go b/business.go index 9232f22..e938123 100644 --- a/business.go +++ b/business.go @@ -85,6 +85,24 @@ func (b *Business) Swap(from, fromAssetID, officialAssetID uint, sequence, fromA }) } +// Batch batch. +func (b *Business) Batch(cmd []*BatchCommand) (*BusinessResult, error) { + return b.session.businessPost("/api/v1/business/batch", map[string]interface{}{ + "cmd": cmd, + }) +} + +// BatchCommand ... +type BatchCommand struct { + Name string `json:"name"` + Args interface{} `json:"args"` +} + +// OrderGetBySequence fetch the order by the sequence. +func (b *Business) OrderGetBySequence(sequence string) (*BusinessResult, error) { + return b.session.businessGet("/api/v1/business/order/sequence/" + sequence) +} + // Business represents a business instance. type Business struct { Addr string diff --git a/cmd/ctl/main.go b/cmd/ctl/main.go index a4822b3..501bfaa 100644 --- a/cmd/ctl/main.go +++ b/cmd/ctl/main.go @@ -471,6 +471,31 @@ func runCommand(arguments docopt.Opts) (*sdk.Result, error) { return nil, err } return result.ToResult(), nil + case "BusinessBatch": + if len(params) != 1 { + return nil, errors.New("invalid params") + } + + cmd := []*sdk.BatchCommand{} + err := json.NewDecoder(strings.NewReader(params[0])).Decode(&cmd) + if err != nil { + return nil, err + } + result, err := getBusiness(addr, key, secret, pubKey).Batch(cmd) + if err != nil { + return nil, err + } + return result.ToResult(), nil + case "BusinessOrderGet": + if len(params) != 1 { + return nil, errors.New("invalid params") + } + + result, err := getBusiness(addr, key, secret, pubKey).OrderGetBySequence(params[0]) + if err != nil { + return nil, err + } + return result.ToResult(), nil default: return nil, errors.New("unknown action: " + action) } From 3a79833607e87f655939f3f0fb34b274bb5eb697 Mon Sep 17 00:00:00 2001 From: wuyingfengsui Date: Thu, 14 Apr 2022 17:19:58 +0800 Subject: [PATCH 06/20] business transfer --- business.go | 12 ++++++++++++ cmd/ctl/main.go | 25 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/business.go b/business.go index e938123..569b7f6 100644 --- a/business.go +++ b/business.go @@ -72,6 +72,18 @@ func (b *Business) BalanceUnlock(userID, assetID uint, sequence, amount string) }) } +// Transfer transfer. +func (b *Business) Transfer(from, to, assetID uint, sequence, amount, note string) (*BusinessResult, error) { + return b.session.businessPost("/api/v1/business/transfer", map[string]interface{}{ + "from": from, + "to": to, + "sequence": sequence, + "assetID": assetID, + "amount": amount, + "note": note, + }) +} + // Swap swap. func (b *Business) Swap(from, fromAssetID, officialAssetID uint, sequence, fromAmount, officialAmount, note string) (*BusinessResult, error) { return b.session.businessPost("/api/v1/business/swap", map[string]interface{}{ diff --git a/cmd/ctl/main.go b/cmd/ctl/main.go index 501bfaa..c9c4de2 100644 --- a/cmd/ctl/main.go +++ b/cmd/ctl/main.go @@ -446,6 +446,31 @@ func runCommand(arguments docopt.Opts) (*sdk.Result, error) { return nil, err } return result.ToResult(), nil + case "BusinessTransfer": + if len(params) != 5 { + return nil, errors.New("invalid params") + } + + aid, err := strconv.ParseUint(params[1], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + fid, err := strconv.ParseUint(params[3], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + tid, err := strconv.ParseUint(params[4], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + result, err := getBusiness(addr, key, secret, pubKey).Transfer(uint(fid), uint(tid), uint(aid), params[0], params[2], "") + if err != nil { + return nil, err + } + return result.ToResult(), nil case "BusinessSwap": if len(params) != 6 { return nil, errors.New("invalid params") From 3b761191cda64d47a091faa88a79555ae958fc4b Mon Sep 17 00:00:00 2001 From: Yang Xu <41315800+alexxuyang@users.noreply.github.com> Date: Wed, 20 Apr 2022 13:26:50 +0800 Subject: [PATCH 07/20] Create business.md --- doc/business.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 doc/business.md diff --git a/doc/business.md b/doc/business.md new file mode 100644 index 0000000..6f4582e --- /dev/null +++ b/doc/business.md @@ -0,0 +1,34 @@ +git clone git@github.com:nbltrust/hashkey-custody-sdk-go.git + +git checkout business + +go mod tidy + +prepare pri_hashkey-hub.pem and pub_xpert_238.pem + +go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessAssetsGet -a "https://develop-saas.nbltrust.com/business" -p pub_xpert_238.pem + +should see something like below + +` +code: 0 +message: success +sign: true +data: +{ + "assets": [ + { + "decimal": 18, + "id": 1, + "name": "ETH", + "switch": true + }, + { + "decimal": 8, + "id": 2, + "name": "BTC", + "switch": true + } + ] +} +` From febd1705621c469ba44317499094e8955ae6a8df Mon Sep 17 00:00:00 2001 From: Yang Xu <41315800+alexxuyang@users.noreply.github.com> Date: Wed, 20 Apr 2022 13:47:28 +0800 Subject: [PATCH 08/20] Update business.md --- doc/business.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/business.md b/doc/business.md index 6f4582e..cb324d8 100644 --- a/doc/business.md +++ b/doc/business.md @@ -6,7 +6,7 @@ go mod tidy prepare pri_hashkey-hub.pem and pub_xpert_238.pem -go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessAssetsGet -a "https://develop-saas.nbltrust.com/business" -p pub_xpert_238.pem +go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessAssetsGet -a "https://develop-saas.nbltrust.com/saas-business" -p pub_xpert_238.pem should see something like below From 3f1f130ad3f4cabe45abdda2083babc943c9d880 Mon Sep 17 00:00:00 2001 From: Yang Xu <41315800+alexxuyang@users.noreply.github.com> Date: Wed, 20 Apr 2022 20:27:24 +0800 Subject: [PATCH 09/20] Update business.md --- doc/business.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/doc/business.md b/doc/business.md index cb324d8..b4a8e87 100644 --- a/doc/business.md +++ b/doc/business.md @@ -8,13 +8,7 @@ prepare pri_hashkey-hub.pem and pub_xpert_238.pem go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessAssetsGet -a "https://develop-saas.nbltrust.com/saas-business" -p pub_xpert_238.pem -should see something like below - ` -code: 0 -message: success -sign: true -data: { "assets": [ { @@ -32,3 +26,15 @@ data: ] } ` + +go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessClientGet 235 -a "https://develop-saas.nbltrust.com/saas-business" -p pub_xpert_238.pem + +` +{ + "email": "cob63176@tuofs.com", + "id": 235, + "kycLevel": 1, + "name": "Christina", + "phone": "+86-13817572905" +} +` From d5935da01d985f38ce126868fb1b34c1fab625f7 Mon Sep 17 00:00:00 2001 From: Yang Xu <41315800+alexxuyang@users.noreply.github.com> Date: Wed, 20 Apr 2022 20:28:35 +0800 Subject: [PATCH 10/20] Update business.md --- doc/business.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/business.md b/doc/business.md index b4a8e87..1d421bd 100644 --- a/doc/business.md +++ b/doc/business.md @@ -8,7 +8,7 @@ prepare pri_hashkey-hub.pem and pub_xpert_238.pem go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessAssetsGet -a "https://develop-saas.nbltrust.com/saas-business" -p pub_xpert_238.pem -` +```json { "assets": [ { @@ -25,11 +25,11 @@ go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessAssetsGet -a "htt } ] } -` +``` go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessClientGet 235 -a "https://develop-saas.nbltrust.com/saas-business" -p pub_xpert_238.pem -` +```json { "email": "cob63176@tuofs.com", "id": 235, @@ -37,4 +37,4 @@ go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessClientGet 235 -a "name": "Christina", "phone": "+86-13817572905" } -` +``` From fbfa67ddca06cb83366a0bc7ca3584607a284687 Mon Sep 17 00:00:00 2001 From: Yang Xu <41315800+alexxuyang@users.noreply.github.com> Date: Wed, 20 Apr 2022 20:59:00 +0800 Subject: [PATCH 11/20] Update business.md --- doc/business.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/business.md b/doc/business.md index 1d421bd..6d203ef 100644 --- a/doc/business.md +++ b/doc/business.md @@ -38,3 +38,19 @@ go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessClientGet 235 -a "phone": "+86-13817572905" } ``` + +go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessWalletBalancesGet 435 1 -a "https://develop-saas.nbltrust.com/saas-business" -p pub_xpert_238.pem + +```json +{ + "balances": [ + { + "assetID": 1, + "assetName": "ETH", + "available": "1000.000000000000000000", + "locked": "0.000000000000000000", + "total": "1000.000000000000000000" + } + ] +} +``` From fba38aeed2ba64415f45b7187a292468d7d622a1 Mon Sep 17 00:00:00 2001 From: Yang Xu <41315800+alexxuyang@users.noreply.github.com> Date: Wed, 20 Apr 2022 21:10:54 +0800 Subject: [PATCH 12/20] Update business.md --- doc/business.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/doc/business.md b/doc/business.md index 6d203ef..cc8e40c 100644 --- a/doc/business.md +++ b/doc/business.md @@ -54,3 +54,29 @@ go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessWalletBalancesGet ] } ``` + +go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessBalanceLock 165045845511 435 1 11 -a "https://develop-saas.nbltrust.com/saas-business" -p pub_xpert_238.pem + +```json +{ + "available": "989.000000000000000000", + "total": "1000.000000000000000000" +} +``` + +go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessBalanceUnlock 165045845512 435 1 6 -a "https://develop-saas.nbltrust.com/saas-business" -p pub_xpert_238.pem + +```json +{ + "available": "995.000000000000000000", + "total": "1000.000000000000000000" +} +``` + +go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessTransfer 165045845513 1 4.1 435 235 -a "https://develop-saas.nbltrust.com/saas-business" -p pub_xpert_238.pem + +```json +{ + "id": 4 +} +``` From 4858139eb7b21acdfa52240e30cbbd745800bf04 Mon Sep 17 00:00:00 2001 From: Yang Xu <41315800+alexxuyang@users.noreply.github.com> Date: Wed, 20 Apr 2022 21:18:44 +0800 Subject: [PATCH 13/20] Update business.md --- doc/business.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/business.md b/doc/business.md index cc8e40c..d3a8a44 100644 --- a/doc/business.md +++ b/doc/business.md @@ -80,3 +80,11 @@ go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessTransfer 16504584 "id": 4 } ``` + +go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessSwap 165045845515 435 2 0.11 1 1.22 -a "https://develop-saas.nbltrust.com/saas-business" -p pub_xpert_238.pem + +```json +{ + "id": 7 +} +``` From ed3f7250e5c5197a8a26aa63b1a54c5749fed688 Mon Sep 17 00:00:00 2001 From: Yang Xu <41315800+alexxuyang@users.noreply.github.com> Date: Wed, 20 Apr 2022 21:55:37 +0800 Subject: [PATCH 14/20] Update business.md --- doc/business.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/doc/business.md b/doc/business.md index d3a8a44..318767c 100644 --- a/doc/business.md +++ b/doc/business.md @@ -88,3 +88,24 @@ go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessSwap 165045845515 "id": 7 } ``` + +go run cmd/ctl/main.go hashkey-hub pri_hashkey-hub.pem BusinessOrderGet 165045845515 -a "https://develop-saas.nbltrust.com/saas-business" -p pub_xpert_238.pem + +```json +{ + "detail": { + "from": 435, + "fromAmount": "0.11", + "fromAssetID": 2, + "fromWalletID": 1339, + "note": "", + "officialAmount": "1.22", + "officialAssetID": 1, + "officialWalletID": 1307, + "sequence": "165045845515" + }, + "id": 7, + "status": "DONE", + "type": "SWAP" +} +``` From a8160ccd1f3260fe70bd00594a890af963b4dc29 Mon Sep 17 00:00:00 2001 From: xuyang Date: Thu, 12 May 2022 18:35:44 +0800 Subject: [PATCH 15/20] add business client demo code --- alice_shared_secret.bin | Bin 0 -> 32 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 alice_shared_secret.bin diff --git a/alice_shared_secret.bin b/alice_shared_secret.bin new file mode 100644 index 0000000000000000000000000000000000000000..13e65ac763947b179eea16ffc2f9315c1636d984 GIT binary patch literal 32 ocmY$4===Qo=jBBvXQX=0F|$w0^EkxdQTL^ytk0vuEbNgg01u@PX#fBK literal 0 HcmV?d00001 From 11ba08a85ac923ea7bf7d2e22d79612f622de4d6 Mon Sep 17 00:00:00 2001 From: xuyang Date: Thu, 12 May 2022 18:38:51 +0800 Subject: [PATCH 16/20] remove useless file; add business client demo code --- alice_shared_secret.bin | Bin 32 -> 0 bytes cmd/business/main.go | 275 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 275 insertions(+) delete mode 100644 alice_shared_secret.bin create mode 100644 cmd/business/main.go diff --git a/alice_shared_secret.bin b/alice_shared_secret.bin deleted file mode 100644 index 13e65ac763947b179eea16ffc2f9315c1636d984..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32 ocmY$4===Qo=jBBvXQX=0F|$w0^EkxdQTL^ytk0vuEbNgg01u@PX#fBK diff --git a/cmd/business/main.go b/cmd/business/main.go new file mode 100644 index 0000000..167b6eb --- /dev/null +++ b/cmd/business/main.go @@ -0,0 +1,275 @@ +package main + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "time" + + "github.com/btcsuite/btcd/btcec" + "github.com/labstack/echo/v4" +) + +// ParseEcdsaPubKeyFromPem ... +func ParseEcdsaPubKeyFromPem(pemContent []byte) (*btcec.PublicKey, error) { + block, _ := pem.Decode(pemContent) + if block == nil { + return nil, errors.New("invalid pem") + } + + var ecp ecPublicKey + _, err := asn1.Unmarshal(block.Bytes, &ecp) + if err != nil { + return nil, err + } + + return btcec.ParsePubKey(ecp.PublicKey.RightAlign(), btcec.S256()) +} + +// ParseEcdsaPrivateKeyFromPem ... +func ParseEcdsaPrivateKeyFromPem(pemContent []byte) (*btcec.PrivateKey, error) { + block, _ := pem.Decode(pemContent) + if block == nil { + return nil, errors.New("invalid pem") + } + + var ecp ecPrivateKey + _, err := asn1.Unmarshal(block.Bytes, &ecp) + if err != nil { + return nil, err + } + + priKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), ecp.PrivateKey) + return priKey, nil +} + +//This type provides compatibility with the btcec package +type ecPrivateKey struct { + Version int + PrivateKey []byte + NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"` + PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"` +} + +//This type provides compatibility with the btcec package +type ecPublicKey struct { + Raw asn1.RawContent + Algorithm pkix.AlgorithmIdentifier + PublicKey asn1.BitString +} + +func genShareKey() string { + apiGatewayPubKey := string(`LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUFvRFFnQUVyb0QxMG53SzJkcElqYSszb1pncGNtbk40MWFCc0FFSQpsSE9MMEpublJad3pRa3k5cmFDSW5iSk9YcGtzcDBFUVZqdDVkdkJUMEw3b2pXQXFVSlk3b1E9PQotLS0tLUVORCBQVUJMSUMgS0VZLS0tLS0K`) + myPriKey := string(`LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IUUNBUUVFSUNrOGw2UFJ6QTRhWjVVZkl1aXViaGplUDJBSTEvVENJajE2OUwzWU9xekFvQWNHQlN1QkJBQUsKb1VRRFFnQUVvVVpwRXNCRFpWTC9TQ29DanlreXAwdXRNMDc2b29YNUU2eEtzQW5TZlpOMFEwM3VlbGVVL09aeAovMmxsUXFBdU9aVlFLNE9aSGdFODh1c3RWVkY3YWc9PQotLS0tLUVORCBFQyBQUklWQVRFIEtFWS0tLS0tCg==`) + + apiGatewayPubKeyPem, err := base64.StdEncoding.DecodeString(apiGatewayPubKey) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + pubKey, err := ParseEcdsaPubKeyFromPem(apiGatewayPubKeyPem) + if err != nil { + fmt.Println(err) + os.Exit(2) + } + priKeyPem, err := base64.StdEncoding.DecodeString(myPriKey) + if err != nil { + fmt.Println(err) + os.Exit(3) + } + priKey, err := ParseEcdsaPrivateKeyFromPem(priKeyPem) + if err != nil { + fmt.Println(err) + os.Exit(4) + } + + aesKey := btcec.GenerateSharedSecret(priKey, pubKey) + fmt.Println(string(aesKey)) + + return base64.StdEncoding.EncodeToString(aesKey) +} + +type encryptedReqBody struct { + Encrypted string `json:"encrypted"` +} + +type encryptedResBody struct { + Encrypted string `json:"encrypted"` + Iv string `json:"iv"` +} + +func GenerateRandomBytes(n int) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + + if err != nil { + return nil, err + } + + return b, nil +} + +func encryptResBody(msg, aesKey string) ([]byte, []byte, error) { + iv, err := GenerateRandomBytes(16) + if err != nil { + return nil, nil, errors.New("iv generaton error") + } + + mKey, err := base64.StdEncoding.DecodeString(aesKey) + if err != nil { + return nil, nil, errors.New("invalid aes key") + } + + encrypted, err := AESEncryptStr(msg, mKey, iv) + if err != nil { + return nil, nil, err + } + return []byte(encrypted), iv, nil +} + +func decryptReqBody(encrypted *encryptedReqBody, aesKey, iv string) ([]byte, error) { + aesIV, err := base64.StdEncoding.DecodeString(iv) + if err != nil || len(aesIV) != 16 { + return nil, errors.New("invalid aes iv") + } + + mKey, err := base64.StdEncoding.DecodeString(aesKey) + if err != nil { + return nil, errors.New("invalid aes key") + } + + decrypted, err := AESDecryptStr(encrypted.Encrypted, mKey, aesIV) + if err != nil { + return nil, err + } + return []byte(decrypted), nil +} + +// AESEncryptStr base64加密字符串 +func AESEncryptStr(src string, key, iv []byte) (encmess string, err error) { + ciphertext, err := AESEncrypt([]byte(src), key, iv) + if err != nil { + return + } + + encmess = base64.StdEncoding.EncodeToString(ciphertext) + return +} + +// AESEncrypt 加密 +func AESEncrypt(src []byte, key []byte, iv []byte) ([]byte, error) { + if len(iv) == 0 { + iv = key[:16] + } + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + src = padding(src, block.BlockSize()) + blockMode := cipher.NewCBCEncrypter(block, iv) + blockMode.CryptBlocks(src, src) + return src, nil +} + +// 填充数据 +func padding(src []byte, blockSize int) []byte { + padNum := blockSize - len(src)%blockSize + pad := bytes.Repeat([]byte{byte(padNum)}, padNum) + return append(src, pad...) +} + +// 去掉填充数据 +func unpadding(src []byte) []byte { + n := len(src) + unPadNum := int(src[n-1]) + return src[:n-unPadNum] +} + +func AESDecryptStr(src string, key, iv []byte) (string, error) { + bsrc, err := base64.StdEncoding.DecodeString(src) + bret, err := AESDecrypt(bsrc, key, iv) + if err != nil { + return "", err + } + return string(bret), nil +} + +// AESDecrypt 解密 +func AESDecrypt(src []byte, key []byte, iv []byte) ([]byte, error) { + if len(iv) == 0 { + iv = key[:16] + } + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + blockMode := cipher.NewCBCDecrypter(block, iv) + blockMode.CryptBlocks(src, src) + src = unpadding(src) + return src, nil +} + +func main() { + aesKey := genShareKey() + + e := echo.New() + e.GET("/ping", func(c echo.Context) error { + return c.String(http.StatusOK, "Hello, World!") + }) + e.POST("/pong", func(c echo.Context) error { + requestBody, _ := ioutil.ReadAll(c.Request().Body) + + iv := c.Request().Header.Get("X-Encrypt-Iv") + var encrypted encryptedReqBody + err := json.Unmarshal(requestBody, &encrypted) + if err != nil { + fmt.Printf("err: %v\n", err) + } + + if err == nil && len(encrypted.Encrypted) > 0 { + fmt.Printf("request msg: %v\n", encrypted.Encrypted) + // fmt.Printf("%v\n", aesKey) + fmt.Printf("request iv: %v\n", iv) + + request_plain_msg, err := decryptReqBody(&encrypted, aesKey, iv) + if err != nil { + fmt.Printf("err: %v\n", err) + } + + fmt.Printf("request decrypted msg: %v\n", string(request_plain_msg)) + } + + timeStr := time.Now().Format("2006-01-02 15:04:05") + response_plain_msg := fmt.Sprintf(`{"kkk":"hahaha","ts":%v}`, timeStr) + msg, ivNew, err2 := encryptResBody(response_plain_msg, aesKey) + if err2 != nil { + fmt.Printf("%v\n", err2) + } + + ivBase64 := base64.StdEncoding.EncodeToString(ivNew) + + fmt.Printf("response msg:%v\n", string(msg)) + fmt.Printf("response iv: %v\n", ivBase64) + + u := &encryptedResBody{ + Encrypted: string(msg), + Iv: ivBase64, + } + + c.Response().Header().Set("X-Encrypted", "true") + + return c.JSON(http.StatusOK, u) + }) + e.Logger.Fatal(e.Start(":8080")) +} From 00409696888a2f6f149e1852d633203d4a784388 Mon Sep 17 00:00:00 2001 From: xuyang Date: Fri, 13 May 2022 15:46:30 +0800 Subject: [PATCH 17/20] print header values --- cmd/business/main.go | 13 ++++++++++++- go.mod | 4 +++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/cmd/business/main.go b/cmd/business/main.go index 167b6eb..396503c 100644 --- a/cmd/business/main.go +++ b/cmd/business/main.go @@ -229,6 +229,17 @@ func main() { }) e.POST("/pong", func(c echo.Context) error { requestBody, _ := ioutil.ReadAll(c.Request().Body) + fmt.Println(string(requestBody)) + + for key, value := range c.Request().Header { + fmt.Print(key) + + for v := range value { + fmt.Printf("\t%v", v) + } + + fmt.Println() + } iv := c.Request().Header.Get("X-Encrypt-Iv") var encrypted encryptedReqBody @@ -259,7 +270,7 @@ func main() { ivBase64 := base64.StdEncoding.EncodeToString(ivNew) - fmt.Printf("response msg:%v\n", string(msg)) + fmt.Printf("response msg: %v\n", string(msg)) fmt.Printf("response iv: %v\n", ivBase64) u := &encryptedResBody{ diff --git a/go.mod b/go.mod index 2916ef6..2b5f200 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,7 @@ require ( github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 github.com/imroc/req v0.2.4 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + github.com/labstack/echo/v4 v4.7.2 + github.com/sirupsen/logrus v1.8.1 + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 ) From 3124766897c42e286e1f3ce7d4bdf533f73d0342 Mon Sep 17 00:00:00 2001 From: xuyang Date: Fri, 13 May 2022 17:37:31 +0800 Subject: [PATCH 18/20] return the user id --- cmd/business/main.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/business/main.go b/cmd/business/main.go index 396503c..ff8251b 100644 --- a/cmd/business/main.go +++ b/cmd/business/main.go @@ -234,7 +234,7 @@ func main() { for key, value := range c.Request().Header { fmt.Print(key) - for v := range value { + for _, v := range value { fmt.Printf("\t%v", v) } @@ -242,6 +242,7 @@ func main() { } iv := c.Request().Header.Get("X-Encrypt-Iv") + userId := c.Request().Header.Get("X-User-Id") var encrypted encryptedReqBody err := json.Unmarshal(requestBody, &encrypted) if err != nil { @@ -262,7 +263,7 @@ func main() { } timeStr := time.Now().Format("2006-01-02 15:04:05") - response_plain_msg := fmt.Sprintf(`{"kkk":"hahaha","ts":%v}`, timeStr) + response_plain_msg := fmt.Sprintf(`{"kkk":"hahaha","userID":"%v","ts":%v}`, userId, timeStr) msg, ivNew, err2 := encryptResBody(response_plain_msg, aesKey) if err2 != nil { fmt.Printf("%v\n", err2) From fdffa921f605713e71306c560af7a4b4a50c365d Mon Sep 17 00:00:00 2001 From: wuyingfengsui Date: Tue, 26 Jul 2022 15:33:06 +0800 Subject: [PATCH 19/20] business balance settle --- business.go | 22 ++++++++++++++++++++++ cmd/ctl/main.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/business.go b/business.go index 569b7f6..98b7745 100644 --- a/business.go +++ b/business.go @@ -52,6 +52,17 @@ func (b *Business) WalletBalancesGet(userID, assetID uint) (*BusinessResult, err }) } +// BalanceSettle settle the balance. +func (b *Business) BalanceSettle(userID, assetID uint, mType, sequence, amount string) (*BusinessResult, error) { + return b.session.businessPost("/api/v1/business/balance/settle", map[string]interface{}{ + "userID": userID, + "assetID": assetID, + "type": mType, + "sequence": sequence, + "amount": amount, + }) +} + // BalanceLock lock the balance. func (b *Business) BalanceLock(userID, assetID uint, sequence, amount string) (*BusinessResult, error) { return b.session.businessPut("/api/v1/business/balance/lock", map[string]interface{}{ @@ -115,6 +126,17 @@ func (b *Business) OrderGetBySequence(sequence string) (*BusinessResult, error) return b.session.businessGet("/api/v1/business/order/sequence/" + sequence) } +// TransactionsGet fetch transactions. +func (b *Business) TransactionsGet(userID uint, mType, status string, page, amount uint) (*BusinessResult, error) { + return b.session.businessGetWithParams("/api/v1/business/transactions", map[string]interface{}{ + "userID": userID, + "type": mType, + "status": status, + "page": page, + "amount": amount, + }) +} + // Business represents a business instance. type Business struct { Addr string diff --git a/cmd/ctl/main.go b/cmd/ctl/main.go index c9c4de2..f51f0f7 100644 --- a/cmd/ctl/main.go +++ b/cmd/ctl/main.go @@ -406,6 +406,26 @@ func runCommand(arguments docopt.Opts) (*sdk.Result, error) { return nil, err } return result.ToResult(), nil + case "BusinessBalanceSettle": + if len(params) != 5 { + return nil, errors.New("invalid params") + } + + uid, err := strconv.ParseUint(params[1], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + aid, err := strconv.ParseUint(params[2], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + result, err := getBusiness(addr, key, secret, pubKey).BalanceSettle(uint(uid), uint(aid), params[0], params[3], params[4]) + if err != nil { + return nil, err + } + return result.ToResult(), nil case "BusinessBalanceLock": if len(params) != 4 { return nil, errors.New("invalid params") @@ -521,6 +541,31 @@ func runCommand(arguments docopt.Opts) (*sdk.Result, error) { return nil, err } return result.ToResult(), nil + case "BusinessTransactionsGet": + if len(params) != 5 { + return nil, errors.New("invalid params") + } + + uid, err := strconv.ParseUint(params[0], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + page, err := strconv.ParseUint(params[3], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + amount, err := strconv.ParseUint(params[4], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + result, err := getBusiness(addr, key, secret, pubKey).TransactionsGet(uint(uid), params[1], params[2], uint(page), uint(amount)) + if err != nil { + return nil, err + } + return result.ToResult(), nil default: return nil, errors.New("unknown action: " + action) } From d1004ea58d8e7588928bcd1ed77eba4302e344b9 Mon Sep 17 00:00:00 2001 From: wuyingfengsui Date: Thu, 28 Jul 2022 11:57:38 +0800 Subject: [PATCH 20/20] business client cards --- business.go | 15 +++++++++++++++ cmd/ctl/main.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/business.go b/business.go index 98b7745..d1850f2 100644 --- a/business.go +++ b/business.go @@ -39,6 +39,21 @@ func (b *Business) ClientGet(userID uint) (*BusinessResult, error) { }) } +// ClientsGet fetch clients. +func (b *Business) ClientsGet(page, amount uint) (*BusinessResult, error) { + return b.session.businessGetWithParams("/api/v1/business/clients", map[string]interface{}{ + "page": page, + "amount": amount, + }) +} + +// ClientCardsGet fetch client's cards. +func (b *Business) ClientCardsGet(userID uint) (*BusinessResult, error) { + return b.session.businessGetWithParams("/api/v1/business/client/cards", map[string]interface{}{ + "userID": userID, + }) +} + // AssetsGet fetch all assets in the wallet. func (b *Business) AssetsGet() (*BusinessResult, error) { return b.session.businessGet("/api/v1/business/assets") diff --git a/cmd/ctl/main.go b/cmd/ctl/main.go index f51f0f7..91aa0e9 100644 --- a/cmd/ctl/main.go +++ b/cmd/ctl/main.go @@ -386,6 +386,41 @@ func runCommand(arguments docopt.Opts) (*sdk.Result, error) { return nil, err } return result.ToResult(), nil + case "BusinessClientsGet": + if len(params) != 2 { + return nil, errors.New("invalid params") + } + + page, err := strconv.ParseUint(params[0], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + amount, err := strconv.ParseUint(params[1], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + result, err := getBusiness(addr, key, secret, pubKey).ClientsGet(uint(page), uint(amount)) + if err != nil { + return nil, err + } + return result.ToResult(), nil + case "BusinessClientCardsGet": + if len(params) != 1 { + return nil, errors.New("invalid params") + } + + uid, err := strconv.ParseUint(params[0], 10, 64) + if err != nil { + return nil, errors.New("invalid params") + } + + result, err := getBusiness(addr, key, secret, pubKey).ClientCardsGet(uint(uid)) + if err != nil { + return nil, err + } + return result.ToResult(), nil case "BusinessWalletBalancesGet": if len(params) != 2 { return nil, errors.New("invalid params")