github.com/iotexproject/iotex-core@v1.14.1-rc1/action/consignment_transfer.go (about) 1 // Copyright (c) 2020 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 action 7 8 import ( 9 "encoding/hex" 10 "encoding/json" 11 12 "github.com/pkg/errors" 13 14 "github.com/ethereum/go-ethereum/accounts" 15 16 "github.com/iotexproject/go-pkgs/crypto" 17 "github.com/iotexproject/iotex-address/address" 18 ) 19 20 const ( 21 _reclaim = "This is to certify I am transferring the ownership of said bucket to said recipient on IoTeX blockchain" 22 ) 23 24 // Errors 25 var ( 26 ErrNotSupported = errors.New("signature type not supported") 27 ) 28 29 type ( 30 // a consignment is a transaction signed by transferee which contains an embedded message signed by transferor 31 // 32 // transferee: the entity/address to receive the ownership of an asset/object 33 // transferor: the entity/address to transfer the ownership of the asset/object 34 // 35 // the transaction contains 2 part: 36 // 1. an embedded message that clearly identifies (1) the transferee, (2) the transferor, (3) nonce of the transferee, 37 // and (4) a unique ID of the asset/object to be transferred 38 // 2. a payload that constitutes a valid ECC signature of the message, by verifying that: 39 // (1) the signature is valid 40 // (2) signer matches the transferor in the message 41 // (3) signer matches the actual owner of the asset/object 42 // (4) asset ID in the message matches the ID of asset/object to be transferred on blockchain 43 // (5) nonce in the message matches transferee's nonce on blockchain 44 // 45 // successful verification of above is considered a consent that transferor does own the asset/object and transfer it 46 // to transferee, because transferee was able to present such a valid signature (hence the name "consignment"), which 47 // can only be generated by transferor 48 49 // Consignment represents a consignment 50 Consignment interface { 51 Transferor() address.Address 52 Transferee() address.Address 53 AssetID() uint64 54 TransfereeNonce() uint64 55 } 56 57 // ConsignMsgEther is the consignment message format of Ethereum 58 ConsignMsgEther struct { 59 BucketIdx uint64 `json:"bucket"` 60 Nonce uint64 `json:"nonce"` 61 Recipient string `json:"recipient"` 62 Reclaim string `json:"reclaim"` 63 } 64 65 // ConsignJSON is the JSON format of a consignment, it contains the type, message, and signature 66 ConsignJSON struct { 67 Type string `json:"type"` 68 Msg string `json:"msg"` 69 Sig string `json:"sig"` 70 } 71 72 consignment struct { 73 index uint64 74 nonce uint64 75 signer address.Address 76 recipient address.Address 77 } 78 ) 79 80 // NewConsignment creates a consignment from data 81 func NewConsignment(data []byte) (Consignment, error) { 82 c := ConsignJSON{} 83 if err := json.Unmarshal(data, &c); err != nil { 84 return nil, err 85 } 86 87 switch c.Type { 88 case "Ethereum": 89 return processConsignmentEther(c) 90 default: 91 return nil, ErrNotSupported 92 } 93 } 94 95 func processConsignmentEther(c ConsignJSON) (Consignment, error) { 96 // parse embedded msg 97 msg := ConsignMsgEther{} 98 if err := json.Unmarshal([]byte(c.Msg), &msg); err != nil { 99 return nil, err 100 } 101 if msg.Reclaim != _reclaim { 102 return nil, errors.New("reclaim text does not match") 103 } 104 105 // verify signature 106 sig, err := hex.DecodeString(c.Sig) 107 if err != nil { 108 return nil, err 109 } 110 pk, err := RecoverPubkeyFromEccSig(c.Type, []byte(c.Msg), sig) 111 if err != nil { 112 return nil, err 113 } 114 115 con := consignment{} 116 con.signer = pk.Address() 117 if con.signer == nil { 118 return nil, errors.New("failed to get address") 119 } 120 con.recipient, err = address.FromString(msg.Recipient) 121 if err != nil { 122 return nil, err 123 } 124 con.index = uint64(msg.BucketIdx) 125 con.nonce = uint64(msg.Nonce) 126 return &con, nil 127 } 128 129 func (c *consignment) Transferor() address.Address { 130 return c.signer 131 } 132 133 func (c *consignment) Transferee() address.Address { 134 return c.recipient 135 } 136 137 func (c *consignment) AssetID() uint64 { 138 return c.index 139 } 140 141 func (c *consignment) TransfereeNonce() uint64 { 142 return c.nonce 143 } 144 145 // NewConsignMsg creates a consignment message from inputs 146 func NewConsignMsg(sigType, recipient string, bucketIdx, nonce uint64) ([]byte, error) { 147 switch sigType { 148 case "Ethereum": 149 msg := ConsignMsgEther{ 150 BucketIdx: bucketIdx, 151 Nonce: nonce, 152 Recipient: recipient, 153 Reclaim: _reclaim, 154 } 155 msgBytes, err := json.Marshal(msg) 156 if err != nil { 157 return nil, err 158 } 159 return msgBytes, nil 160 default: 161 return nil, ErrNotSupported 162 } 163 } 164 165 // NewConsignJSON creates a consignment JSON from inputs 166 func NewConsignJSON(sigType, recipient, sig string, bucketIdx, nonce uint64) ([]byte, error) { 167 msgBytes, err := NewConsignMsg(sigType, recipient, bucketIdx, nonce) 168 if err != nil { 169 return nil, err 170 } 171 172 msgJSON := ConsignJSON{ 173 Type: sigType, 174 Msg: string(msgBytes), 175 Sig: sig, 176 } 177 msgBytes, err = json.Marshal(msgJSON) 178 if err != nil { 179 return nil, err 180 } 181 return msgBytes, nil 182 } 183 184 // RecoverPubkeyFromEccSig recovers public key from ECC signature 185 func RecoverPubkeyFromEccSig(sigType string, msg, sig []byte) (crypto.PublicKey, error) { 186 h, err := MsgHash(sigType, msg) 187 if err != nil { 188 return nil, err 189 } 190 pk, err := crypto.RecoverPubkey(h, sig) 191 if err != nil { 192 return nil, err 193 } 194 if pk.Verify(h, sig) { 195 return pk, nil 196 } 197 return nil, crypto.ErrInvalidKey 198 } 199 200 // MsgHash calculate the hash of msg 201 func MsgHash(sigType string, msg []byte) ([]byte, error) { 202 switch sigType { 203 case "Ethereum": 204 h, _ := accounts.TextAndHash(msg) 205 return h, nil 206 default: 207 return nil, ErrNotSupported 208 } 209 }