OSDN Git Service

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