mirror of
https://github.com/muun/recovery.git
synced 2025-02-23 03:22:31 -05:00
153 lines
4.7 KiB
Go
153 lines
4.7 KiB
Go
package swaps
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/muun/libwallet/btcsuitew/btcutilw"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightningnetwork/lnd/zpay32"
|
|
"github.com/muun/libwallet/addresses"
|
|
)
|
|
|
|
func (swap *SubmarineSwap) validateV1(rawInvoice string, userPublicKey, muunPublicKey *KeyDescriptor, network *chaincfg.Params) error {
|
|
|
|
invoice, err := zpay32.Decode(rawInvoice, network)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode invoice: %w", err)
|
|
}
|
|
|
|
// Check the payment hash matches
|
|
|
|
serverPaymentHash, err := hex.DecodeString(swap.FundingOutput.ServerPaymentHashInHex)
|
|
if err != nil {
|
|
return fmt.Errorf("server payment hash is not valid hex: %w", err)
|
|
}
|
|
|
|
if !bytes.Equal(invoice.PaymentHash[:], serverPaymentHash) {
|
|
return fmt.Errorf("payment hash doesn't match %v != %v", hex.EncodeToString(invoice.PaymentHash[:]), swap.FundingOutput.ServerPaymentHashInHex)
|
|
}
|
|
|
|
// TODO: check that timelock is acceptable
|
|
|
|
// Validate that the refund address is one we can derive
|
|
|
|
swapRefundAddress := swap.FundingOutput.UserRefundAddress
|
|
derivedUserKey, err := userPublicKey.DeriveTo(swapRefundAddress.DerivationPath())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to derive user key: %w", err)
|
|
}
|
|
derivedMuunKey, err := muunPublicKey.DeriveTo(swapRefundAddress.DerivationPath())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to derive muun key: %w", err)
|
|
}
|
|
|
|
refundAddress, err := addresses.Create(
|
|
swapRefundAddress.Version(),
|
|
derivedUserKey,
|
|
derivedMuunKey,
|
|
swapRefundAddress.DerivationPath(),
|
|
network,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate refund address: %w", err)
|
|
}
|
|
|
|
if refundAddress.Address() != swapRefundAddress.Address() {
|
|
return fmt.Errorf("refund address doesn't match generated (%v != %v)", swapRefundAddress.Address(), refundAddress.Address())
|
|
}
|
|
|
|
// Check the swap's witness script is a valid swap script
|
|
|
|
serverPubKey, err := hex.DecodeString(swap.FundingOutput.ServerPublicKeyInHex)
|
|
if err != nil {
|
|
return fmt.Errorf("server pub key is not hex: %w", err)
|
|
}
|
|
|
|
witnessScript, err := CreateWitnessScriptSubmarineSwapV1(
|
|
swapRefundAddress.Address(),
|
|
serverPaymentHash,
|
|
serverPubKey,
|
|
swap.FundingOutput.UserLockTime,
|
|
network)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to compute witness script: %w", err)
|
|
}
|
|
|
|
redeemScript, err := createNonNativeSegwitRedeemScript(witnessScript)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build redeem script: %w", err)
|
|
}
|
|
|
|
address, err := btcutil.NewAddressScriptHash(redeemScript, network)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build address for swap script: %w", err)
|
|
}
|
|
|
|
if address.EncodeAddress() != swap.FundingOutput.OutputAddress {
|
|
return fmt.Errorf("address for swap script mismatch (%v != %v)", address.EncodeAddress(), swap.FundingOutput.OutputAddress)
|
|
}
|
|
|
|
if len(swap.PreimageInHex) > 0 {
|
|
preimage, err := hex.DecodeString(swap.PreimageInHex)
|
|
if err != nil {
|
|
return fmt.Errorf("preimagehex is not actually hex: %w", err)
|
|
}
|
|
|
|
calculatedPaymentHash := sha256.Sum256(preimage)
|
|
if !bytes.Equal(invoice.PaymentHash[:], calculatedPaymentHash[:]) {
|
|
return fmt.Errorf("payment hash doesn't match preimage (%v != hash(%v)", invoice.PaymentHash, swap.PreimageInHex)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func CreateWitnessScriptSubmarineSwapV1(refundAddress string, paymentHash []byte, swapServerPubKey []byte, lockTime int64, network *chaincfg.Params) ([]byte, error) {
|
|
|
|
// It turns out that the payment hash present in an invoice is just the SHA256 of the
|
|
// payment preimage, so we still have to do a pass of RIPEMD160 before pushing it to the
|
|
// script
|
|
paymentHash160 := ripemd160(paymentHash)
|
|
decodedRefundAddress, err := btcutilw.DecodeAddress(refundAddress, network)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("refund address is invalid: %w", err)
|
|
}
|
|
|
|
refundAddressHash := decodedRefundAddress.ScriptAddress()
|
|
|
|
builder := txscript.NewScriptBuilder()
|
|
builder.AddOp(txscript.OP_DUP)
|
|
|
|
// Condition to decide which branch to follow:
|
|
builder.AddOp(txscript.OP_HASH160).
|
|
AddData(paymentHash160).
|
|
AddOp(txscript.OP_EQUAL)
|
|
|
|
// SubmarineSwap service spending script, for successful LN payments:
|
|
builder.AddOp(txscript.OP_IF).
|
|
AddOp(txscript.OP_DROP).
|
|
AddData(swapServerPubKey)
|
|
|
|
// User spending script, for failed LN payments:
|
|
builder.AddOp(txscript.OP_ELSE).
|
|
AddInt64(lockTime).
|
|
AddOp(txscript.OP_CHECKLOCKTIMEVERIFY).
|
|
AddOp(txscript.OP_DROP).
|
|
AddOp(txscript.OP_DUP).
|
|
AddOp(txscript.OP_HASH160).
|
|
AddData(refundAddressHash).
|
|
AddOp(txscript.OP_EQUALVERIFY)
|
|
|
|
// Final verification for both branches:
|
|
builder.AddOp(txscript.OP_ENDIF).
|
|
AddOp(txscript.OP_CHECKSIG)
|
|
|
|
return builder.Script()
|
|
}
|