From a2a693885be0678cb26f541549234c2200e81c1a Mon Sep 17 00:00:00 2001 From: ywlmac Date: Wed, 13 May 2026 10:30:19 +0800 Subject: [PATCH] Initial commit --- .gitignore | 13 + README.md | 23 + SDK_USAGE.md | 142 ++ go.mod | 27 + go.sum | 109 + pkg/zxchainsdk/certificate.go | 421 ++++ pkg/zxchainsdk/certificate_flow.go | 412 ++++ pkg/zxchainsdk/client.go | 24 + pkg/zxchainsdk/error.go | 15 + pkg/zxchainsdk/model.go | 64 + pkg/zxchainsdk/sdk_client.go | 129 + test.go | 250 ++ .../gosdk_demo/zxsdk/constant/code.go | 49 + .../gosdk_demo/zxsdk/constant/common.go | 8 + .../gosdk_demo/zxsdk/constant/response.go | 17 + .../official/gosdk_demo/zxsdk/ev_sdk.go | 82 + third_party/official/gosdk_demo/zxsdk/go.mod | 24 + third_party/official/gosdk_demo/zxsdk/go.sum | 101 + .../official/gosdk_demo/zxsdk/kv_sdk.go | 87 + .../official/gosdk_demo/zxsdk/log/logger.go | 62 + .../zxsdk/model/create_ev_cert_model.go | 9 + .../zxsdk/model/create_ev_task_model.go | 15 + .../zxsdk/model/create_kv_task_model.go | 9 + .../zxsdk/model/query_ev_cert_model.go | 10 + .../zxsdk/model/query_ev_task_status_model.go | 15 + .../zxsdk/model/query_kv_task_status_model.go | 15 + .../gosdk_demo/zxsdk/tools/caculate_tool.go | 16 + .../gosdk_demo/zxsdk/tools/http_tool.go | 203 ++ .../gosdk_demo/zxsdk/wallet_sdk/go.mod | 11 + .../gosdk_demo/zxsdk/wallet_sdk/go.sum | 94 + .../zxsdk/wallet_sdk/hdsignature/gmhds.go | 433 ++++ .../zxsdk/wallet_sdk/hdsignature/utils.go | 134 ++ .../gosdk_demo/zxsdk/wallet_sdk/hdw.go | 300 +++ .../gosdk_demo/zxsdk/wallet_sdk/hdw_mobile.go | 89 + .../zxsdk/wallet_sdk/mnemonics/mnemonics.go | 134 ++ .../wallet_sdk/mnemonics/wordlists/english.go | 2071 +++++++++++++++++ .../zxsdk/wallet_sdk/upload_to_cos.go | 75 + .../gosdk_demo/zxsdk/zxchain_client.go | 65 + 38 files changed, 5757 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 SDK_USAGE.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg/zxchainsdk/certificate.go create mode 100644 pkg/zxchainsdk/certificate_flow.go create mode 100644 pkg/zxchainsdk/client.go create mode 100644 pkg/zxchainsdk/error.go create mode 100644 pkg/zxchainsdk/model.go create mode 100644 pkg/zxchainsdk/sdk_client.go create mode 100644 test.go create mode 100644 third_party/official/gosdk_demo/zxsdk/constant/code.go create mode 100644 third_party/official/gosdk_demo/zxsdk/constant/common.go create mode 100644 third_party/official/gosdk_demo/zxsdk/constant/response.go create mode 100644 third_party/official/gosdk_demo/zxsdk/ev_sdk.go create mode 100644 third_party/official/gosdk_demo/zxsdk/go.mod create mode 100644 third_party/official/gosdk_demo/zxsdk/go.sum create mode 100644 third_party/official/gosdk_demo/zxsdk/kv_sdk.go create mode 100644 third_party/official/gosdk_demo/zxsdk/log/logger.go create mode 100644 third_party/official/gosdk_demo/zxsdk/model/create_ev_cert_model.go create mode 100644 third_party/official/gosdk_demo/zxsdk/model/create_ev_task_model.go create mode 100644 third_party/official/gosdk_demo/zxsdk/model/create_kv_task_model.go create mode 100644 third_party/official/gosdk_demo/zxsdk/model/query_ev_cert_model.go create mode 100644 third_party/official/gosdk_demo/zxsdk/model/query_ev_task_status_model.go create mode 100644 third_party/official/gosdk_demo/zxsdk/model/query_kv_task_status_model.go create mode 100644 third_party/official/gosdk_demo/zxsdk/tools/caculate_tool.go create mode 100644 third_party/official/gosdk_demo/zxsdk/tools/http_tool.go create mode 100644 third_party/official/gosdk_demo/zxsdk/wallet_sdk/go.mod create mode 100644 third_party/official/gosdk_demo/zxsdk/wallet_sdk/go.sum create mode 100644 third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdsignature/gmhds.go create mode 100644 third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdsignature/utils.go create mode 100644 third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdw.go create mode 100644 third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdw_mobile.go create mode 100644 third_party/official/gosdk_demo/zxsdk/wallet_sdk/mnemonics/mnemonics.go create mode 100644 third_party/official/gosdk_demo/zxsdk/wallet_sdk/mnemonics/wordlists/english.go create mode 100644 third_party/official/gosdk_demo/zxsdk/wallet_sdk/upload_to_cos.go create mode 100644 third_party/official/gosdk_demo/zxsdk/zxchain_client.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5492163 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +.env +.env.* + +.gocache/ +out/ + +.idea/ +*.iml + +bin/ +dist/ +coverage.out diff --git a/README.md b/README.md new file mode 100644 index 0000000..c880f35 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# zxchainsdk + +这是一个纯 Go SDK 包,用于在业务项目中调用至信链 Hash 存证、Hash 取证查询、KV 存证和 Hash 存证证书查询能力。 + +## 保留内容 + +```text +pkg/zxchainsdk/ # SDK 源码 +third_party/official/gosdk_demo/zxsdk/ # 官方 Go SDK 本地依赖 +go.mod +go.sum +SDK_USAGE.md +``` + +## 不再包含 + +本仓库已经不再提供 HTTP 服务、路由、handler、上传目录、命令行工具和 `.env` 配置读取逻辑。接入方项目需要自己读取配置,然后把凭据传给 SDK。 + +```go +client, err := zxchainsdk.NewSDKClient(secretID, secretKey, privateKey) +``` + +完整接入方式见 [SDK_USAGE.md](./SDK_USAGE.md)。 diff --git a/SDK_USAGE.md b/SDK_USAGE.md new file mode 100644 index 0000000..6397f03 --- /dev/null +++ b/SDK_USAGE.md @@ -0,0 +1,142 @@ +# ZXChain SDK 使用文档 + +## 初始化客户端 + +SDK 不主动读取 `.env`,需要自己管理配置,并把凭据传给 SDK。 + +```go +client, err := zxchainsdk.NewSDKClient( + os.Getenv("ZXCHAIN_SECRET_ID"), + os.Getenv("ZXCHAIN_SECRET_KEY"), + os.Getenv("ZXCHAIN_PRIVATE_KEY"), +) +if err != nil { + log.Fatalf("初始化至信链客户端失败: %v", err) +} +``` + +需要的环境变量: + +- `ZXCHAIN_SECRET_ID`:至信链访问密钥 ID。 +- `ZXCHAIN_SECRET_KEY`:至信链访问密钥。 +- `ZXCHAIN_PRIVATE_KEY`:至信链私钥,用于签名存证请求。 +- `COS_BUCKET_URL`:证书上传 COS 桶地址。 +- `COS_PUBLIC_BASE_URL`:证书公开访问基础地址。 +- `COS_SECRET_ID`:COS 访问密钥 ID。 +- `COS_SECRET_KEY`:COS 访问密钥。 + +## 存证和生成证书并一起返回 + +`h4` 对应 SDK 方法: + +```go +CreateSimpleHashAttestationCertificate(ctx, request) +``` + +它会完成以下流程: + +```text +从 WorkPreviewURL 下载作品预览图 +计算作品预览图 EvidenceHash +生成证据包并计算 SM3 Hash +调用至信链 Hash 存证 +生成 Popi 证书图片 +上传证书图片到 COS +返回 evId、txId、区块高度、证书地址等结果 +``` + +示例: + +```go +result, err := client.CreateSimpleHashAttestationCertificate(context.Background(), zxchainsdk.SimpleHashAttestationCertificateRequest{ + UserName: "zzzzzq", + UserID: "56142321", + WorkName: "测试作品", + TaskID: "9999999", + CompletedAt: "2026.5.11 15:20:01", + WorkPreviewURL: "https://your-cdn/path/work-preview.jpg", + EvidenceHash: "作品hash", //可选参数,可以不传 + + CertificateStorage: &zxchainsdk.COSCertificateStorage{ //可选参数,可以不传 + BucketURL: os.Getenv("COS_BUCKET_URL"), + PublicBaseURL: os.Getenv("COS_PUBLIC_BASE_URL"), + SecretID: os.Getenv("COS_SECRET_ID"), + SecretKey: os.Getenv("COS_SECRET_KEY"), + }, +}) +if err != nil { + log.Fatalf("简化证书生成失败: %v", err) +} + +fmt.Println("evId:", result.ReceiptID) +fmt.Println("txId:", result.RequestID) +fmt.Println("blockHeight:", result.BlockHeight) +fmt.Println("txTime:", result.TxTime) +fmt.Println("sm3Hash:", result.SM3Hash) +fmt.Println("certificateURL:", result.PopiCertificateCertificateImageURL) +fmt.Println("zxChainCertURL:", result.ZXChainCertURL) +``` + +请求参数: + +- `UserName`:申请用户名称。 +- `UserID`:申请用户 ID。 +- `WorkName`:作品名称。 +- `TaskID`:生成任务 ID。 +- `CompletedAt`:作品完成时间。 +- `WorkPreviewURL`:作品图片 URL。 +- `EvidenceHash`:可选。作品文件哈希;不传时 SDK 会从 `WorkPreviewURL` 下载内容并自行计算hash值。 +- `CertificateStorage`:可选。COS 上传配置。 + +返回字段: + +- `ReceiptID`:至信链 `evId`,后续给 `h3` 查询使用。 +- `RequestID`:至信链 `txId`。 +- `BlockHeight`:上链区块高度。 +- `TxTime`:上链时间。 +- `SM3Hash`:本次上链证据包的 SM3 Hash。 +- `EvidenceHash`:作品预览图或业务传入证据的哈希。 +- `PopiCertificateCertNo`:Popi 证书编号。 +- `PopiCertificateVerifyURL`:Popi 证书二维码核验地址。 +- `PopiCertificateCertificateImageURL`:Popi 证书图片公开地址。 +- `ZXChainCertURL`:至信链官方证书地址。 + +## h3:查询 Hash 取证记录 + +`h3` 对应 SDK 方法: + +```go +QueryHashAttestation(ctx, request) +``` + +Hash 存证成功后,可以用 `ReceiptID` 查询链上记录。 + +```go +result, err := client.QueryHashAttestation(context.Background(), zxchainsdk.HashAttestationQueryRequest{ + EvID: receiptID, +}) +if err != nil { + log.Fatalf("Hash 取证查询失败: %v", err) +} + +fmt.Println("evId:", result.EvID) +fmt.Println("txId:", result.TxID) +fmt.Println("blockHeight:", result.BlockHeight) +fmt.Println("txTime:", result.TxTime) +fmt.Println("extendInfo:", result.ExtendInfo) +``` + +查询条件至少传一个: + +- `EvID`:`h4` 返回的 `ReceiptID`。 +- `Hash`:存证内容的 SM3 Hash。 +- `TxID`:`h4` 返回的 `RequestID`。 + +返回字段: + +- `EvID`:Hash 存证回执中的 `evId`。 +- `TxID`:Hash 存证回执中的 `txId`。 +- `BlockHeight`:上链区块高度。 +- `TxTime`:官方返回的上链时间。 +- `ExtendInfo`:存证时写入的业务场景信息。 + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..acc0b71 --- /dev/null +++ b/go.mod @@ -0,0 +1,27 @@ +module zxchainsdk + +go 1.26 + +require zxsdk v1.0.0 + +require ( + github.com/fogleman/gg v1.3.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/google/go-querystring v1.0.0 // indirect + github.com/google/uuid v1.1.1 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/mozillazg/go-httpheader v0.2.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + github.com/tencentyun/cos-go-sdk-v5 v0.7.7 // indirect + github.com/tjfoc/gmsm v1.4.0 // indirect + golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee // indirect + golang.org/x/image v0.40.0 // indirect + golang.org/x/sys v0.22.0 // indirect + wallet_sdk v1.0.0 // indirect +) + +replace ( + wallet_sdk => ./third_party/official/gosdk_demo/zxsdk/wallet_sdk + zxsdk => ./third_party/official/gosdk_demo/zxsdk +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ca5e317 --- /dev/null +++ b/go.sum @@ -0,0 +1,109 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ= +github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tencentyun/cos-go-sdk-v5 v0.7.7 h1:7YI5nNwLEhseUoWzzz1CDAwQMeJyJfD3Tj0XnQRzqO8= +github.com/tencentyun/cos-go-sdk-v5 v0.7.7/go.mod h1:wQBO5HdAkLjj2q6XQiIfDSP8DXDNrppDRw2Kp/1BODA= +github.com/tjfoc/gmsm v1.4.0 h1:8nbaiZG+iVdh+fXVw0DZoZZa7a4TGm3Qab+xdrdzj8s= +github.com/tjfoc/gmsm v1.4.0/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8= +golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/zxchainsdk/certificate.go b/pkg/zxchainsdk/certificate.go new file mode 100644 index 0000000..aa39b89 --- /dev/null +++ b/pkg/zxchainsdk/certificate.go @@ -0,0 +1,421 @@ +package zxchainsdk + +import ( + "bytes" + "context" + "errors" + "fmt" + "image" + "image/color" + "image/jpeg" + "image/png" + "io" + "net/http" + "net/url" + "os" + "path" + "strings" + "time" + + "github.com/fogleman/gg" + "github.com/skip2/go-qrcode" + xdraw "golang.org/x/image/draw" +) + +const ( + defaultCertificateBackgroundURL = "https://popiart-public-1313913486.cos.ap-guangzhou.myqcloud.com/media/2026/0511/83034.png" + defaultCertificateLogoURL = "https://popiart-public-1313913486.cos.ap-guangzhou.myqcloud.com/media/2026/0511/83035.png" + defaultCertificateBucketBaseURL = "https://popi-certificate-1386008232.cos.ap-guangzhou.myqcloud.com" +) + +var ( + ErrMissingCertificateNo = errors.New("missing certificate no") + ErrMissingVerifyURL = errors.New("missing verify url") +) + +// HashCertificateRequest 定义 Popi Hash 存证证书生成请求。 +type HashCertificateRequest struct { + Title string + CertNo string + UserName string + UserID string + WorkName string + TaskID string + CompletedAt string + EvidenceID string + TxID string + BlockHeight string + FinalHash string + EvidenceHash string + TrustedAt string + VerifyURL string + + WorkPreviewURL string + WorkPreviewBytes []byte + + BackgroundURL string + BackgroundBytes []byte + LogoURL string + LogoBytes []byte + FontPath string + HTTPClient *http.Client + BucketBaseURL string +} + +// HashCertificateResult 保存生成后的证书 PNG 和建议上传路径。 +type HashCertificateResult struct { + CertNo string + VerifyURL string + PNG []byte + CertificateImageKey string + CertificateImageURL string +} + +// GenerateHashCertificate 按 Figma 模板合成 Popi Hash 存证证书 PNG。 +func GenerateHashCertificate(ctx context.Context, req HashCertificateRequest) (*HashCertificateResult, error) { + if req.CertNo == "" { + return nil, ErrMissingCertificateNo + } + if req.VerifyURL == "" { + return nil, ErrMissingVerifyURL + } + + if req.Title == "" { + req.Title = "数字作品区块链存证证明" + } + if req.BackgroundURL == "" && len(req.BackgroundBytes) == 0 { + req.BackgroundURL = defaultCertificateBackgroundURL + } + if req.LogoURL == "" && len(req.LogoBytes) == 0 { + req.LogoURL = defaultCertificateLogoURL + } + if req.BucketBaseURL == "" { + req.BucketBaseURL = defaultCertificateBucketBaseURL + } + + background, err := loadCertificateImage(ctx, req.HTTPClient, req.BackgroundURL, req.BackgroundBytes) + if err != nil { + return nil, fmt.Errorf("load certificate background: %w", err) + } + logo, err := loadCertificateImage(ctx, req.HTTPClient, req.LogoURL, req.LogoBytes) + if err != nil { + return nil, fmt.Errorf("load certificate logo: %w", err) + } + + var workPreview image.Image + if req.WorkPreviewURL != "" || len(req.WorkPreviewBytes) > 0 { + workPreview, err = loadCertificateImage(ctx, req.HTTPClient, req.WorkPreviewURL, req.WorkPreviewBytes) + if err != nil { + return nil, fmt.Errorf("load work preview: %w", err) + } + } + + qr, err := newQRCodeImage(req.VerifyURL, 184) + if err != nil { + return nil, fmt.Errorf("generate qr code: %w", err) + } + + fontPath, err := resolveCertificateFont(req.FontPath) + if err != nil { + return nil, err + } + + dc := gg.NewContext(1369, 1920) + dc.DrawImage(resizeImage(background, 1369, 1920), 0, 0) + dc.DrawImage(resizeImage(logo, 304, 60), 533, 470) + + drawTitle(dc, fontPath, req.Title) + drawCertNo(dc, fontPath, req.CertNo) + drawCertificateText(dc, fontPath, req) + drawWorkPreview(dc, workPreview) + dc.DrawImage(resizeImage(qr, 184, 184), 593, 1493) + drawQRCodeText(dc, fontPath) + drawDisclaimer(dc, fontPath) + + var buf bytes.Buffer + if err := png.Encode(&buf, dc.Image()); err != nil { + return nil, err + } + + imageKey := certificateImageObjectKey(req.EvidenceID, req.CertNo) + return &HashCertificateResult{ + CertNo: req.CertNo, + VerifyURL: req.VerifyURL, + PNG: buf.Bytes(), + CertificateImageKey: imageKey, + CertificateImageURL: joinBucketURL(req.BucketBaseURL, imageKey), + }, nil +} + +func drawTitle(dc *gg.Context, fontPath, title string) { + _ = dc.LoadFontFace(fontPath, 85) + dc.SetHexColor("#000000") + dc.DrawStringAnchored(title, 684.5, 550, 0.5, 1) +} + +func drawCertNo(dc *gg.Context, fontPath, certNo string) { + gradient := gg.NewLinearGradient(331.5, 670, 1037.5, 670) + gradient.AddColorStop(0, color.White) + gradient.AddColorStop(0.15, mustHexColor("#eed595")) + gradient.AddColorStop(0.50142, mustHexColor("#9c6b3c")) + gradient.AddColorStop(0.85, mustHexColor("#eed595")) + gradient.AddColorStop(1, color.White) + + dc.SetFillStyle(gradient) + dc.DrawRectangle(331.5, 670, 706, 60) + dc.Fill() + + _ = dc.LoadFontFace(fontPath, 25) + dc.SetHexColor("#ffffff") + dc.DrawStringAnchored("编号:"+certNo, 684.5, 700, 0.5, 0.5) +} + +func drawCertificateText(dc *gg.Context, fontPath string, req HashCertificateRequest) { + subjectLines := []certificateLine{ + {Text: "一、存证主体", Medium: true}, + {Text: "申请用户名称:" + req.UserName}, + {Text: "用户ID:" + req.UserID}, + } + drawLines(dc, fontPath, subjectLines, 143, 796, 1083, 49) + + summaryLines := []certificateLine{ + {Text: "二、存证信息摘要", Medium: true}, + {Text: "作品名称:" + req.WorkName}, + {Text: "生成任务ID:" + req.TaskID}, + {Text: "作品完成时间:" + req.CompletedAt}, + {Text: "存证ID:" + req.EvidenceID}, + {Text: `区块链交易ID:"` + req.TxID + `"`}, + {Text: "区块高度:" + req.BlockHeight}, + {Text: `最终产物 Hash:"` + req.FinalHash + `"`}, + {Text: `证据包 Hash:"` + req.EvidenceHash + `"`}, + {Text: "可信存证时间:" + req.TrustedAt}, + } + drawLines(dc, fontPath, summaryLines, 143, 963, 1083, 49) +} + +type certificateLine struct { + Text string + Medium bool +} + +func drawLines(dc *gg.Context, fontPath string, lines []certificateLine, x, y, maxWidth, lineStep float64) { + for i, line := range lines { + size := fitFontSize(dc, fontPath, line.Text, 25, 18, maxWidth) + _ = dc.LoadFontFace(fontPath, size) + dc.SetHexColor("#000000") + dc.DrawStringAnchored(line.Text, x, y+float64(i)*lineStep, 0, 1) + } +} + +func drawWorkPreview(dc *gg.Context, preview image.Image) { + dc.Push() + dc.RotateAbout(gg.Radians(4.33), 959, 950) + gradient := gg.NewLinearGradient(727, 823, 1190, 1078) + gradient.AddColorStop(0.10985, mustHexColor("#eed594")) + gradient.AddColorStop(0.89015, mustHexColor("#b78e5a")) + dc.SetFillStyle(gradient) + dc.DrawRectangle(735, 825, 463, 255) + dc.Fill() + dc.Pop() + + dc.SetHexColor("#ffffff") + dc.DrawRectangle(717, 813, 488, 273) + dc.Fill() + if preview != nil { + fitted := containImage(preview, 488, 273) + dc.DrawImageAnchored(fitted, 961, 950, 0.5, 0.5) + } + dc.SetHexColor("#eed594") + dc.SetLineWidth(2) + dc.DrawRectangle(717, 813, 488, 273) + dc.Stroke() +} + +func drawQRCodeText(dc *gg.Context, fontPath string) { + _ = dc.LoadFontFace(fontPath, 25) + dc.SetHexColor("#000000") + dc.DrawStringAnchored("扫码查看在线证书", 684.5, 1715, 0.5, 1) +} + +func drawDisclaimer(dc *gg.Context, fontPath string) { + _ = dc.LoadFontFace(fontPath, 18) + dc.SetHexColor("#999999") + text := "*本凭证为电子数据存证凭证,不等同于官方著作权登记证书。作品原创性、素材合法性及第三方授权责任由申请用户承担。" + dc.DrawStringAnchored(text, 194, 1853.5, 0, 0.5) +} + +func fitFontSize(dc *gg.Context, fontPath, text string, preferred, minimum, maxWidth float64) float64 { + for size := preferred; size >= minimum; size-- { + if err := dc.LoadFontFace(fontPath, size); err != nil { + return preferred + } + width, _ := dc.MeasureString(text) + if width <= maxWidth { + return size + } + } + return minimum +} + +func newQRCodeImage(content string, size int) (image.Image, error) { + qr, err := qrcode.New(content, qrcode.Medium) + if err != nil { + return nil, err + } + qr.DisableBorder = true + return qr.Image(size), nil +} + +func loadCertificateImage(ctx context.Context, client *http.Client, imageURL string, inline []byte) (image.Image, error) { + var data []byte + var err error + if len(inline) > 0 { + data = inline + } else { + if imageURL == "" { + return nil, errors.New("missing image url") + } + data, err = fetchBytes(ctx, client, imageURL) + if err != nil { + return nil, err + } + } + + img, format, err := image.Decode(bytes.NewReader(data)) + if err != nil { + return nil, err + } + if format == "jpeg" { + return img, nil + } + return img, nil +} + +func fetchBytes(ctx context.Context, client *http.Client, rawURL string) ([]byte, error) { + if client == nil { + client = &http.Client{Timeout: 20 * time.Second} + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil) + if err != nil { + return nil, err + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, fmt.Errorf("unexpected status %d", resp.StatusCode) + } + return io.ReadAll(resp.Body) +} + +func resizeImage(src image.Image, width, height int) image.Image { + dst := image.NewRGBA(image.Rect(0, 0, width, height)) + xdraw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), xdraw.Over, nil) + return dst +} + +func containImage(src image.Image, boxWidth, boxHeight int) image.Image { + bounds := src.Bounds() + sourceWidth := bounds.Dx() + sourceHeight := bounds.Dy() + if sourceWidth == 0 || sourceHeight == 0 { + return image.NewRGBA(image.Rect(0, 0, boxWidth, boxHeight)) + } + + scale := min(float64(boxWidth)/float64(sourceWidth), float64(boxHeight)/float64(sourceHeight)) + width := max(1, int(float64(sourceWidth)*scale)) + height := max(1, int(float64(sourceHeight)*scale)) + + dst := image.NewRGBA(image.Rect(0, 0, boxWidth, boxHeight)) + x := (boxWidth - width) / 2 + y := (boxHeight - height) / 2 + target := image.Rect(x, y, x+width, y+height) + xdraw.CatmullRom.Scale(dst, target, src, bounds, xdraw.Over, nil) + return dst +} + +func resolveCertificateFont(explicitPath string) (string, error) { + if explicitPath != "" { + if _, err := os.Stat(explicitPath); err != nil { + return "", err + } + if _, err := gg.LoadFontFace(explicitPath, 25); err != nil { + return "", fmt.Errorf("load certificate font %s: %w", explicitPath, err) + } + return explicitPath, nil + } + + candidates := []string{ + "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "/System/Library/Fonts/Supplemental/Arial.ttf", + "/System/Library/Fonts/Supplemental/Microsoft Sans Serif.ttf", + "/System/Library/Fonts/STHeiti Medium.ttc", + "/System/Library/Fonts/Hiragino Sans GB.ttc", + "/Library/Fonts/Arial Unicode.ttf", + "/usr/share/fonts/opentype/source-han-sans/SourceHanSansCN-Regular.otf", + "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.otf", + "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc", + } + for _, candidate := range candidates { + if _, err := os.Stat(candidate); err != nil { + continue + } + if _, err := gg.LoadFontFace(candidate, 25); err == nil { + return candidate, nil + } + } + return "", errors.New("missing certificate font, set HashCertificateRequest.FontPath to a Chinese TTF/TTC font") +} + +func certificateImageObjectKey(evidenceID, certNo string) string { + dirName := evidenceID + if dirName == "" { + dirName = certNo + } + dirName = strings.Trim(dirName, "/") + if dirName == "" { + dirName = "unknown" + } + return path.Join("certificates", dirName, "certificate.png") +} + +func joinBucketURL(bucketBaseURL, objectKey string) string { + if bucketBaseURL == "" || objectKey == "" { + return "" + } + escaped := strings.TrimLeft(objectKey, "/") + u, err := url.Parse(strings.TrimRight(bucketBaseURL, "/")) + if err != nil { + return strings.TrimRight(bucketBaseURL, "/") + "/" + escaped + } + u.Path = path.Join(u.Path, escaped) + return u.String() +} + +func mustHexColor(hex string) color.Color { + c, err := parseHexColor(hex) + if err != nil { + panic(err) + } + return c +} + +func parseHexColor(hex string) (color.Color, error) { + hex = strings.TrimPrefix(hex, "#") + if len(hex) != 6 { + return nil, fmt.Errorf("invalid hex color %q", hex) + } + var r, g, b uint8 + if _, err := fmt.Sscanf(hex, "%02x%02x%02x", &r, &g, &b); err != nil { + return nil, err + } + return color.RGBA{R: r, G: g, B: b, A: 255}, nil +} + +func init() { + image.RegisterFormat("jpeg", "\xff\xd8", jpeg.Decode, jpeg.DecodeConfig) + image.RegisterFormat("png", "\x89PNG\r\n\x1a\n", png.Decode, png.DecodeConfig) +} diff --git a/pkg/zxchainsdk/certificate_flow.go b/pkg/zxchainsdk/certificate_flow.go new file mode 100644 index 0000000..ed8b8da --- /dev/null +++ b/pkg/zxchainsdk/certificate_flow.go @@ -0,0 +1,412 @@ +package zxchainsdk + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "path" + "strings" + "time" + + "github.com/tencentyun/cos-go-sdk-v5" + "wallet_sdk" +) + +var ( + ErrMissingCertificateStorage = errors.New("missing certificate storage") + ErrMissingCOSSecretID = errors.New("missing cos secret id") + ErrMissingCOSSecretKey = errors.New("missing cos secret key") +) + +// downloadAndHashFromURL 从指定的 URL 下载内容并计算 SM3 哈希。 +func downloadAndHashFromURL(ctx context.Context, url string) (string, error) { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return "", fmt.Errorf("failed to create request: %w", err) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", fmt.Errorf("failed to download from URL: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("failed to download from URL, status: %d", resp.StatusCode) + } + + var buf bytes.Buffer + _, err = buf.ReadFrom(resp.Body) + if err != nil { + return "", fmt.Errorf("failed to read response body: %w", err) + } + + hash, err := wallet_sdk.SM3Hash(buf.Bytes()) + if err != nil { + return "", fmt.Errorf("failed to calculate SM3 hash: %w", err) + } + + return hash, nil +} + +// SimpleHashAttestationCertificateRequest 是简化的一站式证书请求,只需要最少的业务字段。 +type SimpleHashAttestationCertificateRequest struct { + UserName string `json:"user_name"` // UserName 表示申请用户名称 + UserID string `json:"user_id"` // UserID 表示申请用户 ID + WorkName string `json:"work_name"` // WorkName 表示作品名称 + TaskID string `json:"task_id"` // TaskID 表示生成任务 ID + CompletedAt string `json:"completed_at"` // CompletedAt 表示作品完成时间 + WorkPreviewURL string `json:"work_preview_url"` // WorkPreviewURL 表示作品预览图 URL + EvidenceHash string `json:"evidence_hash,omitempty"` // EvidenceHash 表示作品文件哈希,可选。如果不传,SDK 将从 WorkPreviewURL 下载并计算哈希 + + // 可选:如果不传,将使用内置默认值 + CertificateStorage *COSCertificateStorage `json:"certificate_storage,omitempty"` // CertificateStorage 表示 COS 上传配置 +} + +// HashAttestationCertificateRequest 是业务层发起 Hash 存证并生成证书的一站式请求。 +type HashAttestationCertificateRequest struct { + HashAttestationRequest + + UserName string `json:"user_name"` // UserName 表示申请用户名称 + UserID string `json:"user_id"` // UserID 表示申请用户 ID + WorkName string `json:"work_name"` // WorkName 表示作品名称 + TaskID string `json:"task_id"` // TaskID 表示生成任务 ID + CompletedAt string `json:"completed_at"` // CompletedAt 表示作品完成时间 + + WorkPreviewURL string `json:"work_preview_url"` // WorkPreviewURL 表示作品预览图 URL + WorkPreviewBytes []byte `json:"-"` // WorkPreviewBytes 表示作品预览图二进制数据 + EvidenceHash string `json:"evidence_hash"` // EvidenceHash 表示作品文件哈希 + + BackgroundURL string `json:"background_url"` // BackgroundURL 表示证书背景图 URL + LogoURL string `json:"logo_url"` // LogoURL 表示证书 logo URL + FontPath string `json:"font_path"` // FontPath 表示用于绘制证书的字体路径 + HTTPClient *http.Client `json:"-"` // HTTPClient 表示证书生成时使用的 HTTP 客户端 + CertBucketBase string `json:"cert_bucket_base"`// CertBucketBase 表示证书对象 URL 的基础 Bucket URL + + CertificateStorage *COSCertificateStorage `json:"certificate_storage"` // CertificateStorage 表示 COS 上传配置 +} + +// COSCertificateStorage 定义 SDK 上传 Popi 证书到 COS 所需的配置。 +type COSCertificateStorage struct { + BucketURL string `json:"bucket_url"` // BucketURL 表示 COS 存储桶地址 + PublicBaseURL string `json:"public_base_url"` // PublicBaseURL 表示公开访问的 Bucket 基础 URL + SecretID string `json:"secret_id"` // SecretID 表示 COS 访问密钥 ID + SecretKey string `json:"secret_key"` // SecretKey 表示 COS 访问密钥 + SessionToken string `json:"session_token"` // SessionToken 表示 COS 临时会话令牌 + ObjectKey string `json:"object_key"` // ObjectKey 表示证书对象完整 Key + ObjectKeyPrefix string `json:"object_key_prefix"` // ObjectKeyPrefix 表示证书对象 Key 前缀 +} + +// HashAttestationCertificateResult 是业务接口可直接返回或落库的证书结果快照。 +type HashAttestationCertificateResult struct { + UserName string `json:"user_name"` // UserName 表示申请用户名称 + UserID string `json:"user_id"` // UserID 表示申请用户 ID + WorkName string `json:"work_name"` // WorkName 表示作品名称 + TaskID string `json:"task_id"` // TaskID 表示生成任务 ID + CompletedAt string `json:"completed_at"` // CompletedAt 表示作品完成时间 + WorkPreviewURL string `json:"work_preview_url,omitempty"` // WorkPreviewURL 表示作品预览图 URL + EvidenceHash string `json:"evidence_hash,omitempty"` // EvidenceHash 表示作品哈希 + + ReceiptID string `json:"receipt_id,omitempty"` // ReceiptID 表示官方 EvSave 返回的 evId + RequestID string `json:"request_id,omitempty"` // RequestID 表示官方 EvSave 返回的 txId + BlockHeight int64 `json:"block_height,omitempty"` // BlockHeight 表示上链区块高度 + TxTime string `json:"tx_time,omitempty"` // TxTime 表示上链时间 + SM3Hash string `json:"sm3_hash,omitempty"` // SM3Hash 表示本次上链内容的 SM3 哈希 + + PopiCertificateCertNo string `json:"popi_certificate_cert_no,omitempty"` // PopiCertificateCertNo 表示 popiart 证书编号 + PopiCertificateVerifyURL string `json:"popi_certificate_verify_url,omitempty"` // PopiCertificateVerifyURL 表示 popiart 证书二维码映射地址 + PopiCertificateCertificateImageURL string `json:"popi_certificate_certificate_image_url,omitempty"` // PopiCertificateCertificateImageURL 表示 popiart 证书地址 + + ZXChainCertURL string `json:"zxchain_cert_url"` // ZXChainCertURL 表示至信链官方证书 URL + + Attestation *AttestationResult `json:"attestation,omitempty"` // Attestation 表示链上存证返回结果 + PopiCertificate PopiCertificateFields `json:"popi_certificate,omitempty"` // PopiCertificate 表示 Popi 证书字段 +} + +// PopiCertificateFields 定义 Popi 自定义证书字段。 +type PopiCertificateFields struct { + CertNo string `json:"cert_no"` // CertNo 表示证书编号 + VerifyURL string `json:"verify_url"` // VerifyURL 表示证书核验 URL + CertificateImageKey string `json:"certificate_image_key"` // CertificateImageKey 表示证书图片对象 Key + CertificateImageURL string `json:"certificate_image_url"` // CertificateImageURL 表示证书图片公开 URL +} + +type evidencePackage struct { + UserName string `json:"user_name"` // UserName 表示申请用户名称 + UserID string `json:"user_id"` // UserID 表示申请用户 ID + WorkName string `json:"work_name"` // WorkName 表示作品名称 + TaskID string `json:"task_id"` // TaskID 表示生成任务 ID + CompletedAt string `json:"completed_at"` // CompletedAt 表示作品完成时间 + WorkPreviewURL string `json:"work_preview_url"` // WorkPreviewURL 表示作品预览图 URL + WorkFileHash string `json:"work_file_hash"` // WorkFileHash 表示作品文件哈希 +} + +func buildEvidencePackage(req HashAttestationCertificateRequest) ([]byte, error) { + workFileHash := firstNonEmpty(req.EvidenceHash, req.HashAttestationRequest.Hash) + if workFileHash == "" { + data := req.HashAttestationRequest.ContentBytes + if len(data) == 0 && req.HashAttestationRequest.Content != "" { + data = []byte(req.HashAttestationRequest.Content) + } + if len(data) > 0 { + hash, err := wallet_sdk.SM3Hash(data) + if err != nil { + return nil, err + } + workFileHash = hash + } + } + if workFileHash == "" { + return nil, errors.New("missing work file hash for evidence package") + } + + pkg := evidencePackage{ + UserName: req.UserName, + UserID: req.UserID, + WorkName: req.WorkName, + TaskID: req.TaskID, + CompletedAt: req.CompletedAt, + WorkPreviewURL: req.WorkPreviewURL, + WorkFileHash: workFileHash, + } + return json.Marshal(pkg) +} + +// CreateSimpleHashAttestationCertificate 简化的一站式证书生成方法,只需要最少的业务字段。 +func (c *SDKClient) CreateSimpleHashAttestationCertificate(ctx context.Context, req SimpleHashAttestationCertificateRequest) (*HashAttestationCertificateResult, error) { + // 如果没有提供 EvidenceHash,从 WorkPreviewURL 下载并计算哈希 + evidenceHash := req.EvidenceHash + if evidenceHash == "" { + if req.WorkPreviewURL == "" { + return nil, errors.New("either EvidenceHash or WorkPreviewURL must be provided") + } + hash, err := downloadAndHashFromURL(ctx, req.WorkPreviewURL) + if err != nil { + return nil, fmt.Errorf("failed to download and hash from WorkPreviewURL: %w", err) + } + evidenceHash = hash + } + + // 转换为完整的请求结构体,使用默认值 + fullReq := HashAttestationCertificateRequest{ + HashAttestationRequest: HashAttestationRequest{ + BizID: req.TaskID, // 使用 TaskID 作为 BizID + Scene: "simple-popi-work-hash", + }, + UserName: req.UserName, + UserID: req.UserID, + WorkName: req.WorkName, + TaskID: req.TaskID, + CompletedAt: req.CompletedAt, + WorkPreviewURL: req.WorkPreviewURL, + EvidenceHash: evidenceHash, + CertificateStorage: req.CertificateStorage, + } + + // 如果没有传 CertificateStorage,使用默认的 + if fullReq.CertificateStorage == nil { + fullReq.CertificateStorage = &COSCertificateStorage{ + BucketURL: defaultCertificateBucketBaseURL, + PublicBaseURL: defaultCertificateBucketBaseURL, + SecretID: "", // 需要业务侧传入 + SecretKey: "", // 需要业务侧传入 + } + } + + return c.CreateHashAttestationCertificate(ctx, fullReq) +} + +// CreateHashAttestationCertificate 完成 Hash 存证、至信链证书查询、Popi 证书生成和上传。 +func (c *SDKClient) CreateHashAttestationCertificate(ctx context.Context, req HashAttestationCertificateRequest) (*HashAttestationCertificateResult, error) { + evidenceBytes, err := buildEvidencePackage(req) + if err != nil { + return nil, err + } + req.HashAttestationRequest.ContentBytes = evidenceBytes + req.HashAttestationRequest.Content = "" + + attestation, err := c.CreateHashAttestation(ctx, req.HashAttestationRequest) + if err != nil { + return nil, err + } + + zxCert, err := c.GetHashCert(ctx, attestation.ReceiptID) + if err != nil { + return nil, err + } + + certNo := NewHashCertificateNo(time.Now(), req.UserID) + + storage := req.CertificateStorage + objectKey := certificateImageObjectKey(attestation.ReceiptID, certNo) + if storage != nil && storage.ObjectKeyPrefix != "" { + objectKey = certificateObjectKeyWithPrefix(storage.ObjectKeyPrefix, attestation.ReceiptID, certNo) + } + if storage != nil && storage.ObjectKey != "" { + objectKey = strings.TrimLeft(storage.ObjectKey, "/") + } + certificateImageURL := joinBucketURL(firstNonEmpty(req.CertBucketBase, storagePublicBaseURL(storage), defaultCertificateBucketBaseURL), objectKey) + verifyURL := certificateImageURL + + cert, err := GenerateHashCertificate(ctx, HashCertificateRequest{ + Title: "数字作品区块链存证证明", + CertNo: certNo, + UserName: req.UserName, + UserID: req.UserID, + WorkName: req.WorkName, + TaskID: req.TaskID, + CompletedAt: req.CompletedAt, + EvidenceID: attestation.ReceiptID, + TxID: attestation.RequestID, + BlockHeight: fmt.Sprint(attestation.BlockHeight), + FinalHash: attestation.SM3Hash, + EvidenceHash: req.EvidenceHash, + TrustedAt: attestation.TxTime, + VerifyURL: verifyURL, + WorkPreviewURL: req.WorkPreviewURL, + WorkPreviewBytes: req.WorkPreviewBytes, + BackgroundURL: req.BackgroundURL, + LogoURL: req.LogoURL, + FontPath: req.FontPath, + HTTPClient: req.HTTPClient, + BucketBaseURL: firstNonEmpty(req.CertBucketBase, storagePublicBaseURL(storage)), + }) + if err != nil { + return nil, err + } + + if storage != nil && storage.ObjectKey == "" { + storageCopy := *storage + storageCopy.ObjectKey = objectKey + storage = &storageCopy + } + + certURL, certKey, err := UploadCertificatePNGToCOS(ctx, cert.PNG, cert.CertificateImageKey, storage) + if err != nil { + return nil, err + } + if certURL != "" { + cert.CertificateImageURL = certURL + } + if certKey != "" { + cert.CertificateImageKey = certKey + } + + return &HashAttestationCertificateResult{ + UserName: req.UserName, + UserID: req.UserID, + WorkName: req.WorkName, + TaskID: req.TaskID, + CompletedAt: req.CompletedAt, + WorkPreviewURL: req.WorkPreviewURL, + EvidenceHash: req.EvidenceHash, + + ReceiptID: attestation.ReceiptID, + RequestID: attestation.RequestID, + BlockHeight: attestation.BlockHeight, + TxTime: attestation.TxTime, + SM3Hash: attestation.SM3Hash, + + PopiCertificateCertNo: cert.CertNo, + PopiCertificateVerifyURL: cert.VerifyURL, + PopiCertificateCertificateImageURL: cert.CertificateImageURL, + ZXChainCertURL: zxCert.CertURL, + + Attestation: attestation, + PopiCertificate: PopiCertificateFields{ + CertNo: cert.CertNo, + VerifyURL: cert.VerifyURL, + CertificateImageKey: cert.CertificateImageKey, + CertificateImageURL: cert.CertificateImageURL, + }, + }, nil +} + +// BuildCertificateVerifyURL 生成二维码里使用的 Popi 在线核验 URL。 +func BuildCertificateVerifyURL(baseURL, evidenceID string) string { + if baseURL == "" { + baseURL = "https://popi.art/cert/verify" + } + return strings.TrimRight(baseURL, "/") + "/" + url.PathEscape(evidenceID) +} + +// NewHashCertificateNo 生成证书编号,格式为 yyyyMMddHHmmss + 用户ID。 +func NewHashCertificateNo(t time.Time, userID string) string { + return t.Format("20060102150405") + userID +} + +// UploadCertificatePNGToCOS 上传 Popi 证书 PNG 到 COS,并返回公开 URL 和对象 Key。 +func UploadCertificatePNGToCOS(ctx context.Context, pngBytes []byte, defaultObjectKey string, storage *COSCertificateStorage) (string, string, error) { + if storage == nil { + return "", "", ErrMissingCertificateStorage + } + if storage.SecretID == "" { + return "", "", ErrMissingCOSSecretID + } + if storage.SecretKey == "" { + return "", "", ErrMissingCOSSecretKey + } + + bucketURL := firstNonEmpty(storage.BucketURL, defaultCertificateBucketBaseURL) + parsed, err := url.Parse(bucketURL) + if err != nil { + return "", "", err + } + objectKey := strings.TrimLeft(firstNonEmpty(storage.ObjectKey, defaultObjectKey), "/") + if objectKey == "" { + return "", "", errors.New("missing certificate object key") + } + + client := cos.NewClient(&cos.BaseURL{BucketURL: parsed}, &http.Client{ + Transport: &cos.AuthorizationTransport{ + SecretID: storage.SecretID, + SecretKey: storage.SecretKey, + SessionToken: storage.SessionToken, + }, + }) + + _, err = client.Object.Put(ctx, objectKey, bytes.NewReader(pngBytes), &cos.ObjectPutOptions{ + ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ + ContentType: "image/png", + ContentLength: len(pngBytes), + CacheControl: "public, max-age=31536000, immutable", + }, + }) + if err != nil { + return "", "", err + } + + publicBaseURL := firstNonEmpty(storage.PublicBaseURL, bucketURL) + return joinBucketURL(publicBaseURL, objectKey), objectKey, nil +} + +func storagePublicBaseURL(storage *COSCertificateStorage) string { + if storage == nil { + return "" + } + return firstNonEmpty(storage.PublicBaseURL, storage.BucketURL) +} + +func firstNonEmpty(values ...string) string { + for _, value := range values { + if value != "" { + return value + } + } + return "" +} + +func certificateObjectKeyWithPrefix(prefix, evidenceID, certNo string) string { + dirName := evidenceID + if dirName == "" { + dirName = certNo + } + if prefix == "" { + prefix = "certificates" + } + return path.Join(prefix, strings.Trim(dirName, "/"), "certificate.png") +} diff --git a/pkg/zxchainsdk/client.go b/pkg/zxchainsdk/client.go new file mode 100644 index 0000000..63f3db5 --- /dev/null +++ b/pkg/zxchainsdk/client.go @@ -0,0 +1,24 @@ +package zxchainsdk + +import "context" + +// Client 定义可复用的至信链存证 SDK 能力。 +type Client interface { + // CreateHashAttestation 发起 Hash 存证。 + CreateHashAttestation(ctx context.Context, req HashAttestationRequest) (*AttestationResult, error) + + // QueryHashAttestation 查询 Hash 存证链上记录。 + QueryHashAttestation(ctx context.Context, req HashAttestationQueryRequest) (*HashAttestationQueryResult, error) + + // CreateKVAttestation 发起 KV 存证。 + CreateKVAttestation(ctx context.Context, req KVAttestationRequest) (*AttestationResult, error) + + // GetHashCert 根据 Hash 存证 evId 获取官方证书地址。 + GetHashCert(ctx context.Context, evID string) (*HashCertResult, error) + + // CreateHashAttestationCertificate 发起 Hash 存证,并生成 Popi 自定义证书。 + CreateHashAttestationCertificate(ctx context.Context, req HashAttestationCertificateRequest) (*HashAttestationCertificateResult, error) + + // CreateSimpleHashAttestationCertificate 简化的一站式证书生成方法,只需要最少的业务字段。 + CreateSimpleHashAttestationCertificate(ctx context.Context, req SimpleHashAttestationCertificateRequest) (*HashAttestationCertificateResult, error) +} diff --git a/pkg/zxchainsdk/error.go b/pkg/zxchainsdk/error.go new file mode 100644 index 0000000..2a449b0 --- /dev/null +++ b/pkg/zxchainsdk/error.go @@ -0,0 +1,15 @@ +package zxchainsdk + +import "errors" + +var ( + ErrMissingBizID = errors.New("missing biz_id") + ErrMissingContent = errors.New("missing content") + ErrMissingEvID = errors.New("missing ev_id") + ErrMissingHash = errors.New("missing hash") + ErrMissingScene = errors.New("missing scene") + ErrMissingKVFields = errors.New("missing kv fields") + ErrMissingFileName = errors.New("missing file_name") + ErrMissingQueryKey = errors.New("missing ev_id, hash or tx_id") + ErrEmptyAttestation = errors.New("empty attestation request") +) diff --git a/pkg/zxchainsdk/model.go b/pkg/zxchainsdk/model.go new file mode 100644 index 0000000..c192e9f --- /dev/null +++ b/pkg/zxchainsdk/model.go @@ -0,0 +1,64 @@ +package zxchainsdk + +// HashAttestationRequest 定义 Hash 存证请求模型。 +type HashAttestationRequest struct { + BizID string // BizID 表示业务主键。 + Scene string // Scene 表示业务场景,会作为官方 EvSave 的 extendInfo。 + Content string // Content 表示待存证的文本内容。 + ContentBytes []byte // ContentBytes 表示待存证的二进制内容,优先级高于 Content。 + Hash string // Hash 表示业务侧传入的哈希值,SDK 实际上链仍按内容计算 SM3。 + FileName string // FileName 表示原始文件名。 + FileSize int64 // FileSize 表示原始文件大小。 + ContentType string // ContentType 表示原始内容类型。 + StorageURI string // StorageURI 表示业务侧保存原始文件的位置。 + CreatedAt string // CreatedAt 表示业务侧创建存证请求的时间。 + Metadata map[string]string // Metadata 表示附加元数据。 +} + +// HashAttestationQueryRequest 定义 Hash 存证取证查询请求模型。 +type HashAttestationQueryRequest struct { + EvID string // EvID 表示 Hash 存证回执中的 evId。 + Hash string // Hash 表示本次存证内容的 SM3 哈希。 + TxID string // TxID 表示 Hash 存证回执中的 txId。 +} + +// KVAttestationRequest 定义 KV 存证请求模型。 +type KVAttestationRequest struct { + BizID string // BizID 表示业务主键,同时作为链上 KV key。 + Scene string // Scene 表示业务场景。 + Fields map[string]string // Fields 表示待上链的键值字段集合。 + Metadata map[string]string // Metadata 表示附加元数据。 +} + +// AttestationResult 定义存证结果模型。 +type AttestationResult struct { + ReceiptID string // ReceiptID 表示链上回执编号,Hash 存证时对应 evId。 + RequestID string // RequestID 表示请求追踪编号,通常对应 txId。 + Status string // Status 表示存证状态。 + RawResponse []byte // RawResponse 保存官方 SDK 原始响应内容。 + ExternalRefID string // ExternalRefID 表示外部引用标识。 + BlockHeight int64 `json:"blockHeight,omitempty"` // BlockHeight 表示上链区块高度。 + TxTime string `json:"txTime,omitempty"` // TxTime 表示官方返回的上链时间。 + FileName string `json:"fileName,omitempty"` // FileName 表示原始文件名。 + FileSize int64 `json:"fileSize,omitempty"` // FileSize 表示原始文件大小。 + ContentType string `json:"contentType,omitempty"` // ContentType 表示原始内容类型。 + StorageURI string `json:"storageUri,omitempty"` // StorageURI 表示业务侧保存原始文件的位置。 + SM3Hash string `json:"sm3Hash,omitempty"` // SM3Hash 表示本次上链内容的 SM3 哈希。 + CreatedAt string `json:"createdAt,omitempty"` // CreatedAt 表示业务侧创建存证请求的时间。 +} + +// HashAttestationQueryResult 定义 Hash 存证取证查询结果。 +type HashAttestationQueryResult struct { + EvID string `json:"evId,omitempty"` // EvID 表示 Hash 存证回执中的 evId。 + TxID string `json:"txId,omitempty"` // TxID 表示 Hash 存证回执中的 txId。 + BlockHeight int64 `json:"blockHeight,omitempty"` // BlockHeight 表示上链区块高度。 + TxTime string `json:"txTime,omitempty"` // TxTime 表示官方返回的上链时间。 + ExtendInfo string `json:"extendInfo,omitempty"` // ExtendInfo 表示存证时传入的业务场景信息。 + RawResponse []byte `json:"-"` // RawResponse 保存官方 SDK 原始响应内容。 +} + +// HashCertResult 定义 Hash 存证证书查询结果。 +type HashCertResult struct { + EvID string // EvID 表示 Hash 存证回执中的 evId。 + CertURL string // CertURL 表示官方证书访问地址。 +} diff --git a/pkg/zxchainsdk/sdk_client.go b/pkg/zxchainsdk/sdk_client.go new file mode 100644 index 0000000..f9488ef --- /dev/null +++ b/pkg/zxchainsdk/sdk_client.go @@ -0,0 +1,129 @@ +package zxchainsdk + +import ( + "context" + "encoding/json" + "time" + + "wallet_sdk" + "zxsdk" +) + +// SDKClient 基于官方至信链 Go SDK 实现 Client。 +type SDKClient struct { + client *zxsdk.ZxChainClient +} + +// NewSDKClient 使用官方凭据创建至信链 SDK 客户端。 +func NewSDKClient(secretID, secretKey, privateKey string) (*SDKClient, error) { + client, err := zxsdk.NewZxChainClient(secretID, secretKey, privateKey) + if err != nil { + return nil, err + } + + return &SDKClient{client: client}, nil +} + +// CreateHashAttestation 使用官方 EvSave 发起 Hash 存证。 +func (c *SDKClient) CreateHashAttestation(_ context.Context, req HashAttestationRequest) (*AttestationResult, error) { + data := req.ContentBytes + if len(data) == 0 && req.Content != "" { + data = []byte(req.Content) + } + if len(data) == 0 { + return nil, ErrMissingContent + } + + sm3Hash, err := wallet_sdk.SM3Hash(data) + if err != nil { + return nil, err + } + createdAt := req.CreatedAt + if createdAt == "" { + createdAt = time.Now().Format(time.RFC3339) + } + + resp, err := c.client.EvSave(data, req.Scene) + if err != nil { + return nil, err + } + + raw, _ := json.Marshal(resp) + return &AttestationResult{ + ReceiptID: resp.EvId, // ReceiptID 保存官方 EvSave 返回的 evId。 + RequestID: resp.TxId, // RequestID 保存官方 EvSave 返回的 txId。 + Status: "success", // Status 表示本次 SDK 调用成功。 + RawResponse: raw, // RawResponse 保存官方 SDK 原始响应 JSON。 + ExternalRefID: req.BizID, // ExternalRefID 关联业务侧传入的 BizID。 + BlockHeight: resp.BlockHeight, // BlockHeight 表示上链区块高度。 + TxTime: resp.TxTime, // TxTime 表示官方返回的上链时间。 + FileName: req.FileName, // FileName 回传业务侧传入的文件名。 + FileSize: int64(len(data)), // FileSize 表示本次参与 Hash 存证的内容字节数。 + ContentType: req.ContentType, // ContentType 回传业务侧传入的内容类型。 + StorageURI: req.StorageURI, // StorageURI 回传业务侧保存原始文件的位置。 + SM3Hash: sm3Hash, // SM3Hash 表示本次上链内容的 SM3 哈希。 + CreatedAt: createdAt, // CreatedAt 表示业务侧传入或 SDK 自动生成的创建时间。 + }, nil +} + +// QueryHashAttestation 使用官方 EvQuery 查询 Hash 存证链上记录。 +func (c *SDKClient) QueryHashAttestation(_ context.Context, req HashAttestationQueryRequest) (*HashAttestationQueryResult, error) { + if req.EvID == "" && req.Hash == "" && req.TxID == "" { + return nil, ErrMissingQueryKey + } + + resp, err := c.client.EvQuery(req.EvID, req.Hash, req.TxID) + if err != nil { + return nil, err + } + + raw, _ := json.Marshal(resp) + return &HashAttestationQueryResult{ + EvID: resp.EvId, // EvID 表示查询到的 Hash 存证 evId。 + TxID: resp.TxId, // TxID 表示查询到的链上交易 txId。 + BlockHeight: resp.BlockHeight, // BlockHeight 表示查询到的上链区块高度。 + TxTime: resp.TxTime, // TxTime 表示查询到的上链时间。 + ExtendInfo: resp.ExtendInfo, // ExtendInfo 表示存证时传入的业务场景信息。 + RawResponse: raw, // RawResponse 保存官方 SDK 原始响应 JSON。 + }, nil +} + +// CreateKVAttestation 使用官方 KvSave 发起 KV 存证。 +func (c *SDKClient) CreateKVAttestation(_ context.Context, req KVAttestationRequest) (*AttestationResult, error) { + kvValueBytes, _ := json.Marshal(req.Fields) + err := c.client.KvSave(req.BizID, string(kvValueBytes), req.Scene) + if err != nil { + return nil, err + } + + resp, err := c.client.KvQuery(req.BizID, true) + if err != nil { + return nil, err + } + + raw, _ := json.Marshal(resp) + return &AttestationResult{ + ReceiptID: resp.TxId, // ReceiptID 保存 KV 查询结果中的 txId。 + RequestID: resp.TxId, // RequestID 保存 KV 查询结果中的 txId。 + Status: "success", // Status 表示本次 SDK 调用成功。 + RawResponse: raw, // RawResponse 保存官方 SDK 原始响应 JSON。 + ExternalRefID: resp.KvKey, // ExternalRefID 保存链上的 kvKey。 + }, nil +} + +// GetHashCert 根据 evId 获取官方证书地址。 +func (c *SDKClient) GetHashCert(_ context.Context, evID string) (*HashCertResult, error) { + if evID == "" { + return nil, ErrMissingEvID + } + + certURL, err := c.client.GetEvCert(evID) + if err != nil { + return nil, err + } + + return &HashCertResult{ + EvID: evID, // EvID 表示 Hash 存证回执中的 evId。 + CertURL: certURL, // CertURL 表示官方证书访问地址。 + }, nil +} diff --git a/test.go b/test.go new file mode 100644 index 0000000..14f7837 --- /dev/null +++ b/test.go @@ -0,0 +1,250 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "time" + + "zxchainsdk/pkg/zxchainsdk" +) + +// loadDotEnv 加载当前目录下的 .env,方便本地测试。 +func loadDotEnv(path string) { + file, err := os.Open(path) + if err != nil { + return + } + defer file.Close() + + content, err := os.ReadFile(path) + if err != nil { + return + } + + // 按行分割 + lines := strings.Split(string(content), "\n") + + for i := 0; i < len(lines); i++ { + line := strings.TrimSpace(lines[i]) + if line == "" || strings.HasPrefix(line, "#") || !strings.Contains(line, "=") { + continue + } + + // 检查是否是多行值(以引号开始但不以引号结束) + if strings.HasPrefix(line, `ZXCHAIN_PRIVATE_KEY="`) && !strings.HasSuffix(line, `"`) { + // 收集多行私钥内容 + var privateKeyLines []string + privateKeyLines = append(privateKeyLines, strings.TrimPrefix(line, `ZXCHAIN_PRIVATE_KEY="`)) + + // 继续读取后续行直到找到结束引号 + for i+1 < len(lines) && !strings.HasSuffix(strings.TrimSpace(lines[i+1]), `"`) { + i++ + privateKeyLines = append(privateKeyLines, lines[i]) + } + + // 添加最后一行(去掉结尾引号) + if i+1 < len(lines) { + i++ + lastLine := strings.TrimSpace(lines[i]) + if strings.HasSuffix(lastLine, `"`) { + privateKeyLines = append(privateKeyLines, strings.TrimSuffix(lastLine, `"`)) + } + } + + privateKey := strings.Join(privateKeyLines, "\n") + _ = os.Setenv("ZXCHAIN_PRIVATE_KEY", privateKey) + continue + } + + // 处理普通单行配置 + key, value, ok := strings.Cut(line, "=") + if !ok { + continue + } + + key = strings.TrimSpace(key) + value = strings.TrimSpace(value) + if key == "" || os.Getenv(key) != "" { + continue + } + + value = strings.Trim(value, `"'`) + value = strings.ReplaceAll(value, `\n`, "\n") + _ = os.Setenv(key, strings.TrimSpace(value)) + } +} + +// newClient 初始化至信链 SDK 客户端。 +func newClient() *zxchainsdk.SDKClient { + privateKey := os.Getenv("ZXCHAIN_PRIVATE_KEY") + + client, err := zxchainsdk.NewSDKClient( + os.Getenv("ZXCHAIN_SECRET_ID"), + os.Getenv("ZXCHAIN_SECRET_KEY"), + privateKey, + ) + if err != nil { + log.Fatalf("初始化至信链客户端失败: %v", err) + } + + return client +} + +// h1 演示 Hash 上链。 +func h1() { + client := newClient() + + filePath := "/Users/ywlmac/Desktop/81520.jpeg" + fileBytes, err := os.ReadFile(filePath) + if err != nil { + log.Fatalf("读取图片文件失败: %v", err) + } + + result, err := client.CreateHashAttestation(context.Background(), zxchainsdk.HashAttestationRequest{ + BizID: fmt.Sprintf("demo-image-%d", time.Now().Unix()), + Scene: "sdk-image-hash-demo", + ContentBytes: fileBytes, + FileName: "81520.jpeg", + FileSize: int64(len(fileBytes)), + ContentType: "image/jpeg", + StorageURI: filePath, + Metadata: map[string]string{ + "source": "sdk-test", + }, + }) + if err != nil { + log.Fatalf("Hash 上链失败: %v", err) + } + + fmt.Println("Hash 上链成功") + fmt.Println("evId:", result.ReceiptID) + fmt.Println("txId:", result.RequestID) + fmt.Println("sm3Hash:", result.SM3Hash) + fmt.Println("blockHeight:", result.BlockHeight) + fmt.Println("txTime:", result.TxTime) +} + +// h2 演示 Hash 证书查询。 +func h2(evID string) { + client := newClient() + + // 把这里替换成 h1 输出的 evId。 + // evID := "替换成你的evId" + + result, err := client.GetHashCert(context.Background(), evID) + if err != nil { + log.Fatalf("查询 Hash 证书失败: %v", err) + } + + fmt.Println("Hash 证书查询成功") + fmt.Println("evId:", result.EvID) + fmt.Println("certURL:", result.CertURL) +} + +// h3 演示 Hash 取证查询。 +func h3(evID string) { + client := newClient() + + // 至少填写一个查询条件:evId、sm3Hash 或 txId。 + req := zxchainsdk.HashAttestationQueryRequest{ + EvID: evID, + // Hash: "也可以替换成 h1 输出的 sm3Hash", + // TxID: "也可以替换成 h1 输出的 txId", + } + + var result *zxchainsdk.HashAttestationQueryResult + var err error + for attempt := 1; attempt <= 5; attempt++ { + result, err = client.QueryHashAttestation(context.Background(), req) + if err == nil { + break + } + if attempt < 5 { + log.Printf("Hash 取证查询暂未成功,第 %d 次失败:%v,等待后重试", attempt, err) + time.Sleep(time.Duration(attempt*2) * time.Second) + } + } + if err != nil { + log.Fatalf("Hash 取证查询失败: %v", err) + } + + fmt.Println("Hash 取证查询成功") + fmt.Println("evId:", result.EvID) + fmt.Println("txId:", result.TxID) + fmt.Println("blockHeight:", result.BlockHeight) + fmt.Println("txTime:", result.TxTime) + fmt.Println("extendInfo:", result.ExtendInfo) +} + +// h4 演示简化的一站式证书生成(从 WorkPreviewURL 自动下载并计算哈希)。 +func h4() (string, error) { + client := newClient() + + result, err := client.CreateSimpleHashAttestationCertificate(context.Background(), zxchainsdk.SimpleHashAttestationCertificateRequest{ + UserName: "zzzzzq", // 申请用户名称 + UserID: "56142321", // 申请用户 ID + WorkName: "测试作品", // 作品名称 + TaskID: "1231223", // 生成任务 ID + CompletedAt: "2026.5.11 15:20:01", // 作品完成时间 + WorkPreviewURL: "https://popiart-public-1313913486.cos.ap-guangzhou.myqcloud.com/media/2026/0511/83218.jpg", // 作品预览图 URL + // EvidenceHash 不传,SDK 会自动从 WorkPreviewURL 下载并计算 SM3 哈希 + + CertificateStorage: &zxchainsdk.COSCertificateStorage{ + BucketURL: os.Getenv("COS_BUCKET_URL"), + PublicBaseURL: os.Getenv("COS_PUBLIC_BASE_URL"), + SecretID: os.Getenv("COS_SECRET_ID"), + SecretKey: os.Getenv("COS_SECRET_KEY"), + }, + }) + if err != nil { + return "", fmt.Errorf("简化证书生成失败: %w", err) + } + + fmt.Println("简化证书生成成功") + fmt.Println("UserName:", result.UserName) + fmt.Println("UserID:", result.UserID) + fmt.Println("WorkName:", result.WorkName) + fmt.Println("TaskID:", result.TaskID) + fmt.Println("CompletedAt:", result.CompletedAt) + fmt.Println("WorkPreviewURL:", result.WorkPreviewURL) + fmt.Println("EvidenceHash:", result.EvidenceHash) + fmt.Println("ReceiptID (evId):", result.ReceiptID) + fmt.Println("RequestID (txId):", result.RequestID) + fmt.Println("BlockHeight:", result.BlockHeight) + fmt.Println("TxTime:", result.TxTime) + fmt.Println("SM3Hash:", result.SM3Hash) + fmt.Println("PopiCertificateCertNo:", result.PopiCertificateCertNo) + fmt.Println("PopiCertificateVerifyURL:", result.PopiCertificateVerifyURL) + fmt.Println("PopiCertificateCertificateImageURL:", result.PopiCertificateCertificateImageURL) + fmt.Println("ZXChainCertURL:", result.ZXChainCertURL) + + return result.ReceiptID, nil +} + +func main() { + loadDotEnv(".env") + + // 只测试 h4 和 h3:先生成证书,再用 h4 返回的 evId 查询 Hash 取证。 + evID, err := h4() + if err != nil { + log.Printf("h4 测试失败: %v", err) + evID = os.Getenv("ZXCHAIN_TEST_EV_ID") + if evID == "" { + evID = "302d355f4b7911f1bf330a77039e1980" + } + log.Printf("继续使用已有 evId 测试 h3: %s", evID) + } + h3(evID) + + // 第一次测试:执行 Hash 上链,复制输出里的 evId。 + //h1() + + // 第二次测试:把 h1() 注释掉,填好 h2 里的 evId,再打开下面这一行。 + //h2("302d355f4b7911f1bf330a77039e1980") + + // 第三次测试:把 h1() 注释掉,填好 h3 里的 evId/sm3Hash/txId,再打开下面这一行。 + //h3("302d355f4b7911f1bf330a77039e1980") +} diff --git a/third_party/official/gosdk_demo/zxsdk/constant/code.go b/third_party/official/gosdk_demo/zxsdk/constant/code.go new file mode 100644 index 0000000..230abed --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/constant/code.go @@ -0,0 +1,49 @@ +package constant + +var ( + RESP_SUCC *RetBase + RESP_FAIL *RetBase + RESP_PARAM_ERROR *RetBase + RESP_HTTP_REQ_ERROR *RetBase + JSON_UNMARSHAL_ERROR *RetBase + SM3HASH_ERROR *RetBase + SIGNATURE_ERROR *RetBase + EVIDENCE_DATA_EMPTY_ERROR *RetBase + SECRET_KEY_EMPTY_ERROR *RetBase + SECRET_ID_EMPTY_ERROR *RetBase + PRIVATE_KEY_FORMAT_ERROR *RetBase + KVKEY_CANNOT_BE_EMPTY_ERROR *RetBase + KVVALUE_CANNOT_BE_EMPTY_ERROR *RetBase + OPERATE_ID_CANNOT_BE_EMPTY_ERROR *RetBase + TXID_CANNOT_BE_EMPTY_ERROR *RetBase + EVID_CANNOT_BE_EMPTY_ERROR *RetBase + QUERY_EVSAVE_ERROR *RetBase +) + +func init() { + RESP_SUCC = Build(0, "请求成功") + RESP_FAIL = Build(0xFFFF, "请求失败") + RESP_PARAM_ERROR = Build(10, "参数错误") + JSON_UNMARSHAL_ERROR = Build(39, "json反序列化失败") + RESP_HTTP_REQ_ERROR = Build(102, "Http请求失败") + QUERY_EVSAVE_ERROR = Build(16008, "查询存证失败") + + SM3HASH_ERROR = Build(1000001, "SM3哈希计算失败") + SIGNATURE_ERROR = Build(1000002, "私钥签名失败") + EVIDENCE_DATA_EMPTY_ERROR = Build(1000003, "存证数据为空") + SECRET_KEY_EMPTY_ERROR = Build(1000004, "SecretKey为空") + SECRET_ID_EMPTY_ERROR = Build(1000005, "SecretId为空") + PRIVATE_KEY_FORMAT_ERROR = Build(1000006, "私钥格式错误") + KVKEY_CANNOT_BE_EMPTY_ERROR = Build(1000007, "KVKey不能为空") + KVVALUE_CANNOT_BE_EMPTY_ERROR = Build(1000008, "KVValue不能为空") + OPERATE_ID_CANNOT_BE_EMPTY_ERROR = Build(1000009, "OperateId不能为空") + TXID_CANNOT_BE_EMPTY_ERROR = Build(1000010, "TxId不能为空") + EVID_CANNOT_BE_EMPTY_ERROR = Build(1000011, "EvId不能为空") +} + +func Build(retCode int, retMsg string) *RetBase { + return &RetBase{ + RetCode: retCode, + RetMsg: retMsg, + } +} diff --git a/third_party/official/gosdk_demo/zxsdk/constant/common.go b/third_party/official/gosdk_demo/zxsdk/constant/common.go new file mode 100644 index 0000000..28d9e4b --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/constant/common.go @@ -0,0 +1,8 @@ +package constant + +const ( + TraceKey = "Cloud-Trace-Id" + ZxChainBaseUrl = "https://open.zxchain.qq.com" + EvCertBaseUrl = "https://zxscan.qq.com/cert/ev?evId=" + KvCertBaseUrl = "https://zxscan.qq.com/cert/kvc?txId=" +) diff --git a/third_party/official/gosdk_demo/zxsdk/constant/response.go b/third_party/official/gosdk_demo/zxsdk/constant/response.go new file mode 100644 index 0000000..0b9d241 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/constant/response.go @@ -0,0 +1,17 @@ +package constant + +import "strconv" + +type RetBase struct { + RetCode int `json:"retCode"` + RetMsg string `json:"retMsg"` +} + +func (r *RetBase) Error() string { + return strconv.Itoa(r.RetCode) + "-" + r.RetMsg +} + +type RetData struct { + RetBase + Data interface{} `json:"data"` +} diff --git a/third_party/official/gosdk_demo/zxsdk/ev_sdk.go b/third_party/official/gosdk_demo/zxsdk/ev_sdk.go new file mode 100644 index 0000000..bc9d162 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/ev_sdk.go @@ -0,0 +1,82 @@ +package zxsdk + +import ( + "wallet_sdk" + "zxsdk/constant" + "zxsdk/log" + model2 "zxsdk/model" + "zxsdk/tools" +) + +func (zx ZxChainClient) EvSave(data []byte, extendInfo string) (*model2.EvSaveResp, error) { + if data == nil || len(data) == 0 { + log.Logger.Errorf("local sdk error: evidence data cannot be empty") + return nil, constant.EVIDENCE_DATA_EMPTY_ERROR + } + + hash, err := wallet_sdk.SM3Hash(data) + if err != nil { + log.Logger.Errorf("local sdk error: calculate hash error: %s", err.Error()) + return nil, constant.SM3HASH_ERROR + } + + sign, err := wallet_sdk.SignByPriKey(zx.privateKey, tools.GetSignData(zx.publicKey, hash, extendInfo)) + if err != nil { + log.Logger.Errorf("local sdk error: signature evsave param error: %s", err.Error()) + return nil, constant.SIGNATURE_ERROR + } + + trigger := tools.HttpTrigger{ + HeaderDic: zx.makeHeader(), + Client: zx.httpClient, + } + + req := &model2.EvSaveReq{ + Hash: hash, + PublicKey: zx.publicKey, + Sign: sign, + ExtendInfo: extendInfo, + } + resp := &model2.EvSaveResp{} + err = trigger.HttpPost(constant.ZxChainBaseUrl+"/api/v1/ev/save", req, resp) + if err != nil { + log.Logger.TraceErrorf(trigger.HeaderDic[constant.TraceKey], "evsave error: %v", err.Error()) + return nil, err + } + + return resp, nil +} + +func (zx ZxChainClient) EvQuery(evId string, hash string, txId string) (*model2.QueryEvStatusResp, error) { + if evId == "" && hash == "" && txId == "" { + log.Logger.Errorf("local sdk error: evid, hash, txId cannot be empty at the same time") + return nil, constant.RESP_PARAM_ERROR + } + + trigger := tools.HttpTrigger{ + Client: tools.DefaultHttpClient, + HeaderDic: zx.makeHeader(), + QueryDic: map[string]string{ + "evId": evId, + "hash": hash, + "txId": txId, + }, + } + + resp := &model2.QueryEvStatusResp{} + err := trigger.HttpGet(constant.ZxChainBaseUrl+"/api/v1/ev/query", resp) + if err != nil { + log.Logger.TraceErrorf(trigger.HeaderDic[constant.TraceKey], "evquery error: %v", err.Error()) + return nil, err + } + + return resp, nil +} + +func (zx ZxChainClient) GetEvCert(evId string) (string, error) { + if evId == "" { + log.Logger.Errorf("local sdk error: evid cannot be empty") + return "", constant.EVID_CANNOT_BE_EMPTY_ERROR + } + return constant.EvCertBaseUrl + evId, nil +} diff --git a/third_party/official/gosdk_demo/zxsdk/go.mod b/third_party/official/gosdk_demo/zxsdk/go.mod new file mode 100644 index 0000000..db3fad8 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/go.mod @@ -0,0 +1,24 @@ +module zxsdk + +go 1.18 + +require ( + github.com/google/uuid v1.1.1 + github.com/mitchellh/mapstructure v1.4.3 + github.com/sirupsen/logrus v1.9.3 + wallet_sdk v1.0.0 +) + +require ( + github.com/google/go-querystring v1.0.0 // indirect + github.com/mozillazg/go-httpheader v0.2.1 // indirect + github.com/tencentyun/cos-go-sdk-v5 v0.7.7 // indirect + github.com/tjfoc/gmsm v1.4.0 // indirect + golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee // indirect + golang.org/x/sys v0.22.0 // indirect +) + +replace ( + github.com/tencentyun/cos-go-sdk-v5 => github.com/tencentyun/cos-go-sdk-v5 v0.7.7 + wallet_sdk => ./wallet_sdk +) diff --git a/third_party/official/gosdk_demo/zxsdk/go.sum b/third_party/official/gosdk_demo/zxsdk/go.sum new file mode 100644 index 0000000..7e1bfe8 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/go.sum @@ -0,0 +1,101 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ= +github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tencentyun/cos-go-sdk-v5 v0.7.7 h1:7YI5nNwLEhseUoWzzz1CDAwQMeJyJfD3Tj0XnQRzqO8= +github.com/tencentyun/cos-go-sdk-v5 v0.7.7/go.mod h1:wQBO5HdAkLjj2q6XQiIfDSP8DXDNrppDRw2Kp/1BODA= +github.com/tjfoc/gmsm v1.4.0 h1:8nbaiZG+iVdh+fXVw0DZoZZa7a4TGm3Qab+xdrdzj8s= +github.com/tjfoc/gmsm v1.4.0/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/third_party/official/gosdk_demo/zxsdk/kv_sdk.go b/third_party/official/gosdk_demo/zxsdk/kv_sdk.go new file mode 100644 index 0000000..724b980 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/kv_sdk.go @@ -0,0 +1,87 @@ +package zxsdk + +import ( + "fmt" + wallet "wallet_sdk" + "zxsdk/constant" + "zxsdk/log" + model2 "zxsdk/model" + "zxsdk/tools" +) + +func (zx ZxChainClient) KvSave(kvKey, kvValue, operateId string) error { + if kvKey == "" { + log.Logger.Errorf("local sdk error: kv key cannot be empty") + return constant.KVKEY_CANNOT_BE_EMPTY_ERROR + } + + if kvValue == "" { + log.Logger.Errorf("local sdk error: kv value cannot be empty") + return constant.KVVALUE_CANNOT_BE_EMPTY_ERROR + } + + if operateId == "" { + log.Logger.Errorf("local sdk error: operate id cannot be empty") + return constant.OPERATE_ID_CANNOT_BE_EMPTY_ERROR + } + + hash, _ := wallet.SM3Hash([]byte(kvValue)) + req := &model2.KvSaveReq{ + PublicKey: zx.publicKey, + KvKey: zx.addr + "_" + kvKey, + KvValue: kvValue, + OperateId: operateId, + } + + sign, err := wallet.SignByPriKey(zx.privateKey, tools.GetSignData(req.PublicKey, req.KvKey, hash, req.OperateId)) + if err != nil { + log.Logger.Errorf("local sdk error: signature kvsave param error: %s", err.Error()) + return constant.SIGNATURE_ERROR + } + req.Sign = sign + + trigger := tools.HttpTrigger{ + Client: tools.DefaultHttpClient, + HeaderDic: zx.makeHeader(), + } + err = trigger.HttpPost(constant.ZxChainBaseUrl+"/api/v1/kv/save", req, nil) + if err != nil { + log.Logger.TraceErrorf(trigger.HeaderDic[constant.TraceKey], "kv save error: %s", err.Error()) + return err + } + + return nil +} + +func (zx ZxChainClient) KvQuery(kvKey string, isNeedValue bool) (*model2.KvQueryResp, error) { + if kvKey == "" { + log.Logger.Errorf("local sdk error: kv key cannot be empty") + return nil, constant.KVKEY_CANNOT_BE_EMPTY_ERROR + } + + trigger := tools.HttpTrigger{ + Client: tools.DefaultHttpClient, + HeaderDic: zx.makeHeader(), + QueryDic: map[string]string{ + "kvKey": zx.addr + "_" + kvKey, + "isNeedValue": fmt.Sprintf("%v", isNeedValue), + }, + } + + resp := &model2.KvQueryResp{} + err := trigger.HttpGet(constant.ZxChainBaseUrl+"/api/v1/kv/query", resp) + if err != nil { + log.Logger.Errorf(trigger.HeaderDic[constant.TraceKey], "kv query error: %s", err.Error()) + return nil, err + } + + return resp, nil +} + +func (zx ZxChainClient) GetKvCert(txId string) (string, error) { + if txId == "" { + log.Logger.Errorf("local sdk error: tx id cannot be empty") + return "", constant.TXID_CANNOT_BE_EMPTY_ERROR + } + return constant.KvCertBaseUrl + txId, nil +} diff --git a/third_party/official/gosdk_demo/zxsdk/log/logger.go b/third_party/official/gosdk_demo/zxsdk/log/logger.go new file mode 100644 index 0000000..c8b199a --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/log/logger.go @@ -0,0 +1,62 @@ +package log + +import ( + "fmt" + "github.com/sirupsen/logrus" + "runtime" + "strings" +) + +type LoggerItf interface { + Errorf(format string, args ...interface{}) + Infof(format string, args ...interface{}) +} + +type LoggerS struct { + LoggerItf +} + +var ( + Logger LoggerS + enableLogger bool +) + +func init() { + Logger.LoggerItf = logrus.New() + enableLogger = true +} + +// SetLogger todo: replace the log plugin +func (l *LoggerS) SetLogger(logger LoggerItf) { + if logger == nil { + return + } + + l.LoggerItf = logger +} + +func (l *LoggerS) DisableLogger() { + enableLogger = false +} + +func (l *LoggerS) TraceErrorf(traceId, format string, args ...interface{}) { + if !enableLogger { + return + } + + _, file, line, _ := runtime.Caller(1) + split := strings.Split(file, "/") + file = strings.Join(split[len(split)-2:], "/") + l.LoggerItf.Errorf(fmt.Sprintf("cloud-trace-id:[%s]:::[%s]:::line[%d]:::%s", traceId, file, line, format), args...) +} + +func (l *LoggerS) TraceInfof(traceId, format string, args ...interface{}) { + if !enableLogger { + return + } + + _, file, line, _ := runtime.Caller(1) + split := strings.Split(file, "/") + file = strings.Join(split[len(split)-2:], "/") + l.LoggerItf.Infof(fmt.Sprintf("cloud-trace-id:[%s]:::[%s]:::line[%d]:::%s", traceId, file, line, format), args...) +} diff --git a/third_party/official/gosdk_demo/zxsdk/model/create_ev_cert_model.go b/third_party/official/gosdk_demo/zxsdk/model/create_ev_cert_model.go new file mode 100644 index 0000000..86e53d1 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/model/create_ev_cert_model.go @@ -0,0 +1,9 @@ +package model + +type CreateEvCertReq struct { + EvId string `json:"evId"` +} + +type CreateEvCertResp struct { + Status int `json:"status"` +} diff --git a/third_party/official/gosdk_demo/zxsdk/model/create_ev_task_model.go b/third_party/official/gosdk_demo/zxsdk/model/create_ev_task_model.go new file mode 100644 index 0000000..f1c5e52 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/model/create_ev_task_model.go @@ -0,0 +1,15 @@ +package model + +type EvSaveReq struct { + PublicKey string `json:"publicKey"` + Hash string `json:"hash"` + Sign string `json:"sign"` + ExtendInfo string `json:"extendInfo"` +} + +type EvSaveResp struct { + EvId string `json:"evId"` + TxId string `json:"txId"` + BlockHeight int64 `json:"blockHeight"` + TxTime string `json:"txTime"` +} diff --git a/third_party/official/gosdk_demo/zxsdk/model/create_kv_task_model.go b/third_party/official/gosdk_demo/zxsdk/model/create_kv_task_model.go new file mode 100644 index 0000000..f01df31 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/model/create_kv_task_model.go @@ -0,0 +1,9 @@ +package model + +type KvSaveReq struct { + PublicKey string `json:"publicKey"` + KvKey string `json:"kvKey"` + KvValue string `json:"kvValue"` + Sign string `json:"sign"` + OperateId string `json:"operateId"` +} diff --git a/third_party/official/gosdk_demo/zxsdk/model/query_ev_cert_model.go b/third_party/official/gosdk_demo/zxsdk/model/query_ev_cert_model.go new file mode 100644 index 0000000..5e42e90 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/model/query_ev_cert_model.go @@ -0,0 +1,10 @@ +package model + +type QueryEvCertReq struct { + EvId string `json:"evId" mapstructure:"evId"` +} + +type QueryEvCertResp struct { + Status int `json:"status"` + CertUrl string `json:"certUrl"` +} diff --git a/third_party/official/gosdk_demo/zxsdk/model/query_ev_task_status_model.go b/third_party/official/gosdk_demo/zxsdk/model/query_ev_task_status_model.go new file mode 100644 index 0000000..c50b7ac --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/model/query_ev_task_status_model.go @@ -0,0 +1,15 @@ +package model + +type QueryEvStatusReq struct { + EvId string `json:"evId" mapstructure:"evId"` + Hash string `json:"hash" mapstructure:"hash"` + TxId string `json:"txId" mapstructure:"txId"` +} + +type QueryEvStatusResp struct { + EvId string `json:"evId"` + TxId string `json:"txId"` + BlockHeight int64 `json:"blockHeight"` + TxTime string `json:"txTime"` + ExtendInfo string `json:"extendInfo"` +} diff --git a/third_party/official/gosdk_demo/zxsdk/model/query_kv_task_status_model.go b/third_party/official/gosdk_demo/zxsdk/model/query_kv_task_status_model.go new file mode 100644 index 0000000..0295d52 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/model/query_kv_task_status_model.go @@ -0,0 +1,15 @@ +package model + +type KvQueryReq struct { + KvKey string `json:"kvKey" mapstructure:"kvKey"` + IsNeedValue bool `json:"isNeedValue" mapstructure:"isNeedValue"` +} + +type KvQueryResp struct { + KvStatus int `json:"kvStatus"` + KvKey string `json:"kvKey"` + KvValue string `json:"kvValue"` + TxId string `json:"txId"` + BlockHeight int64 `json:"blockHeight"` + TxTime string `json:"txTime"` +} diff --git a/third_party/official/gosdk_demo/zxsdk/tools/caculate_tool.go b/third_party/official/gosdk_demo/zxsdk/tools/caculate_tool.go new file mode 100644 index 0000000..a82f393 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/tools/caculate_tool.go @@ -0,0 +1,16 @@ +package tools + +import ( + "fmt" + "strings" +) + +func GetSignData(args ...interface{}) string { + var builder strings.Builder + + builder.WriteString(fmt.Sprintf("%v", args[0])) + for _, arg := range args[1:] { + builder.WriteString(fmt.Sprintf("_%v", arg)) + } + return builder.String() +} diff --git a/third_party/official/gosdk_demo/zxsdk/tools/http_tool.go b/third_party/official/gosdk_demo/zxsdk/tools/http_tool.go new file mode 100644 index 0000000..acc1757 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/tools/http_tool.go @@ -0,0 +1,203 @@ +package tools + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "github.com/mitchellh/mapstructure" + "io" + "net/http" + "net/url" + "time" + "zxsdk/constant" + "zxsdk/log" +) + +var DefaultHttpClient = &http.Client{ + Timeout: 10 * time.Second, +} + +// HttpTrigger struct http trigger +type HttpTrigger struct { + Method string + Url string + From string + To string + DisableReqBodyPrint bool + DisableRespBodyPrint bool + HeaderDic map[string]string + QueryDic map[string]string + Client *http.Client + QueryStr string //如果不为空,则忽略QueryDic +} + +// SendJsonRequest func sendJsonRequest +func (p *HttpTrigger) SendJsonRequest(client *http.Client, queryStrDic, + headerDic map[string]string, + body interface{}) ([]byte, error) { + return SendRequest(client, queryStrDic, headerDic, p.Method, p.Url, body, + p.From, p.To, p.DisableReqBodyPrint, p.DisableRespBodyPrint, p.QueryStr) +} + +// Execute send and get response +func (p *HttpTrigger) Execute(body interface{}) (*constant.RetData, error) { + //不存在则设置默认json格式 + if p.HeaderDic == nil { + p.HeaderDic = make(map[string]string) + } + + traceId := p.HeaderDic[constant.TraceKey] + if _, ok := p.HeaderDic["Content-Type"]; !ok { + p.HeaderDic["Content-Type"] = "application/json;charset=utf-8" + } + + respStr, err := p.SendJsonRequest(p.Client, p.QueryDic, p.HeaderDic, body) + if err != nil { + log.Logger.TraceErrorf(traceId, "SendJsonRequest error:%s", err.Error()) + return nil, constant.RESP_HTTP_REQ_ERROR + } + + var respData = &constant.RetData{} + err = json.Unmarshal(respStr, respData) + if err != nil { + log.Logger.TraceErrorf(traceId, "Unmarshal error:%s", err.Error()) + return nil, constant.JSON_UNMARSHAL_ERROR + } + + return respData, nil +} + +// HttpGet JSON Get请求,需要服务端的响应体格式符合RetData +// resp必须是结构体的指针,字段是响应Data的内容 +// 响应报文JSON的key,需要和respData中的字段名相同(忽略大小写),所以key最好是驼峰命名。否则需要在respData的字段标签中用指定名称,例如`mapstructure:"user_name"` +func (p *HttpTrigger) HttpGet(url string, retData interface{}) error { + p.Method = http.MethodGet + p.Url = url + + data, err := p.Execute(nil) + if err != nil { + return err + } + if data.RetCode != constant.RESP_SUCC.RetCode { + err := constant.Build(data.RetCode, data.RetMsg) + return err + } + + if retData != nil { + err = mapstructure.Decode(data.Data, retData) + if err != nil { + log.Logger.TraceErrorf(p.HeaderDic[constant.TraceKey], "mapstructure Decode error:%s", err.Error()) + return constant.JSON_UNMARSHAL_ERROR + } + } + return nil +} + +// HttpPost JSON Post请求,需要服务端的响应体格式符合RetData +// body和resp必须是结构体的指针,resp字段是响应Data的内容 +// 响应报文JSON的key,需要和respData中的字段名相同(忽略大小写), +// 所以key最好是驼峰命名。否则需要在respData的字段标签中用指定名称,例如`mapstructure:"user_name"` +func (p *HttpTrigger) HttpPost(url string, body interface{}, retData interface{}) error { + p.Method = http.MethodPost + p.Url = url + + data, err := p.Execute(body) + if err != nil { + return err + } + if data.RetCode != constant.RESP_SUCC.RetCode { + err = constant.Build(data.RetCode, data.RetMsg) + return err + } + + if retData != nil { + err = mapstructure.Decode(data.Data, retData) + if err != nil { + log.Logger.TraceErrorf(p.HeaderDic[constant.TraceKey], "mapstructure Decode error:%s", err.Error()) + return constant.JSON_UNMARSHAL_ERROR + } + } + return nil +} + +// SendRequest common func, send request +func SendRequest(client *http.Client, queryStrDic, headerDic map[string]string, + method, reqUrl string, body interface{}, from, to string, + disableReqBodyPrint, disableRespPrint bool, queryStr string) ([]byte, error) { + traceId := headerDic[constant.TraceKey] + // step 1: put query string into url + baseUrl, err := url.Parse(reqUrl) + if err != nil { + log.Logger.TraceErrorf(traceId, "url.Parse err %s", err.Error()) + return nil, err + } + if queryStr != "" { + //处理get请求直接透传的场景 + baseUrl.RawQuery = queryStr + } else { + params := url.Values{} + for k, v := range queryStrDic { + params.Add(k, v) + } + baseUrl.RawQuery = params.Encode() + } + reqUrl = baseUrl.String() + log.Logger.TraceInfof(traceId, "call %s with reqUrl %s, method %s, from %s", to, reqUrl, method, from) + if !disableReqBodyPrint { + log.Logger.TraceInfof(traceId, "call %s with reqUrl %s, body is %v", to, reqUrl, body) + } + var reader io.Reader + if method != http.MethodGet && body != nil { + if bodyReader, ok := body.(io.Reader); ok { + //处理post请求直接透传的场景 + reader = bodyReader + } else { + b, err := json.Marshal(body) + if err != nil { + log.Logger.TraceErrorf(traceId, "json.Marshal err %s", err.Error()) + return nil, err + } + log.Logger.TraceInfof(traceId, "post content %s", string(b)) + reqBody := bytes.NewBuffer(b) + reader = reqBody + } + } + + req, err := http.NewRequest(method, reqUrl, reader) + if err != nil { + log.Logger.TraceErrorf(traceId, "http.NewRequest err %s, reqUrl %s", err.Error(), reqUrl) + return nil, err + } + for k, v := range headerDic { + //Host的请求头要特殊处理,否则设置不到header里去 + if k == "Host" { + req.Host = v + } else { + req.Header.Set(k, v) + } + } + + beginTime := time.Now() + resp, err := client.Do(req) + if err != nil { + log.Logger.TraceErrorf(traceId, "Client.Do err %s", err.Error()) + return nil, err + } + if resp == nil { + errMsg := fmt.Sprintf("resp is nil, may be server can't reach") + log.Logger.TraceErrorf(traceId, errMsg) + return nil, errors.New(errMsg) + } + content, err := io.ReadAll(resp.Body) + if err != nil { + log.Logger.TraceErrorf(traceId, "ioutil.ReadAll err %s", err.Error()) + return nil, err + } + log.Logger.TraceInfof(traceId, "call %s with reqUrl %s success and cost %v", to, reqUrl, time.Since(beginTime)) + if !disableRespPrint { + log.Logger.TraceInfof(traceId, "call %s with reqUrl %s resp %s", to, reqUrl, string(content)) + } + defer resp.Body.Close() + return content, nil +} diff --git a/third_party/official/gosdk_demo/zxsdk/wallet_sdk/go.mod b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/go.mod new file mode 100644 index 0000000..e6016f9 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/go.mod @@ -0,0 +1,11 @@ +module wallet_sdk + +go 1.15 + +require ( + github.com/stretchr/testify v1.6.1 + github.com/tencentyun/cos-go-sdk-v5 v0.7.7 + github.com/tjfoc/gmsm v1.4.0 +) + +replace github.com/tencentyun/cos-go-sdk-v5 => github.com/tencentyun/cos-go-sdk-v5 v0.7.7 diff --git a/third_party/official/gosdk_demo/zxsdk/wallet_sdk/go.sum b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/go.sum new file mode 100644 index 0000000..210d7ca --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/go.sum @@ -0,0 +1,94 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ= +github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tencentyun/cos-go-sdk-v5 v0.7.7 h1:7YI5nNwLEhseUoWzzz1CDAwQMeJyJfD3Tj0XnQRzqO8= +github.com/tencentyun/cos-go-sdk-v5 v0.7.7/go.mod h1:wQBO5HdAkLjj2q6XQiIfDSP8DXDNrppDRw2Kp/1BODA= +github.com/tjfoc/gmsm v1.4.0 h1:8nbaiZG+iVdh+fXVw0DZoZZa7a4TGm3Qab+xdrdzj8s= +github.com/tjfoc/gmsm v1.4.0/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdsignature/gmhds.go b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdsignature/gmhds.go new file mode 100644 index 0000000..a2ce232 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdsignature/gmhds.go @@ -0,0 +1,433 @@ +package hdsignature + +import ( + "crypto/elliptic" + "crypto/rand" + "encoding/asn1" + "encoding/base64" + "encoding/hex" + "encoding/pem" + "fmt" + "math/big" + + "github.com/tjfoc/gmsm/sm2" + "github.com/tjfoc/gmsm/sm3" + sm2x509 "github.com/tjfoc/gmsm/x509" +) + +var ( + curve = sm2.P256Sm2() +) + +type PublicKey struct { + Key *sm2.PublicKey + Code []byte +} + +type PrivateKey struct { + Key *sm2.PrivateKey + Code []byte +} + +func CreateMasterKey(mnemonic string) (*PrivateKey, *PublicKey, error) { + keyStr, err := deriveKeyString([]byte(mnemonic), []byte(ZXRootSeed)) + if err != nil { + return nil, nil, err + } + + skStr := keyStr[:HDWKeyLength] + code := keyStr[HDWKeyLength:] + d := new(big.Int).SetBytes(skStr) + sk, err := composePrivateKey(d, code) + if err != nil { + return nil, nil, err + } + + pk := sk.GetPublicKey() + + return sk, pk, nil +} + +// Private key methods + +func (sk *PrivateKey) GetPublicKey() *PublicKey { + return &PublicKey{ + Key: &sk.Key.PublicKey, + Code: sk.Code, + } +} + +func (sk *PrivateKey) GetPEM(password []byte) (string, error) { + var skDER []byte + var err error + if password == nil { + skDER, err = sm2x509.MarshalSm2UnecryptedPrivateKey(sk.Key) + if err != nil { + return "", err + } + } else { + skDER, err = sm2x509.MarshalSm2EcryptedPrivateKey(sk.Key, password) + if err != nil { + return "", err + } + } + keyASN1 := HDKeyASN1{ + Key: skDER, + Code: sk.Code, + } + keyBytes, err := asn1.Marshal(keyASN1) + if err != nil { + return "", err + } + keyBlock := &pem.Block{ + Type: ZXPrivateKey, + Bytes: keyBytes, + } + return string(pem.EncodeToMemory(keyBlock)), nil +} + +func (sk *PrivateKey) GetPrunedPEM(password []byte) (string, error) { + var skDER []byte + var err error + if password == nil { + skDER, err = sm2x509.MarshalSm2UnecryptedPrivateKey(sk.Key) + if err != nil { + return "", err + } + } else { + skDER, err = sm2x509.MarshalSm2EcryptedPrivateKey(sk.Key, password) + if err != nil { + return "", err + } + } + keyBlock := &pem.Block{ + Type: PEMPrivateKey, + Bytes: skDER, + } + return string(pem.EncodeToMemory(keyBlock)), nil +} + +func (sk *PrivateKey) GetBase64String(password []byte) (string, error) { + var skDER []byte + var err error + if password == nil { + skDER, err = sm2x509.MarshalSm2UnecryptedPrivateKey(sk.Key) + if err != nil { + return "", err + } + } else { + skDER, err = sm2x509.MarshalSm2EcryptedPrivateKey(sk.Key, password) + if err != nil { + return "", err + } + } + return base64.StdEncoding.EncodeToString(skDER), nil +} + +func (sk *PrivateKey) Sign(data string) ([]byte, error) { + r, s, err := sm2.Sm2Sign(sk.Key, []byte(data), []byte(SM2DefaultUID), rand.Reader) + if err != nil { + return nil, err + } + return asn1.Marshal(ECSignature{R: r, S: s,}) +} + +func (sk *PrivateKey) GetAddress() (string, error) { + return sk.GetPublicKey().GetAddress() +} + +func (sk *PrivateKey) DeriveChildPrivateKey(index uint32) (*PrivateKey, error) { + if sk.Code == nil { + return nil, fmt.Errorf("parent key is pruned") + } + var keySeed []byte + indexBytes := uint32Bytes(index) + if index >= FirstHardenedKey { + skBytes := sk.Key.D.Bytes() + if len(skBytes) < HDWKeyLength { + skBytes = append([]byte{0}, skBytes...) + } + keySeed = append(skBytes, indexBytes...) + } else { + pkBytes := sk.GetPublicKey().Bytes() + keySeed = append(pkBytes, indexBytes...) + } + + keyString, err := deriveKeyString(keySeed, sk.Code) + if err != nil { + return nil, err + } + + skStr := keyString[:HDWKeyLength] + code := keyString[HDWKeyLength:] + d := new(big.Int).Mod(new(big.Int).Add(new(big.Int).SetBytes(skStr), sk.Key.D), curve.Params().N) + + skChild, err := composePrivateKey(d, code) + if err != nil { + return nil, err + } + + return skChild, nil +} + +func (sk *PrivateKey) DeriveChildPublicKey(index uint32) (*PublicKey, error) { + skChild, err := sk.DeriveChildPrivateKey(index) + if err != nil { + return nil, err + } + return skChild.GetPublicKey(), nil +} + +func (sk *PrivateKey) DeriveKeyPairObjFromFullPath(indexPath []uint32) (*PrivateKey, error) { + var err error + + if indexPath == nil || len(indexPath) == 0 { + return nil, fmt.Errorf("empty index path") + } + + skChild := sk + for _, index := range indexPath { + skChild, err = skChild.DeriveChildPrivateKey(index) + if err != nil { + return nil, err + } + } + + return skChild, nil +} + +func (sk *PrivateKey) CreateCSRFromTemplatePEM(templateStr string) ([]byte, error) { + templateBlock, _ := pem.Decode([]byte(templateStr)) + if templateBlock == nil { + return nil, fmt.Errorf("invalid CSR template") + } + template, err := sm2x509.ParseCertificateRequest(templateBlock.Bytes) + if err != nil { + return nil, err + } + + return sk.CreateCSRFromTemplateObj(template) +} + +func (sk *PrivateKey) CreateCSRFromTemplateObj(template *sm2x509.CertificateRequest) ([]byte, error) { + csrDER, err := sm2x509.CreateCertificateRequest(rand.Reader, template, sk.Key) + if err != nil { + return nil, err + } + + return pem.EncodeToMemory(&pem.Block{ + Type: PEMCSR, + Bytes: csrDER, + }), nil +} + +// Public key methods + +func (pk *PublicKey) GetPEM() (string, error) { + pkDER, err := sm2x509.MarshalSm2PublicKey(pk.Key) + if err != nil { + return "", err + } + keyASN1 := HDKeyASN1{ + Key: pkDER, + Code: pk.Code, + } + keyBytes, err := asn1.Marshal(keyASN1) + if err != nil { + return "", err + } + keyBlock := &pem.Block{ + Type: ZXPublicKey, + Bytes: keyBytes, + } + return string(pem.EncodeToMemory(keyBlock)), nil +} + +func (pk *PublicKey) GetPrunedPEM() (string, error) { + pkDER, err := sm2x509.MarshalSm2PublicKey(pk.Key) + if err != nil { + return "", err + } + keyBlock := &pem.Block{ + Type: PEMPublicKey, + Bytes: pkDER, + } + return string(pem.EncodeToMemory(keyBlock)), nil +} + +func (pk *PublicKey) GetBase64String() (string, error) { + pkDER, err := sm2x509.MarshalSm2PublicKey(pk.Key) + if err != nil { + return "", nil + } + return base64.StdEncoding.EncodeToString(pkDER), nil +} + +func (pk *PublicKey) Verify(data string, sig []byte) (bool, error) { + sigEC := &ECSignature{} + _, err := asn1.Unmarshal(sig, sigEC) + if err != nil { + return false, err + } + return sm2.Sm2Verify(pk.Key, []byte(data), []byte(SM2DefaultUID), sigEC.R, sigEC.S), nil +} + +func (pk *PublicKey) Bytes() []byte { + return elliptic.Marshal(curve, pk.Key.X, pk.Key.Y) +} + +func (pk *PublicKey) GetAddress() (string, error) { + pkBytes := pk.Bytes() + sm3Hash := sm3.New() + _, err := sm3Hash.Write(pkBytes) + if err != nil { + return "", err + } + pkDgst := sm3Hash.Sum(nil) + if len(pkDgst) <= HDWAddressLength { + return "", fmt.Errorf("invalid public key") + } + addrBytes := pkDgst[:HDWAddressLength] + addrHex := hex.EncodeToString(addrBytes) + return ZXAddrPrefix + addrHex, nil +} + +func (pk *PublicKey) DeriveChildPublicKey(index uint32) (*PublicKey, error) { + if pk.Code == nil { + return nil, fmt.Errorf("parent key is pruned") + } + if index >= FirstHardenedKey { + return nil, fmt.Errorf("only private key can derive keys for hardened child %v", index) + } + + indexBytes := uint32Bytes(index) + keySeed := append(pk.Bytes(), indexBytes...) + + keyString, err := deriveKeyString(keySeed, pk.Code) + if err != nil { + return nil, err + } + + skStr := keyString[:HDWKeyLength] + code := keyString[HDWKeyLength:] + + x, y := curve.ScalarBaseMult(new(big.Int).SetBytes(skStr).Bytes()) + xChild, yChild := curve.Add(x, y, pk.Key.X, pk.Key.Y) + + return &PublicKey{ + Key: &sm2.PublicKey{ + Curve: curve, + X: xChild, + Y: yChild, + }, + Code: code, + }, nil +} + +func ParsePrivateKey(skPEM []byte, password []byte) (*PrivateKey, error) { + skBlock, _ := pem.Decode(skPEM) + if skBlock == nil { + return nil, fmt.Errorf("invalid private key PEM") + } + keyASN1 := &HDKeyASN1{} + _, err := asn1.Unmarshal(skBlock.Bytes, keyASN1) + if err == nil { + skSM2, err := sm2x509.ParsePKCS8PrivateKey(keyASN1.Key, password) + if err != nil { + return nil, err + } + return &PrivateKey{ + Key: skSM2, + Code: keyASN1.Code, + }, nil + } + + skSM2, err := sm2x509.ParsePKCS8PrivateKey(skBlock.Bytes, password) + if err != nil { + return nil, err + } + return &PrivateKey{ + Key: skSM2, + Code: nil, + }, nil +} + +func ParsePublicKey(pkPEM []byte) (*PublicKey, error) { + pkBlock, _ := pem.Decode(pkPEM) + if pkBlock == nil { + return nil, fmt.Errorf("invalid public key PEM") + } + keyASN1 := &HDKeyASN1{} + _, err := asn1.Unmarshal(pkBlock.Bytes, keyASN1) + if err == nil { + pkSM2, err := sm2x509.ParseSm2PublicKey(keyASN1.Key) + if err != nil { + return nil, err + } + return &PublicKey{ + Key: pkSM2, + Code: keyASN1.Code, + }, nil + } + + pkSM2, err := sm2x509.ParseSm2PublicKey(pkBlock.Bytes) + if err != nil { + return nil, err + } + return &PublicKey{ + Key: pkSM2, + Code: nil, + }, nil +} + +func ParseBase64PrivateKey(skBase64 string, password []byte) (*PrivateKey, error) { + skDER, err := base64.StdEncoding.DecodeString(skBase64) + if err != nil { + return nil, err + } + + key, err := sm2x509.ParsePKCS8PrivateKey(skDER, password) + if err != nil { + return nil, err + } + + return &PrivateKey{ + Key: key, + Code: nil, + }, nil +} + +func ParseBase64PublicKey(pkBase64 string) (*PublicKey, error) { + pkDER, err := base64.StdEncoding.DecodeString(pkBase64) + if err != nil { + return nil, err + } + + key, err := sm2x509.ParseSm2PublicKey(pkDER) + if err != nil { + return nil, err + } + + return &PublicKey{ + Key: key, + Code: nil, + }, nil +} + +func (pk *PublicKey) DeriveKeyPairObjFromFullPath(indexPath []uint32) (*PublicKey, error) { + var err error + + if indexPath == nil || len(indexPath) == 0 { + return nil, fmt.Errorf("empty index path") + } + + pkChild := pk + for _, index := range indexPath { + pkChild, err = pkChild.DeriveChildPublicKey(index) + if err != nil { + return nil, err + } + } + + return pkChild, nil +} diff --git a/third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdsignature/utils.go b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdsignature/utils.go new file mode 100644 index 0000000..a3fcca3 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdsignature/utils.go @@ -0,0 +1,134 @@ +package hdsignature + +import ( + "bytes" + "crypto/hmac" + "encoding/binary" + "fmt" + "math/big" + + "github.com/tjfoc/gmsm/sm2" + "github.com/tjfoc/gmsm/sm3" +) + +const ( + FirstHardenedKey uint32 = 1 << 31 + + HDWKeyLength = 32 + HDWKeyStringLength = HDWKeyLength * 2 + HDWAddressLength = 20 + + SM2DefaultUID = "1234567812345678" + + ZXAddrPrefix = "ZX" + ZXRootSeed = "ZX seed" + + ZXPublicKey = "ZX PUBLIC KEY" + ZXPrivateKey = "ZX PRIVATE KEY" + + PEMPublicKey = "PUBLIC KEY" + PEMPrivateKey = "PRIVATE KEY" + + PEMCSR = "CERTIFICATE REQUEST" + + SigMaxLength = 73 + SigMinLength = 70 + + PubKeyMaxLength = 150 + PubKeyMinLength = 80 + + ZXAddrPrefixV1 = "zx" + ZXAddrPrefixV2 = "Zx" + ZXAddrPrefixV3 = "zX" +) + +type HDKeyASN1 struct { + Key []byte + Code []byte +} + +type ECSignature struct { + R *big.Int `json:"r"` + S *big.Int `json:"s"` +} + +func SM3HMAC(key []byte, data []byte) ([]byte, error) { + hmacSM3 := hmac.New(sm3.New, key) + _, err := hmacSM3.Write(data) + if err != nil { + return nil, err + } + return hmacSM3.Sum(nil), nil +} + +func SM3Hash(data []byte) ([]byte, error) { + hashSM3 := sm3.New() + _, err := hashSM3.Write(data) + if err != nil { + return nil, err + } + return hashSM3.Sum(nil), nil +} + +func uint32Bytes(i uint32) []byte { + bytes := make([]byte, 4) + binary.BigEndian.PutUint32(bytes, i) + return bytes +} + +func validatePrivateKey(key []byte) error { + if fmt.Sprintf("%x", key) == "0000000000000000000000000000000000000000000000000000000000000000" || //if the key is zero + bytes.Compare(key, curve.Params().N.Bytes()) >= 0 || //or is outside of the curve + len(key) != 32 { //or is too short + return fmt.Errorf("invalid integer") + } + + return nil +} + +func deriveKeyString(seed, code []byte) ([]byte, error) { + skL, err := SM3HMAC(code, seed) + if err != nil { + return nil, err + } + + err = validatePrivateKey(skL) + + for err != nil { + skL, err = SM3HMAC(code, skL) + if err != nil { + return nil, err + } + + err = validatePrivateKey(skL) + } + + skR, err := SM3HMAC(code, skL) + if err != nil { + return nil, err + } + + return append(skL, skR...), nil +} + +func composePrivateKey(d *big.Int, code []byte) (*PrivateKey, error) { + x, y := curve.ScalarBaseMult(d.Bytes()) + + pkSM2 := sm2.PublicKey{ + Curve: curve, + X: x, + Y: y, + } + + skSM2 := sm2.PrivateKey{ + PublicKey: pkSM2, + D: d, + } + + sk := &PrivateKey{ + Key: &skSM2, + Code: code, + } + + return sk, nil +} diff --git a/third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdw.go b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdw.go new file mode 100644 index 0000000..175ebf3 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdw.go @@ -0,0 +1,300 @@ +package wallet_sdk + +import ( + "crypto/rand" + "encoding/hex" + "encoding/pem" + "fmt" + "math/big" + "strconv" + "time" + hds "wallet_sdk/hdsignature" + "wallet_sdk/mnemonics" +) + +const ( + defaultEntropy = 128 +) + +var ( + defaultIndexPathPrefix = []uint32{66, 0, 0, 0} +) + +// Mnemonics + +func CreateMnemonic() (string, error) { + entropy, err := mnemonics.NewEntropy(defaultEntropy) + if err != nil { + return "", err + } + return mnemonics.NewMnemonic(entropy) +} + +// HD Signature + +// DeriveKeyPair derives key pair strings for the node m/66/0/0/0/index +func DeriveKeyPair(mnemonic string, index uint32) (string, string, error) { + return DeriveKeyPairEncrypted(mnemonic, index, nil) +} + +func DeriveKeyPairEncrypted(mnemonic string, index uint32, password []byte) (string, string, error) { + msk, _, err := hds.CreateMasterKey(mnemonic) + if err != nil { + return "", "", err + } + + psk, err := msk.DeriveKeyPairObjFromFullPath(defaultIndexPathPrefix) + if err != nil { + return "", "", err + } + + sk, err := psk.DeriveChildPrivateKey(index) + if err != nil { + return "", "", err + } + + skPEM, err := sk.GetPrunedPEM(password) + if err != nil { + return "", "", err + } + + pkPEM, err := sk.GetPublicKey().GetPrunedPEM() + if err != nil { + return "", "", err + } + + return skPEM, pkPEM, nil +} + +// DeriveKeyPairFullPath derives key pair strings for the node m/path[0]/... +func DeriveKeyPairFullPath(mnemonic string, path []uint32) (string, string, error) { + return DeriveKeyPairFullPathEncrypted(mnemonic, path, nil) +} +func DeriveKeyPairFullPathEncrypted(mnemonic string, path []uint32, password []byte) (string, string, error) { + msk, _, err := hds.CreateMasterKey(mnemonic) + if err != nil { + return "", "", err + } + + sk, err := msk.DeriveKeyPairObjFromFullPath(path) + if err != nil { + return "", "", err + } + + skPEM, err := sk.GetPrunedPEM(password) + if err != nil { + return "", "", err + } + + pkPEM, err := sk.GetPublicKey().GetPrunedPEM() + if err != nil { + return "", "", err + } + + return skPEM, pkPEM, nil +} + +func PriKey2PubKey(pri string) (string, error) { + return EncryptedPriKey2PubKey(pri, nil) +} + +func EncryptedPriKey2PubKey(pri string, password []byte) (string, error) { + sk, err := hds.ParsePrivateKey([]byte(pri), password) + if err != nil { + return "", err + } + + return sk.GetPublicKey().GetPrunedPEM() +} + +func PubKey2Address(pubKey string) (string, error) { + pk, err := hds.ParsePublicKey([]byte(pubKey)) + if err != nil { + return "", err + } + + return pk.GetAddress() +} + +func PriKey2Address(priKey string) (string, error) { + return EncryptedPriKey2Address(priKey, nil) +} + +func EncryptedPriKey2Address(priKey string, password []byte) (string, error) { + sk, err := hds.ParsePrivateKey([]byte(priKey), password) + if err != nil { + return "", err + } + + return sk.GetAddress() +} + +func SignByPriKey(priKey string, data string) (string, error) { + return SignByEncryptedPriKey(priKey, data, nil) +} + +func SignByEncryptedPriKey(priKey string, data string, password []byte) (string, error) { + sk, err := hds.ParsePrivateKey([]byte(priKey), password) + if err != nil { + return "", err + } + + sig, err := sk.Sign(data) + if err != nil { + return "", err + } + sigHex := hex.EncodeToString(sig) + return sigHex, nil +} + +func VerifyByPubKey(pubKey string, signedData string, data string) (bool, error) { + pk, err := hds.ParsePublicKey([]byte(pubKey)) + if err != nil { + return false, err + } + + sig, err := hex.DecodeString(signedData) + if err != nil { + return false, err + } + return pk.Verify(data, sig) +} + +func GetBase64PrivateKey(priKey string) (string, error) { + return GetBase64EncryptedPrivateKey(priKey, nil) +} + +func GetBase64EncryptedPrivateKey(priKey string, password []byte) (string, error) { + sk, err := hds.ParsePrivateKey([]byte(priKey), password) + if err != nil { + return "", err + } + return sk.GetBase64String(password) +} + +func GetBase64PublicKey(pubKey string) (string, error) { + pk, err := hds.ParsePublicKey([]byte(pubKey)) + if err != nil { + return "", err + } + return pk.GetBase64String() +} + +// Certificate with SM2 public key + +// Create certificate request (csr) +func CreateCSR(priKey string, templateStr string) (string, error) { + return CreateCSRFromEncryptedPrivateKey(priKey, templateStr, nil) +} + +func CreateCSRFromEncryptedPrivateKey(priKey string, templateStr string, password []byte) (string, error) { + sk, err := hds.ParsePrivateKey([]byte(priKey), password) + if err != nil { + return "", err + } + + csr, err := sk.CreateCSRFromTemplatePEM(templateStr) + if err != nil { + return "", err + } + + return string(csr), nil +} + +func CheckSignatureValidity(sig string) (bool, error) { + sigBytes, err := hex.DecodeString(sig) + if err != nil { + return false, err + } + + if len(sigBytes) < hds.SigMinLength || len(sigBytes) > hds.SigMaxLength { + return false, fmt.Errorf("invalid signature length (%d)", len(sig)) + } + + return true, nil +} + +func CheckPublicKeyValidity(pubKey string) (bool, error) { + pubKeyBlock, _ := pem.Decode([]byte(pubKey)) + if pubKeyBlock == nil { + return false, fmt.Errorf("invalid public key encoding") + } + + if len(pubKeyBlock.Bytes) < hds.PubKeyMinLength || len(pubKeyBlock.Bytes) > hds.PubKeyMaxLength { + return false, fmt.Errorf("invalid public key length (%d)", len(pubKeyBlock.Bytes)) + } + + return true, nil +} + +func CheckAddressValidity(address string) (bool, error) { + addressBytes := []byte(address) + prefix := string(addressBytes[:2]) + pkHash := addressBytes[2:] + + if len(pkHash) != hds.HDWAddressLength*2 { + return false, fmt.Errorf("hash part should be %d bytes", hds.HDWAddressLength*2) + } + + _, err := hex.DecodeString(string(pkHash)) + if err != nil { + return false, err + } + + switch prefix { + case hds.ZXAddrPrefix, hds.ZXAddrPrefixV1, hds.ZXAddrPrefixV2, hds.ZXAddrPrefixV3: + return true, nil + default: + return false, fmt.Errorf("invalid prefix") + } +} + +type SignData struct { + Signature string + SignatureTime string + Nonce int +} + +const ( + MaxRandNum = 1000000 + MinRandNum = 100000 +) + +func GenerateApiSign(appId, appKey string) (signData *SignData, err error) { + now := time.Now() + signTime := strconv.FormatInt(now.UnixNano()/1e6, 10) + nonce, err := generateNonce() + if err != nil { + return nil, err + } + signData = &SignData{} + + signStr := "" + if appId != "" && appKey != "" { + signStr = appId + "&" + appKey + "&" + signTime + "&" + strconv.Itoa(nonce) + } else { + signStr = signTime + "&" + strconv.Itoa(nonce) + } + + sign, err := hds.SM3HMAC([]byte(appKey), []byte(signStr)) + if err != nil { + return nil, err + } + signData.Signature = hex.EncodeToString(sign) + signData.SignatureTime = signTime + signData.Nonce = nonce + return signData, nil +} + +func generateNonce() (int, error) { + b := new(big.Int).SetInt64(MaxRandNum) + randNum, err := rand.Int(rand.Reader, b) + if err != nil { + return -1, err + } + nonce := int(randNum.Int64()) + if nonce < MinRandNum { + nonce = nonce + MinRandNum + } + return nonce, nil +} diff --git a/third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdw_mobile.go b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdw_mobile.go new file mode 100644 index 0000000..25f10c8 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/hdw_mobile.go @@ -0,0 +1,89 @@ +package wallet_sdk + +import ( + "encoding/hex" + "math/rand" + "strconv" + "time" + "wallet_sdk/hdsignature" +) + +func SM3Hash(data []byte) (string, error) { + data, err := hdsignature.SM3Hash(data) + if err != nil { + return "", err + } + return hex.EncodeToString(data), nil +} + +type KeyPair struct { + Sk string + Pk string +} + +func DeriveKeyPairMobile(mnemonic string, index int) (*KeyPair, error) { + keyPair := &KeyPair{} + sk, pk, err := DeriveKeyPair(mnemonic, uint32(index)) + + if err != nil { + return keyPair, err + } + + keyPair.Sk = sk + keyPair.Pk = pk + + return keyPair, nil +} + +func DeriveKeyPairFullPathMobile(mnemonic string, path []int) (*KeyPair, error) { + keyPair := &KeyPair{} + + var uintPath []uint32 + for _, i := range path { + uintPath = append(uintPath, uint32(i)) + } + + sk, pk, err := DeriveKeyPairFullPath(mnemonic, uintPath) + if err != nil { + return keyPair, err + } + + keyPair.Sk = sk + keyPair.Pk = pk + + return keyPair, nil +} + +type SignDataMobile struct { + Signature string + SignatureTime string + Nonce string + OriginSignData string +} + +func GenerateApiSignMobile(appId, appKey string) (signData *SignDataMobile, err error) { + now := time.Now() + signTime := strconv.FormatInt(now.UnixNano()/1e6, 10) + nonce := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(1000000) + + signData = &SignDataMobile{} + + nonceStr := strconv.Itoa(nonce) + + signStr := "" + if appId != "" && appKey != "" { + signStr = appId + "&" + appKey + "&" + signTime + "&" + nonceStr + } else { + signStr = signTime + "&" + nonceStr + } + + sign, err := hdsignature.SM3HMAC([]byte(appKey), []byte(signStr)) + if err != nil { + return signData, err + } + signData.Signature = hex.EncodeToString(sign) + signData.SignatureTime = signTime + signData.Nonce = nonceStr + signData.OriginSignData = signStr + return signData, nil +} diff --git a/third_party/official/gosdk_demo/zxsdk/wallet_sdk/mnemonics/mnemonics.go b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/mnemonics/mnemonics.go new file mode 100644 index 0000000..7976062 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/mnemonics/mnemonics.go @@ -0,0 +1,134 @@ +package mnemonics + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "math/big" + "strings" + "wallet_sdk/mnemonics/wordlists" + + "github.com/tjfoc/gmsm/sm3" +) + +var ( + // Some bitwise operands for working with big.Ints. + last11BitsMask = big.NewInt(2047) + shift11BitsMask = big.NewInt(2048) + bigOne = big.NewInt(1) + bigTwo = big.NewInt(2) + + wordList []string +) + +func init() { + wordList = wordlists.English +} + +func validateEntropyBitSize(bitSize int) error { + if (bitSize%32) != 0 || bitSize < 128 || bitSize > 256 { + return fmt.Errorf("invalid entropy length") + } + + return nil +} + +func computeChecksum(data []byte) []byte { + hasher := sm3.New() + _, _ = hasher.Write(data) // This error is guaranteed to be nil + + return hasher.Sum(nil) +} + +func addChecksum(data []byte) []byte { + // Get first byte of sha256 + hash := computeChecksum(data) + firstChecksumByte := hash[0] + + // len() is in bytes so we divide by 4 + checksumBitLength := uint(len(data) / 4) + + // For each bit of check sum we want we shift the data one the left + // and then set the (new) right most bit equal to checksum bit at that index + // staring from the left + dataBigInt := new(big.Int).SetBytes(data) + + for i := uint(0); i < checksumBitLength; i++ { + // Bitshift 1 left + dataBigInt.Mul(dataBigInt, bigTwo) + + // Set rightmost bit if leftmost checksum bit is set + if firstChecksumByte&(1<<(7-i)) > 0 { + dataBigInt.Or(dataBigInt, bigOne) + } + } + + return dataBigInt.Bytes() +} + +func padByteSlice(slice []byte, length int) []byte { + offset := length - len(slice) + if offset <= 0 { + return slice + } + + newSlice := make([]byte, length) + copy(newSlice[offset:], slice) + + return newSlice +} + +func NewEntropy(bitSize int) ([]byte, error) { + if err := validateEntropyBitSize(bitSize); err != nil { + return nil, err + } + + entropy := make([]byte, bitSize/8) + _, _ = rand.Read(entropy) // err is always nil + + return entropy, nil +} + +func NewMnemonic(entropy []byte) (string, error) { + // Compute some lengths for convenience. + entropyBitLength := len(entropy) * 8 + checksumBitLength := entropyBitLength / 32 + sentenceLength := (entropyBitLength + checksumBitLength) / 11 + + // Validate that the requested size is supported. + err := validateEntropyBitSize(entropyBitLength) + if err != nil { + return "", err + } + + // Add checksum to entropy. + entropy = addChecksum(entropy) + + // Break entropy up into sentenceLength chunks of 11 bits. + // For each word AND mask the rightmost 11 bits and find the word at that index. + // Then bitshift entropy 11 bits right and repeat. + // Add to the last empty slot so we can work with LSBs instead of MSB. + + // Entropy as an int so we can bitmask without worrying about bytes slices. + entropyInt := new(big.Int).SetBytes(entropy) + + // Slice to hold words in. + words := make([]string, sentenceLength) + + // Throw away big.Int for AND masking. + word := big.NewInt(0) + + for i := sentenceLength - 1; i >= 0; i-- { + // Get 11 right most bits and bitshift 11 to the right for next time. + word.And(entropyInt, last11BitsMask) + entropyInt.Div(entropyInt, shift11BitsMask) + + // Get the bytes representing the 11 bits as a 2 byte slice. + wordBytes := padByteSlice(word.Bytes(), 2) + + // Convert bytes to an index and add that word to the list. + words[i] = wordList[binary.BigEndian.Uint16(wordBytes)] + } + + return strings.Join(words, " "), nil +} diff --git a/third_party/official/gosdk_demo/zxsdk/wallet_sdk/mnemonics/wordlists/english.go b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/mnemonics/wordlists/english.go new file mode 100644 index 0000000..f69e8a4 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/mnemonics/wordlists/english.go @@ -0,0 +1,2071 @@ +package wordlists + +import ( + "fmt" + "hash/crc32" + "strings" +) + +func init() { + // Ensure word list is correct + // $ wget https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt + // $ crc32 english.txt + // c1dbd296 + checksum := crc32.ChecksumIEEE([]byte(english)) + if fmt.Sprintf("%x", checksum) != "c1dbd296" { + panic("english checksum invalid") + } +} + +// English is a slice of mnemonic words taken from the bip39 specification +// https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt +var English = strings.Split(strings.TrimSpace(english), "\n") +var english = `abandon +ability +able +about +above +absent +absorb +abstract +absurd +abuse +access +accident +account +accuse +achieve +acid +acoustic +acquire +across +act +action +actor +actress +actual +adapt +add +addict +address +adjust +admit +adult +advance +advice +aerobic +affair +afford +afraid +again +age +agent +agree +ahead +aim +air +airport +aisle +alarm +album +alcohol +alert +alien +all +alley +allow +almost +alone +alpha +already +also +alter +always +amateur +amazing +among +amount +amused +analyst +anchor +ancient +anger +angle +angry +animal +ankle +announce +annual +another +answer +antenna +antique +anxiety +any +apart +apology +appear +apple +approve +april +arch +arctic +area +arena +argue +arm +armed +armor +army +around +arrange +arrest +arrive +arrow +art +artefact +artist +artwork +ask +aspect +assault +asset +assist +assume +asthma +athlete +atom +attack +attend +attitude +attract +auction +audit +august +aunt +author +auto +autumn +average +avocado +avoid +awake +aware +away +awesome +awful +awkward +axis +baby +bachelor +bacon +badge +bag +balance +balcony +ball +bamboo +banana +banner +bar +barely +bargain +barrel +base +basic +basket +battle +beach +bean +beauty +because +become +beef +before +begin +behave +behind +believe +below +belt +bench +benefit +best +betray +better +between +beyond +bicycle +bid +bike +bind +biology +bird +birth +bitter +black +blade +blame +blanket +blast +bleak +bless +blind +blood +blossom +blouse +blue +blur +blush +board +boat +body +boil +bomb +bone +bonus +book +boost +border +boring +borrow +boss +bottom +bounce +box +boy +bracket +brain +brand +brass +brave +bread +breeze +brick +bridge +brief +bright +bring +brisk +broccoli +broken +bronze +broom +brother +brown +brush +bubble +buddy +budget +buffalo +build +bulb +bulk +bullet +bundle +bunker +burden +burger +burst +bus +business +busy +butter +buyer +buzz +cabbage +cabin +cable +cactus +cage +cake +call +calm +camera +camp +can +canal +cancel +candy +cannon +canoe +canvas +canyon +capable +capital +captain +car +carbon +card +cargo +carpet +carry +cart +case +cash +casino +castle +casual +cat +catalog +catch +category +cattle +caught +cause +caution +cave +ceiling +celery +cement +census +century +cereal +certain +chair +chalk +champion +change +chaos +chapter +charge +chase +chat +cheap +check +cheese +chef +cherry +chest +chicken +chief +child +chimney +choice +choose +chronic +chuckle +chunk +churn +cigar +cinnamon +circle +citizen +city +civil +claim +clap +clarify +claw +clay +clean +clerk +clever +click +client +cliff +climb +clinic +clip +clock +clog +close +cloth +cloud +clown +club +clump +cluster +clutch +coach +coast +coconut +code +coffee +coil +coin +collect +color +column +combine +come +comfort +comic +common +company +concert +conduct +confirm +congress +connect +consider +control +convince +cook +cool +copper +copy +coral +core +corn +correct +cost +cotton +couch +country +couple +course +cousin +cover +coyote +crack +cradle +craft +cram +crane +crash +crater +crawl +crazy +cream +credit +creek +crew +cricket +crime +crisp +critic +crop +cross +crouch +crowd +crucial +cruel +cruise +crumble +crunch +crush +cry +crystal +cube +culture +cup +cupboard +curious +current +curtain +curve +cushion +custom +cute +cycle +dad +damage +damp +dance +danger +daring +dash +daughter +dawn +day +deal +debate +debris +decade +december +decide +decline +decorate +decrease +deer +defense +define +defy +degree +delay +deliver +demand +demise +denial +dentist +deny +depart +depend +deposit +depth +deputy +derive +describe +desert +design +desk +despair +destroy +detail +detect +develop +device +devote +diagram +dial +diamond +diary +dice +diesel +diet +differ +digital +dignity +dilemma +dinner +dinosaur +direct +dirt +disagree +discover +disease +dish +dismiss +disorder +display +distance +divert +divide +divorce +dizzy +doctor +document +dog +doll +dolphin +domain +donate +donkey +donor +door +dose +double +dove +draft +dragon +drama +drastic +draw +dream +dress +drift +drill +drink +drip +drive +drop +drum +dry +duck +dumb +dune +during +dust +dutch +duty +dwarf +dynamic +eager +eagle +early +earn +earth +easily +east +easy +echo +ecology +economy +edge +edit +educate +effort +egg +eight +either +elbow +elder +electric +elegant +element +elephant +elevator +elite +else +embark +embody +embrace +emerge +emotion +employ +empower +empty +enable +enact +end +endless +endorse +enemy +energy +enforce +engage +engine +enhance +enjoy +enlist +enough +enrich +enroll +ensure +enter +entire +entry +envelope +episode +equal +equip +era +erase +erode +erosion +error +erupt +escape +essay +essence +estate +eternal +ethics +evidence +evil +evoke +evolve +exact +example +excess +exchange +excite +exclude +excuse +execute +exercise +exhaust +exhibit +exile +exist +exit +exotic +expand +expect +expire +explain +expose +express +extend +extra +eye +eyebrow +fabric +face +faculty +fade +faint +faith +fall +false +fame +family +famous +fan +fancy +fantasy +farm +fashion +fat +fatal +father +fatigue +fault +favorite +feature +february +federal +fee +feed +feel +female +fence +festival +fetch +fever +few +fiber +fiction +field +figure +file +film +filter +final +find +fine +finger +finish +fire +firm +first +fiscal +fish +fit +fitness +fix +flag +flame +flash +flat +flavor +flee +flight +flip +float +flock +floor +flower +fluid +flush +fly +foam +focus +fog +foil +fold +follow +food +foot +force +forest +forget +fork +fortune +forum +forward +fossil +foster +found +fox +fragile +frame +frequent +fresh +friend +fringe +frog +front +frost +frown +frozen +fruit +fuel +fun +funny +furnace +fury +future +gadget +gain +galaxy +gallery +game +gap +garage +garbage +garden +garlic +garment +gas +gasp +gate +gather +gauge +gaze +general +genius +genre +gentle +genuine +gesture +ghost +giant +gift +giggle +ginger +giraffe +girl +give +glad +glance +glare +glass +glide +glimpse +globe +gloom +glory +glove +glow +glue +goat +goddess +gold +good +goose +gorilla +gospel +gossip +govern +gown +grab +grace +grain +grant +grape +grass +gravity +great +green +grid +grief +grit +grocery +group +grow +grunt +guard +guess +guide +guilt +guitar +gun +gym +habit +hair +half +hammer +hamster +hand +happy +harbor +hard +harsh +harvest +hat +have +hawk +hazard +head +health +heart +heavy +hedgehog +height +hello +helmet +help +hen +hero +hidden +high +hill +hint +hip +hire +history +hobby +hockey +hold +hole +holiday +hollow +home +honey +hood +hope +horn +horror +horse +hospital +host +hotel +hour +hover +hub +huge +human +humble +humor +hundred +hungry +hunt +hurdle +hurry +hurt +husband +hybrid +ice +icon +idea +identify +idle +ignore +ill +illegal +illness +image +imitate +immense +immune +impact +impose +improve +impulse +inch +include +income +increase +index +indicate +indoor +industry +infant +inflict +inform +inhale +inherit +initial +inject +injury +inmate +inner +innocent +input +inquiry +insane +insect +inside +inspire +install +intact +interest +into +invest +invite +involve +iron +island +isolate +issue +item +ivory +jacket +jaguar +jar +jazz +jealous +jeans +jelly +jewel +job +join +joke +journey +joy +judge +juice +jump +jungle +junior +junk +just +kangaroo +keen +keep +ketchup +key +kick +kid +kidney +kind +kingdom +kiss +kit +kitchen +kite +kitten +kiwi +knee +knife +knock +know +lab +label +labor +ladder +lady +lake +lamp +language +laptop +large +later +latin +laugh +laundry +lava +law +lawn +lawsuit +layer +lazy +leader +leaf +learn +leave +lecture +left +leg +legal +legend +leisure +lemon +lend +length +lens +leopard +lesson +letter +level +liar +liberty +library +license +life +lift +light +like +limb +limit +link +lion +liquid +list +little +live +lizard +load +loan +lobster +local +lock +logic +lonely +long +loop +lottery +loud +lounge +love +loyal +lucky +luggage +lumber +lunar +lunch +luxury +lyrics +machine +mad +magic +magnet +maid +mail +main +major +make +mammal +man +manage +mandate +mango +mansion +manual +maple +marble +march +margin +marine +market +marriage +mask +mass +master +match +material +math +matrix +matter +maximum +maze +meadow +mean +measure +meat +mechanic +medal +media +melody +melt +member +memory +mention +menu +mercy +merge +merit +merry +mesh +message +metal +method +middle +midnight +milk +million +mimic +mind +minimum +minor +minute +miracle +mirror +misery +miss +mistake +mix +mixed +mixture +mobile +model +modify +mom +moment +monitor +monkey +monster +month +moon +moral +more +morning +mosquito +mother +motion +motor +mountain +mouse +move +movie +much +muffin +mule +multiply +muscle +museum +mushroom +music +must +mutual +myself +mystery +myth +naive +name +napkin +narrow +nasty +nation +nature +near +neck +need +negative +neglect +neither +nephew +nerve +nest +net +network +neutral +never +news +next +nice +night +noble +noise +nominee +noodle +normal +north +nose +notable +note +nothing +notice +novel +now +nuclear +number +nurse +nut +oak +obey +object +oblige +obscure +observe +obtain +obvious +occur +ocean +october +odor +off +offer +office +often +oil +okay +old +olive +olympic +omit +once +one +onion +online +only +open +opera +opinion +oppose +option +orange +orbit +orchard +order +ordinary +organ +orient +original +orphan +ostrich +other +outdoor +outer +output +outside +oval +oven +over +own +owner +oxygen +oyster +ozone +pact +paddle +page +pair +palace +palm +panda +panel +panic +panther +paper +parade +parent +park +parrot +party +pass +patch +path +patient +patrol +pattern +pause +pave +payment +peace +peanut +pear +peasant +pelican +pen +penalty +pencil +people +pepper +perfect +permit +person +pet +phone +photo +phrase +physical +piano +picnic +picture +piece +pig +pigeon +pill +pilot +pink +pioneer +pipe +pistol +pitch +pizza +place +planet +plastic +plate +play +please +pledge +pluck +plug +plunge +poem +poet +point +polar +pole +police +pond +pony +pool +popular +portion +position +possible +post +potato +pottery +poverty +powder +power +practice +praise +predict +prefer +prepare +present +pretty +prevent +price +pride +primary +print +priority +prison +private +prize +problem +process +produce +profit +program +project +promote +proof +property +prosper +protect +proud +provide +public +pudding +pull +pulp +pulse +pumpkin +punch +pupil +puppy +purchase +purity +purpose +purse +push +put +puzzle +pyramid +quality +quantum +quarter +question +quick +quit +quiz +quote +rabbit +raccoon +race +rack +radar +radio +rail +rain +raise +rally +ramp +ranch +random +range +rapid +rare +rate +rather +raven +raw +razor +ready +real +reason +rebel +rebuild +recall +receive +recipe +record +recycle +reduce +reflect +reform +refuse +region +regret +regular +reject +relax +release +relief +rely +remain +remember +remind +remove +render +renew +rent +reopen +repair +repeat +replace +report +require +rescue +resemble +resist +resource +response +result +retire +retreat +return +reunion +reveal +review +reward +rhythm +rib +ribbon +rice +rich +ride +ridge +rifle +right +rigid +ring +riot +ripple +risk +ritual +rival +river +road +roast +robot +robust +rocket +romance +roof +rookie +room +rose +rotate +rough +round +route +royal +rubber +rude +rug +rule +run +runway +rural +sad +saddle +sadness +safe +sail +salad +salmon +salon +salt +salute +same +sample +sand +satisfy +satoshi +sauce +sausage +save +say +scale +scan +scare +scatter +scene +scheme +school +science +scissors +scorpion +scout +scrap +screen +script +scrub +sea +search +season +seat +second +secret +section +security +seed +seek +segment +select +sell +seminar +senior +sense +sentence +series +service +session +settle +setup +seven +shadow +shaft +shallow +share +shed +shell +sheriff +shield +shift +shine +ship +shiver +shock +shoe +shoot +shop +short +shoulder +shove +shrimp +shrug +shuffle +shy +sibling +sick +side +siege +sight +sign +silent +silk +silly +silver +similar +simple +since +sing +siren +sister +situate +six +size +skate +sketch +ski +skill +skin +skirt +skull +slab +slam +sleep +slender +slice +slide +slight +slim +slogan +slot +slow +slush +small +smart +smile +smoke +smooth +snack +snake +snap +sniff +snow +soap +soccer +social +sock +soda +soft +solar +soldier +solid +solution +solve +someone +song +soon +sorry +sort +soul +sound +soup +source +south +space +spare +spatial +spawn +speak +special +speed +spell +spend +sphere +spice +spider +spike +spin +spirit +split +spoil +sponsor +spoon +sport +spot +spray +spread +spring +spy +square +squeeze +squirrel +stable +stadium +staff +stage +stairs +stamp +stand +start +state +stay +steak +steel +stem +step +stereo +stick +still +sting +stock +stomach +stone +stool +story +stove +strategy +street +strike +strong +struggle +student +stuff +stumble +style +subject +submit +subway +success +such +sudden +suffer +sugar +suggest +suit +summer +sun +sunny +sunset +super +supply +supreme +sure +surface +surge +surprise +surround +survey +suspect +sustain +swallow +swamp +swap +swarm +swear +sweet +swift +swim +swing +switch +sword +symbol +symptom +syrup +system +table +tackle +tag +tail +talent +talk +tank +tape +target +task +taste +tattoo +taxi +teach +team +tell +ten +tenant +tennis +tent +term +test +text +thank +that +theme +then +theory +there +they +thing +this +thought +three +thrive +throw +thumb +thunder +ticket +tide +tiger +tilt +timber +time +tiny +tip +tired +tissue +title +toast +tobacco +today +toddler +toe +together +toilet +token +tomato +tomorrow +tone +tongue +tonight +tool +tooth +top +topic +topple +torch +tornado +tortoise +toss +total +tourist +toward +tower +town +toy +track +trade +traffic +tragic +train +transfer +trap +trash +travel +tray +treat +tree +trend +trial +tribe +trick +trigger +trim +trip +trophy +trouble +truck +true +truly +trumpet +trust +truth +try +tube +tuition +tumble +tuna +tunnel +turkey +turn +turtle +twelve +twenty +twice +twin +twist +two +type +typical +ugly +umbrella +unable +unaware +uncle +uncover +under +undo +unfair +unfold +unhappy +uniform +unique +unit +universe +unknown +unlock +until +unusual +unveil +update +upgrade +uphold +upon +upper +upset +urban +urge +usage +use +used +useful +useless +usual +utility +vacant +vacuum +vague +valid +valley +valve +van +vanish +vapor +various +vast +vault +vehicle +velvet +vendor +venture +venue +verb +verify +version +very +vessel +veteran +viable +vibrant +vicious +victory +video +view +village +vintage +violin +virtual +virus +visa +visit +visual +vital +vivid +vocal +voice +void +volcano +volume +vote +voyage +wage +wagon +wait +walk +wall +walnut +want +warfare +warm +warrior +wash +wasp +waste +water +wave +way +wealth +weapon +wear +weasel +weather +web +wedding +weekend +weird +welcome +west +wet +whale +what +wheat +wheel +when +where +whip +whisper +wide +width +wife +wild +will +win +window +wine +wing +wink +winner +winter +wire +wisdom +wise +wish +witness +wolf +woman +wonder +wood +wool +word +work +world +worry +worth +wrap +wreck +wrestle +wrist +write +wrong +yard +year +yellow +you +young +youth +zebra +zero +zone +zoo +` diff --git a/third_party/official/gosdk_demo/zxsdk/wallet_sdk/upload_to_cos.go b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/upload_to_cos.go new file mode 100644 index 0000000..80bf824 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/wallet_sdk/upload_to_cos.go @@ -0,0 +1,75 @@ +package wallet_sdk + +import ( + "context" + "fmt" + "github.com/tencentyun/cos-go-sdk-v5" + "net/http" + "net/url" + "strings" + "time" +) + +type URLToken struct { + SessionToken string `url:"x-cos-security-token,omitempty" header:"-"` +} + +func UploadToCos(cosUrl, tempSecretId, tempSecretKey, sessionToken, filePath string) error { + if !strings.HasPrefix(cosUrl, "http") { + cosUrl = "https://" + cosUrl + } + u, err := url.Parse(cosUrl) + if err != nil { + return fmt.Errorf("parse url failed, [err:%s]", err) + } + + b := &cos.BaseURL{BucketURL: u} + c := cos.NewClient(b, &http.Client{}) + + var uploadUrl string + if strings.HasPrefix(cosUrl, "https://") { + uploadUrl = cosUrl[len(u.Host)+9 : len(cosUrl)] + } else { + uploadUrl = cosUrl[len(u.Host)+8 : len(cosUrl)] + } + + ctx := context.Background() + token := &URLToken{ + SessionToken: sessionToken, + } + + presignedURL, err := c.Object.GetPresignedURL(ctx, http.MethodHead, uploadUrl, tempSecretId, tempSecretKey, time.Hour, token) + if err != nil { + return fmt.Errorf("get presigned url failed, [err:%s]", err) + } + + req, err := http.NewRequest(http.MethodHead, presignedURL.String(), nil) + if err != nil { + return fmt.Errorf("create head file request failed, [err:%s]", err) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("head file failed, [err:%s]", err) + } else { + if resp.StatusCode != http.StatusNotFound { + if resp.StatusCode == http.StatusOK { + return fmt.Errorf("key already exists") + } else { + return fmt.Errorf("head file failed, resp: %+v", resp) + } + } + } + client := cos.NewClient(b, &http.Client{ + Transport: &cos.AuthorizationTransport{ + SecretID: tempSecretId, + SecretKey: tempSecretKey, + SessionToken: sessionToken, + }, + }) + _, _, err = client.Object.Upload(context.Background(), uploadUrl, filePath, nil) + if err != nil { + return fmt.Errorf("upload file to cos failed, [err:%s]:", err) + } + return nil +} diff --git a/third_party/official/gosdk_demo/zxsdk/zxchain_client.go b/third_party/official/gosdk_demo/zxsdk/zxchain_client.go new file mode 100644 index 0000000..f5015f0 --- /dev/null +++ b/third_party/official/gosdk_demo/zxsdk/zxchain_client.go @@ -0,0 +1,65 @@ +package zxsdk + +import ( + "github.com/google/uuid" + "net/http" + "strconv" + "wallet_sdk" + "zxsdk/constant" + "zxsdk/tools" +) + +type ZxChainClient struct { + secretId string + secretKey string + privateKey string + publicKey string + addr string + httpClient *http.Client +} + +func NewZxChainClient(secretId, secretKey, privateKey string) (*ZxChainClient, error) { + if secretId == "" { + return nil, constant.SECRET_ID_EMPTY_ERROR + } + + if secretKey == "" { + return nil, constant.SECRET_KEY_EMPTY_ERROR + } + + if privateKey == "" { + return nil, constant.PRIVATE_KEY_FORMAT_ERROR + } + + publicKey, err := wallet_sdk.PriKey2PubKey(privateKey) + if err != nil { + return nil, constant.PRIVATE_KEY_FORMAT_ERROR + } + + addr, _ := wallet_sdk.PubKey2Address(publicKey) + + return &ZxChainClient{ + secretId: secretId, + secretKey: secretKey, + publicKey: publicKey, + privateKey: privateKey, + addr: addr, + httpClient: tools.DefaultHttpClient, + }, nil +} + +func (zx ZxChainClient) makeHeader() map[string]string { + apiSignData, _ := wallet_sdk.GenerateApiSign(zx.secretId, zx.secretKey) + header := map[string]string{ + "Secret-Id": zx.secretId, + "Signature": apiSignData.Signature, + "Signature-Time": apiSignData.SignatureTime, + "Nonce": strconv.Itoa(apiSignData.Nonce), + constant.TraceKey: uuid.New().String(), + } + return header +} + +func (zx ZxChainClient) SetHttpClient(client *http.Client) { + zx.httpClient = client +}