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
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":[
{
"string":"%s"
},
{
- "integer":%s
+ "integer":%d
},
{
"string":"%s"
]
}`
-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"
"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"
"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",
},
%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
}
}
// 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
}