OSDN Git Service

update signUnlockHTLCContractTransaction
[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 var signMessagePayload = `{
277     "address": "%s",
278     "message": "%s",
279     "password": "%s"
280 }`
281
282 type signMessageResponse struct {
283         Signature   string `json:"signature"`
284         DerivedXPub string `json:"derived_xpub"`
285 }
286
287 func signMessage(address, message, password string) (string, error) {
288         payload := []byte(fmt.Sprintf(
289                 signMessagePayload,
290                 address,
291                 message,
292                 password,
293         ))
294         res := new(signMessageResponse)
295         if err := request(signMessageURl, payload, res); err != nil {
296                 return "", nil
297         }
298         return res.Signature, nil
299 }
300
301 type signUnlockHTLCContractTransactionRequest struct {
302         Password    string                                     `json:"password"`
303         Transaction buildUnlockHTLCContractTransactionResponse `json:"transaction"`
304 }
305
306 var signUnlockHTLCContractTransactionPayload = `{
307     "password": "%s",
308     "transaction": {
309         "raw_transaction": "%s",
310         "signing_instructions": [
311             {
312                 "position": 0,
313                 "witness_components": [
314                     {
315                         "type": "data",
316                         "value": "%s"
317                     },
318                     {
319                         "type": "data",
320                         "value": "%s"
321                     },
322                     {
323                         "type": "data",
324                         "value": ""
325                     }
326                 ]
327             },
328             %s
329         ],
330         "fee": %s,
331         "allow_additional_actions": false
332     }
333 }`
334
335 func signUnlockHTLCContractTransaction(account HTLCAccount, preimage, recipientSig, rawTransaction, signingInst string) (string, error) {
336         payload := []byte(fmt.Sprintf(
337                 signUnlockHTLCContractTransactionPayload,
338                 account.Password,
339                 rawTransaction,
340                 preimage,
341                 recipientSig,
342                 signingInst,
343                 strconv.FormatUint(account.TxFee, 10),
344         ))
345         res := new(signTransactionResponse)
346         if err := request(signTransactionURL, payload, res); err != nil {
347                 return "", err
348         }
349
350         return res.Tx.RawTransaction, nil
351 }
352
353 // DeployHTLCContract deploy HTLC contract.
354 func DeployHTLCContract(account HTLCAccount, contractValue AssetAmount, contractArgs HTLCContractArgs) (string, error) {
355         // compile locked HTLC cotnract
356         HTLCContractControlProgram, err := compileLockHTLCContract(contractArgs)
357         if err != nil {
358                 return "", err
359         }
360
361         // build locked HTLC contract
362         txLocked, err := buildLockHTLCContractTransaction(account, contractValue, HTLCContractControlProgram)
363         if err != nil {
364                 return "", err
365         }
366
367         // sign locked HTLC contract transaction
368         signedTransaction, err := signTransaction(account.Password, txLocked)
369         if err != nil {
370                 return "", err
371         }
372
373         // submit signed HTLC contract transaction
374         txID, err := submitTransaction(signedTransaction)
375         if err != nil {
376                 return "", err
377         }
378
379         // get HTLC contract output ID
380         contractUTXOID, err := getContractUTXOID(txID, HTLCContractControlProgram)
381         if err != nil {
382                 return "", err
383         }
384         return contractUTXOID, nil
385 }
386
387 // // CallHTLCContract call HTLC contract.
388 // func CallHTLCContract(account HTLCAccount, contractUTXOID, preimage, recipientSig string, contractArgs HTLCContractArgs, contractValue AssetAmount) (string, string, string, error) {
389 //      // build unlocked contract transaction
390 //      buildTxResp, err := buildUnlockHTLCContractTransaction(account, contractUTXOID, contractValue)
391 //      if err != nil {
392 //              return "", "", "", err
393 //      }
394
395 //      fmt.Println("raw transaction:", buildTxResp.RawTransaction)
396 //      contractControlProgram, signData, err := decodeRawTransaction(buildTxResp.RawTransaction, contractValue)
397 //      if err != nil {
398 //              fmt.Println(err)
399 //      }
400 //      fmt.Println("contractControlProgram:", contractControlProgram)
401 //      fmt.Println("signData:", signData)
402
403 //      return buildTxResp.RawTransaction, contractControlProgram, signData, nil
404
405 //      // // sign unlocked HTLC contract transaction
406 //      // signedTransaction, err := signUnlockHTLCContractTransaction(account, preimage, recipientSig, *buildTxResp)
407 //      // if err != nil {
408 //      //      return "", "", err
409 //      // }
410
411 //      // // submit signed HTLC contract transaction
412 //      // txID, err := submitTransaction(signedTransaction)
413 //      // if err != nil {
414 //      //      return "", "", err
415 //      // }
416
417 //      // return "", txID, nil
418 // }