package libwallet import ( "bytes" "crypto/sha256" "encoding/hex" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/pkg/errors" ) type SubmarineSwap interface { SwapUuid() string Invoice() string Receiver() SubmarineSwapReceiver FundingOutput() SubmarineSwapFundingOutput PreimageInHex() string } type SubmarineSwapReceiver interface { Alias() string PublicKey() string } type SubmarineSwapFundingOutput interface { OutputAddress() string OutputAmount() int64 ConfirmationsNeeded() int UserLockTime() int64 UserRefundAddress() MuunAddress ServerPaymentHashInHex() string ServerPublicKeyInHex() string } func CreateAddressSubmarineSwap(publicKey *HDPublicKey) (MuunAddress, error) { pubkey, err := btcutil.NewAddressPubKey(publicKey.Raw(), publicKey.Network.network) if err != nil { return nil, err } pubkeyHash := pubkey.AddressPubKeyHash() address := pubkeyHash.String() return &muunAddress{address: address, version: addressSubmarineSwap, derivationPath: publicKey.Path}, nil } func addUserSignatureInputSubmarineSwap(input Input, index int, tx *wire.MsgTx, privateKey *HDPrivateKey, muunKey *HDPublicKey) (*wire.TxIn, error) { submarineSwap := input.SubmarineSwap() if submarineSwap == nil { return nil, errors.Errorf("submarine swap data is nil for ss input") } witnessScript, err := createWitnessScriptSubmarineSwap(submarineSwap.RefundAddress(), submarineSwap.PaymentHash256(), submarineSwap.ServerPublicKey(), submarineSwap.LockTime(), privateKey.Network) if err != nil { return nil, err } redeemScript, err := createNonNativeSegwitRedeemScript(witnessScript) if err != nil { return nil, errors.Wrapf(err, "failed to build reedem script for signing") } sig, err := signNonNativeSegwitInput(input, index, tx, privateKey, redeemScript, witnessScript) if err != nil { return nil, err } txInput := tx.TxIn[index] txInput.Witness = wire.TxWitness{sig, privateKey.PublicKey().Raw(), witnessScript} return txInput, nil } func createRedeemScriptSubmarineSwapForUser(publicKey *HDPublicKey) { } func createWitnessScriptSubmarineSwap(refundAddress string, paymentHash []byte, swapServerPubKey []byte, lockTime int64, network *Network) ([]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 := btcutil.DecodeAddress(refundAddress, network.network) if err != nil { return nil, errors.Wrapf(err, "refund address is invalid") } 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() } func ValidateSubmarineSwap(rawInvoice string, userPublicKey *HDPublicKey, muunPublicKey *HDPublicKey, swap SubmarineSwap, network *Network) error { invoice, err := ParseInvoice(rawInvoice, network) if err != nil { return errors.Wrapf(err, "failed to decode invoice") } // Check the payment hash matches serverPaymentHash, err := hex.DecodeString(swap.FundingOutput().ServerPaymentHashInHex()) if err != nil { return errors.Wrapf(err, "server payment hash is not valid hex") } if !bytes.Equal(invoice.PaymentHash[:], serverPaymentHash) { return errors.Errorf("payment hash doesn't match %v != %v", 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 errors.Wrapf(err, "failed to derive user key") } derivedMuunKey, err := muunPublicKey.DeriveTo(swapRefundAddress.DerivationPath()) if err != nil { return errors.Wrapf(err, "failed to derive muun key") } refundAddress, err := newMuunAddress(AddressVersion(swapRefundAddress.Version()), derivedUserKey, derivedMuunKey) if err != nil { return errors.Wrapf(err, "failed to generate refund address") } if refundAddress.Address() != swapRefundAddress.Address() { return errors.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 errors.Wrapf(err, "server pub key is not hex") } witnessScript, err := createWitnessScriptSubmarineSwap( swapRefundAddress.Address(), serverPaymentHash, serverPubKey, swap.FundingOutput().UserLockTime(), network) if err != nil { return errors.Wrapf(err, "failed to compute witness script") } redeemScript, err := createNonNativeSegwitRedeemScript(witnessScript) if err != nil { return errors.Wrapf(err, "failed to build redeem script") } address, err := btcutil.NewAddressScriptHash(redeemScript, network.network) if err != nil { return errors.Wrapf(err, "failed to build address for swap script") } if address.EncodeAddress() != swap.FundingOutput().OutputAddress() { return errors.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 errors.Wrapf(err, "preimagehex is not actually hex 🤔") } calculatedPaymentHash := sha256.Sum256(preimage) if !bytes.Equal(invoice.PaymentHash[:], calculatedPaymentHash[:]) { return errors.Errorf("payment hash doesn't match preimage (%v != hash(%v)", invoice.PaymentHash, swap.PreimageInHex()) } } return nil }