Skip to content

Commit

Permalink
Enhance dump-address to include public key dumping as well. Refactor …
Browse files Browse the repository at this point in the history
…code
  • Loading branch information
10gic committed Aug 28, 2024
1 parent b830b36 commit d6ceac1
Show file tree
Hide file tree
Showing 28 changed files with 215 additions and 136 deletions.
45 changes: 36 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ Usage:
Available Commands:
balance Check eth balance for address
transfer Transfer AMOUNT of eth to TARGET-ADDRESS
call Invokes the (paid) contract method
query Invokes the (constant) contract method
transfer Transfer native token
call Invoke the (paid) contract method
query Invoke the (constant) contract method
deploy Deploy contract
deploy-erc20 Deploy an ERC20 token
drop-tx Drop pending tx for address
4byte Get the function signatures for the given selector from https://openchain.xyz/signatures
encode-param Encode input arguments, it's useful when you call contract's method manually
gen-key Generate eth mnemonic words, private key, and its address
dump-address Dump address from private key or mnemonic
dump-address Dump address from mnemonics or private key or public key
compute-contract-addr Compute contract address before deployment
build-raw-tx Build raw transaction, the output can be used by rpc eth_sendRawTransaction
broadcast-tx Broadcast tx by rpc eth_sendRawTransaction
decode-tx Decode raw transaction
code Get runtime bytecode of a contract on the blockchain
erc20 Call ERC20 contract, a helper for subcommand call/query
keccak Compute keccak hash
keccak Compute keccak hash, read data from file or stdin (file name -)
personal-sign Create EIP191 personal sign
eip712-sign Create EIP712 sign
aa-simple-account AA (EIP4337) simple account, owned by an EOA account
Expand Down Expand Up @@ -57,7 +57,7 @@ Use "ethutil [command] --help" for more information about a command.

# Install
```shell
$ go install github.com/10gic/ethutil@latest
go install github.com/10gic/ethutil@latest
```

# Usage Example
Expand Down Expand Up @@ -166,6 +166,7 @@ Generate mnemonic words and private key:
$ ethutil gen-key
mnemonic: obvious element orbit option muffin crop abuse duck general mule satoshi doll
private key: 0x836263588c9ea3ffa2a73b71a32d4eb886779d1e0e25f6324c582d2f1008d57f
public key: 0x04049817a72deed750a27a7abc772169378f1547133861d564eba86561a45f861658bcf9ba9cd1be852df8e1c2df76492402c665b9644fb2a37b29644ac17c0d45
addr: 0x2Ed852F7F064E56aa60fDA0a703ed4A7DCC5F9fb
```

Expand All @@ -184,10 +185,36 @@ $ ethutil --terse gen-key -n 10
0x69d5b18f0f6a17b5b0d8b9d5ddc4531d6a14d023b789c4b3d753e40562254a1b 0x8F36975cdeA2e6E64f85719788C8EFBBe89DFBbb
```

## Dump Address From Private Key
## Dump Address
Dump address from mnemonic:
```shell
$ ethutil dump-address 0xef065dcbc43081c63c0fbf389ec8df3872d9d61b1bc2e98d7a0a4395d11314d2
private key 0xef065dcbc43081c63c0fbf389ec8df3872d9d61b1bc2e98d7a0a4395d11314d2, addr 0xB2aC853cF815B47903bc19BF4860540306F4f944
$ ethutil dump-address 'obvious element orbit option muffin crop abuse duck general mule satoshi doll'
private key: 0x836263588c9ea3ffa2a73b71a32d4eb886779d1e0e25f6324c582d2f1008d57f
public key: 0x04049817a72deed750a27a7abc772169378f1547133861d564eba86561a45f861658bcf9ba9cd1be852df8e1c2df76492402c665b9644fb2a37b29644ac17c0d45
addr: 0x2Ed852F7F064E56aa60fDA0a703ed4A7DCC5F9fb
```

Dump address from mnemonic with derivation path:
```shell
$ ethutil dump-address --derivation-path "m/44'/61'/0'/0/0" 'obvious element orbit option muffin crop abuse duck general mule satoshi doll'
private key: 0x25b2cb153e31292c0ad8f394292d0f3838338763a97c23376d4ff4d30901b487
public key: 0x044c296796ec2bc3c2f02b77064d71bd198254d3c8f72a648a4f3f59e0830b20e53299dad3a5edccc8d73e9821d92e508b108a3e8c1e689a3b5914260e33dfe278
addr: 0xeBe604aD190F42F587b01308Ce084dF6163A0411
```

Dump address from private key:
```shell
$ ethutil dump-address 0x836263588c9ea3ffa2a73b71a32d4eb886779d1e0e25f6324c582d2f1008d57f
private key: 0x836263588c9ea3ffa2a73b71a32d4eb886779d1e0e25f6324c582d2f1008d57f
public key: 0x04049817a72deed750a27a7abc772169378f1547133861d564eba86561a45f861658bcf9ba9cd1be852df8e1c2df76492402c665b9644fb2a37b29644ac17c0d45
addr: 0x2Ed852F7F064E56aa60fDA0a703ed4A7DCC5F9fb
```

Dump address from public key:
```shell
$ ethutil dump-address 0x04049817a72deed750a27a7abc772169378f1547133861d564eba86561a45f861658bcf9ba9cd1be852df8e1c2df76492402c665b9644fb2a37b29644ac17c0d45
public key: 0x04049817a72deed750a27a7abc772169378f1547133861d564eba86561a45f861658bcf9ba9cd1be852df8e1c2df76492402c665b9644fb2a37b29644ac17c0d45
addr: 0x2Ed852F7F064E56aa60fDA0a703ed4A7DCC5F9fb
```

## Compute Contract Address
Expand Down
6 changes: 3 additions & 3 deletions cmd/aadeploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

// aaDeployCmd represents the aaDeploy command
var aaDeployCmd = &cobra.Command{
Use: "deploy ACCOUNT-OWNER-ADDRESS",
Use: "deploy <account-owner-address>",
Short: "Deploy AA (EIP4337) account contract, solidity source contracts/AASimpleAccountFactory.sol",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -73,7 +73,7 @@ func deployAASimpleAccountFactory() error {

log.Printf("deploying AA simple account factory contract")
contract := singletonFactoryAddr
tx, err := Transact(globalClient.RpcClient, globalClient.EthClient, buildPrivateKeyFromHex(globalOptPrivateKey), &contract, big.NewInt(0), nil, txInputData)
tx, err := Transact(globalClient.RpcClient, globalClient.EthClient, hexToPrivateKey(globalOptPrivateKey), &contract, big.NewInt(0), nil, txInputData)
if err != nil {
return err
}
Expand Down Expand Up @@ -115,7 +115,7 @@ func deployAASimpleAccount(accountOwner string) error {

log.Printf("deploying AA account contract")
contract := getAASimpleAccountFactoryAddress()
tx, err := Transact(globalClient.RpcClient, globalClient.EthClient, buildPrivateKeyFromHex(globalOptPrivateKey), &contract, big.NewInt(0), nil, txInputData)
tx, err := Transact(globalClient.RpcClient, globalClient.EthClient, hexToPrivateKey(globalOptPrivateKey), &contract, big.NewInt(0), nil, txInputData)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/aatransfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ func init() {

// aaTransferCmd represents the AA Simple Account transfer command
var aaTransferCmd = &cobra.Command{
Use: "transfer TARGET-ADDRESS AMOUNT",
Use: "transfer <target-address> <amount>",
Short: "Transfer AMOUNT of eth from AA-ACCOUNT-CONTRACT to TARGET-ADDRESS",
Args: cobra.MinimumNArgs(2),
Run: func(cmd *cobra.Command, args []string) {
if aaOwnerPrivateKey == "" {
log.Fatalf("--owner-private-key is required for this command")
}
ownerPrivateKey := buildPrivateKeyFromHex(aaOwnerPrivateKey)
ownerPrivateKey := hexToPrivateKey(aaOwnerPrivateKey)

targetAddress := args[0]
transferAmt := args[1]
Expand Down
14 changes: 7 additions & 7 deletions cmd/balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func validationBalanceCmdOpts() bool {
var addresses []string

var balanceCmd = &cobra.Command{
Use: "balance ETH-ADDRESS1 [ETH-ADDRESS2 ...]",
Use: "balance <eth-address1> <eth-address2> ...",
Short: "Check eth balance for address",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 && len(balanceInputFile) == 0 {
Expand Down Expand Up @@ -121,9 +121,9 @@ var balanceCmd = &cobra.Command{
// print output immediately if no sort demand
if balanceSortOpt == sortNo {
if globalOptTerseOutput {
fmt.Printf("%v %s\n", addr, wei2Other(bigInt2Decimal(balance), balanceUnit).String())
fmt.Printf("%v %s\n", addr, wei2Other(bigIntToDecimal(balance), balanceUnit).String())
} else {
fmt.Printf("addr %v, balance %s %s\n", addr, wei2Other(bigInt2Decimal(balance), balanceUnit).String(), balanceUnit)
fmt.Printf("addr %v, balance %s %s\n", addr, wei2Other(bigIntToDecimal(balance), balanceUnit).String(), balanceUnit)
}
finishOutput = true
}
Expand All @@ -139,9 +139,9 @@ var balanceCmd = &cobra.Command{
// print output immediately if no sort demand
if balanceSortOpt == sortNo {
if globalOptTerseOutput {
fmt.Printf("%v %s\n", addr, wei2Other(bigInt2Decimal(balance), balanceUnit).String())
fmt.Printf("%v %s\n", addr, wei2Other(bigIntToDecimal(balance), balanceUnit).String())
} else {
fmt.Printf("addr %v, balance %s %s\n", addr, wei2Other(bigInt2Decimal(balance), balanceUnit).String(), balanceUnit)
fmt.Printf("addr %v, balance %s %s\n", addr, wei2Other(bigIntToDecimal(balance), balanceUnit).String(), balanceUnit)
}
finishOutput = true
}
Expand All @@ -161,9 +161,9 @@ var balanceCmd = &cobra.Command{
if !finishOutput {
for _, result := range results {
if globalOptTerseOutput {
fmt.Printf("%v %s\n", result.addr, wei2Other(bigInt2Decimal(&result.balance), balanceUnit).String())
fmt.Printf("%v %s\n", result.addr, wei2Other(bigIntToDecimal(&result.balance), balanceUnit).String())
} else {
fmt.Printf("addr %v, balance %s %s\n", result.addr, wei2Other(bigInt2Decimal(&result.balance), balanceUnit).String(), balanceUnit)
fmt.Printf("addr %v, balance %s %s\n", result.addr, wei2Other(bigIntToDecimal(&result.balance), balanceUnit).String(), balanceUnit)
}
}
finishOutput = true
Expand Down
2 changes: 1 addition & 1 deletion cmd/broadcasttx.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

// broadcastTxCmd represents the broadcastTx command
var broadcastTxCmd = &cobra.Command{
Use: "broadcast-tx SIGNED-RAW-TX",
Use: "broadcast-tx <signed-raw-tx>",
Short: "Broadcast tx by rpc eth_sendRawTransaction",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
Expand Down
4 changes: 2 additions & 2 deletions cmd/buildrawtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func init() {

// buildRawTxCmd represents the encode-raw-tx command
var buildRawTxCmd = &cobra.Command{
Use: "build-raw-tx FROM-ADDRESS TO-ADDRESS VALUE-IN-ETHER",
Use: "build-raw-tx <from-address> <to-address> <value-in-ether>",
Short: "Build raw transaction, the output can be used by rpc eth_sendRawTransaction",
Args: cobra.ExactArgs(3),
Run: func(cmd *cobra.Command, args []string) {
Expand All @@ -31,7 +31,7 @@ var buildRawTxCmd = &cobra.Command{

var privateKey *ecdsa.PrivateKey
if globalOptPrivateKey != "" {
privateKey = buildPrivateKeyFromHex(globalOptPrivateKey)
privateKey = hexToPrivateKey(globalOptPrivateKey)
}

if globalOptPrivateKey == "" && buildRawTxSignData == "" {
Expand Down
6 changes: 3 additions & 3 deletions cmd/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ func init() {
}

var callCmd = &cobra.Command{
Use: "call CONTRACT-ADDRESS 'function signature' arg1 arg2 ...",
Short: "Invokes the (paid) contract method",
Use: "call <contract-address> 'function signature' arg1 arg2 ...",
Short: "Invoke the (paid) contract method",
Args: cobra.MinimumNArgs(2),
Run: func(cmd *cobra.Command, args []string) {
if !validationCallCmdOpts(args) {
Expand Down Expand Up @@ -73,7 +73,7 @@ var callCmd = &cobra.Command{
var valueInWei = unify2Wei(value, callCmdTransferUnit)

var contract = common.HexToAddress(contractAddr)
tx, err := Transact(globalClient.RpcClient, globalClient.EthClient, buildPrivateKeyFromHex(globalOptPrivateKey), &contract, valueInWei.BigInt(), nil, txInputData)
tx, err := Transact(globalClient.RpcClient, globalClient.EthClient, hexToPrivateKey(globalOptPrivateKey), &contract, valueInWei.BigInt(), nil, txInputData)
checkErr(err)

log.Printf("transaction %s finished", tx)
Expand Down
67 changes: 50 additions & 17 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/shopspring/decimal"
Expand Down Expand Up @@ -110,38 +111,39 @@ func has0xPrefix(str string) bool {
return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')
}

// remove0xPrefix remove 0x prefix if
func remove0xPrefix(str string) string {
if has0xPrefix(str) {
return str[2:]
}
return str
}

// isValidHexString returns true if str is a valid hex string or empty string.
func isValidHexString(str string) bool {
if str == "" {
return true
}
var hexWithout0x = str
if has0xPrefix(str) {
hexWithout0x = str[2:]
}
_, err := hex.DecodeString(hexWithout0x)
_, err := hex.DecodeString(remove0xPrefix(str))
if err != nil {
return false
}

return true
}

// bigInt2Decimal converts x from big.Int to decimal.Decimal.
func bigInt2Decimal(x *big.Int) decimal.Decimal {
// bigIntToDecimal converts x from big.Int to decimal.Decimal.
func bigIntToDecimal(x *big.Int) decimal.Decimal {
if x == nil {
return decimal.New(0, 0)
}
return decimal.NewFromBigInt(x, 0)
}

// buildPrivateKeyFromHex builds ecdsa.PrivateKey from hex string (the leading 0x is optional),
// hexToPrivateKey builds ecdsa.PrivateKey from hex string (the leading 0x is optional),
// it would panic if input an invalid hex string.
func buildPrivateKeyFromHex(privateKeyHex string) *ecdsa.PrivateKey {
if has0xPrefix(privateKeyHex) {
privateKeyHex = privateKeyHex[2:] // remove leading 0x
}

func hexToPrivateKey(privateKeyHex string) *ecdsa.PrivateKey {
privateKeyHex = remove0xPrefix(privateKeyHex)
privateKey, err := crypto.HexToECDSA(privateKeyHex)
if err != nil {
panic(fmt.Sprintf("parse private key failed: %s", err))
Expand All @@ -150,6 +152,38 @@ func buildPrivateKeyFromHex(privateKeyHex string) *ecdsa.PrivateKey {
return privateKey
}

// hexToPublicKey builds ecdsa.PublicKey from hex string (the leading 0x is optional),
// it would panic if input an invalid hex string.
func hexToPublicKey(publicKeyHex string) (*ecdsa.PublicKey, error) {
publicKeyHex = remove0xPrefix(publicKeyHex)

// Convert the hexadecimal string to a byte slice
pubKeyBytes, err := hex.DecodeString(publicKeyHex)
if err != nil {
return nil, fmt.Errorf("failed to decode hex string: %v", err)
}

uncompressedPubKey := make([]byte, 65)
if len(pubKeyBytes) == 65 {
// In the case of a uncompressed public key, directly use it
uncompressedPubKey = pubKeyBytes
} else {
// In the case of a compressed public key, decompress it to x, y coordinates
x, y := secp256k1.DecompressPubkey(pubKeyBytes)
if x == nil {
return nil, fmt.Errorf("failed to decompress public key")
}

// Convert the x, y coordinates to a uncompressed public key
uncompressedPubKey[0] = 0x04
x.FillBytes(uncompressedPubKey[1:33])
y.FillBytes(uncompressedPubKey[33:])
}

// Parse the uncompressed public key
return crypto.UnmarshalPubkey(uncompressedPubKey)
}

// wei2Other converts wei to other unit (specified by targetUnit).
func wei2Other(sourceAmtInWei decimal.Decimal, targetUnit string) decimal.Decimal {
decimal.DivisionPrecision = 18
Expand Down Expand Up @@ -217,7 +251,7 @@ recheck:

const EthGasStationUrl = "https://ethgasstation.info/json/ethgasAPI.json"

// GasStationPrice, the struct of response of EthGasStationUrl
// GasStationPrice the struct of response of EthGasStationUrl
type GasStationPrice struct {
Fast float64
Fastest float64
Expand Down Expand Up @@ -710,7 +744,6 @@ func getRecoveryId(v *big.Int) int {
// buildECDSASignature builds a 65-byte compact ECDSA signature (containing the recovery id as the last element)
func buildECDSASignature(v, r, s *big.Int) []byte {
var recoveryId = getRecoveryId(v)
// println("recoveryId", recoveryId)

var rBytes = make([]byte, 32, 32)
var sBytes = make([]byte, 32, 32)
Expand All @@ -732,7 +765,7 @@ func RecoverPubkey(v, r, s *big.Int, msg []byte) ([]byte, error) {
return crypto.Ecrecover(msg, signature)
}

// getFuncSig recover function signature from 4 bytes hash
// GetFuncSig recover function signature from 4 bytes hash
// For example:
//
// param: "0x8c905368"
Expand Down Expand Up @@ -802,6 +835,6 @@ func MnemonicToPrivateKey(mnemonic string, derivationPath string) (*ecdsa.Privat

privateKeyBytes := currentKey.Key // 32 bytes private key

privateKey := buildPrivateKeyFromHex(hexutil.Encode(privateKeyBytes))
privateKey := hexToPrivateKey(hexutil.Encode(privateKeyBytes))
return privateKey, nil
}
2 changes: 1 addition & 1 deletion cmd/computecontractaddr.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func validationComputeContractAddrCmdOpts() bool {
}

var computeContractAddrCmd = &cobra.Command{
Use: "compute-contract-addr deployer-address",
Use: "compute-contract-addr <deployer-address>",
Short: "Compute contract address before deployment",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
Expand Down
Loading

0 comments on commit d6ceac1

Please sign in to comment.