11 "github.com/bytom/crypto"
12 "github.com/bytom/protocol/vm/vmutil"
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")
21 type HTLCContractArgs struct {
22 SenderPublicKey string
23 RecipientPublicKey string
28 type compileLockHTLCContractResp struct {
29 Program string `json:"program"`
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}}",
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),
57 res := new(compileLockHTLCContractResp)
58 if err := request(compileURL, payload, res); err != nil {
61 return res.Program, nil
64 var buildLockHTLCContractTxReq = `{
70 "use_unconfirmed":true,
71 "type": "spend_account"
76 "control_program": "%s",
77 "type": "control_program"
82 "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
83 "use_unconfirmed":true,
84 "type": "spend_account"
88 "base_transaction": null
91 func buildLockHTLCContractTransaction(account AccountInfo, contractValue AssetAmount, contractControlProgram string) (interface{}, error) {
92 payload := []byte(fmt.Sprintf(buildLockHTLCContractTxReq,
94 strconv.FormatUint(contractValue.Amount, 10),
96 strconv.FormatUint(contractValue.Amount, 10),
98 contractControlProgram,
100 strconv.FormatUint(account.TxFee, 10),
102 res := new(interface{})
103 if err := request(buildTransactionURL, payload, res); err != nil {
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"`
116 var buildUnlockHTLCContractTxReq = `{
119 "type": "spend_account_unspent_output",
120 "use_unconfirmed":true,
127 "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
128 "use_unconfirmed":true,
129 "type": "spend_account"
134 "control_program": "%s",
135 "type": "control_program"
139 "base_transaction": null
142 func buildUnlockHTLCContractTransaction(account AccountInfo, contractUTXOID string, contractValue AssetAmount) (*buildUnlockHTLCContractTxResp, error) {
143 payload := []byte(fmt.Sprintf(buildUnlockHTLCContractTxReq,
146 strconv.FormatUint(account.TxFee, 10),
147 strconv.FormatUint(contractValue.Amount, 10),
151 res := new(buildUnlockHTLCContractTxResp)
152 if err := request(buildTransactionURL, payload, res); err != nil {
158 type TransactionInput struct {
159 AssetID string `json:"asset_id"`
160 ControlProgram string `json:"control_program"`
161 SignData string `json:"sign_data"`
164 type decodeRawTxResp struct {
165 TransactionInputs []TransactionInput `json:"inputs"`
168 var decodeRawTxReq = `{
169 "raw_transaction":"%s"
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 {
179 for _, v := range res.TransactionInputs {
180 if v.AssetID == contractValue.Asset {
181 return v.ControlProgram, v.SignData, nil
184 return "", "", errFailedGetSignData
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 {
194 publicKey := strings.Fields(res.Instructions)[5]
195 return publicKey, nil
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"`
205 var listAddressesPayload = `{
209 func listAddresses(accountID string) ([]AddressInfo, error) {
210 payload := []byte(fmt.Sprintf(
211 listAddressesPayload,
214 res := new([]AddressInfo)
215 if err := request(listAddressesURL, payload, res); err != nil {
222 func getAddress(accountID, contractControlProgram string) (string, error) {
223 publicKey, err := getRecipientPublicKey(contractControlProgram)
228 publicKeyBytes, err := hex.DecodeString(publicKey)
233 publicKeyHash := crypto.Ripemd160(publicKeyBytes)
234 controlProgram, err := vmutil.P2WPKHProgram(publicKeyHash)
239 addressInfos, err := listAddresses(accountID)
244 for _, addressInfo := range addressInfos {
245 if addressInfo.ControlProgram == hex.EncodeToString(controlProgram) {
246 return addressInfo.Address, nil
249 return "", errFailedGetAddress
252 var signMessageReq = `{
258 type signMessageResp struct {
259 Signature string `json:"signature"`
260 DerivedXPub string `json:"derived_xpub"`
263 func signMessage(address, message, password string) (string, error) {
264 payload := []byte(fmt.Sprintf(
270 res := new(signMessageResp)
271 if err := request(signMessageURl, payload, res); err != nil {
274 return res.Signature, nil
277 var signUnlockHTLCContractTxReq = `{
280 "raw_transaction": "%s",
281 "signing_instructions": [
284 "witness_components": [
302 "allow_additional_actions": false
306 func signUnlockHTLCContractTransaction(account AccountInfo, preimage, recipientSig, rawTransaction, signingInst string) (string, error) {
307 payload := []byte(fmt.Sprintf(signUnlockHTLCContractTxReq,
313 strconv.FormatUint(account.TxFee, 10),
315 res := new(signTxResp)
316 if err := request(signTransactionURL, payload, res); err != nil {
320 return res.Tx.RawTransaction, nil
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 {
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
339 blockHeight, err := parseUint64(instructions[3])
344 contractArgs.BlockHeight = blockHeight
345 return contractArgs, nil
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)
356 // build locked HTLC contract
357 txLocked, err := buildLockHTLCContractTransaction(account, contractValue, HTLCContractControlProgram)
362 // sign locked HTLC contract transaction
363 signedTransaction, err := signTransaction(account.Password, txLocked)
368 // submit signed HTLC contract transaction
369 txID, err := submitTransaction(signedTransaction)
374 // get HTLC contract output ID
375 contractUTXOID, err := getContractUTXOID(txID, HTLCContractControlProgram)
379 return contractUTXOID, nil
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)
390 signingInst, err := json.Marshal(buildTxResp.SigningInstructions[1])
395 contractControlProgram, signData, err := decodeRawTransaction(buildTxResp.RawTransaction, contractValue)
400 // get address by account ID and contract control program
401 address, err := getAddress(account.AccountID, contractControlProgram)
406 // sign raw transaction
407 recipientSig, err := signMessage(address, signData, account.Password)
412 // sign raw transaction
413 signedTransaction, err := signUnlockHTLCContractTransaction(account, preimage, recipientSig, buildTxResp.RawTransaction, string(signingInst))
418 // submit signed HTLC contract transaction
419 txID, err := submitTransaction(signedTransaction)