279 lines
7.3 KiB
Go
Raw Normal View History

2020-11-09 10:05:29 -03:00
package libwallet
import (
"bytes"
"crypto/sha256"
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/muun/libwallet/hdpath"
"github.com/muun/libwallet/sphinx"
"github.com/pkg/errors"
)
type coinIncomingSwap struct {
Network *chaincfg.Params
MuunSignature []byte
Sphinx []byte
HtlcTx []byte
PaymentHash256 []byte
SwapServerPublicKey []byte
ExpirationHeight int64
VerifyOutputAmount bool // used only for fulfilling swaps through IncomingSwap
}
func (c *coinIncomingSwap) SignInput(index int, tx *wire.MsgTx, userKey *HDPrivateKey, muunKey *HDPublicKey) error {
// Deserialize the HTLC transaction
htlcTx := wire.MsgTx{}
err := htlcTx.Deserialize(bytes.NewReader(c.HtlcTx))
if err != nil {
return fmt.Errorf("could not deserialize htlc tx: %w", err)
}
// Lookup invoice data matching this HTLC using the payment hash
db, err := openDB()
if err != nil {
return err
}
defer db.Close()
secrets, err := db.FindByPaymentHash(c.PaymentHash256)
if err != nil {
return fmt.Errorf("could not find invoice data for payment hash: %w", err)
}
// Recreate the HTLC script to verify it matches the transaction. For this
// we must derive the keys used in the HTLC script
htlcKeyPath := hdpath.MustParse(secrets.KeyPath).Child(htlcKeyChildIndex)
// Derive first the private key, which we are going to use for signing later
userPrivateKey, err := userKey.DeriveTo(htlcKeyPath.String())
if err != nil {
return err
}
userPublicKey := userPrivateKey.PublicKey()
muunPublicKey, err := muunKey.DeriveTo(htlcKeyPath.String())
if err != nil {
return err
}
htlcScript, err := c.createHtlcScript(userPublicKey, muunPublicKey)
if err != nil {
return fmt.Errorf("could not create htlc script: %w", err)
}
// Try to find the script we just built inside the HTLC output scripts
htlcOutputIndex, err := c.findHtlcOutputIndex(&htlcTx, htlcScript)
if err != nil {
return err
}
// Next, we must validate the sphinx data. We derive the client identity
// key used by this invoice with the key path stored in the db.
identityKeyPath := hdpath.MustParse(secrets.KeyPath).Child(identityKeyChildIndex)
nodeHDKey, err := userKey.DeriveTo(identityKeyPath.String())
if err != nil {
return err
}
nodeKey, err := nodeHDKey.key.ECPrivKey()
if err != nil {
return err
}
txInput := tx.TxIn[index]
if txInput.PreviousOutPoint.Hash != htlcTx.TxHash() {
return fmt.Errorf("expected fulfillment tx input to point to htlc tx")
}
if txInput.PreviousOutPoint.Index != uint32(htlcOutputIndex) {
return fmt.Errorf("expected fulfillment tx input to point to correct htlc output")
}
sigHashes := txscript.NewTxSigHashes(tx)
muunSigKey, err := muunPublicKey.key.ECPubKey()
if err != nil {
return err
}
// Verify Muun signature
htlcOutputAmount := htlcTx.TxOut[htlcOutputIndex].Value
err = verifyTxWitnessSignature(
tx,
sigHashes,
index,
htlcOutputAmount,
htlcScript,
c.MuunSignature,
muunSigKey,
)
if err != nil {
return fmt.Errorf("could not verify Muun signature for htlc: %w", err)
}
var outputAmount lnwire.MilliSatoshi
if c.VerifyOutputAmount {
outputAmount = lnwire.MilliSatoshi(tx.TxOut[0].Value * 1000)
}
// Now check the information we have against the sphinx created by the payer
if len(c.Sphinx) > 0 {
err = sphinx.Validate(
c.Sphinx,
c.PaymentHash256,
secrets.PaymentSecret,
nodeKey,
uint32(c.ExpirationHeight),
outputAmount,
c.Network,
)
if err != nil {
return fmt.Errorf("could not verify sphinx blob: %w", err)
}
}
// Sign the fulfillment tx
sig, err := signNativeSegwitInput(
index,
tx,
userPrivateKey,
htlcScript,
btcutil.Amount(htlcOutputAmount),
)
if err != nil {
return fmt.Errorf("could not sign fulfillment tx: %w", err)
}
txInput.Witness = wire.TxWitness{
secrets.Preimage,
sig,
c.MuunSignature,
htlcScript,
}
return nil
}
func (c *coinIncomingSwap) FullySignInput(index int, tx *wire.MsgTx, userKey, muunKey *HDPrivateKey) error {
// Lookup invoice data matching this HTLC using the payment hash
db, err := openDB()
if err != nil {
return err
}
defer db.Close()
secrets, err := db.FindByPaymentHash(c.PaymentHash256)
if err != nil {
return fmt.Errorf("could not find invoice data for payment hash: %w", err)
}
derivedMuunKey, err := muunKey.DeriveTo(secrets.KeyPath)
if err != nil {
return errors.Wrapf(err, "failed to derive muun key")
}
muunSignature, err := c.signature(index, tx, userKey.PublicKey(), derivedMuunKey.PublicKey(), derivedMuunKey)
if err != nil {
return err
}
c.MuunSignature = muunSignature
return c.SignInput(index, tx, userKey, muunKey.PublicKey())
}
func (c *coinIncomingSwap) createHtlcScript(userPublicKey, muunPublicKey *HDPublicKey) ([]byte, error) {
return createHtlcScript(
userPublicKey.Raw(),
muunPublicKey.Raw(),
c.SwapServerPublicKey,
c.ExpirationHeight,
c.PaymentHash256,
)
}
func (c *coinIncomingSwap) signature(index int, tx *wire.MsgTx, userKey *HDPublicKey, muunKey *HDPublicKey,
signingKey *HDPrivateKey) ([]byte, error) {
htlcTx := wire.MsgTx{}
err := htlcTx.Deserialize(bytes.NewReader(c.HtlcTx))
if err != nil {
return nil, fmt.Errorf("could not deserialize htlc tx: %w", err)
}
htlcScript, err := c.createHtlcScript(userKey, muunKey)
if err != nil {
return nil, fmt.Errorf("could not create htlc script: %w", err)
}
htlcOutputIndex, err := c.findHtlcOutputIndex(&htlcTx, htlcScript)
if err != nil {
return nil, err
}
prevOutAmount := htlcTx.TxOut[htlcOutputIndex].Value
sig, err := signNativeSegwitInput(
index,
tx,
signingKey,
htlcScript,
btcutil.Amount(prevOutAmount),
)
if err != nil {
return nil, fmt.Errorf("could not sign fulfillment tx: %w", err)
}
return sig, nil
}
func (c *coinIncomingSwap) findHtlcOutputIndex(htlcTx *wire.MsgTx, htlcScript []byte) (int, error) {
witnessHash := sha256.Sum256(htlcScript)
address, err := btcutil.NewAddressWitnessScriptHash(witnessHash[:], c.Network)
if err != nil {
return 0, fmt.Errorf("could not create htlc address: %w", err)
}
pkScript, err := txscript.PayToAddrScript(address)
if err != nil {
return 0, fmt.Errorf("could not create pk script: %w", err)
}
// Try to find the script we just built inside the HTLC output scripts
for i, out := range htlcTx.TxOut {
if bytes.Equal(pkScript, out.PkScript) {
return i, nil
}
}
return 0, errors.New("could not find valid htlc output in htlc tx")
}
func createHtlcScript(userPublicKey, muunPublicKey, swapServerPublicKey []byte, expiry int64, paymentHash []byte) ([]byte, error) {
sb := txscript.NewScriptBuilder()
sb.AddData(muunPublicKey)
sb.AddOp(txscript.OP_CHECKSIG)
sb.AddOp(txscript.OP_NOTIF)
sb.AddOp(txscript.OP_DUP)
sb.AddOp(txscript.OP_HASH160)
sb.AddData(btcutil.Hash160(swapServerPublicKey))
sb.AddOp(txscript.OP_EQUALVERIFY)
sb.AddOp(txscript.OP_CHECKSIGVERIFY)
sb.AddInt64(expiry)
sb.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
sb.AddOp(txscript.OP_ELSE)
sb.AddData(userPublicKey)
sb.AddOp(txscript.OP_CHECKSIGVERIFY)
sb.AddOp(txscript.OP_SIZE)
sb.AddInt64(32)
sb.AddOp(txscript.OP_EQUALVERIFY)
sb.AddOp(txscript.OP_HASH160)
sb.AddData(ripemd160(paymentHash))
sb.AddOp(txscript.OP_EQUAL)
sb.AddOp(txscript.OP_ENDIF)
return sb.Script()
}