Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Copyright (c) 2017 The Decred developers
- // Use of this source code is governed by an ISC
- // license that can be found in the LICENSE file.
- package main
- import (
- "bufio"
- "bytes"
- "crypto/rand"
- "crypto/sha256"
- "encoding/hex"
- "encoding/json"
- "errors"
- "flag"
- "fmt"
- "net"
- "os"
- "strconv"
- "strings"
- "time"
- "github.com/btcsuite/btcd/chaincfg"
- "github.com/btcsuite/btcd/chaincfg/chainhash"
- rpc "github.com/btcsuite/btcd/rpcclient"
- "github.com/btcsuite/btcd/txscript"
- "github.com/btcsuite/btcd/wire"
- "github.com/btcsuite/btcutil"
- "github.com/btcsuite/btcwallet/wallet/txrules"
- "golang.org/x/crypto/ripemd160"
- )
- const verify = true
- const secretSize = 32
- const txVersion = 2
- var (
- chainParams = &chaincfg.MainNetParams
- )
- var (
- flagset = flag.NewFlagSet("", flag.ExitOnError)
- connectFlag = flagset.String("s", "localhost", "host[:port] of Bitcoin Core wallet RPC server")
- rpcuserFlag = flagset.String("rpcuser", "", "username for wallet RPC authentication")
- rpcpassFlag = flagset.String("rpcpass", "", "password for wallet RPC authentication")
- testnetFlag = flagset.Bool("testnet", false, "use testnet network")
- regtestFlag = flagset.Bool("regtest", false, "use regtest network")
- )
- // There are two directions that the atomic swap can be performed, as the
- // initiator can be on either chain. This tool only deals with creating the
- // Bitcoin transactions for these swaps. A second tool should be used for the
- // transaction on the other chain. Any chain can be used so long as it supports
- // OP_SHA256 and OP_CHECKLOCKTIMEVERIFY.
- //
- // Example scenerios using bitcoin as the second chain:
- //
- // Scenerio 1:
- // cp1 initiates (dcr)
- // cp2 participates with cp1 H(S) (btc)
- // cp1 redeems btc revealing S
- // - must verify H(S) in contract is hash of known secret
- // cp2 redeems dcr with S
- //
- // Scenerio 2:
- // cp1 initiates (btc)
- // cp2 participates with cp1 H(S) (dcr)
- // cp1 redeems dcr revealing S
- // - must verify H(S) in contract is hash of known secret
- // cp2 redeems btc with S
- func init() {
- flagset.Usage = func() {
- fmt.Println("Usage: btcatomicswap [flags] cmd [cmd args]")
- fmt.Println()
- fmt.Println("Commands:")
- fmt.Println(" initiate <participant address> <amount>")
- fmt.Println(" participate <initiator address> <amount> <secret hash>")
- fmt.Println(" redeem <contract> <contract transaction> <secret>")
- fmt.Println(" refund <contract> <contract transaction>")
- fmt.Println(" extractsecret <redemption transaction> <secret hash>")
- fmt.Println(" auditcontract <contract> <contract transaction>")
- fmt.Println()
- fmt.Println("Flags:")
- flagset.PrintDefaults()
- }
- }
- type command interface {
- runCommand(*rpc.Client) error
- }
- // offline commands don't require wallet RPC.
- type offlineCommand interface {
- command
- runOfflineCommand() error
- }
- type initiateCmd struct {
- cp2Addr *btcutil.AddressPubKeyHash
- amount btcutil.Amount
- }
- type participateCmd struct {
- cp1Addr *btcutil.AddressPubKeyHash
- amount btcutil.Amount
- secretHash []byte
- }
- type redeemCmd struct {
- contract []byte
- contractTx *wire.MsgTx
- secret []byte
- }
- type refundCmd struct {
- contract []byte
- contractTx *wire.MsgTx
- }
- type extractSecretCmd struct {
- redemptionTx *wire.MsgTx
- secretHash []byte
- }
- type auditContractCmd struct {
- contract []byte
- contractTx *wire.MsgTx
- }
- func main() {
- err, showUsage := run()
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- }
- if showUsage {
- flagset.Usage()
- }
- if err != nil || showUsage {
- os.Exit(1)
- }
- }
- func checkCmdArgLength(args []string, required int) (nArgs int) {
- if len(args) < required {
- return 0
- }
- for i, arg := range args[:required] {
- if len(arg) != 1 && strings.HasPrefix(arg, "-") {
- return i
- }
- }
- return required
- }
- func run() (err error, showUsage bool) {
- flagset.Parse(os.Args[1:])
- args := flagset.Args()
- if len(args) == 0 {
- return nil, true
- }
- cmdArgs := 0
- switch args[0] {
- case "initiate":
- cmdArgs = 2
- case "participate":
- cmdArgs = 3
- case "redeem":
- cmdArgs = 3
- case "refund":
- cmdArgs = 2
- case "extractsecret":
- cmdArgs = 2
- case "auditcontract":
- cmdArgs = 2
- default:
- return fmt.Errorf("unknown command %v", args[0]), true
- }
- nArgs := checkCmdArgLength(args[1:], cmdArgs)
- flagset.Parse(args[1+nArgs:])
- if nArgs < cmdArgs {
- return fmt.Errorf("%s: too few arguments", args[0]), true
- }
- if flagset.NArg() != 0 {
- return fmt.Errorf("unexpected argument: %s", flagset.Arg(0)), true
- }
- if *testnetFlag {
- chainParams = &chaincfg.TestNet3Params
- }
- if *regtestFlag {
- chainParams = &chaincfg.RegressionNetParams
- }
- var cmd command
- switch args[0] {
- case "initiate":
- cp2Addr, err := btcutil.DecodeAddress(args[1], chainParams)
- if err != nil {
- return fmt.Errorf("failed to decode participant address: %v", err), true
- }
- if !cp2Addr.IsForNet(chainParams) {
- return fmt.Errorf("participant address is not "+
- "intended for use on %v", chainParams.Name), true
- }
- cp2AddrP2PKH, ok := cp2Addr.(*btcutil.AddressPubKeyHash)
- if !ok {
- return errors.New("participant address is not P2PKH"), true
- }
- amountF64, err := strconv.ParseFloat(args[2], 64)
- if err != nil {
- return fmt.Errorf("failed to decode amount: %v", err), true
- }
- amount, err := btcutil.NewAmount(amountF64)
- if err != nil {
- return err, true
- }
- cmd = &initiateCmd{cp2Addr: cp2AddrP2PKH, amount: amount}
- case "participate":
- cp1Addr, err := btcutil.DecodeAddress(args[1], chainParams)
- if err != nil {
- return fmt.Errorf("failed to decode initiator address: %v", err), true
- }
- if !cp1Addr.IsForNet(chainParams) {
- return fmt.Errorf("initiator address is not "+
- "intended for use on %v", chainParams.Name), true
- }
- cp1AddrP2PKH, ok := cp1Addr.(*btcutil.AddressPubKeyHash)
- if !ok {
- return errors.New("initiator address is not P2PKH"), true
- }
- amountF64, err := strconv.ParseFloat(args[2], 64)
- if err != nil {
- return fmt.Errorf("failed to decode amount: %v", err), true
- }
- amount, err := btcutil.NewAmount(amountF64)
- if err != nil {
- return err, true
- }
- secretHash, err := hex.DecodeString(args[3])
- if err != nil {
- return errors.New("secret hash must be hex encoded"), true
- }
- if len(secretHash) != sha256.Size {
- return errors.New("secret hash has wrong size"), true
- }
- cmd = &participateCmd{cp1Addr: cp1AddrP2PKH, amount: amount, secretHash: secretHash}
- case "redeem":
- contract, err := hex.DecodeString(args[1])
- if err != nil {
- return fmt.Errorf("failed to decode contract: %v", err), true
- }
- contractTxBytes, err := hex.DecodeString(args[2])
- if err != nil {
- return fmt.Errorf("failed to decode contract transaction: %v", err), true
- }
- var contractTx wire.MsgTx
- err = contractTx.Deserialize(bytes.NewReader(contractTxBytes))
- if err != nil {
- return fmt.Errorf("failed to decode contract transaction: %v", err), true
- }
- secret, err := hex.DecodeString(args[3])
- if err != nil {
- return fmt.Errorf("failed to decode secret: %v", err), true
- }
- cmd = &redeemCmd{contract: contract, contractTx: &contractTx, secret: secret}
- case "refund":
- contract, err := hex.DecodeString(args[1])
- if err != nil {
- return fmt.Errorf("failed to decode contract: %v", err), true
- }
- contractTxBytes, err := hex.DecodeString(args[2])
- if err != nil {
- return fmt.Errorf("failed to decode contract transaction: %v", err), true
- }
- var contractTx wire.MsgTx
- err = contractTx.Deserialize(bytes.NewReader(contractTxBytes))
- if err != nil {
- return fmt.Errorf("failed to decode contract transaction: %v", err), true
- }
- cmd = &refundCmd{contract: contract, contractTx: &contractTx}
- case "extractsecret":
- redemptionTxBytes, err := hex.DecodeString(args[1])
- if err != nil {
- return fmt.Errorf("failed to decode redemption transaction: %v", err), true
- }
- var redemptionTx wire.MsgTx
- err = redemptionTx.Deserialize(bytes.NewReader(redemptionTxBytes))
- if err != nil {
- return fmt.Errorf("failed to decode redemption transaction: %v", err), true
- }
- secretHash, err := hex.DecodeString(args[2])
- if err != nil {
- return errors.New("secret hash must be hex encoded"), true
- }
- if len(secretHash) != sha256.Size {
- return errors.New("secret hash has wrong size"), true
- }
- cmd = &extractSecretCmd{redemptionTx: &redemptionTx, secretHash: secretHash}
- case "auditcontract":
- contract, err := hex.DecodeString(args[1])
- if err != nil {
- return fmt.Errorf("failed to decode contract: %v", err), true
- }
- contractTxBytes, err := hex.DecodeString(args[2])
- if err != nil {
- return fmt.Errorf("failed to decode contract transaction: %v", err), true
- }
- var contractTx wire.MsgTx
- err = contractTx.Deserialize(bytes.NewReader(contractTxBytes))
- if err != nil {
- return fmt.Errorf("failed to decode contract transaction: %v", err), true
- }
- cmd = &auditContractCmd{contract: contract, contractTx: &contractTx}
- }
- // Offline commands don't need to talk to the wallet.
- if cmd, ok := cmd.(offlineCommand); ok {
- return cmd.runOfflineCommand(), false
- }
- connect, err := normalizeAddress(*connectFlag, walletPort(chainParams))
- if err != nil {
- return fmt.Errorf("wallet server address: %v", err), true
- }
- connConfig := &rpc.ConnConfig{
- Host: connect,
- User: *rpcuserFlag,
- Pass: *rpcpassFlag,
- DisableTLS: true,
- HTTPPostMode: true,
- }
- client, err := rpc.New(connConfig, nil)
- if err != nil {
- return fmt.Errorf("rpc connect: %v", err), false
- }
- defer func() {
- client.Shutdown()
- client.WaitForShutdown()
- }()
- err = cmd.runCommand(client)
- return err, false
- }
- func normalizeAddress(addr string, defaultPort string) (hostport string, err error) {
- host, port, origErr := net.SplitHostPort(addr)
- if origErr == nil {
- return net.JoinHostPort(host, port), nil
- }
- addr = net.JoinHostPort(addr, defaultPort)
- _, _, err = net.SplitHostPort(addr)
- if err != nil {
- return "", origErr
- }
- return addr, nil
- }
- func walletPort(params *chaincfg.Params) string {
- switch params {
- case &chaincfg.MainNetParams:
- return "8332"
- case &chaincfg.TestNet3Params:
- return "18332"
- case &chaincfg.RegressionNetParams:
- return "18443"
- default:
- return ""
- }
- }
- // createSig creates and returns the serialized raw signature and compressed
- // pubkey for a transaction input signature. Due to limitations of the Bitcoin
- // Core RPC API, this requires dumping a private key and signing in the client,
- // rather than letting the wallet sign.
- func createSig(tx *wire.MsgTx, idx int, pkScript []byte, addr btcutil.Address,
- c *rpc.Client) (sig, pubkey []byte, err error) {
- wif, err := c.DumpPrivKey(addr)
- if err != nil {
- return nil, nil, err
- }
- sig, err = txscript.RawTxInSignature(tx, idx, pkScript, txscript.SigHashAll, wif.PrivKey)
- if err != nil {
- return nil, nil, err
- }
- return sig, wif.PrivKey.PubKey().SerializeCompressed(), nil
- }
- // fundRawTransaction calls the fundrawtransaction JSON-RPC method. It is
- // implemented manually as client support is currently missing from the
- // btcd/rpcclient package.
- func fundRawTransaction(c *rpc.Client, tx *wire.MsgTx, feePerKb btcutil.Amount) (fundedTx *wire.MsgTx, fee btcutil.Amount, err error) {
- var buf bytes.Buffer
- buf.Grow(tx.SerializeSize())
- tx.Serialize(&buf)
- param0, err := json.Marshal(hex.EncodeToString(buf.Bytes()))
- if err != nil {
- return nil, 0, err
- }
- param1, err := json.Marshal(struct {
- FeeRate float64 `json:"feeRate"`
- }{
- FeeRate: feePerKb.ToBTC(),
- })
- if err != nil {
- return nil, 0, err
- }
- params := []json.RawMessage{param0, param1}
- rawResp, err := c.RawRequest("fundrawtransaction", params)
- if err != nil {
- return nil, 0, err
- }
- var resp struct {
- Hex string `json:"hex"`
- Fee float64 `json:"fee"`
- ChangePos float64 `json:"changepos"`
- }
- err = json.Unmarshal(rawResp, &resp)
- if err != nil {
- return nil, 0, err
- }
- fundedTxBytes, err := hex.DecodeString(resp.Hex)
- if err != nil {
- return nil, 0, err
- }
- fundedTx = &wire.MsgTx{}
- err = fundedTx.Deserialize(bytes.NewReader(fundedTxBytes))
- if err != nil {
- return nil, 0, err
- }
- feeAmount, err := btcutil.NewAmount(resp.Fee)
- if err != nil {
- return nil, 0, err
- }
- return fundedTx, feeAmount, nil
- }
- // getFeePerKb queries the wallet for the transaction relay fee/kB to use and
- // the minimum mempool relay fee. It first tries to get the user-set fee in the
- // wallet. If unset, it attempts to find an estimate using estimatefee 6. If
- // both of these fail, it falls back to mempool relay fee policy.
- func getFeePerKb(c *rpc.Client) (useFee, relayFee btcutil.Amount, err error) {
- var netInfoResp struct {
- RelayFee float64 `json:"relayfee"`
- }
- var walletInfoResp struct {
- PayTxFee float64 `json:"paytxfee"`
- }
- var estimateResp struct {
- FeeRate float64 `json:"feerate"`
- }
- netInfoRawResp, err := c.RawRequest("getnetworkinfo", nil)
- if err == nil {
- err = json.Unmarshal(netInfoRawResp, &netInfoResp)
- if err != nil {
- return 0, 0, err
- }
- }
- walletInfoRawResp, err := c.RawRequest("getwalletinfo", nil)
- if err == nil {
- err = json.Unmarshal(walletInfoRawResp, &walletInfoResp)
- if err != nil {
- return 0, 0, err
- }
- }
- relayFee, err = btcutil.NewAmount(netInfoResp.RelayFee)
- if err != nil {
- return 0, 0, err
- }
- payTxFee, err := btcutil.NewAmount(walletInfoResp.PayTxFee)
- if err != nil {
- return 0, 0, err
- }
- // Use user-set wallet fee when set and not lower than the network relay
- // fee.
- if payTxFee != 0 {
- maxFee := payTxFee
- if relayFee > maxFee {
- maxFee = relayFee
- }
- return maxFee, relayFee, nil
- }
- params := []json.RawMessage{[]byte("6")}
- estimateRawResp, err := c.RawRequest("estimatesmartfee", params)
- if err != nil {
- return 0, 0, err
- }
- err = json.Unmarshal(estimateRawResp, &estimateResp)
- if err == nil && estimateResp.FeeRate > 0 {
- useFee, err = btcutil.NewAmount(estimateResp.FeeRate)
- if relayFee > useFee {
- useFee = relayFee
- }
- return useFee, relayFee, err
- }
- fmt.Println("warning: falling back to mempool relay fee policy")
- return relayFee, relayFee, nil
- }
- // getRawChangeAddress calls the getrawchangeaddress JSON-RPC method. It is
- // implemented manually as the rpcclient implementation always passes the
- // account parameter which was removed in Bitcoin Core 0.15.
- func getRawChangeAddress(c *rpc.Client) (btcutil.Address, error) {
- params := []json.RawMessage{[]byte(`"legacy"`)}
- rawResp, err := c.RawRequest("getrawchangeaddress", params)
- if err != nil {
- return nil, err
- }
- var addrStr string
- err = json.Unmarshal(rawResp, &addrStr)
- if err != nil {
- return nil, err
- }
- addr, err := btcutil.DecodeAddress(addrStr, chainParams)
- if err != nil {
- return nil, err
- }
- if !addr.IsForNet(chainParams) {
- return nil, fmt.Errorf("address %v is not intended for use on %v",
- addrStr, chainParams.Name)
- }
- if _, ok := addr.(*btcutil.AddressPubKeyHash); !ok {
- return nil, fmt.Errorf("getrawchangeaddress: address %v is not P2PKH",
- addr)
- }
- return addr, nil
- }
- func promptPublishTx(c *rpc.Client, tx *wire.MsgTx, name string) error {
- reader := bufio.NewReader(os.Stdin)
- for {
- fmt.Printf("Publish %s transaction? [y/N] ", name)
- answer, err := reader.ReadString('\n')
- if err != nil {
- return err
- }
- answer = strings.TrimSpace(strings.ToLower(answer))
- switch answer {
- case "y", "yes":
- case "n", "no", "":
- return nil
- default:
- fmt.Println("please answer y or n")
- continue
- }
- txHash, err := c.SendRawTransaction(tx, false)
- if err != nil {
- return fmt.Errorf("sendrawtransaction: %v", err)
- }
- fmt.Printf("Published %s transaction (%v)\n", name, txHash)
- return nil
- }
- }
- // contractArgs specifies the common parameters used to create the initiator's
- // and participant's contract.
- type contractArgs struct {
- them *btcutil.AddressPubKeyHash
- amount btcutil.Amount
- locktime int64
- secretHash []byte
- }
- // builtContract houses the details regarding a contract and the contract
- // payment transaction, as well as the transaction to perform a refund.
- type builtContract struct {
- contract []byte
- contractP2SH btcutil.Address
- contractTxHash *chainhash.Hash
- contractTx *wire.MsgTx
- contractFee btcutil.Amount
- refundTx *wire.MsgTx
- refundFee btcutil.Amount
- }
- // buildContract creates a contract for the parameters specified in args, using
- // wallet RPC to generate an internal address to redeem the refund and to sign
- // the payment to the contract transaction.
- func buildContract(c *rpc.Client, args *contractArgs) (*builtContract, error) {
- refundAddr, err := getRawChangeAddress(c)
- if err != nil {
- return nil, fmt.Errorf("getrawchangeaddress: %v", err)
- }
- refundAddrH, ok := refundAddr.(interface {
- Hash160() *[ripemd160.Size]byte
- })
- if !ok {
- return nil, errors.New("unable to create hash160 from change address")
- }
- contract, err := atomicSwapContract(refundAddrH.Hash160(), args.them.Hash160(),
- args.locktime, args.secretHash)
- if err != nil {
- return nil, err
- }
- contractP2SH, err := btcutil.NewAddressScriptHash(contract, chainParams)
- if err != nil {
- return nil, err
- }
- contractP2SHPkScript, err := txscript.PayToAddrScript(contractP2SH)
- if err != nil {
- return nil, err
- }
- feePerKb, minFeePerKb, err := getFeePerKb(c)
- if err != nil {
- return nil, err
- }
- unsignedContract := wire.NewMsgTx(txVersion)
- unsignedContract.AddTxOut(wire.NewTxOut(int64(args.amount), contractP2SHPkScript))
- unsignedContract, contractFee, err := fundRawTransaction(c, unsignedContract, feePerKb)
- if err != nil {
- return nil, fmt.Errorf("fundrawtransaction: %v", err)
- }
- contractTx, complete, err := c.SignRawTransaction(unsignedContract)
- if err != nil {
- return nil, fmt.Errorf("signrawtransaction: %v", err)
- }
- if !complete {
- return nil, errors.New("signrawtransaction: failed to completely sign contract transaction")
- }
- contractTxHash := contractTx.TxHash()
- refundTx, refundFee, err := buildRefund(c, contract, contractTx, feePerKb, minFeePerKb)
- if err != nil {
- return nil, err
- }
- return &builtContract{
- contract,
- contractP2SH,
- &contractTxHash,
- contractTx,
- contractFee,
- refundTx,
- refundFee,
- }, nil
- }
- func buildRefund(c *rpc.Client, contract []byte, contractTx *wire.MsgTx, feePerKb, minFeePerKb btcutil.Amount) (
- refundTx *wire.MsgTx, refundFee btcutil.Amount, err error) {
- contractP2SH, err := btcutil.NewAddressScriptHash(contract, chainParams)
- if err != nil {
- return nil, 0, err
- }
- contractP2SHPkScript, err := txscript.PayToAddrScript(contractP2SH)
- if err != nil {
- return nil, 0, err
- }
- contractTxHash := contractTx.TxHash()
- contractOutPoint := wire.OutPoint{Hash: contractTxHash, Index: ^uint32(0)}
- for i, o := range contractTx.TxOut {
- if bytes.Equal(o.PkScript, contractP2SHPkScript) {
- contractOutPoint.Index = uint32(i)
- break
- }
- }
- if contractOutPoint.Index == ^uint32(0) {
- return nil, 0, errors.New("contract tx does not contain a P2SH contract payment")
- }
- refundAddress, err := getRawChangeAddress(c)
- if err != nil {
- return nil, 0, fmt.Errorf("getrawchangeaddress: %v", err)
- }
- refundOutScript, err := txscript.PayToAddrScript(refundAddress)
- if err != nil {
- return nil, 0, err
- }
- pushes, err := txscript.ExtractAtomicSwapDataPushes(0, contract)
- if err != nil {
- // expected to only be called with good input
- panic(err)
- }
- refundAddr, err := btcutil.NewAddressPubKeyHash(pushes.RefundHash160[:], chainParams)
- if err != nil {
- return nil, 0, err
- }
- refundTx = wire.NewMsgTx(txVersion)
- refundTx.LockTime = uint32(pushes.LockTime)
- refundTx.AddTxOut(wire.NewTxOut(0, refundOutScript)) // amount set below
- refundSize := estimateRefundSerializeSize(contract, refundTx.TxOut)
- refundFee = txrules.FeeForSerializeSize(feePerKb, refundSize)
- refundTx.TxOut[0].Value = contractTx.TxOut[contractOutPoint.Index].Value - int64(refundFee)
- if txrules.IsDustOutput(refundTx.TxOut[0], minFeePerKb) {
- return nil, 0, fmt.Errorf("refund output value of %v is dust", btcutil.Amount(refundTx.TxOut[0].Value))
- }
- txIn := wire.NewTxIn(&contractOutPoint, nil, nil)
- txIn.Sequence = 0
- refundTx.AddTxIn(txIn)
- refundSig, refundPubKey, err := createSig(refundTx, 0, contract, refundAddr, c)
- if err != nil {
- return nil, 0, err
- }
- refundSigScript, err := refundP2SHContract(contract, refundSig, refundPubKey)
- if err != nil {
- return nil, 0, err
- }
- refundTx.TxIn[0].SignatureScript = refundSigScript
- if verify {
- e, err := txscript.NewEngine(contractTx.TxOut[contractOutPoint.Index].PkScript,
- refundTx, 0, txscript.StandardVerifyFlags, txscript.NewSigCache(10),
- txscript.NewTxSigHashes(refundTx), contractTx.TxOut[contractOutPoint.Index].Value)
- if err != nil {
- panic(err)
- }
- err = e.Execute()
- if err != nil {
- panic(err)
- }
- }
- return refundTx, refundFee, nil
- }
- func sha256Hash(x []byte) []byte {
- h := sha256.Sum256(x)
- return h[:]
- }
- func calcFeePerKb(absoluteFee btcutil.Amount, serializeSize int) float64 {
- return float64(absoluteFee) / float64(serializeSize) / 1e5
- }
- func (cmd *initiateCmd) runCommand(c *rpc.Client) error {
- var secret [secretSize]byte
- _, err := rand.Read(secret[:])
- if err != nil {
- return err
- }
- secretHash := sha256Hash(secret[:])
- // locktime after 500,000,000 (Tue Nov 5 00:53:20 1985 UTC) is interpreted
- // as a unix time rather than a block height.
- locktime := time.Now().Add(48 * time.Hour).Unix()
- b, err := buildContract(c, &contractArgs{
- them: cmd.cp2Addr,
- amount: cmd.amount,
- locktime: locktime,
- secretHash: secretHash,
- })
- if err != nil {
- return err
- }
- refundTxHash := b.refundTx.TxHash()
- contractFeePerKb := calcFeePerKb(b.contractFee, b.contractTx.SerializeSize())
- refundFeePerKb := calcFeePerKb(b.refundFee, b.refundTx.SerializeSize())
- fmt.Printf("Secret: %x\n", secret)
- fmt.Printf("Secret hash: %x\n\n", secretHash)
- fmt.Printf("Contract fee: %v (%0.8f BTC/kB)\n", b.contractFee, contractFeePerKb)
- fmt.Printf("Refund fee: %v (%0.8f BTC/kB)\n\n", b.refundFee, refundFeePerKb)
- fmt.Printf("Contract (%v):\n", b.contractP2SH)
- fmt.Printf("%x\n\n", b.contract)
- var contractBuf bytes.Buffer
- contractBuf.Grow(b.contractTx.SerializeSize())
- b.contractTx.Serialize(&contractBuf)
- fmt.Printf("Contract transaction (%v):\n", b.contractTxHash)
- fmt.Printf("%x\n\n", contractBuf.Bytes())
- var refundBuf bytes.Buffer
- refundBuf.Grow(b.refundTx.SerializeSize())
- b.refundTx.Serialize(&refundBuf)
- fmt.Printf("Refund transaction (%v):\n", &refundTxHash)
- fmt.Printf("%x\n\n", refundBuf.Bytes())
- return promptPublishTx(c, b.contractTx, "contract")
- }
- func (cmd *participateCmd) runCommand(c *rpc.Client) error {
- // locktime after 500,000,000 (Tue Nov 5 00:53:20 1985 UTC) is interpreted
- // as a unix time rather than a block height.
- locktime := time.Now().Add(24 * time.Hour).Unix()
- b, err := buildContract(c, &contractArgs{
- them: cmd.cp1Addr,
- amount: cmd.amount,
- locktime: locktime,
- secretHash: cmd.secretHash,
- })
- if err != nil {
- return err
- }
- refundTxHash := b.refundTx.TxHash()
- contractFeePerKb := calcFeePerKb(b.contractFee, b.contractTx.SerializeSize())
- refundFeePerKb := calcFeePerKb(b.refundFee, b.refundTx.SerializeSize())
- fmt.Printf("Contract fee: %v (%0.8f BTC/kB)\n", b.contractFee, contractFeePerKb)
- fmt.Printf("Refund fee: %v (%0.8f BTC/kB)\n\n", b.refundFee, refundFeePerKb)
- fmt.Printf("Contract (%v):\n", b.contractP2SH)
- fmt.Printf("%x\n\n", b.contract)
- var contractBuf bytes.Buffer
- contractBuf.Grow(b.contractTx.SerializeSize())
- b.contractTx.Serialize(&contractBuf)
- fmt.Printf("Contract transaction (%v):\n", b.contractTxHash)
- fmt.Printf("%x\n\n", contractBuf.Bytes())
- var refundBuf bytes.Buffer
- refundBuf.Grow(b.refundTx.SerializeSize())
- b.refundTx.Serialize(&refundBuf)
- fmt.Printf("Refund transaction (%v):\n", &refundTxHash)
- fmt.Printf("%x\n\n", refundBuf.Bytes())
- return promptPublishTx(c, b.contractTx, "contract")
- }
- func (cmd *redeemCmd) runCommand(c *rpc.Client) error {
- pushes, err := txscript.ExtractAtomicSwapDataPushes(0, cmd.contract)
- if err != nil {
- return err
- }
- if pushes == nil {
- return errors.New("contract is not an atomic swap script recognized by this tool")
- }
- recipientAddr, err := btcutil.NewAddressPubKeyHash(pushes.RecipientHash160[:],
- chainParams)
- if err != nil {
- return err
- }
- contractHash := btcutil.Hash160(cmd.contract)
- contractOut := -1
- for i, out := range cmd.contractTx.TxOut {
- sc, addrs, _, _ := txscript.ExtractPkScriptAddrs(out.PkScript, chainParams)
- if sc == txscript.ScriptHashTy &&
- bytes.Equal(addrs[0].(*btcutil.AddressScriptHash).Hash160()[:], contractHash) {
- contractOut = i
- break
- }
- }
- if contractOut == -1 {
- return errors.New("transaction does not contain a contract output")
- }
- addr, err := getRawChangeAddress(c)
- if err != nil {
- return fmt.Errorf("getrawchangeaddres: %v", err)
- }
- outScript, err := txscript.PayToAddrScript(addr)
- if err != nil {
- return err
- }
- contractTxHash := cmd.contractTx.TxHash()
- contractOutPoint := wire.OutPoint{
- Hash: contractTxHash,
- Index: uint32(contractOut),
- }
- feePerKb, minFeePerKb, err := getFeePerKb(c)
- if err != nil {
- return err
- }
- redeemTx := wire.NewMsgTx(txVersion)
- redeemTx.LockTime = uint32(pushes.LockTime)
- redeemTx.AddTxIn(wire.NewTxIn(&contractOutPoint, nil, nil))
- redeemTx.AddTxOut(wire.NewTxOut(0, outScript)) // amount set below
- redeemSize := estimateRedeemSerializeSize(cmd.contract, redeemTx.TxOut)
- fee := txrules.FeeForSerializeSize(feePerKb, redeemSize)
- redeemTx.TxOut[0].Value = cmd.contractTx.TxOut[contractOut].Value - int64(fee)
- if txrules.IsDustOutput(redeemTx.TxOut[0], minFeePerKb) {
- return fmt.Errorf("redeem output value of %v is dust", btcutil.Amount(redeemTx.TxOut[0].Value))
- }
- redeemSig, redeemPubKey, err := createSig(redeemTx, 0, cmd.contract, recipientAddr, c)
- if err != nil {
- return err
- }
- redeemSigScript, err := redeemP2SHContract(cmd.contract, redeemSig, redeemPubKey, cmd.secret)
- if err != nil {
- return err
- }
- redeemTx.TxIn[0].SignatureScript = redeemSigScript
- redeemTxHash := redeemTx.TxHash()
- redeemFeePerKb := calcFeePerKb(fee, redeemTx.SerializeSize())
- var buf bytes.Buffer
- buf.Grow(redeemTx.SerializeSize())
- redeemTx.Serialize(&buf)
- fmt.Printf("Redeem fee: %v (%0.8f BTC/kB)\n\n", fee, redeemFeePerKb)
- fmt.Printf("Redeem transaction (%v):\n", &redeemTxHash)
- fmt.Printf("%x\n\n", buf.Bytes())
- if verify {
- e, err := txscript.NewEngine(cmd.contractTx.TxOut[contractOutPoint.Index].PkScript,
- redeemTx, 0, txscript.StandardVerifyFlags, txscript.NewSigCache(10),
- txscript.NewTxSigHashes(redeemTx), cmd.contractTx.TxOut[contractOut].Value)
- if err != nil {
- panic(err)
- }
- err = e.Execute()
- if err != nil {
- panic(err)
- }
- }
- return promptPublishTx(c, redeemTx, "redeem")
- }
- func (cmd *refundCmd) runCommand(c *rpc.Client) error {
- pushes, err := txscript.ExtractAtomicSwapDataPushes(0, cmd.contract)
- if err != nil {
- return err
- }
- if pushes == nil {
- return errors.New("contract is not an atomic swap script recognized by this tool")
- }
- feePerKb, minFeePerKb, err := getFeePerKb(c)
- if err != nil {
- return err
- }
- refundTx, refundFee, err := buildRefund(c, cmd.contract, cmd.contractTx, feePerKb, minFeePerKb)
- if err != nil {
- return err
- }
- refundTxHash := refundTx.TxHash()
- var buf bytes.Buffer
- buf.Grow(refundTx.SerializeSize())
- refundTx.Serialize(&buf)
- refundFeePerKb := calcFeePerKb(refundFee, refundTx.SerializeSize())
- fmt.Printf("Refund fee: %v (%0.8f BTC/kB)\n\n", refundFee, refundFeePerKb)
- fmt.Printf("Refund transaction (%v):\n", &refundTxHash)
- fmt.Printf("%x\n\n", buf.Bytes())
- return promptPublishTx(c, refundTx, "refund")
- }
- func (cmd *extractSecretCmd) runCommand(c *rpc.Client) error {
- return cmd.runOfflineCommand()
- }
- func (cmd *extractSecretCmd) runOfflineCommand() error {
- // Loop over all pushed data from all inputs, searching for one that hashes
- // to the expected hash. By searching through all data pushes, we avoid any
- // issues that could be caused by the initiator redeeming the participant's
- // contract with some "nonstandard" or unrecognized transaction or script
- // type.
- for _, in := range cmd.redemptionTx.TxIn {
- pushes, err := txscript.PushedData(in.SignatureScript)
- if err != nil {
- return err
- }
- for _, push := range pushes {
- if bytes.Equal(sha256Hash(push), cmd.secretHash) {
- fmt.Printf("Secret: %x\n", push)
- return nil
- }
- }
- }
- return errors.New("transaction does not contain the secret")
- }
- func (cmd *auditContractCmd) runCommand(c *rpc.Client) error {
- return cmd.runOfflineCommand()
- }
- func (cmd *auditContractCmd) runOfflineCommand() error {
- contractHash160 := btcutil.Hash160(cmd.contract)
- contractOut := -1
- for i, out := range cmd.contractTx.TxOut {
- sc, addrs, _, err := txscript.ExtractPkScriptAddrs(out.PkScript, chainParams)
- if err != nil || sc != txscript.ScriptHashTy {
- continue
- }
- if bytes.Equal(addrs[0].(*btcutil.AddressScriptHash).Hash160()[:], contractHash160) {
- contractOut = i
- break
- }
- }
- if contractOut == -1 {
- return errors.New("transaction does not contain the contract output")
- }
- pushes, err := txscript.ExtractAtomicSwapDataPushes(0, cmd.contract)
- if err != nil {
- return err
- }
- if pushes == nil {
- return errors.New("contract is not an atomic swap script recognized by this tool")
- }
- if pushes.SecretSize != secretSize {
- return fmt.Errorf("contract specifies strange secret size %v", pushes.SecretSize)
- }
- contractAddr, err := btcutil.NewAddressScriptHash(cmd.contract, chainParams)
- if err != nil {
- return err
- }
- recipientAddr, err := btcutil.NewAddressPubKeyHash(pushes.RecipientHash160[:],
- chainParams)
- if err != nil {
- return err
- }
- refundAddr, err := btcutil.NewAddressPubKeyHash(pushes.RefundHash160[:],
- chainParams)
- if err != nil {
- return err
- }
- fmt.Printf("Contract address: %v\n", contractAddr)
- fmt.Printf("Contract value: %v\n", btcutil.Amount(cmd.contractTx.TxOut[contractOut].Value))
- fmt.Printf("Recipient address: %v\n", recipientAddr)
- fmt.Printf("Author's refund address: %v\n\n", refundAddr)
- fmt.Printf("Secret hash: %x\n\n", pushes.SecretHash[:])
- if pushes.LockTime >= int64(txscript.LockTimeThreshold) {
- t := time.Unix(pushes.LockTime, 0)
- fmt.Printf("Locktime: %v\n", t.UTC())
- reachedAt := time.Until(t).Truncate(time.Second)
- if reachedAt > 0 {
- fmt.Printf("Locktime reached in %v\n", reachedAt)
- } else {
- fmt.Printf("Contract refund time lock has expired\n")
- }
- } else {
- fmt.Printf("Locktime: block %v\n", pushes.LockTime)
- }
- return nil
- }
- // atomicSwapContract returns an output script that may be redeemed by one of
- // two signature scripts:
- //
- // <their sig> <their pubkey> <initiator secret> 1
- //
- // <my sig> <my pubkey> 0
- //
- // The first signature script is the normal redemption path done by the other
- // party and requires the initiator's secret. The second signature script is
- // the refund path performed by us, but the refund can only be performed after
- // locktime.
- func atomicSwapContract(pkhMe, pkhThem *[ripemd160.Size]byte, locktime int64, secretHash []byte) ([]byte, error) {
- b := txscript.NewScriptBuilder()
- b.AddOp(txscript.OP_IF) // Normal redeem path
- {
- // Require initiator's secret to be a known length that the redeeming
- // party can audit. This is used to prevent fraud attacks between two
- // currencies that have different maximum data sizes.
- b.AddOp(txscript.OP_SIZE)
- b.AddInt64(secretSize)
- b.AddOp(txscript.OP_EQUALVERIFY)
- // Require initiator's secret to be known to redeem the output.
- b.AddOp(txscript.OP_SHA256)
- b.AddData(secretHash)
- b.AddOp(txscript.OP_EQUALVERIFY)
- // Verify their signature is being used to redeem the output. This
- // would normally end with OP_EQUALVERIFY OP_CHECKSIG but this has been
- // moved outside of the branch to save a couple bytes.
- b.AddOp(txscript.OP_DUP)
- b.AddOp(txscript.OP_HASH160)
- b.AddData(pkhThem[:])
- }
- b.AddOp(txscript.OP_ELSE) // Refund path
- {
- // Verify locktime and drop it off the stack (which is not done by
- // CLTV).
- b.AddInt64(locktime)
- b.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
- b.AddOp(txscript.OP_DROP)
- // Verify our signature is being used to redeem the output. This would
- // normally end with OP_EQUALVERIFY OP_CHECKSIG but this has been moved
- // outside of the branch to save a couple bytes.
- b.AddOp(txscript.OP_DUP)
- b.AddOp(txscript.OP_HASH160)
- b.AddData(pkhMe[:])
- }
- b.AddOp(txscript.OP_ENDIF)
- // Complete the signature check.
- b.AddOp(txscript.OP_EQUALVERIFY)
- b.AddOp(txscript.OP_CHECKSIG)
- return b.Script()
- }
- // redeemP2SHContract returns the signature script to redeem a contract output
- // using the redeemer's signature and the initiator's secret. This function
- // assumes P2SH and appends the contract as the final data push.
- func redeemP2SHContract(contract, sig, pubkey, secret []byte) ([]byte, error) {
- b := txscript.NewScriptBuilder()
- b.AddData(sig)
- b.AddData(pubkey)
- b.AddData(secret)
- b.AddInt64(1)
- b.AddData(contract)
- return b.Script()
- }
- // refundP2SHContract returns the signature script to refund a contract output
- // using the contract author's signature after the locktime has been reached.
- // This function assumes P2SH and appends the contract as the final data push.
- func refundP2SHContract(contract, sig, pubkey []byte) ([]byte, error) {
- b := txscript.NewScriptBuilder()
- b.AddData(sig)
- b.AddData(pubkey)
- b.AddInt64(0)
- b.AddData(contract)
- return b.Script()
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement