github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/cmd/did/common.go (about)

     1  // Copyright (c) 2022 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package did
     7  
     8  import (
     9  	"bytes"
    10  	"crypto/ecdsa"
    11  	"encoding/json"
    12  	"fmt"
    13  	"io"
    14  	"math/big"
    15  	"net/http"
    16  
    17  	"github.com/ethereum/go-ethereum/common/hexutil"
    18  	"github.com/ethereum/go-ethereum/crypto"
    19  	"github.com/iotexproject/iotex-core/ioctl/cmd/account"
    20  	"github.com/iotexproject/iotex-core/ioctl/cmd/action"
    21  	"github.com/iotexproject/iotex-core/ioctl/output"
    22  	"github.com/iotexproject/iotex-core/ioctl/util"
    23  	"github.com/iotexproject/iotex-core/pkg/util/addrutil"
    24  	"golang.org/x/crypto/sha3"
    25  )
    26  
    27  type (
    28  	// Permit permit content for DID
    29  	Permit struct {
    30  		Separator  string `json:"DOMAIN_SEPARATOR"`
    31  		PermitHash string `json:"permitHash"`
    32  	}
    33  
    34  	// Signature signature for typed message
    35  	Signature struct {
    36  		R string `json:"r"`
    37  		S string `json:"s"`
    38  		V uint64 `json:"v"`
    39  	}
    40  
    41  	// CreateRequest create DID request
    42  	CreateRequest struct {
    43  		Signature
    44  		PublicKey string `json:"publicKey"`
    45  	}
    46  
    47  	// ServiceAddRequest add service to DID request
    48  	ServiceAddRequest struct {
    49  		Signature
    50  		Tag             string `json:"tag"`
    51  		Type            string `json:"type"`
    52  		ServiceEndpoint string `json:"serviceEndpoint"`
    53  	}
    54  
    55  	// ServiceRemoveRequest remove service from DID request
    56  	ServiceRemoveRequest struct {
    57  		Signature
    58  		Tag string `json:"tag"`
    59  	}
    60  )
    61  
    62  // getPermit fetch DID permit from resolver
    63  func getPermit(endpoint, address string) (*Permit, error) {
    64  	resp, err := http.Get(endpoint + "/did/" + address + "/permit")
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	defer resp.Body.Close()
    69  
    70  	var data Permit
    71  	err = json.NewDecoder(resp.Body).Decode(&data)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	return &data, nil
    76  }
    77  
    78  // signType sign typed message
    79  func signType(key *ecdsa.PrivateKey, separator, hash string) (*Signature, error) {
    80  	separatorBytes, err := hexutil.Decode(separator)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	hashBytes, err := hexutil.Decode(hash)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	data := append([]byte{0x19, 0x01}, append(separatorBytes, hashBytes...)...)
    90  	sha := sha3.NewLegacyKeccak256()
    91  	sha.Write(data)
    92  	signHash := sha.Sum(nil)
    93  
    94  	sig, err := crypto.Sign(signHash, key)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	v := new(big.Int).SetBytes([]byte{sig[64] + 27})
    99  
   100  	return &Signature{
   101  		R: hexutil.Encode(sig[:32]),
   102  		S: hexutil.Encode(sig[32:64]),
   103  		V: v.Uint64(),
   104  	}, nil
   105  }
   106  
   107  // loadPrivateKey load private key and address from signer
   108  func loadPrivateKey() (*ecdsa.PrivateKey, string, error) {
   109  	signer, err := action.Signer()
   110  	if err != nil {
   111  		return nil, "", output.NewError(output.InputError, "failed to get signer addr", err)
   112  	}
   113  	fmt.Printf("Enter password #%s:\n", signer)
   114  	password, err := util.ReadSecretFromStdin()
   115  	if err != nil {
   116  		return nil, "", output.NewError(output.InputError, "failed to get password", err)
   117  	}
   118  	pri, err := account.PrivateKeyFromSigner(signer, password)
   119  	if err != nil {
   120  		return nil, "", output.NewError(output.InputError, "failed to decrypt key", err)
   121  	}
   122  	ethAddress, err := addrutil.IoAddrToEvmAddr(signer)
   123  	if err != nil {
   124  		return nil, "", output.NewError(output.AddressError, "", err)
   125  	}
   126  
   127  	return pri.EcdsaPrivateKey().(*ecdsa.PrivateKey), ethAddress.String(), nil
   128  }
   129  
   130  // loadPublicKey load public key by private key
   131  func loadPublicKey(key *ecdsa.PrivateKey) ([]byte, error) {
   132  	publicKey := key.Public()
   133  	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
   134  	if !ok {
   135  		return nil, output.NewError(output.ConvertError, "generate public key error", nil)
   136  	}
   137  	return crypto.FromECDSAPub(publicKeyECDSA), nil
   138  }
   139  
   140  // signPermit fetch permit and sign and return signature, publicKey and address
   141  func signPermit(endpoint string) (*Signature, []byte, string, error) {
   142  	key, addr, err := loadPrivateKey()
   143  	if err != nil {
   144  		return nil, nil, "", err
   145  	}
   146  
   147  	publicKey, err := loadPublicKey(key)
   148  	if err != nil {
   149  		return nil, nil, "", err
   150  	}
   151  
   152  	permit, err := getPermit(endpoint, addr)
   153  	if err != nil {
   154  		return nil, nil, "", output.NewError(output.InputError, "failed to fetch permit", err)
   155  	}
   156  	signature, err := signType(key, permit.Separator, permit.PermitHash)
   157  	if err != nil {
   158  		return nil, nil, "", output.NewError(output.InputError, "failed to sign typed permit", err)
   159  	}
   160  	return signature, publicKey, addr, nil
   161  }
   162  
   163  // postToResolver post data to resolver
   164  func postToResolver(url string, reqBytes []byte) error {
   165  	req, err := http.NewRequest("POST", url, bytes.NewBuffer(reqBytes))
   166  	if err != nil {
   167  		return output.NewError(output.ConvertError, "failed to create request", err)
   168  	}
   169  	req.Header.Set("Content-Type", "application/json")
   170  
   171  	client := &http.Client{}
   172  	resp, err := client.Do(req)
   173  	if err != nil {
   174  		return output.NewError(output.NetworkError, "failed to post request", err)
   175  	}
   176  	defer resp.Body.Close()
   177  
   178  	body, err := io.ReadAll(resp.Body)
   179  	if err != nil {
   180  		return output.NewError(output.ConvertError, "failed to read response", err)
   181  	}
   182  	output.PrintResult(string(body))
   183  	return nil
   184  }