mirror of
https://github.com/muun/recovery.git
synced 2025-11-11 06:20:16 -05:00
Release v0.3.0
This commit is contained in:
278
vendor/github.com/muun/libwallet/incoming_swap.go
generated
vendored
Normal file
278
vendor/github.com/muun/libwallet/incoming_swap.go
generated
vendored
Normal file
@@ -0,0 +1,278 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user