OSDN Git Service

fix underflow bug
[bytom/shuttle.git] / swap / htlc.go
index e18e0c2..a75bb3e 100644 (file)
@@ -1,23 +1,22 @@
 package swap
 
 import (
+       "encoding/hex"
        "encoding/json"
        "errors"
        "fmt"
-       "strconv"
+       "strings"
+
+       "github.com/bytom/crypto"
+       "github.com/bytom/protocol/vm/vmutil"
 )
 
 var (
-       errFailedGetSignData = errors.New("Failed to get sign data")
+       errFailedGetSignData     = errors.New("Failed to get sign data")
+       errFailedGetAddress      = errors.New("Failed to get address by account ID")
+       errHTLCParametersInvalid = errors.New("HTLC parameters invalid")
 )
 
-type HTLCAccount struct {
-       AccountID string
-       Password  string
-       Receiver  string
-       TxFee     uint64
-}
-
 type HTLCContractArgs struct {
        SenderPublicKey    string
        RecipientPublicKey string
@@ -25,11 +24,11 @@ type HTLCContractArgs struct {
        Hash               string
 }
 
-type compileLockHTLCContractResponse struct {
+type compileLockHTLCContractResp struct {
        Program string `json:"program"`
 }
 
-var compileLockHTLCContractPayload = `{
+var compileLockHTLCContractReq = `{
     "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}}",
     "args":[
         {
@@ -39,7 +38,7 @@ var compileLockHTLCContractPayload = `{
             "string":"%s"
         },
         {
-            "integer":%s
+            "integer":%d
         },
         {
             "string":"%s"
@@ -47,39 +46,38 @@ var compileLockHTLCContractPayload = `{
     ]
 }`
 
-func compileLockHTLCContract(contractArgs HTLCContractArgs) (string, error) {
-       payload := []byte(fmt.Sprintf(
-               compileLockHTLCContractPayload,
+func compileLockHTLCContract(s *Server, contractArgs HTLCContractArgs) (string, error) {
+       payload := []byte(fmt.Sprintf(compileLockHTLCContractReq,
                contractArgs.SenderPublicKey,
                contractArgs.RecipientPublicKey,
-               strconv.FormatUint(contractArgs.BlockHeight, 10),
+               contractArgs.BlockHeight,
                contractArgs.Hash,
        ))
-       res := new(compileLockHTLCContractResponse)
-       if err := request(compileURL, payload, res); err != nil {
+       res := new(compileLockHTLCContractResp)
+       if err := s.request(compileURL, payload, res); err != nil {
                return "", err
        }
        return res.Program, nil
 }
 
-var buildLockHTLCContractTransactionPayload = `{
+var buildLockHTLCContractTxReq = `{
     "actions": [
         {
             "account_id": "%s",
-            "amount": %s,
+            "amount": %d,
             "asset_id": "%s",
             "use_unconfirmed":true,
             "type": "spend_account"
         },
         {
-            "amount": %s,
+            "amount": %d,
             "asset_id": "%s",
             "control_program": "%s",
             "type": "control_program"
         },
         {
             "account_id": "%s",
-            "amount": %s,
+            "amount": %d,
             "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
             "use_unconfirmed":true,
             "type": "spend_account"
@@ -89,49 +87,65 @@ var buildLockHTLCContractTransactionPayload = `{
     "base_transaction": null
 }`
 
-func buildLockHTLCContractTransaction(account HTLCAccount, contractValue AssetAmount, contractControlProgram string) (interface{}, error) {
-       payload := []byte(fmt.Sprintf(
-               buildLockHTLCContractTransactionPayload,
+func buildLockHTLCContractTransaction(s *Server, account AccountInfo, contractValue AssetAmount, contractControlProgram string) (interface{}, error) {
+       payload := []byte(fmt.Sprintf(buildLockHTLCContractTxReq,
                account.AccountID,
-               strconv.FormatUint(contractValue.Amount, 10),
+               contractValue.Amount,
                contractValue.Asset,
-               strconv.FormatUint(contractValue.Amount, 10),
+               contractValue.Amount,
                contractValue.Asset,
                contractControlProgram,
                account.AccountID,
-               strconv.FormatUint(account.TxFee, 10),
+               account.TxFee,
        ))
        res := new(interface{})
-       if err := request(buildTransactionURL, payload, res); err != nil {
+       if err := s.request(buildTransactionURL, payload, res); err != nil {
                return "", err
        }
        return res, nil
 }
 
-type buildUnlockHTLCContractTransactionResponse struct {
-       RawTransaction         string               `json:"raw_transaction"`
-       SigningInstructions    []SigningInstruction `json:"signing_instructions"`
-       TxFee                  uint64               `json:"fee"`
-       AllowAdditionalActions bool                 `json:"allow_additional_actions"`
+type buildUnlockHTLCContractTxResp struct {
+       RawTransaction         string        `json:"raw_transaction"`
+       SigningInstructions    []interface{} `json:"signing_instructions"`
+       TxFee                  uint64        `json:"fee"`
+       AllowAdditionalActions bool          `json:"allow_additional_actions"`
 }
 
-var buildUnlockHTLCContractTransactionPayload = `{
+var buildUnlockHTLCContractTxReq = `{
     "actions": [
         {
             "type": "spend_account_unspent_output",
             "use_unconfirmed":true,
-            "arguments": [],
+            "arguments": [
+                               {
+                    "type": "data",
+                    "raw_data": {
+                        "value": "%s"
+                    }
+                },
+                {
+                    "type": "raw_tx_signature",
+                    "raw_data": %s
+                },
+                {
+                    "type": "integer",
+                    "raw_data": {
+                        "value": 0
+                    }
+                }
+                       ],
             "output_id": "%s"
         },
         {
             "account_id": "%s",
-            "amount": %s,
+            "amount": %d,
             "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
             "use_unconfirmed":true,
             "type": "spend_account"
         },
         {
-            "amount": %s,
+            "amount": %d,
             "asset_id": "%s",
             "control_program": "%s",
             "type": "control_program"
@@ -141,60 +155,161 @@ var buildUnlockHTLCContractTransactionPayload = `{
     "base_transaction": null
 }`
 
-func buildUnlockHTLCContractTransaction(account HTLCAccount, contractUTXOID string, contractArgs HTLCContractArgs, contractValue AssetAmount) (*buildUnlockHTLCContractTransactionResponse, error) {
-       payload := []byte(fmt.Sprintf(
-               buildUnlockHTLCContractTransactionPayload,
+func buildUnlockHTLCContractTransaction(s *Server, account AccountInfo, contractUTXOID, preimage string, xpubKeyInfo *XPubKeyInfo, contractValue AssetAmount) (interface{}, error) {
+       xpubKeyInfoStr, err := json.Marshal(xpubKeyInfo)
+       if err != nil {
+               return nil, err
+       }
+
+       payload := []byte(fmt.Sprintf(buildUnlockHTLCContractTxReq,
+               preimage,
+               xpubKeyInfoStr,
                contractUTXOID,
                account.AccountID,
-               strconv.FormatUint(account.TxFee, 10),
-               strconv.FormatUint(contractValue.Amount, 10),
+               account.TxFee,
+               contractValue.Amount,
                contractValue.Asset,
                account.Receiver,
        ))
-       res := new(buildUnlockHTLCContractTransactionResponse)
-       if err := request(buildTransactionURL, payload, res); err != nil {
+       res := new(interface{})
+       if err := s.request(buildTransactionURL, payload, res); err != nil {
                return nil, err
        }
        return res, nil
 }
 
 type TransactionInput struct {
+       AssetID        string `json:"asset_id"`
        ControlProgram string `json:"control_program"`
        SignData       string `json:"sign_data"`
 }
 
-type decodeRawTransactionResponse struct {
+type decodeRawTxResp struct {
        TransactionInputs []TransactionInput `json:"inputs"`
 }
 
-var decodeRawTransactionPayload = `{
-       "raw_transaction":"%s"
-}`
+type decodeRawTxReq struct {
+       RawTx string `json:"raw_transaction"`
+}
 
-func decodeRawTransaction(rawTransaction, controlProgram string) (string, error) {
-       payload := []byte(fmt.Sprintf(
-               decodeRawTransactionPayload,
-               rawTransaction,
-       ))
-       res := new(decodeRawTransactionResponse)
-       if err := request(decodeRawTransactionPayload, payload, res); err != nil {
-               return "", err
+func decodeRawTransaction(s *Server, rawTransaction string, contractValue AssetAmount) (string, string, error) {
+       payload, err := json.Marshal(decodeRawTxReq{RawTx: rawTransaction})
+       if err != nil {
+               return "", "", err
+       }
+
+       res := new(decodeRawTxResp)
+       if err := s.request(decodeRawTransactionURL, payload, res); err != nil {
+               return "", "", err
        }
 
        for _, v := range res.TransactionInputs {
-               if v.ControlProgram == controlProgram {
-                       return v.SignData, nil
+               if v.AssetID == contractValue.Asset {
+                       return v.ControlProgram, v.SignData, nil
+               }
+       }
+       return "", "", errFailedGetSignData
+}
+
+func getRecipientPublicKey(s *Server, contractControlProgram string) (string, error) {
+       payload, err := json.Marshal(decodeProgramReq{Program: contractControlProgram})
+       if err != nil {
+               return "", err
+       }
+
+       res := new(decodeProgramResp)
+       if err := s.request(decodeProgramURL, payload, res); err != nil {
+               return "", err
+       }
+
+       publicKey := strings.Fields(res.Instructions)[5]
+       return publicKey, nil
+}
+
+type AddressInfo struct {
+       AccountAlias   string `json:"account_alias"`
+       AccountID      string `json:"account_id"`
+       Address        string `json:"address"`
+       ControlProgram string `json:"control_program"`
+}
+
+type listAddressesReq struct {
+       AccountID string `json:"account_id"`
+}
+
+func listAddresses(s *Server, accountID string) ([]AddressInfo, error) {
+       payload, err := json.Marshal(listAddressesReq{AccountID: accountID})
+       if err != nil {
+               return nil, err
+       }
+
+       res := new([]AddressInfo)
+       if err := s.request(listAddressesURL, payload, res); err != nil {
+               return nil, err
+       }
+
+       return *res, nil
+}
+
+func getAddress(s *Server, accountID, contractControlProgram string) (string, error) {
+       publicKey, err := getRecipientPublicKey(s, contractControlProgram)
+       if err != nil {
+               return "", err
+       }
+
+       publicKeyBytes, err := hex.DecodeString(publicKey)
+       if err != nil {
+               return "", err
+       }
+
+       publicKeyHash := crypto.Ripemd160(publicKeyBytes)
+       controlProgram, err := vmutil.P2WPKHProgram(publicKeyHash)
+       if err != nil {
+               return "", err
+       }
+
+       addressInfos, err := listAddresses(s, accountID)
+       if err != nil {
+               return "", err
+       }
+
+       for _, addressInfo := range addressInfos {
+               if addressInfo.ControlProgram == hex.EncodeToString(controlProgram) {
+                       return addressInfo.Address, nil
                }
        }
-       return "", errFailedGetSignData
+       return "", errFailedGetAddress
+}
+
+type signMessageReq struct {
+       Address  string `json:"address"`
+       Message  string `json:"message"`
+       Password string `json:"password"`
 }
 
-type signUnlockHTLCContractTransactionRequest struct {
-       Password    string                                     `json:"password"`
-       Transaction buildUnlockHTLCContractTransactionResponse `json:"transaction"`
+type signMessageResp struct {
+       Signature   string `json:"signature"`
+       DerivedXPub string `json:"derived_xpub"`
 }
 
-var signUnlockHTLCContractTransactionPayload = `{
+func signMessage(s *Server, address, message, password string) (string, error) {
+       payload, err := json.Marshal(signMessageReq{
+               Address:  address,
+               Message:  message,
+               Password: password,
+       })
+       if err != nil {
+               return "", err
+       }
+
+       res := new(signMessageResp)
+       if err := s.request(signMessageURl, payload, res); err != nil {
+               return "", nil
+       }
+       return res.Signature, nil
+}
+
+var signUnlockHTLCContractTxReq = `{
     "password": "%s",
     "transaction": {
         "raw_transaction": "%s",
@@ -218,74 +333,89 @@ var signUnlockHTLCContractTransactionPayload = `{
             },
             %s
         ],
-        "fee": %s,
+        "fee": %d,
         "allow_additional_actions": false
     }
 }`
 
-// type WitnessComponent struct {
-//     Type  string `json:"type"`
-//     Value string `json:"value"`
-// }
+func signUnlockHTLCContractTransaction(s *Server, account AccountInfo, preimage, recipientSig, rawTransaction, signingInst string) (string, error) {
+       payload := []byte(fmt.Sprintf(signUnlockHTLCContractTxReq,
+               account.Password,
+               rawTransaction,
+               preimage,
+               recipientSig,
+               signingInst,
+               account.TxFee,
+       ))
+       res := new(signTxResp)
+       if err := s.request(signTransactionURL, payload, res); err != nil {
+               return "", err
+       }
+
+       if !res.SignComplete {
+               return "", errFailedSignTx
+       }
 
-type SigningInstruction struct {
-       Position          uint64        `json:"position"`
-       WitnessComponents []interface{} `json:"witness_components"`
+       return res.Tx.RawTransaction, nil
 }
 
-func signUnlockHTLCContractTransaction(account HTLCAccount, preimage, recipientSig string, buildTxResp buildUnlockHTLCContractTransactionResponse) (string, error) {
-       rawSigningInstruction, err := json.Marshal(buildTxResp.SigningInstructions[1])
+func decodeHTLCProgram(s *Server, program string) (*HTLCContractArgs, error) {
+       payload, err := json.Marshal(decodeProgramReq{Program: program})
        if err != nil {
-               return "", err
+               return nil, err
+       }
+
+       res := new(decodeProgramResp)
+       if err := s.request(decodeProgramURL, payload, res); err != nil {
+               return nil, err
        }
 
-       fmt.Println("rawSigningInstruction string:", string(rawSigningInstruction))
+       instructions := strings.Fields(res.Instructions)
+       contractArgs := new(HTLCContractArgs)
+       contractArgs.Hash = instructions[1]
+       contractArgs.RecipientPublicKey = instructions[5]
+       contractArgs.SenderPublicKey = instructions[7]
+       if len(contractArgs.Hash) != 64 || len(contractArgs.RecipientPublicKey) != 64 || len(contractArgs.SenderPublicKey) != 64 {
+               return nil, errHTLCParametersInvalid
+       }
 
-       payload := []byte(fmt.Sprintf(
-               signUnlockHTLCContractTransactionPayload,
-               account.Password,
-               buildTxResp.RawTransaction,
-               preimage,
-               recipientSig,
-               string(rawSigningInstruction),
-               strconv.FormatUint(account.TxFee, 10),
-       ))
-       res := new(signTransactionResponse)
-       if err := request(signTransactionURL, payload, res); err != nil {
-               return "", err
+       blockHeight, err := parseUint64(instructions[3])
+       if err != nil {
+               return nil, err
        }
 
-       return res.Tx.RawTransaction, nil
+       contractArgs.BlockHeight = blockHeight
+       return contractArgs, nil
 }
 
 // DeployHTLCContract deploy HTLC contract.
-func DeployHTLCContract(account HTLCAccount, contractValue AssetAmount, contractArgs HTLCContractArgs) (string, error) {
+func DeployHTLCContract(s *Server, account AccountInfo, contractValue AssetAmount, contractArgs HTLCContractArgs) (string, error) {
        // compile locked HTLC cotnract
-       HTLCContractControlProgram, err := compileLockHTLCContract(contractArgs)
+       HTLCContractControlProgram, err := compileLockHTLCContract(s, contractArgs)
        if err != nil {
                return "", err
        }
 
        // build locked HTLC contract
-       txLocked, err := buildLockHTLCContractTransaction(account, contractValue, HTLCContractControlProgram)
+       txLocked, err := buildLockHTLCContractTransaction(s, account, contractValue, HTLCContractControlProgram)
        if err != nil {
                return "", err
        }
 
        // sign locked HTLC contract transaction
-       signedTransaction, err := signTransaction(account.Password, txLocked)
+       signedTransaction, err := signTransaction(s, account.Password, txLocked)
        if err != nil {
                return "", err
        }
 
        // submit signed HTLC contract transaction
-       txID, err := submitTransaction(signedTransaction)
+       txID, err := submitTransaction(s, signedTransaction)
        if err != nil {
                return "", err
        }
 
        // get HTLC contract output ID
-       contractUTXOID, err := getContractUTXOID(txID, HTLCContractControlProgram)
+       contractUTXOID, err := getContractUTXOID(s, txID, HTLCContractControlProgram)
        if err != nil {
                return "", err
        }
@@ -293,21 +423,107 @@ func DeployHTLCContract(account HTLCAccount, contractValue AssetAmount, contract
 }
 
 // CallHTLCContract call HTLC contract.
-func CallHTLCContract(account HTLCAccount, contractUTXOID, preimage, recipientSig string, contractArgs HTLCContractArgs, contractValue AssetAmount) (string, error) {
+func CallHTLCContract(s *Server, account AccountInfo, contractUTXOID, preimage string) (string, error) {
+       contractControlProgram, contractValue, err := ListUnspentOutputs(s, contractUTXOID)
+       if err != nil {
+               return "", err
+       }
+
+       // get public key by contract control program
+       contractArgs, err := decodeHTLCProgram(s, contractControlProgram)
+       if err != nil {
+               return "", err
+       }
+
+       // get public key path and root xpub by contract args
+       xpubInfo, err := getXPubKeyInfo(s, account.AccountID, contractArgs.RecipientPublicKey)
+       if err != nil {
+               return "", err
+       }
+
        // build unlocked contract transaction
-       buildTxResp, err := buildUnlockHTLCContractTransaction(account, contractUTXOID, contractArgs, contractValue)
+       builltTx, err := buildUnlockHTLCContractTransaction(s, account, contractUTXOID, preimage, xpubInfo, *contractValue)
        if err != nil {
                return "", err
        }
 
-       // sign unlocked HTLC contract transaction
-       signedTransaction, err := signUnlockHTLCContractTransaction(account, preimage, recipientSig, *buildTxResp)
+       // signingInst, err := json.Marshal(buildTxResp.SigningInstructions[1])
+       // if err != nil {
+       //      fmt.Println(err)
+       // }
+
+       // contractControlProgram, signData, err := decodeRawTransaction(s, buildTxResp.RawTransaction, *contractValue)
+       // if err != nil {
+       //      fmt.Println(err)
+       // }
+
+       // // get address by account ID and contract control program
+       // address, err := getAddress(s, account.AccountID, contractControlProgram)
+       // if err != nil {
+       //      return "", err
+       // }
+
+       // // sign raw transaction
+       // recipientSig, err := signMessage(s, address, signData, account.Password)
+       // if err != nil {
+       //      return "", err
+       // }
+
+       // // sign raw transaction
+       // signedTransaction, err := signUnlockHTLCContractTransaction(s, account, preimage, recipientSig, buildTxResp.RawTransaction, string(signingInst))
+       // if err != nil {
+       //      return "", err
+       // }
+
+       // sign raw transaction
+       signedTransaction, err := signTransaction(s, account.Password, builltTx)
        if err != nil {
                return "", err
        }
 
        // submit signed HTLC contract transaction
-       txID, err := submitTransaction(signedTransaction)
+       txID, err := submitTransaction(s, signedTransaction)
+       if err != nil {
+               return "", err
+       }
+
+       return txID, nil
+}
+
+// CancelHTLCContract cancel HTLC contract.
+func CancelHTLCContract(s *Server, accountInfo AccountInfo, contractUTXOID string) (string, error) {
+       // get contract control program by contract UTXOID
+       contractControlProgram, contractValue, err := ListUnspentOutputs(s, contractUTXOID)
+       if err != nil {
+               return "", err
+       }
+
+       // get public key by contract control program
+       contractArgs, err := decodeHTLCProgram(s, contractControlProgram)
+       if err != nil {
+               return "", err
+       }
+
+       // get public key path and root xpub by contract args
+       xpubInfo, err := getXPubKeyInfo(s, accountInfo.AccountID, contractArgs.SenderPublicKey)
+       if err != nil {
+               return "", err
+       }
+
+       // build cancel contract transaction
+       builtTx, err := buildCancelContractTransaction(s, accountInfo, contractUTXOID, xpubInfo, contractValue)
+       if err != nil {
+               return "", err
+       }
+
+       // sign cancel contract transaction
+       signedTx, err := signTransaction(s, accountInfo.Password, builtTx)
+       if err != nil {
+               return "", err
+       }
+
+       // submit signed unlocked contract transaction
+       txID, err := submitTransaction(s, signedTx)
        if err != nil {
                return "", err
        }