OSDN Git Service

add getAddress
[bytom/shuttle.git] / swap / htlc.go
1 package swap
2
3 import (
4         "encoding/hex"
5         "errors"
6         "fmt"
7         "strconv"
8         "strings"
9
10         "github.com/bytom/crypto"
11         "github.com/bytom/protocol/vm/vmutil"
12 )
13
14 var (
15         errFailedGetSignData = errors.New("Failed to get sign data")
16         errFailedGetAddress  = errors.New("Failed to get address by account ID")
17 )
18
19 type HTLCAccount struct {
20         AccountID string
21         Password  string
22         Receiver  string
23         TxFee     uint64
24 }
25
26 type HTLCContractArgs struct {
27         SenderPublicKey    string
28         RecipientPublicKey string
29         BlockHeight        uint64
30         Hash               string
31 }
32
33 type compileLockHTLCContractResponse struct {
34         Program string `json:"program"`
35 }
36
37 var compileLockHTLCContractPayload = `{
38     "contract":"contract HTLC(sender: PublicKey, recipient: PublicKey, blockHeight: Integer, hash: Hash) locks valueAmount of valueAsset { clause complete(preimage: String, sig: Signature) {verify sha256(preimage) == hash verify checkTxSig(recipient, sig) unlock valueAmount of valueAsset} clause cancel(sig: Signature) {verify above(blockHeight) verify checkTxSig(sender, sig) unlock valueAmount of valueAsset}}",
39     "args":[
40         {
41             "string":"%s"
42         },
43         {
44             "string":"%s"
45         },
46         {
47             "integer":%s
48         },
49         {
50             "string":"%s"
51         }
52     ]
53 }`
54
55 func compileLockHTLCContract(contractArgs HTLCContractArgs) (string, error) {
56         payload := []byte(fmt.Sprintf(
57                 compileLockHTLCContractPayload,
58                 contractArgs.SenderPublicKey,
59                 contractArgs.RecipientPublicKey,
60                 strconv.FormatUint(contractArgs.BlockHeight, 10),
61                 contractArgs.Hash,
62         ))
63         res := new(compileLockHTLCContractResponse)
64         if err := request(compileURL, payload, res); err != nil {
65                 return "", err
66         }
67         return res.Program, nil
68 }
69
70 var buildLockHTLCContractTransactionPayload = `{
71     "actions": [
72         {
73             "account_id": "%s",
74             "amount": %s,
75             "asset_id": "%s",
76             "use_unconfirmed":true,
77             "type": "spend_account"
78         },
79         {
80             "amount": %s,
81             "asset_id": "%s",
82             "control_program": "%s",
83             "type": "control_program"
84         },
85         {
86             "account_id": "%s",
87             "amount": %s,
88             "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
89             "use_unconfirmed":true,
90             "type": "spend_account"
91         }
92     ],
93     "ttl": 0,
94     "base_transaction": null
95 }`
96
97 func buildLockHTLCContractTransaction(account HTLCAccount, contractValue AssetAmount, contractControlProgram string) (interface{}, error) {
98         payload := []byte(fmt.Sprintf(
99                 buildLockHTLCContractTransactionPayload,
100                 account.AccountID,
101                 strconv.FormatUint(contractValue.Amount, 10),
102                 contractValue.Asset,
103                 strconv.FormatUint(contractValue.Amount, 10),
104                 contractValue.Asset,
105                 contractControlProgram,
106                 account.AccountID,
107                 strconv.FormatUint(account.TxFee, 10),
108         ))
109         res := new(interface{})
110         if err := request(buildTransactionURL, payload, res); err != nil {
111                 return "", err
112         }
113         return res, nil
114 }
115
116 // type SigningInstruction struct {
117 //      Position          uint64        `json:"position"`
118 //      WitnessComponents []interface{} `json:"witness_components"`
119 // }
120
121 type buildUnlockHTLCContractTransactionResponse struct {
122         RawTransaction         string        `json:"raw_transaction"`
123         SigningInstructions    []interface{} `json:"signing_instructions"`
124         TxFee                  uint64        `json:"fee"`
125         AllowAdditionalActions bool          `json:"allow_additional_actions"`
126 }
127
128 var buildUnlockHTLCContractTransactionPayload = `{
129     "actions": [
130         {
131             "type": "spend_account_unspent_output",
132             "use_unconfirmed":true,
133             "arguments": [],
134             "output_id": "%s"
135         },
136         {
137             "account_id": "%s",
138             "amount": %s,
139             "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
140             "use_unconfirmed":true,
141             "type": "spend_account"
142         },
143         {
144             "amount": %s,
145             "asset_id": "%s",
146             "control_program": "%s",
147             "type": "control_program"
148         }
149     ],
150     "ttl": 0,
151     "base_transaction": null
152 }`
153
154 func buildUnlockHTLCContractTransaction(account HTLCAccount, contractUTXOID string, contractValue AssetAmount) (*buildUnlockHTLCContractTransactionResponse, error) {
155         payload := []byte(fmt.Sprintf(
156                 buildUnlockHTLCContractTransactionPayload,
157                 contractUTXOID,
158                 account.AccountID,
159                 strconv.FormatUint(account.TxFee, 10),
160                 strconv.FormatUint(contractValue.Amount, 10),
161                 contractValue.Asset,
162                 account.Receiver,
163         ))
164         res := new(buildUnlockHTLCContractTransactionResponse)
165         if err := request(buildTransactionURL, payload, res); err != nil {
166                 return nil, err
167         }
168         // signingInst, err := json.Marshal(res.SigningInstructions[1])
169         // if err != nil {
170         //      return nil, err
171         // }
172         // fmt.Println("signingInst:", string(signingInst))
173         return res, nil
174 }
175
176 type TransactionInput struct {
177         AssetID        string `json:"asset_id"`
178         ControlProgram string `json:"control_program"`
179         SignData       string `json:"sign_data"`
180 }
181
182 type decodeRawTransactionResponse struct {
183         TransactionInputs []TransactionInput `json:"inputs"`
184 }
185
186 var decodeRawTransactionPayload = `{
187         "raw_transaction":"%s"
188 }`
189
190 func decodeRawTransaction(rawTransaction string, contractValue AssetAmount) (string, string, error) {
191         payload := []byte(fmt.Sprintf(
192                 decodeRawTransactionPayload,
193                 rawTransaction,
194         ))
195         res := new(decodeRawTransactionResponse)
196         if err := request(decodeRawTransactionURL, payload, res); err != nil {
197                 return "", "", err
198         }
199
200         for _, v := range res.TransactionInputs {
201                 if v.AssetID == contractValue.Asset {
202                         return v.ControlProgram, v.SignData, nil
203                 }
204         }
205         return "", "", errFailedGetSignData
206 }
207
208 func getRecipientPublicKey(contractControlProgram string) (string, error) {
209         payload := []byte(fmt.Sprintf(
210                 decodeProgramPayload,
211                 contractControlProgram,
212         ))
213         res := new(decodeProgramResponse)
214         if err := request(decodeProgramURL, payload, res); err != nil {
215                 return "", err
216         }
217
218         publicKey := strings.Fields(res.Instructions)[5]
219         return publicKey, nil
220 }
221
222 type AddressInfo struct {
223         AccountAlias   string `json:"account_alias"`
224         AccountID      string `json:"account_id"`
225         Address        string `json:"address"`
226         ControlProgram string `json:"control_program"`
227 }
228
229 var listAddressesPayload = `{
230         "account_id":"%s"
231 }`
232
233 func listAddresses(accountID string) ([]AddressInfo, error) {
234         payload := []byte(fmt.Sprintf(
235                 listAddressesPayload,
236                 accountID,
237         ))
238         res := new([]AddressInfo)
239         if err := request(listAddressesURL, payload, res); err != nil {
240                 return nil, err
241         }
242
243         return *res, nil
244 }
245
246 func getAddress(accountID, contractControlProgram string) (string, error) {
247         publicKey, err := getRecipientPublicKey(contractControlProgram)
248         if err != nil {
249                 return "", err
250         }
251
252         publicKeyBytes, err := hex.DecodeString(publicKey)
253         if err != nil {
254                 return "", err
255         }
256
257         publicKeyHash := crypto.Ripemd160(publicKeyBytes)
258         controlProgram, err := vmutil.P2WPKHProgram(publicKeyHash)
259         if err != nil {
260                 return "", err
261         }
262
263         addressInfos, err := listAddresses(accountID)
264         if err != nil {
265                 return "", err
266         }
267
268         for _, addressInfo := range addressInfos {
269                 if addressInfo.ControlProgram == hex.EncodeToString(controlProgram) {
270                         return addressInfo.Address, nil
271                 }
272         }
273         return "", errFailedGetAddress
274 }
275
276 type signUnlockHTLCContractTransactionRequest struct {
277         Password    string                                     `json:"password"`
278         Transaction buildUnlockHTLCContractTransactionResponse `json:"transaction"`
279 }
280
281 var signUnlockHTLCContractTransactionPayload = `{
282     "password": "%s",
283     "transaction": {
284         "raw_transaction": "%s",
285         "signing_instructions": [
286             {
287                 "position": 0,
288                 "witness_components": [
289                     {
290                         "type": "data",
291                         "value": "%s"
292                     },
293                     {
294                         "type": "data",
295                         "value": "%s"
296                     },
297                     {
298                         "type": "data",
299                         "value": ""
300                     }
301                 ]
302             },
303             %s
304         ],
305         "fee": %s,
306         "allow_additional_actions": false
307     }
308 }`
309
310 // func signUnlockHTLCContractTransaction(account HTLCAccount, preimage, recipientSig, rawTransaction string) (string, error) {
311 //      rawSigningInstruction, err := json.Marshal(buildTxResp.SigningInstructions[1])
312 //      if err != nil {
313 //              return "", err
314 //      }
315
316 //      fmt.Println("rawSigningInstruction string:", string(rawSigningInstruction))
317
318 //      payload := []byte(fmt.Sprintf(
319 //              signUnlockHTLCContractTransactionPayload,
320 //              account.Password,
321 //              rawTransaction,
322 //              preimage,
323 //              recipientSig,
324 //              string(rawSigningInstruction),
325 //              strconv.FormatUint(account.TxFee, 10),
326 //      ))
327 //      res := new(signTransactionResponse)
328 //      if err := request(signTransactionURL, payload, res); err != nil {
329 //              return "", err
330 //      }
331
332 //      return res.Tx.RawTransaction, nil
333 // }
334
335 // DeployHTLCContract deploy HTLC contract.
336 func DeployHTLCContract(account HTLCAccount, contractValue AssetAmount, contractArgs HTLCContractArgs) (string, error) {
337         // compile locked HTLC cotnract
338         HTLCContractControlProgram, err := compileLockHTLCContract(contractArgs)
339         if err != nil {
340                 return "", err
341         }
342
343         // build locked HTLC contract
344         txLocked, err := buildLockHTLCContractTransaction(account, contractValue, HTLCContractControlProgram)
345         if err != nil {
346                 return "", err
347         }
348
349         // sign locked HTLC contract transaction
350         signedTransaction, err := signTransaction(account.Password, txLocked)
351         if err != nil {
352                 return "", err
353         }
354
355         // submit signed HTLC contract transaction
356         txID, err := submitTransaction(signedTransaction)
357         if err != nil {
358                 return "", err
359         }
360
361         // get HTLC contract output ID
362         contractUTXOID, err := getContractUTXOID(txID, HTLCContractControlProgram)
363         if err != nil {
364                 return "", err
365         }
366         return contractUTXOID, nil
367 }
368
369 // // CallHTLCContract call HTLC contract.
370 // func CallHTLCContract(account HTLCAccount, contractUTXOID, preimage, recipientSig string, contractArgs HTLCContractArgs, contractValue AssetAmount) (string, string, string, error) {
371 //      // build unlocked contract transaction
372 //      buildTxResp, err := buildUnlockHTLCContractTransaction(account, contractUTXOID, contractValue)
373 //      if err != nil {
374 //              return "", "", "", err
375 //      }
376
377 //      fmt.Println("raw transaction:", buildTxResp.RawTransaction)
378 //      contractControlProgram, signData, err := decodeRawTransaction(buildTxResp.RawTransaction, contractValue)
379 //      if err != nil {
380 //              fmt.Println(err)
381 //      }
382 //      fmt.Println("contractControlProgram:", contractControlProgram)
383 //      fmt.Println("signData:", signData)
384
385 //      return buildTxResp.RawTransaction, contractControlProgram, signData, nil
386
387 //      // // sign unlocked HTLC contract transaction
388 //      // signedTransaction, err := signUnlockHTLCContractTransaction(account, preimage, recipientSig, *buildTxResp)
389 //      // if err != nil {
390 //      //      return "", "", err
391 //      // }
392
393 //      // // submit signed HTLC contract transaction
394 //      // txID, err := submitTransaction(signedTransaction)
395 //      // if err != nil {
396 //      //      return "", "", err
397 //      // }
398
399 //      // return "", txID, nil
400 // }