Skip to content

Commit

Permalink
Merge pull request #9 from okx/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
yanminmin authored Aug 18, 2023
2 parents f34506b + 19c0430 commit 8e81fa8
Show file tree
Hide file tree
Showing 27 changed files with 756 additions and 15 deletions.
10 changes: 10 additions & 0 deletions coins/aptos/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/okx/go-wallet-sdk/coins/aptos

go 1.19

require (
github.com/okx/go-wallet-sdk/crypto v0.0.1
golang.org/x/crypto v0.12.0
)

require golang.org/x/sys v0.11.0 // indirect
26 changes: 26 additions & 0 deletions coins/bitcoin/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module github.com/okx/go-wallet-sdk/coins/bitcoin

go 1.19

require (
github.com/btcsuite/btcd v0.23.4
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/btcsuite/btcd/btcutil v1.1.3
github.com/btcsuite/btcd/btcutil/psbt v1.1.8
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2
github.com/stretchr/testify v1.8.4
github.com/okx/go-wallet-sdk/crypto v0.0.1
github.com/okx/go-wallet-sdk/util v0.0.1
)

require (
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/btcsuite/btcutil v1.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/sys v0.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
26 changes: 26 additions & 0 deletions coins/cosmos/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module github.com/okx/go-wallet-sdk/coins/cosmos

go 1.19

require (
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/btcsuite/btcd/btcutil v1.1.3
github.com/ethereum/go-ethereum v1.12.2
github.com/stretchr/testify v1.8.4
github.com/tyler-smith/go-bip39 v1.1.0
github.com/okx/go-wallet-sdk/crypto v0.0.1
golang.org/x/crypto v0.12.0
)

require (
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect
github.com/btcsuite/btcd v0.23.0 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tyler-smith/go-bip32 v1.0.0 // indirect
golang.org/x/sys v0.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
240 changes: 240 additions & 0 deletions coins/ethereum/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
package ethereum

import (
"encoding/hex"
"encoding/json"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/okx/go-wallet-sdk/coins/ethereum/token"
"github.com/okx/go-wallet-sdk/util"
"golang.org/x/crypto/sha3"
"math/big"
)

// generate tx with json param
func GenerateTxWithJSON(message string, chainId *big.Int, isToken bool) (*UnsignedTx, error) {
var jsonTx Eip1559Token
err := json.Unmarshal([]byte(message), &jsonTx)
if err != nil {
return nil, err
}
// read chainId, Use the incoming chain id first
if len(jsonTx.ChainId) > 0 {
newChainId, ok := new(big.Int).SetString(jsonTx.ChainId, 10)
if ok {
chainId = newChainId
}
}
// Generate transaction object
// token logic
var data []byte
var toAddress common.Address
if isToken {
data, err = token.Transfer(jsonTx.To, util.ConvertToBigInt(jsonTx.Amount))
if err != nil {
return nil, err
}
toAddress = common.HexToAddress(jsonTx.ContractAddress)
} else {
data = util.RemoveZeroHex(jsonTx.Data)
toAddress = common.HexToAddress(jsonTx.To)
}
if jsonTx.TxType == types.DynamicFeeTxType { // EIP1559 sign
tx := NewEip1559Transaction(
chainId,
util.ConvertToUint64(jsonTx.Nonce),
util.ConvertToBigInt(jsonTx.MaxPriorityFeePerGas),
util.ConvertToBigInt(jsonTx.MaxFeePerGas),
util.ConvertToUint64(jsonTx.GasLimit),
&toAddress,
util.ConvertToBigInt(jsonTx.Value),
data,
)
res, err := tx.MarshalBinary()
if err != nil {
return nil, err
}
hash := tx.Hash()
return &UnsignedTx{Hash: hash.Hex(), Tx: util.EncodeHexWith0x(res)}, nil
} else {
// Token processing
var tx *EthTransaction
if isToken {
tx = NewEthTransaction(util.ConvertToBigInt(jsonTx.Nonce), util.ConvertToBigInt(jsonTx.GasLimit), util.ConvertToBigInt(jsonTx.GasPrice), big.NewInt(0), jsonTx.ContractAddress, util.EncodeHexWith0x(data))
} else {
tx = NewEthTransaction(util.ConvertToBigInt(jsonTx.Nonce), util.ConvertToBigInt(jsonTx.GasLimit), util.ConvertToBigInt(jsonTx.GasPrice), util.ConvertToBigInt(jsonTx.Value), jsonTx.To, util.EncodeHexWith0x(data))
}
hash, res, err := tx.GetSigningHash(chainId)
if err != nil {
return nil, err
}
return &UnsignedTx{Tx: res, Hash: hash}, nil
}
}

// Generate the transaction to be broadcast based on the unsigned transaction and the signature result
func GenerateRawTransactionWithSignature(txType int, chainId, unsignedRawTx, r, s, v string) (string, error) {
unsignedRawTxByte := util.RemoveZeroHex(unsignedRawTx)
chainID, ok := new(big.Int).SetString(chainId, 10)
if !ok {
return "", ErrInvalidParam
}
R, ok := new(big.Int).SetString(r, 16)
if !ok {
return "", ErrInvalidParam
}
S, ok := new(big.Int).SetString(s, 16)
if !ok {
return "", ErrInvalidParam
}
V, ok := new(big.Int).SetString(v, 16)
if !ok {
return "", ErrInvalidParam
}

if txType == types.DynamicFeeTxType { // EIP1559 sign
tx, err := generateEIP1559Tx(unsignedRawTx)
if err != nil {
return "", err
}
signer := types.NewLondonSigner(chainID)
signedTx, err := tx.WithSignature(signer, encodeRSV(R, S, V))
if err != nil {
return "", err
}
rawTx, err := signedTx.MarshalBinary()
if err != nil {
return "", err
}
return util.EncodeHexWith0x(rawTx), err
} else { // legacy sign
var tx EthTransaction
if err := rlp.DecodeBytes(unsignedRawTxByte, &tx); err != nil {
return "", err
}
tx.V = V
tx.R = R
tx.S = S
value, err := rlp.EncodeToBytes(tx)
if err != nil {
return "", err
}
return util.EncodeHexWith0x(value), err
}
}

func CalTxHash(rawTx string) string {
bytes := util.RemoveZeroHex(rawTx)
s256 := sha3.NewLegacyKeccak256()
s256.Write(bytes)
txBytes := s256.Sum(nil)
return util.EncodeHexWith0x(txBytes)
}

func DecodeTx(rawTx string) (string, error) {
rawTxBytes := util.RemoveZeroHex(rawTx)
tx := new(types.Transaction)
if err := tx.UnmarshalBinary(rawTxBytes); err != nil {
return "", err
}
txData := make(map[string]interface{})
txData["nonce"] = big.NewInt(int64(tx.Nonce()))
txData["gasLimit"] = float64(tx.Gas())
txData["to"] = tx.To().String()
txData["value"] = float64(tx.Value().Int64())
txData["chainId"] = tx.ChainId()
txData["txType"] = int(tx.Type())
if tx.Type() == types.DynamicFeeTxType {
txData["maxFeePerGas"] = float64(tx.GasFeeCap().Int64())
txData["maxPriorityFeePerGas"] = float64(tx.GasTipCap().Int64())
} else {
txData["gasPrice"] = float64(tx.GasPrice().Int64())
}
signer := types.NewLondonSigner(tx.ChainId())
if addr, err := types.Sender(signer, tx); err == nil {
txData["from"] = addr.String()
}
v, r, s := tx.RawSignatureValues()
txData["v"] = v
txData["r"] = r
txData["s"] = s
if len(tx.Data()) != 0 {
data := hex.EncodeToString(tx.Data())
txData["inputData"] = "0x" + data
inputData := make(map[string]interface{})
if len(data) >= 72 {
methodId := data[:8]
approveMethod := "approve(address,uint256)"
transferMethod := "transfer(address,uint256)"
approveMethodId := hex.EncodeToString(crypto.Keccak256([]byte("approve(address,uint256)")))[:8]
transferMethodId := hex.EncodeToString(crypto.Keccak256([]byte("transfer(address,uint256)")))[:8]
address := "0x" + data[32:72]
amount, ok := new(big.Int).SetString(data[72:], 16)
if !ok {
return "", ErrInvalidParam
}
if methodId == approveMethodId {
inputData["method"] = approveMethod
inputData["address"] = address
inputData["amount"] = amount
} else if methodId == transferMethodId {
inputData["method"] = transferMethod
inputData["address"] = address
inputData["amount"] = amount
}
}
txData["decodedData"] = inputData
}
b, err := json.Marshal(txData)
return string(b), err
}

func EcRecover(signature, message string, addPrefix bool) string {
signatureData := util.RemoveZeroHex(signature)
R := signatureData[:32]
S := signatureData[32:64]
V := signatureData[64:65]
var err error
realData, _ := hex.DecodeString(hex.EncodeToString(V) + hex.EncodeToString(R) + hex.EncodeToString(S))
var hash []byte
if addPrefix {
hash = calMessageHash(message)
} else {
hash, err = util.DecodeHexString(message)
if err != nil {
return ""
}
}
publicKey, _, err := ecdsa.RecoverCompact(realData, hash)
if err != nil {
return ""
}
return util.EncodeHexWith0x(getEthGroupPubHash(publicKey)[12:])
}

func GetAddress(pubkeyHex string) string {
p, err := util.DecodeHexString(pubkeyHex)
if err != nil {
return ""
}
pubkey, err := btcec.ParsePubKey(p)
if err != nil {
return ""
}
return util.EncodeHexWith0x(getEthGroupPubHash(pubkey)[12:])
}

func ValidateAddress(address string) bool {
if util.HasHexPrefix(address) {
address = address[2:]
}
return len(address) == 2*AddressLength && util.IsHex(address)
}

func MessageHash(data string) string {
return util.EncodeHexWith0x(calMessageHash(data))
}
93 changes: 93 additions & 0 deletions coins/ethereum/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package ethereum

import (
"encoding/hex"
"fmt"
"github.com/stretchr/testify/assert"
"github.com/okx/go-wallet-sdk/util"
"math/big"
"testing"
)

func TestDecodeTx(t *testing.T) {
r, err := DecodeTx("0xf86c81f48504a817c8008252089431c514837ee0f6062eaffb0882d764170a17800487038d7ea4c680008025a08d2cce871494c89cc0057b75ebe7ba5fcb0ca12376f8b7c3b5e1ee0bce77c2a2a028bdf0d6d9e52f50b2eecb6c21aeb288e4727478b2f588c9d0787e845cc95144")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, `{"chainId":1,"from":"0x05d132975D8EfCD67262980C54f9030319C91Af0","gasLimit":21000,"gasPrice":20000000000,"nonce":244,"r":63855278322598500466154057791001702486226441534857140050433689054814601200290,"s":18428110250072635139397296879143057669157476180829444102104539880782742245700,"to":"0x31C514837ee0f6062EafFb0882D764170A178004","txType":0,"v":37,"value":1000000000000000}`, r)
}

func TestMessageHash(t *testing.T) {
r := MessageHash("data")
fmt.Println(r)
assert.Equal(t, "0x8edd100985b029cc35de22b18d970ad826ca8948fc18e6f4783f4728af80b113", r)
}

func TestEcRecover(t *testing.T) {
validRes := EcRecover("25064ca2f492d0a8a99801e57e5f30fc6c69335e02487d652dc98448145866556007ddc34a0ccdce592176f022e05d3e83b83a039d97aae86c1c7839cb44221e1b", "data", true)
fmt.Println(validRes)
assert.Equal(t, "0xdcab8e02b4d06d0a07ddb1dfa6e2c94cf2da2356", validRes)

res := EcRecover("0xb715378a9d1cce098c27399f40e408fe1ac314aac8ced9704905f14d0d6840c7027fdfffb800958ba826ecbdcc411571af9a348e2a78166b3f8518ce77b35c701b", "0x214f333f99d572b10721be1024700ba551b1b18ecd9072c2975cc82da63cc631", false)
fmt.Println(res)
assert.Equal(t, "0xb8cf89fa8f3a0ddcda3d8fdb9006859628665ef4", res)
}

func TestGetAddress(t *testing.T) {
validRes := GetAddress("0x04dcb83211f9cbc7f846595d93613f395fa410ca9bc1ae6ac4cbc5c63c66fc83c599dcdf22033d8a833d28a7f88da8c0d4d25ded358623068f4f2a07bdcb8d6d2c")
fmt.Println(validRes)
assert.Equal(t, "0x769ecd386d4ad9d7f7aea69f674e39efe7ea0720", validRes)
}

func TestValidateAddress(t *testing.T) {
assert.Equal(t, true, ValidateAddress("0x769ecd386d4ad9d7f7aea69f674e39efe7ea0720"))
assert.Equal(t, false, ValidateAddress("0x769ecd386d4ad9d7f7aea69f674e139ef3e7ea0720"))
}

func TestGenerateRawTransactionWithSignature(t *testing.T) {
unsignedRawTx := "eb808505d21dba0082520894917ce722ac0b323c038e141eec7bd6bf5a00c6ac87017056cdfb974a80388080"
r := "6fd922e7bbd9796cc49827f0b9645adfc34a16017e316e8c0b3062270b1a15bc"
s := "685a42b37c414aa0fca332f87c1f566f59065e88f3efdaac74f2739f5788d615"
v := "93"
rr, err := GenerateRawTransactionWithSignature(0, "56", unsignedRawTx, r, s, v)
if err != nil {
t.Fatal(err)
}
fmt.Println(rr)
assert.Equal(t, "0xf86c808505d21dba0082520894917ce722ac0b323c038e141eec7bd6bf5a00c6ac87017056cdfb974a808193a06fd922e7bbd9796cc49827f0b9645adfc34a16017e316e8c0b3062270b1a15bca0685a42b37c414aa0fca332f87c1f566f59065e88f3efdaac74f2739f5788d615", rr)
}

func TestGenerateTxWithJSON(t *testing.T) {
raw := `{"to":"","value":"404993102026570","fee":"100000", "data":"0x123123123123","gasPrice":"20000000000","gasLimit":"21000"}`
tx, err := GenerateTxWithJSON(raw, big.NewInt(1), false)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "dd808504a817c8008252088087017056cdfb974a86123123123123018080", tx.Tx)
raw = `{
"txType":2,
"nonce":"244",
"to":"0x31c514837ee0f6062eaffb0882d764170a178004",
"value":"1000000000000000",
"gasLimit":"21000",
"maxFeePerGas":"20000000000",
"maxPriorityFeePerGas":"1500000000"
}`
tx, err = GenerateTxWithJSON(raw, big.NewInt(1), false)
if err != nil {
t.Fatal(err)
}
fmt.Println(tx.Tx, tx.Hash)
assert.Equal(t, "0x02f30181f48459682f008504a817c8008252089431c514837ee0f6062eaffb0882d764170a17800487038d7ea4c6800080c0808080", tx.Tx)
assert.Equal(t, "0xbf498de3f0bc5fff92798a26f91f0a44842cf4471504de23e2eec80d53d65ad7", tx.Hash)
}

func TestBigint(t *testing.T) {
r, _ := util.DecodeHexString("0x02f30181f48459682f008504a817c8008252089431c514837ee0f6062eaffb0882d764170a17800487038d7ea4c6800080c0808080")
rr := new(big.Int).SetBytes(r)
fmt.Println(hex.EncodeToString(rr.Bytes()))
fmt.Println(rr.String())
bb, ok := new(big.Int).SetString("02f30181f48459682f008504a817c8008252089431c514837ee0f6062eaffb0882d764170a17800487038d7ea4c6800080c0808080", 16)

fmt.Println(ok, hex.EncodeToString(bb.Bytes()))
}
Loading

0 comments on commit 8e81fa8

Please sign in to comment.