diff --git a/V1.go b/V1.go index 444dc82..ef04a46 100644 --- a/V1.go +++ b/V1.go @@ -1,12 +1,13 @@ package libwallet import ( + "fmt" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/muun/libwallet/addresses" - "github.com/pkg/errors" ) // CreateAddressV1 returns a P2PKH MuunAddress from a publicKey for use in TransactionSchemeV1 @@ -23,12 +24,12 @@ type coinV1 struct { func (c *coinV1) SignInput(index int, tx *wire.MsgTx, userKey *HDPrivateKey, _ *HDPublicKey) error { userKey, err := userKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive user key") + return fmt.Errorf("failed to derive user key: %w", err) } sig, err := c.signature(index, tx, userKey) if err != nil { - return errors.Wrapf(err, "failed to sign V1 input") + return fmt.Errorf("failed to sign V1 input: %w", err) } builder := txscript.NewScriptBuilder() @@ -36,7 +37,7 @@ func (c *coinV1) SignInput(index int, tx *wire.MsgTx, userKey *HDPrivateKey, _ * builder.AddData(userKey.PublicKey().Raw()) script, err := builder.Script() if err != nil { - return errors.Wrapf(err, "failed to generate signing script") + return fmt.Errorf("failed to generate signing script: %w", err) } txInput := tx.TxIn[index] @@ -52,7 +53,7 @@ func (c *coinV1) createRedeemScript(publicKey *HDPublicKey) ([]byte, error) { userAddress, err := btcutil.NewAddressPubKey(publicKey.Raw(), c.Network) if err != nil { - return nil, errors.Wrapf(err, "failed to generate address for user") + return nil, fmt.Errorf("failed to generate address for user: %w", err) } return txscript.PayToAddrScript(userAddress.AddressPubKeyHash()) @@ -62,17 +63,17 @@ func (c *coinV1) signature(index int, tx *wire.MsgTx, userKey *HDPrivateKey) ([] redeemScript, err := c.createRedeemScript(userKey.PublicKey()) if err != nil { - return nil, errors.Wrapf(err, "failed to build reedem script for signing") + return nil, fmt.Errorf("failed to build reedem script for signing: %w", err) } privKey, err := userKey.key.ECPrivKey() if err != nil { - return nil, errors.Wrapf(err, "failed to produce EC priv key for signing") + return nil, fmt.Errorf("failed to produce EC priv key for signing: %w", err) } sig, err := txscript.RawTxInSignature(tx, index, redeemScript, txscript.SigHashAll, privKey) if err != nil { - return nil, errors.Wrapf(err, "failed to sign V1 input") + return nil, fmt.Errorf("failed to sign V1 input: %w", err) } return sig, nil diff --git a/V2.go b/V2.go index e3f6f2b..efacd44 100644 --- a/V2.go +++ b/V2.go @@ -1,10 +1,12 @@ package libwallet import ( + "errors" + "fmt" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/muun/libwallet/addresses" - "github.com/pkg/errors" "github.com/btcsuite/btcd/wire" ) @@ -24,23 +26,23 @@ type coinV2 struct { func (c *coinV2) SignInput(index int, tx *wire.MsgTx, userKey *HDPrivateKey, muunKey *HDPublicKey) error { userKey, err := userKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive user key") + return fmt.Errorf("failed to derive user key: %w", err) } muunKey, err = muunKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive muun key") + return fmt.Errorf("failed to derive muun key: %w", err) } if len(c.MuunSignature) == 0 { - return errors.Errorf("muun signature must be present") + return errors.New("muun signature must be present") } txInput := tx.TxIn[index] redeemScript, err := createRedeemScriptV2(userKey.PublicKey(), muunKey) if err != nil { - return errors.Wrapf(err, "failed to build reedem script for signing") + return fmt.Errorf("failed to build reedem script for signing: %w", err) } sig, err := c.signature(index, tx, userKey.PublicKey(), muunKey, userKey) @@ -59,7 +61,7 @@ func (c *coinV2) SignInput(index int, tx *wire.MsgTx, userKey *HDPrivateKey, muu builder.AddData(redeemScript) script, err := builder.Script() if err != nil { - return errors.Wrapf(err, "failed to generate signing script") + return fmt.Errorf("failed to generate signing script: %w", err) } txInput.SignatureScript = script @@ -71,12 +73,12 @@ func (c *coinV2) FullySignInput(index int, tx *wire.MsgTx, userKey, muunKey *HDP derivedUserKey, err := userKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive user key") + return fmt.Errorf("failed to derive user key: %w", err) } derivedMuunKey, err := muunKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive muun key") + return fmt.Errorf("failed to derive muun key: %w", err) } muunSignature, err := c.signature(index, tx, derivedUserKey.PublicKey(), derivedMuunKey.PublicKey(), derivedMuunKey) @@ -92,17 +94,17 @@ func (c *coinV2) signature(index int, tx *wire.MsgTx, userKey, muunKey *HDPublic redeemScript, err := createRedeemScriptV2(userKey, muunKey) if err != nil { - return nil, errors.Wrapf(err, "failed to build reedem script for signing") + return nil, fmt.Errorf("failed to build reedem script for signing: %w", err) } privKey, err := signingKey.key.ECPrivKey() if err != nil { - return nil, errors.Wrapf(err, "failed to produce EC priv key for signing") + return nil, fmt.Errorf("failed to produce EC priv key for signing: %w", err) } sig, err := txscript.RawTxInSignature(tx, index, redeemScript, txscript.SigHashAll, privKey) if err != nil { - return nil, errors.Wrapf(err, "failed to sign V2 output") + return nil, fmt.Errorf("failed to sign V2 output: %w", err) } return sig, nil diff --git a/V3.go b/V3.go index cbbffdd..9ce9526 100644 --- a/V3.go +++ b/V3.go @@ -1,11 +1,12 @@ package libwallet import ( + "errors" + "fmt" + "github.com/btcsuite/btcutil" "github.com/muun/libwallet/addresses" - "github.com/pkg/errors" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" ) @@ -26,16 +27,16 @@ func (c *coinV3) SignInput(index int, tx *wire.MsgTx, userKey *HDPrivateKey, muu userKey, err := userKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive user key") + return fmt.Errorf("failed to derive user key: %w", err) } muunKey, err = muunKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive muun key") + return fmt.Errorf("failed to derive muun key: %w", err) } if len(c.MuunSignature) == 0 { - return errors.Errorf("muun signature must be present") + return errors.New("muun signature must be present") } witnessScript, err := createWitnessScriptV3(userKey.PublicKey(), muunKey) @@ -60,12 +61,12 @@ func (c *coinV3) FullySignInput(index int, tx *wire.MsgTx, userKey, muunKey *HDP derivedUserKey, err := userKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive user key") + return fmt.Errorf("failed to derive user key: %w", err) } derivedMuunKey, err := muunKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive muun key") + return fmt.Errorf("failed to derive muun key: %w", err) } muunSignature, err := c.signature(index, tx, derivedUserKey.PublicKey(), derivedMuunKey.PublicKey(), derivedMuunKey) @@ -94,7 +95,7 @@ func (c *coinV3) signature(index int, tx *wire.MsgTx, userKey *HDPublicKey, muun redeemScript, err := createRedeemScriptV3(userKey, muunKey) if err != nil { - return nil, errors.Wrapf(err, "failed to build reedem script for signing") + return nil, fmt.Errorf("failed to build reedem script for signing: %w", err) } return signNonNativeSegwitInput( diff --git a/V4.go b/V4.go index 7deef29..af53326 100644 --- a/V4.go +++ b/V4.go @@ -1,11 +1,11 @@ package libwallet import ( + "fmt" + "github.com/btcsuite/btcutil" "github.com/muun/libwallet/addresses" - "github.com/pkg/errors" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" ) @@ -27,16 +27,16 @@ func (c *coinV4) SignInput(index int, tx *wire.MsgTx, userKey *HDPrivateKey, muu userKey, err := userKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive user key") + return fmt.Errorf("failed to derive user key: %w", err) } muunKey, err = muunKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive muun key") + return fmt.Errorf("failed to derive muun key: %w", err) } if len(c.MuunSignature) == 0 { - return errors.Errorf("muun signature must be present") + return fmt.Errorf("muun signature must be present: %w", err) } witnessScript, err := createWitnessScriptV4(userKey.PublicKey(), muunKey) @@ -61,12 +61,12 @@ func (c *coinV4) FullySignInput(index int, tx *wire.MsgTx, userKey, muunKey *HDP derivedUserKey, err := userKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive user key") + return fmt.Errorf("failed to derive user key: %w", err) } derivedMuunKey, err := muunKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive muun key") + return fmt.Errorf("failed to derive muun key: %w", err) } muunSignature, err := c.signature(index, tx, derivedUserKey.PublicKey(), derivedMuunKey.PublicKey(), derivedMuunKey) diff --git a/address.go b/address.go index d4c3907..e23b261 100644 --- a/address.go +++ b/address.go @@ -1,6 +1,7 @@ package libwallet import ( + "fmt" "io/ioutil" "net/http" "net/url" @@ -8,10 +9,10 @@ import ( "strings" "github.com/muun/libwallet/addresses" + "github.com/muun/libwallet/errors" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcutil" - "github.com/pkg/errors" "google.golang.org/protobuf/proto" ) @@ -44,11 +45,11 @@ func GetPaymentURI(rawInput string, network *Network) (*MuunPaymentURI, error) { bitcoinUri, components := buildUriFromString(rawInput, bitcoinScheme) if components == nil { - return nil, errors.Errorf("failed to parse uri %v", rawInput) + return nil, errors.Errorf(ErrInvalidURI, "failed to parse uri %v", rawInput) } if components.Scheme != "bitcoin" { - return nil, errors.New("Invalid scheme") + return nil, errors.New(ErrInvalidURI, "Invalid scheme") } base58Address := components.Opaque @@ -61,7 +62,7 @@ func GetPaymentURI(rawInput string, network *Network) (*MuunPaymentURI, error) { queryValues, err := url.ParseQuery(components.RawQuery) if err != nil { - return nil, errors.Wrapf(err, "Couldnt parse query") + return nil, errors.Errorf(ErrInvalidURI, "Couldn't parse query: %v", err) } var label, message, amount string @@ -110,11 +111,11 @@ func GetPaymentURI(rawInput string, network *Network) (*MuunPaymentURI, error) { // Bech32 check validatedBase58Address, err := btcutil.DecodeAddress(base58Address, network.network) if err != nil { - return nil, err + return nil, fmt.Errorf("invalid address: %w", err) } if !validatedBase58Address.IsForNet(network.network) { - return nil, errors.Errorf("Network mismatch") + return nil, errors.New(ErrInvalidURI, "Network mismatch") } return &MuunPaymentURI{ @@ -131,7 +132,7 @@ func GetPaymentURI(rawInput string, network *Network) (*MuunPaymentURI, error) { func DoPaymentRequestCall(url string, network *Network) (*MuunPaymentURI, error) { req, err := http.NewRequest("GET", url, nil) if err != nil { - return nil, errors.Wrapf(err, "Failed to create request to: %s", url) + return nil, fmt.Errorf("failed to create request to: %s", url) } req.Header.Set("Accept", "application/bitcoin-paymentrequest") @@ -139,35 +140,35 @@ func DoPaymentRequestCall(url string, network *Network) (*MuunPaymentURI, error) client := &http.Client{} resp, err := client.Do(req) if err != nil { - return nil, errors.Wrapf(err, "Failed to make request to: %s", url) + return nil, errors.Errorf(ErrNetwork, "failed to make request to: %s", url) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { - return nil, errors.Wrapf(err, "Failed to read body response") + return nil, errors.Errorf(ErrNetwork, "Failed to read body response: %w", err) } payReq := &PaymentRequest{} err = proto.Unmarshal(body, payReq) if err != nil { - return nil, errors.Wrapf(err, "Failed to Unmarshall paymentRequest") + return nil, fmt.Errorf("failed to unmarshal payment request: %w", err) } payDetails := &PaymentDetails{} err = proto.Unmarshal(payReq.SerializedPaymentDetails, payDetails) if err != nil { - return nil, errors.Wrapf(err, "Failed to Unmarshall paymentDetails") + return nil, fmt.Errorf("failed to unmarshall payment details: %w", err) } if len(payDetails.Outputs) == 0 { - return nil, errors.New("No outputs provided") + return nil, fmt.Errorf("no outputs provided") } address, err := getAddressFromScript(payDetails.Outputs[0].Script, network) if err != nil { - return nil, errors.Wrapf(err, "Failed to get address") + return nil, fmt.Errorf("failed to get address: %w", err) } return &MuunPaymentURI{ diff --git a/address_test.go b/address_test.go index 0585bbe..2bad6dc 100644 --- a/address_test.go +++ b/address_test.go @@ -283,7 +283,7 @@ func TestDoPaymentRequestCall(t *testing.T) { serializedPaymentDetails, _ := proto.Marshal(&PaymentDetails{ Network: "test", Outputs: []*Output{ - &Output{ + { Script: script, Amount: 2500, }, diff --git a/addresses/v2.go b/addresses/v2.go index 5cfbb8d..1f38356 100644 --- a/addresses/v2.go +++ b/addresses/v2.go @@ -1,6 +1,8 @@ package addresses import ( + "fmt" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcutil" @@ -12,12 +14,12 @@ func CreateAddressV2(userKey, muunKey *hdkeychain.ExtendedKey, path string, netw script, err := CreateRedeemScriptV2(userKey, muunKey, network) if err != nil { - return nil, errors.Wrapf(err, "failed to generate redeem script v2") + return nil, fmt.Errorf("failed to generate redeem script v2: %w", err) } address, err := btcutil.NewAddressScriptHash(script, network) if err != nil { - return nil, errors.Wrapf(err, "failed to generate multisig address") + return nil, fmt.Errorf("failed to generate multisig address: %w", err) } return &WalletAddress{ diff --git a/addresses/v3.go b/addresses/v3.go index f447c07..71bb8ea 100644 --- a/addresses/v3.go +++ b/addresses/v3.go @@ -2,13 +2,13 @@ package addresses import ( "crypto/sha256" + "fmt" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" - "github.com/pkg/errors" ) func CreateAddressV3(userKey, muunKey *hdkeychain.ExtendedKey, path string, network *chaincfg.Params) (*WalletAddress, error) { @@ -33,7 +33,7 @@ func CreateAddressV3(userKey, muunKey *hdkeychain.ExtendedKey, path string, netw func CreateRedeemScriptV3(userKey, muunKey *hdkeychain.ExtendedKey, network *chaincfg.Params) ([]byte, error) { witnessScript, err := CreateWitnessScriptV3(userKey, muunKey, network) if err != nil { - return nil, errors.Wrapf(err, "failed to generate redeem script v3") + return nil, fmt.Errorf("failed to generate redeem script v3: %w", err) } return createNonNativeSegwitRedeemScript(witnessScript) diff --git a/addresses/v4.go b/addresses/v4.go index cb56037..b99dee6 100644 --- a/addresses/v4.go +++ b/addresses/v4.go @@ -2,11 +2,11 @@ package addresses import ( "crypto/sha256" + "fmt" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" - "github.com/pkg/errors" ) // CreateAddressV4 returns a P2WSH WalletAddress from a user HD-pubkey and a Muun co-signing HD-pubkey. @@ -14,7 +14,7 @@ func CreateAddressV4(userKey, muunKey *hdkeychain.ExtendedKey, path string, netw witnessScript, err := CreateWitnessScriptV4(userKey, muunKey, network) if err != nil { - return nil, errors.Wrapf(err, "failed to generate witness script v4") + return nil, fmt.Errorf("failed to generate witness script v4: %w", err) } witnessScript256 := sha256.Sum256(witnessScript) diff --git a/aescbc/aescbc.go b/aescbc/aescbc.go index 1c4ddc3..a563d98 100644 --- a/aescbc/aescbc.go +++ b/aescbc/aescbc.go @@ -4,8 +4,7 @@ import ( "bytes" "crypto/aes" "crypto/cipher" - - "github.com/pkg/errors" + "errors" ) const KeySize = 32 diff --git a/challenge_keys.go b/challenge_keys.go index f8de085..0f8a6de 100644 --- a/challenge_keys.go +++ b/challenge_keys.go @@ -5,10 +5,19 @@ import ( "crypto/sha256" "encoding/binary" "encoding/hex" + "errors" + "fmt" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcutil/base58" - "github.com/pkg/errors" +) + +const ( + // EncodedKeyLength is the size of a modern encoded key, as exported by the clients. + EncodedKeyLength = 147 + + // EncodedKeyLengthLegacy is the size of a legacy key, when salt resided only in the 2nd key. + EncodedKeyLengthLegacy = 136 ) type ChallengePrivateKey struct { @@ -45,7 +54,7 @@ func (k *ChallengePrivateKey) SignSha(payload []byte) ([]byte, error) { sig, err := k.key.Sign(hash[:]) if err != nil { - return nil, errors.Wrapf(err, "failed to sign payload") + return nil, fmt.Errorf("failed to sign payload: %w", err) } return sig.Serialize(), nil @@ -76,7 +85,7 @@ func (k *ChallengePrivateKey) DecryptKey(encryptedKey string, network *Network) privKey, err := NewHDPrivateKeyFromBytes(rawPrivKey, rawChainCode, network) if err != nil { - return nil, errors.Wrapf(err, "decrypting key: failed to parse key") + return nil, fmt.Errorf("decrypting key: failed to parse key: %w", err) } return &DecryptedPrivateKey{ @@ -89,10 +98,10 @@ func decodeEncryptedPrivateKey(encodedKey string) (*encryptedPrivateKey, error) reader := bytes.NewReader(base58.Decode(encodedKey)) version, err := reader.ReadByte() if err != nil { - return nil, errors.Wrapf(err, "decrypting key") + return nil, fmt.Errorf("decrypting key: %w", err) } if version != 2 { - return nil, errors.Errorf("decrypting key: found key version %v, expected 2", version) + return nil, fmt.Errorf("decrypting key: found key version %v, expected 2", version) } birthdayBytes := make([]byte, 2) @@ -102,23 +111,29 @@ func decodeEncryptedPrivateKey(encodedKey string) (*encryptedPrivateKey, error) n, err := reader.Read(birthdayBytes) if err != nil || n != 2 { - return nil, errors.Errorf("decrypting key: failed to read birthday") + return nil, errors.New("decrypting key: failed to read birthday") } birthday := binary.BigEndian.Uint16(birthdayBytes) n, err = reader.Read(rawPubEph) if err != nil || n != serializedPublicKeyLength { - return nil, errors.Errorf("decrypting key: failed to read pubeph") + return nil, errors.New("decrypting key: failed to read pubeph") } n, err = reader.Read(ciphertext) if err != nil || n != 64 { - return nil, errors.Errorf("decrypting key: failed to read ciphertext") + return nil, errors.New("decrypting key: failed to read ciphertext") } - n, err = reader.Read(recoveryCodeSalt) - if err != nil || n != 8 { - return nil, errors.Errorf("decrypting key: failed to read recoveryCodeSalt") + // NOTE: + // The very, very old format for encrypted keys didn't contain the encryption salt in the first + // of the two keys. This is a valid scenario, and a zero-filled salt can be returned. + if shouldHaveSalt(encodedKey) { + n, err = reader.Read(recoveryCodeSalt) + + if err != nil || n != 8 { + return nil, errors.New("decrypting key: failed to read recoveryCodeSalt") + } } result := &encryptedPrivateKey{ @@ -131,3 +146,7 @@ func decodeEncryptedPrivateKey(encodedKey string) (*encryptedPrivateKey, error) return result, nil } + +func shouldHaveSalt(encodedKey string) bool { + return len(encodedKey) > EncodedKeyLengthLegacy // not military-grade logic, but works for now +} diff --git a/challenge_keys_test.go b/challenge_keys_test.go index bef635a..4ec8144 100644 --- a/challenge_keys_test.go +++ b/challenge_keys_test.go @@ -1,6 +1,8 @@ package libwallet import ( + "bytes" + "encoding/hex" "reflect" "testing" @@ -102,3 +104,69 @@ func TestChallengeKeyCryptoV2(t *testing.T) { t.Fatal(err) } } + +func TestDecodeKeyWithOrWithoutSalt(t *testing.T) { + const ( + // The same encoded key, with one version missing the salt field: + saltedKey = "4LbSKwcepbbx4dPetoxvTWszb6mLyJHFhumzmdPRVprbn8XZBvFa6Ffarm6R3WGKutFzdxxJgQDdSHuYdjhDp1EZfSNbj12gXMND1AgmNijSxEua3LwVURU3nzWsvV5b1AsWEjJca24CaFY6T3C" + unsaltedKey = "5XEEts6mc9WV34krDWsqmpLcPCw2JkK8qJu3gFdZpP8ngkERuQEsaDvYrGkhXUpM6jQRtimTYm4XnBPujpo3MsdYBedsNVxvT3WC6uCCFuzNUZCoydVY39yJXbxva7naDxH5iTra" + ) + + expected := &encryptedPrivateKey{ + Version: 2, + Birthday: 376, + CipherText: unHex("f6af1ecd17052a81b75902c1712567cf1c650329875feb7e24af3e27235f384054ea549025e99dc2659f95bb6447cf861aa2ec0407ea74baf5a9d6a885ae184b"), + EphPublicKey: unHex("020a8d322dda8ff685d80b16681d4e87c109664cdc246a9d3625adfe0de203e71e"), + Salt: unHex("e3305526d0cd675f"), + } + + // Verify the salted version: + actual, err := decodeEncryptedPrivateKey(saltedKey) + if err != nil { + t.Fatal(err) + } + + assertDecodedKeysEqual(t, actual, expected) + + // Verify the unsalted version: + actual, err = decodeEncryptedPrivateKey(unsaltedKey) + if err != nil { + t.Fatal(err) + } + + expected.Salt = make([]byte, 8) // unsalted key should decode with zeroed field + + assertDecodedKeysEqual(t, actual, expected) +} + +func assertDecodedKeysEqual(t *testing.T, actual, expected *encryptedPrivateKey) { + if actual.Version != expected.Version { + t.Fatalf("version %v expected %v", actual.Version, expected.Version) + } + + if actual.Birthday != expected.Birthday { + t.Fatalf("birthday %v, expected %v", actual.Birthday, expected.Birthday) + } + + if !bytes.Equal(actual.CipherText, expected.CipherText) { + t.Fatalf("cipherText %x expected %x", actual.CipherText, expected.CipherText) + } + + if !bytes.Equal(actual.EphPublicKey, expected.EphPublicKey) { + t.Fatalf("ephPublicKey %x expected %x", actual.EphPublicKey, expected.EphPublicKey) + } + + if !bytes.Equal(actual.Salt, expected.Salt) { + t.Fatalf("salt %x expected %x", actual.Salt, expected.Salt) + } +} + +// unHex decodes a string without returning an error (panic instead). +func unHex(text string) []byte { + data, err := hex.DecodeString(text) + if err != nil { + panic(err) + } + + return data +} diff --git a/challenge_public_key.go b/challenge_public_key.go index 545ac14..1effdf8 100644 --- a/challenge_public_key.go +++ b/challenge_public_key.go @@ -3,10 +3,10 @@ package libwallet import ( "bytes" "encoding/binary" + "fmt" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcutil/base58" - "github.com/pkg/errors" ) type ChallengePublicKey struct { @@ -37,7 +37,7 @@ func (k *ChallengePublicKey) EncryptKey(privKey *HDPrivateKey, recoveryCodeSalt plaintext = append(plaintext, rawHDKey[privKeyStart:privKeyStart+privKeyLength]...) plaintext = append(plaintext, rawHDKey[chainCodeStart:chainCodeStart+chainCodeLength]...) if len(plaintext) != 64 { - return "", errors.Errorf("failed to encrypt key: expected payload of 64 bytes, found %v", len(plaintext)) + return "", fmt.Errorf("failed to encrypt key: expected payload of 64 bytes, found %v", len(plaintext)) } pubEph, ciphertext, err := encryptWithPubKey(k.pubKey, plaintext) diff --git a/encrypt.go b/encrypt.go index bb11a5d..808fcf2 100644 --- a/encrypt.go +++ b/encrypt.go @@ -7,6 +7,8 @@ import ( "crypto/rand" "crypto/sha256" "encoding/binary" + "errors" + "fmt" "io" "math" "math/big" @@ -15,7 +17,6 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcutil/base58" - "github.com/pkg/errors" ) const serializedPublicKeyLength = btcec.PubKeyBytesLenCompressed @@ -47,18 +48,18 @@ type hdPubKeyEncrypter struct { func addVariableBytes(writer io.Writer, data []byte) error { if len(data) > math.MaxUint16 { - return errors.Errorf("data length can't exceeed %v", math.MaxUint16) + return fmt.Errorf("data length can't exceeed %v", math.MaxUint16) } dataLen := uint16(len(data)) err := binary.Write(writer, binary.BigEndian, &dataLen) if err != nil { - return errors.Wrapf(err, "failed to write var bytes len") + return fmt.Errorf("failed to write var bytes len: %w", err) } n, err := writer.Write(data) if err != nil || n != len(data) { - return errors.Errorf("failed to write var bytes") + return errors.New("failed to write var bytes") } return nil @@ -88,12 +89,12 @@ func (e *hdPubKeyEncrypter) Encrypt(payload []byte) (string, error) { signingKey, err := e.senderKey.key.ECPrivKey() if err != nil { - return "", errors.Wrapf(err, "Encrypt: failed to extract signing key") + return "", fmt.Errorf("Encrypt: failed to extract signing key: %w", err) } encryptionKey, err := e.receiverKey.key.ECPubKey() if err != nil { - return "", errors.Wrapf(err, "Encrypt: failed to extract pub key") + return "", fmt.Errorf("Encrypt: failed to extract pub key: %w", err) } // Sign "payload || encryptionKey" to protect against payload reuse by 3rd parties @@ -103,34 +104,34 @@ func (e *hdPubKeyEncrypter) Encrypt(payload []byte) (string, error) { hash := sha256.Sum256(signaturePayload) senderSignature, err := btcec.SignCompact(btcec.S256(), signingKey, hash[:], false) if err != nil { - return "", errors.Wrapf(err, "Encrypt: failed to sign payload") + return "", fmt.Errorf("Encrypt: failed to sign payload: %w", err) } // plaintext is "senderSignature || payload" plaintext := bytes.NewBuffer(make([]byte, 0, 2+len(payload)+2+len(senderSignature))) err = addVariableBytes(plaintext, senderSignature) if err != nil { - return "", errors.Wrapf(err, "Encrypter: failed to add senderSignature") + return "", fmt.Errorf("Encrypter: failed to add senderSignature: %w", err) } err = addVariableBytes(plaintext, payload) if err != nil { - return "", errors.Wrapf(err, "Encrypter: failed to add payload") + return "", fmt.Errorf("Encrypter: failed to add payload: %w", err) } pubEph, sharedSecret, err := generateSharedEncryptionSecretForAES(encryptionKey) if err != nil { - return "", errors.Wrapf(err, "Encrypt: failed to generate shared encryption key") + return "", fmt.Errorf("Encrypt: failed to generate shared encryption key: %w", err) } blockCipher, err := aes.NewCipher(sharedSecret) if err != nil { - return "", errors.Wrapf(err, "Encrypt: new aes failed") + return "", fmt.Errorf("Encrypt: new aes failed: %w", err) } gcm, err := cipher.NewGCM(blockCipher) if err != nil { - return "", errors.Wrapf(err, "Encrypt: new gcm failed") + return "", fmt.Errorf("Encrypt: new gcm failed: %w", err) } nonce := randomBytes(gcm.NonceSize()) @@ -143,13 +144,13 @@ func (e *hdPubKeyEncrypter) Encrypt(payload []byte) (string, error) { err = addVariableBytes(result, []byte(e.receiverKey.Path)) if err != nil { - return "", errors.Wrapf(err, "Encrypt: failed to add receiver path") + return "", fmt.Errorf("Encrypt: failed to add receiver path: %w", err) } nonceLen := uint16(len(nonce)) err = binary.Write(result, binary.BigEndian, &nonceLen) if err != nil { - return "", errors.Wrapf(err, "Encrypt: failed to add nonce len") + return "", fmt.Errorf("Encrypt: failed to add nonce len: %w", err) } ciphertext := gcm.Seal(nil, nonce, plaintext.Bytes(), result.Bytes()) @@ -157,12 +158,12 @@ func (e *hdPubKeyEncrypter) Encrypt(payload []byte) (string, error) { // result is "additionalData || nonce || ciphertext" n, err := result.Write(nonce) if err != nil || n != len(nonce) { - return "", errors.Errorf("Encrypt: failed to add nonce") + return "", errors.New("Encrypt: failed to add nonce") } n, err = result.Write(ciphertext) if err != nil || n != len(ciphertext) { - return "", errors.Errorf("Encrypt: failed to add ciphertext") + return "", errors.New("Encrypt: failed to add ciphertext") } return base58.Encode(result.Bytes()), nil @@ -185,13 +186,13 @@ func extractVariableBytes(reader *bytes.Reader, limit int) ([]byte, error) { var len uint16 err := binary.Read(reader, binary.BigEndian, &len) if err != nil || int(len) > limit || int(len) > reader.Len() { - return nil, errors.Errorf("failed to read byte array len") + return nil, errors.New("failed to read byte array len") } result := make([]byte, len) n, err := reader.Read(result) if err != nil || n != int(len) { - return nil, errors.Errorf("failed to extract byte array") + return nil, errors.New("failed to extract byte array") } return result, nil @@ -210,22 +211,22 @@ func (d *hdPrivKeyDecrypter) Decrypt(payload string) ([]byte, error) { reader := bytes.NewReader(decoded) version, err := reader.ReadByte() if err != nil { - return nil, errors.Wrapf(err, "Decrypt: failed to read version byte") + return nil, fmt.Errorf("Decrypt: failed to read version byte: %w", err) } if version != PKEncryptionVersion { - return nil, errors.Errorf("Decrypt: found key version %v, expected %v", + return nil, fmt.Errorf("Decrypt: found key version %v, expected %v", version, PKEncryptionVersion) } rawPubEph := make([]byte, serializedPublicKeyLength) n, err := reader.Read(rawPubEph) if err != nil || n != serializedPublicKeyLength { - return nil, errors.Errorf("Decrypt: failed to read pubeph") + return nil, errors.New("Decrypt: failed to read pubeph") } receiverPath, err := extractVariableString(reader, maxDerivationPathLen) if err != nil { - return nil, errors.Wrapf(err, "Decrypt: failed to extract receiver path") + return nil, fmt.Errorf("Decrypt: failed to extract receiver path: %w", err) } // additionalDataSize is Whatever I've read so far plus two bytes for the nonce len @@ -234,24 +235,24 @@ func (d *hdPrivKeyDecrypter) Decrypt(payload string) ([]byte, error) { minCiphertextLen := 2 // an empty sig with no plaintext nonce, err := extractVariableBytes(reader, reader.Len()-minCiphertextLen) if err != nil || len(nonce) < minNonceLen { - return nil, errors.Errorf("Decrypt: failed to read nonce") + return nil, errors.New("Decrypt: failed to read nonce") } // What's left is the ciphertext ciphertext := make([]byte, reader.Len()) _, err = reader.Read(ciphertext) if err != nil { - return nil, errors.Wrapf(err, "Decrypt: failed to read ciphertext") + return nil, fmt.Errorf("Decrypt: failed to read ciphertext: %w", err) } receiverKey, err := d.receiverKey.DeriveTo(receiverPath) if err != nil { - return nil, errors.Wrapf(err, "Decrypt: failed to derive receiver key to path %v", receiverPath) + return nil, fmt.Errorf("Decrypt: failed to derive receiver key to path %v: %w", receiverPath, err) } encryptionKey, err := receiverKey.key.ECPrivKey() if err != nil { - return nil, errors.Wrapf(err, "Decrypt: failed to extract encryption key") + return nil, fmt.Errorf("Decrypt: failed to extract encryption key: %w", err) } var verificationKey *btcec.PublicKey @@ -259,7 +260,7 @@ func (d *hdPrivKeyDecrypter) Decrypt(payload string) ([]byte, error) { // Use the derived receiver key if the sender key is not provided verificationKey, err = receiverKey.PublicKey().key.ECPubKey() if err != nil { - return nil, errors.Wrapf(err, "Decrypt: failed to extract verification key") + return nil, fmt.Errorf("Decrypt: failed to extract verification key: %w", err) } } else if d.senderKey != nil { verificationKey = d.senderKey.key @@ -267,34 +268,34 @@ func (d *hdPrivKeyDecrypter) Decrypt(payload string) ([]byte, error) { sharedSecret, err := recoverSharedEncryptionSecretForAES(encryptionKey, rawPubEph) if err != nil { - return nil, errors.Wrapf(err, "Decrypt: failed to recover shared secret") + return nil, fmt.Errorf("Decrypt: failed to recover shared secret: %w", err) } blockCipher, err := aes.NewCipher(sharedSecret) if err != nil { - return nil, errors.Wrapf(err, "Decrypt: new aes failed") + return nil, fmt.Errorf("Decrypt: new aes failed: %w", err) } gcm, err := cipher.NewGCMWithNonceSize(blockCipher, len(nonce)) if err != nil { - return nil, errors.Wrapf(err, "Decrypt: new gcm failed") + return nil, fmt.Errorf("Decrypt: new gcm failed: %w", err) } plaintext, err := gcm.Open(nil, nonce, ciphertext, decoded[:additionalDataSize]) if err != nil { - return nil, errors.Wrapf(err, "Decrypt: AEAD failed") + return nil, fmt.Errorf("Decrypt: AEAD failed: %w", err) } plaintextReader := bytes.NewReader(plaintext) sig, err := extractVariableBytes(plaintextReader, maxSignatureLen) if err != nil { - return nil, errors.Wrapf(err, "Decrypt: failed to read sig") + return nil, fmt.Errorf("Decrypt: failed to read sig: %w", err) } data, err := extractVariableBytes(plaintextReader, plaintextReader.Len()) if err != nil { - return nil, errors.Wrapf(err, "Decrypt: failed to extract user data") + return nil, fmt.Errorf("Decrypt: failed to extract user data: %w", err) } signatureData := make([]byte, 0, len(sig)+serializedPublicKeyLength) @@ -303,10 +304,10 @@ func (d *hdPrivKeyDecrypter) Decrypt(payload string) ([]byte, error) { hash := sha256.Sum256(signatureData) signatureKey, _, err := btcec.RecoverCompact(btcec.S256(), sig, hash[:]) if err != nil { - return nil, errors.Wrapf(err, "Decrypt: failed to verify signature") + return nil, fmt.Errorf("Decrypt: failed to verify signature: %w", err) } if verificationKey != nil && !signatureKey.IsEqual(verificationKey) { - return nil, errors.Errorf("Decrypt: signing key mismatch") + return nil, errors.New("Decrypt: signing key mismatch") } return data, nil @@ -331,7 +332,7 @@ func encryptWithPubKey(pubKey *btcec.PublicKey, plaintext []byte) (*btcec.Public ciphertext, err := aescbc.EncryptNoPadding(paddedSerializeBigInt(aescbc.KeySize, sharedSecret), iv, plaintext) if err != nil { - return nil, nil, errors.Wrapf(err, "encryptWithPubKey: encrypt failed") + return nil, nil, fmt.Errorf("encryptWithPubKey: encrypt failed: %w", err) } return pubEph, ciphertext, nil @@ -342,7 +343,7 @@ func encryptWithPubKey(pubKey *btcec.PublicKey, plaintext []byte) (*btcec.Public func generateSharedEncryptionSecret(pubKey *btcec.PublicKey) (*btcec.PublicKey, *big.Int, error) { privEph, err := btcec.NewPrivateKey(btcec.S256()) if err != nil { - return nil, nil, errors.Wrapf(err, "generateSharedEncryptionSecretForAES: failed to generate key") + return nil, nil, fmt.Errorf("generateSharedEncryptionSecretForAES: failed to generate key: %w", err) } sharedSecret, _ := pubKey.ScalarMult(pubKey.X, pubKey.Y, privEph.D.Bytes()) @@ -374,7 +375,7 @@ func decryptWithPrivKey(privKey *btcec.PrivateKey, rawPubEph []byte, ciphertext plaintext, err := aescbc.DecryptNoPadding(paddedSerializeBigInt(aescbc.KeySize, sharedSecret), iv, ciphertext) if err != nil { - return nil, errors.Wrapf(err, "decryptWithPrivKey: failed to decrypt") + return nil, fmt.Errorf("decryptWithPrivKey: failed to decrypt: %w", err) } return plaintext, nil @@ -385,7 +386,7 @@ func decryptWithPrivKey(privKey *btcec.PrivateKey, rawPubEph []byte, ciphertext func recoverSharedEncryptionSecret(privKey *btcec.PrivateKey, rawPubEph []byte) (*big.Int, error) { pubEph, err := btcec.ParsePubKey(rawPubEph, btcec.S256()) if err != nil { - return nil, errors.Wrapf(err, "recoverSharedEncryptionSecretForAES: failed to parse pub eph") + return nil, fmt.Errorf("recoverSharedEncryptionSecretForAES: failed to parse pub eph: %w", err) } sharedSecret, _ := pubEph.ScalarMult(pubEph.X, pubEph.Y, privKey.D.Bytes()) diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..6b9c3e4 --- /dev/null +++ b/errors.go @@ -0,0 +1,22 @@ +package libwallet + +const ( + ErrUnknown = 1 + ErrInvalidURI = 2 + ErrNetwork = 3 + ErrInvalidPrivateKey = 4 + ErrInvalidDerivationPath = 5 + ErrInvalidInvoice = 6 +) + +func ErrorCode(err error) int64 { + type coder interface { + Code() int64 + } + switch e := err.(type) { + case coder: + return e.Code() + default: + return ErrUnknown + } +} diff --git a/errors/errors.go b/errors/errors.go new file mode 100644 index 0000000..c24b075 --- /dev/null +++ b/errors/errors.go @@ -0,0 +1,28 @@ +package errors + +import ( + "errors" + "fmt" +) + +type Error struct { + err error + code int64 +} + +func (e *Error) Error() string { + return e.err.Error() +} + +func (e *Error) Code() int64 { + return e.code +} + +func New(code int64, msg string) error { + return &Error{errors.New(msg), code} +} + +func Errorf(code int64, format string, a ...interface{}) error { + err := fmt.Errorf(format, a...) + return &Error{err, code} +} diff --git a/go.mod b/go.mod index f0d5a8c..5e9299f 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/pdfcpu/pdfcpu v0.3.8 github.com/pkg/errors v0.9.1 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 + golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect google.golang.org/protobuf v1.25.0 gopkg.in/gormigrate.v1 v1.6.0 diff --git a/go.sum b/go.sum index 235ed70..9eba078 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e h1:F2x1bq7RaNCIuqYpswggh1+c1JmwdnkHNC9wy1KDip0= git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4= github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ= github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 h1:MG93+PZYs9PyEsj/n5/haQu2gK0h4tUtSy9ejtMwWa0= @@ -268,12 +269,16 @@ golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190823064033-3a9bac650e44/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -281,7 +286,10 @@ golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayERXBdfZjUYoXEf5BTfDfh8= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -326,9 +334,15 @@ golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/hdprivatekey.go b/hdprivatekey.go index eed1f56..398c319 100644 --- a/hdprivatekey.go +++ b/hdprivatekey.go @@ -2,10 +2,11 @@ package libwallet import ( "crypto/sha256" + "errors" + "fmt" "strings" "github.com/muun/libwallet/hdpath" - "github.com/pkg/errors" "github.com/btcsuite/btcutil/hdkeychain" ) @@ -91,17 +92,17 @@ func (p *HDPrivateKey) DerivedAt(index int64, hardened bool) (*HDPrivateKey, err func (p *HDPrivateKey) DeriveTo(path string) (*HDPrivateKey, error) { if !strings.HasPrefix(path, p.Path) { - return nil, errors.Errorf("derivation path %v is not prefix of the keys path %v", path, p.Path) + return nil, fmt.Errorf("derivation path %v is not prefix of the keys path %v", path, p.Path) } firstPath, err := hdpath.Parse(p.Path) if err != nil { - return nil, errors.Wrapf(err, "couldn't parse derivation path %v", p.Path) + return nil, fmt.Errorf("couldn't parse derivation path %v: %w", p.Path, err) } secondPath, err := hdpath.Parse(path) if err != nil { - return nil, errors.Wrapf(err, "couldn't parse derivation path %v", path) + return nil, fmt.Errorf("couldn't parse derivation path %v: %w", path, err) } indexes := secondPath.IndexesFrom(firstPath) @@ -109,7 +110,7 @@ func (p *HDPrivateKey) DeriveTo(path string) (*HDPrivateKey, error) { for depth, index := range indexes { derivedKey, err = derivedKey.DerivedAt(int64(index.Index), index.Hardened) if err != nil { - return nil, errors.Wrapf(err, "failed to derive key at path %v on depth %v", path, depth) + return nil, fmt.Errorf("failed to derive key at path %v on depth %v: %w", path, depth, err) } } // The generated path has no names in it, so replace it diff --git a/hdpublickey.go b/hdpublickey.go index 4564e06..45203e4 100644 --- a/hdpublickey.go +++ b/hdpublickey.go @@ -1,10 +1,11 @@ package libwallet import ( + "errors" + "fmt" "strings" "github.com/muun/libwallet/hdpath" - "github.com/pkg/errors" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" @@ -43,7 +44,7 @@ func (p *HDPublicKey) String() string { func (p *HDPublicKey) DerivedAt(index int64) (*HDPublicKey, error) { if index&hdkeychain.HardenedKeyStart != 0 { - return nil, errors.Errorf("can't derive a hardened pub key (index %v)", index) + return nil, fmt.Errorf("can't derive a hardened pub key (index %v)", index) } child, err := p.key.Child(uint32(index)) @@ -58,29 +59,29 @@ func (p *HDPublicKey) DerivedAt(index int64) (*HDPublicKey, error) { func (p *HDPublicKey) DeriveTo(path string) (*HDPublicKey, error) { if !strings.HasPrefix(path, p.Path) { - return nil, errors.Errorf("derivation path %v is not prefix of the keys path %v", path, p.Path) + return nil, fmt.Errorf("derivation path %v is not prefix of the keys path %v", path, p.Path) } firstPath, err := hdpath.Parse(p.Path) if err != nil { - return nil, errors.Wrapf(err, "couldn't parse derivation path %v", p.Path) + return nil, fmt.Errorf("couldn't parse derivation path %v: %w", p.Path, err) } secondPath, err := hdpath.Parse(path) if err != nil { - return nil, errors.Wrapf(err, "couldn't parse derivation path %v", path) + return nil, fmt.Errorf("couldn't parse derivation path %v: %w", path, err) } indexes := secondPath.IndexesFrom(firstPath) derivedKey := p for depth, index := range indexes { if index.Hardened { - return nil, errors.Errorf("can't derive a hardened pub key (path %v)", path) + return nil, fmt.Errorf("can't derive a hardened pub key (path %v)", path) } derivedKey, err = derivedKey.DerivedAt(int64(index.Index)) if err != nil { - return nil, errors.Wrapf(err, "failed to derive key at path %v on depth %v", path, depth) + return nil, fmt.Errorf("failed to derive key at path %v on depth %v: %w", path, depth, err) } } // The generated path has no names in it, so replace it diff --git a/incoming_swap.go b/incoming_swap.go index 2cc6850..69c7fe8 100644 --- a/incoming_swap.go +++ b/incoming_swap.go @@ -3,6 +3,7 @@ package libwallet import ( "bytes" "crypto/sha256" + "errors" "fmt" "github.com/btcsuite/btcd/chaincfg" @@ -12,7 +13,6 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/muun/libwallet/hdpath" "github.com/muun/libwallet/sphinx" - "github.com/pkg/errors" ) type coinIncomingSwap struct { @@ -179,7 +179,7 @@ func (c *coinIncomingSwap) FullySignInput(index int, tx *wire.MsgTx, userKey, mu derivedMuunKey, err := muunKey.DeriveTo(secrets.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive muun key") + return fmt.Errorf("failed to derive muun key: %w", err) } muunSignature, err := c.signature(index, tx, userKey.PublicKey(), derivedMuunKey.PublicKey(), derivedMuunKey) diff --git a/invoice.go b/invoice.go index b983fda..983800a 100644 --- a/invoice.go +++ b/invoice.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/lightningnetwork/lnd/zpay32" - "github.com/pkg/errors" + "github.com/muun/libwallet/errors" ) // Invoice is muun's invoice struct @@ -27,11 +27,11 @@ func ParseInvoice(rawInput string, network *Network) (*Invoice, error) { _, components := buildUriFromString(rawInput, lightningScheme) if components == nil { - return nil, errors.Errorf("failed to parse uri %v", rawInput) + return nil, errors.Errorf(ErrInvalidInvoice, "failed to parse uri %v", rawInput) } if components.Scheme != "lightning" { - return nil, errors.Errorf("invalid scheme %v", components.Scheme) + return nil, errors.Errorf(ErrInvalidInvoice, "invalid scheme %v", components.Scheme) } invoice := components.Opaque @@ -44,7 +44,7 @@ func ParseInvoice(rawInput string, network *Network) (*Invoice, error) { parsedInvoice, err := zpay32.Decode(invoice, network.network) if err != nil { - return nil, errors.Wrapf(err, "Couldnt parse invoice") + return nil, errors.Errorf(ErrInvalidInvoice, "Couldn't parse invoice: %w", err) } var fallbackAdd *MuunPaymentURI @@ -52,7 +52,7 @@ func ParseInvoice(rawInput string, network *Network) (*Invoice, error) { if parsedInvoice.FallbackAddr != nil { fallbackAdd, err = GetPaymentURI(parsedInvoice.FallbackAddr.String(), network) if err != nil { - return nil, errors.Wrapf(err, "Couldnt get address") + return nil, errors.Errorf(ErrInvalidInvoice, "Couldn't get address: %w", err) } } diff --git a/invoices.go b/invoices.go index 36a63e4..2a4819f 100644 --- a/invoices.go +++ b/invoices.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/binary" "encoding/hex" + "errors" "fmt" "path" "time" @@ -16,9 +17,9 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/netann" "github.com/lightningnetwork/lnd/zpay32" - "github.com/pkg/errors" "github.com/muun/libwallet/hdpath" + "github.com/muun/libwallet/sphinx" "github.com/muun/libwallet/walletdb" ) @@ -185,6 +186,9 @@ func CreateInvoice(net *Network, userKey *HDPrivateKey, routeHints *RouteHints, if err != nil { return "", err } + if dbInvoice == nil { + return "", nil + } var paymentHash [32]byte copy(paymentHash[:], dbInvoice.PaymentHash) @@ -290,6 +294,54 @@ func ExposePreimage(paymentHash []byte) ([]byte, error) { return secrets.Preimage, nil } +func IsInvoiceFulfillable(paymentHash, onionBlob []byte, amount int64, userKey *HDPrivateKey, net *Network) error { + if len(paymentHash) != 32 { + return fmt.Errorf("IsInvoiceFulfillable: received invalid hash len %v", len(paymentHash)) + } + + // 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(paymentHash) + if err != nil { + return fmt.Errorf("IsInvoiceFulfillable: could not find invoice data for payment hash: %w", err) + } + + if len(onionBlob) == 0 { + return nil + } + + identityKeyPath := hdpath.MustParse(secrets.KeyPath).Child(identityKeyChildIndex) + + nodeHDKey, err := userKey.DeriveTo(identityKeyPath.String()) + if err != nil { + return fmt.Errorf("IsInvoiceFulfillable: failed to derive key: %w", err) + } + nodeKey, err := nodeHDKey.key.ECPrivKey() + if err != nil { + return fmt.Errorf("IsInvoiceFulfillable: failed to get priv key: %w", err) + } + + err = sphinx.Validate( + onionBlob, + paymentHash, + secrets.PaymentSecret, + nodeKey, + 0, // This is used internally by the sphinx decoder but it's not needed + lnwire.MilliSatoshi(uint64(amount)*1000), + net.network, + ) + if err != nil { + return fmt.Errorf("IsInvoiceFuflillable: invalid sphinx: %w", err) + } + + return nil +} + type IncomingSwap struct { FulfillmentTx []byte MuunSignature []byte diff --git a/invoices_test.go b/invoices_test.go index 6da1677..48cb584 100644 --- a/invoices_test.go +++ b/invoices_test.go @@ -421,6 +421,134 @@ func TestVerifyAndFulfillHtlcWithCollect(t *testing.T) { verifyInput(t, signedTx, hex.EncodeToString(swap.HtlcTx), 0, 0) } +func TestIsInvoiceFulfillable(t *testing.T) { + setup() + + network := Regtest() + + userKey, _ := NewHDPrivateKey(randomBytes(32), network) + userKey.Path = "m/schema:1'/recovery:1'" + muunKey, _ := NewHDPrivateKey(randomBytes(32), network) + muunKey.Path = "m/schema:1'/recovery:1'" + + secrets, err := GenerateInvoiceSecrets(userKey.PublicKey(), muunKey.PublicKey()) + if err != nil { + panic(err) + } + err = PersistInvoiceSecrets(secrets) + if err != nil { + panic(err) + } + + t.Run("single part payment", func(t *testing.T) { + invoiceSecrets := secrets.Get(0) + paymentHash := invoiceSecrets.PaymentHash + amt := int64(10000) + lockTime := int64(1000) + + nodePublicKey, err := invoiceSecrets.IdentityKey.key.ECPubKey() + if err != nil { + panic(err) + } + + onion := createSphinxPacket(nodePublicKey, paymentHash, invoiceSecrets.paymentSecret, amt, lockTime) + + if err := IsInvoiceFulfillable(paymentHash, onion, amt, userKey, network); err != nil { + t.Fatal(err) + } + }) + + t.Run("multi part payment fails", func(t *testing.T) { + invoiceSecrets := secrets.Get(0) + paymentHash := invoiceSecrets.PaymentHash + amt := int64(10000) + lockTime := int64(1000) + + nodePublicKey, err := invoiceSecrets.IdentityKey.key.ECPubKey() + if err != nil { + panic(err) + } + + onion := createMppSphinxPacket(nodePublicKey, paymentHash, invoiceSecrets.paymentSecret, amt, lockTime) + + if err := IsInvoiceFulfillable(paymentHash, onion, amt, userKey, network); err == nil { + t.Fatal("expected failure to fulfill mpp payment") + } + }) + + t.Run("non existant invoice", func(t *testing.T) { + paymentHash := randomBytes(32) + + if err := IsInvoiceFulfillable(paymentHash, []byte{}, 0, userKey, network); err == nil { + t.Fatal("expected failure to fulfill non existant invoice") + } + }) + + t.Run("invalid payment secret", func(t *testing.T) { + invoiceSecrets := secrets.Get(0) + paymentHash := invoiceSecrets.PaymentHash + amt := int64(10000) + lockTime := int64(1000) + + nodePublicKey, err := invoiceSecrets.IdentityKey.key.ECPubKey() + if err != nil { + panic(err) + } + + onion := createSphinxPacket(nodePublicKey, paymentHash, randomBytes(32), amt, lockTime) + + if err := IsInvoiceFulfillable(paymentHash, onion, amt, userKey, network); err == nil { + t.Fatal("expected error with random payment secret") + } + }) + + t.Run("muun 2 muun with no blob", func(t *testing.T) { + invoiceSecrets := secrets.Get(0) + paymentHash := invoiceSecrets.PaymentHash + + if err := IsInvoiceFulfillable(paymentHash, nil, 0, userKey, network); err != nil { + t.Fatal(err) + } + }) + + t.Run("invalid amount", func(t *testing.T) { + invoiceSecrets := secrets.Get(0) + paymentHash := invoiceSecrets.PaymentHash + amt := int64(10000) + lockTime := int64(1000) + + nodePublicKey, err := invoiceSecrets.IdentityKey.key.ECPubKey() + if err != nil { + panic(err) + } + + onion := createSphinxPacket(nodePublicKey, paymentHash, invoiceSecrets.paymentSecret, amt, lockTime) + + if err := IsInvoiceFulfillable(paymentHash, onion, amt-1, userKey, network); err == nil { + t.Fatal("expected error with invalid amount") + } + }) + + t.Run("validates amount", func(t *testing.T) { + invoiceSecrets := secrets.Get(0) + paymentHash := invoiceSecrets.PaymentHash + amt := int64(10000) + lockTime := int64(1000) + + nodePublicKey, err := invoiceSecrets.IdentityKey.key.ECPubKey() + if err != nil { + panic(err) + } + + onion := createSphinxPacket(nodePublicKey, paymentHash, invoiceSecrets.paymentSecret, amt, lockTime) + + if err := IsInvoiceFulfillable(paymentHash, onion, amt, userKey, network); err != nil { + t.Fatal(err) + } + }) + +} + func newAddressAt(userKey, muunKey *HDPrivateKey, keyPath string, network *Network) btcutil.Address { userPublicKey, err := userKey.PublicKey().DeriveTo(keyPath) if err != nil { @@ -483,6 +611,54 @@ func createSphinxPacket(nodePublicKey *btcec.PublicKey, paymentHash, paymentSecr return buf.Bytes() } +func createMppSphinxPacket( + nodePublicKey *btcec.PublicKey, + paymentHash, paymentSecret []byte, + amt, lockTime int64, +) []byte { + + var paymentPath sphinx.PaymentPath + paymentPath[0].NodePub = *nodePublicKey + + var secret [32]byte + copy(secret[:], paymentSecret) + uintAmount := uint64(amt * 1000) // msat are expected + uintLocktime := uint32(lockTime) + uintFwdAmount := uintAmount / 2 + tlvRecords := []tlv.Record{ + record.NewAmtToFwdRecord(&uintFwdAmount), + record.NewLockTimeRecord(&uintLocktime), + record.NewMPP(lnwire.MilliSatoshi(uintAmount), secret).Record(), + } + + b := &bytes.Buffer{} + tlv.MustNewStream(tlvRecords...).Encode(b) + hopPayload, err := sphinx.NewHopPayload(nil, b.Bytes()) + if err != nil { + panic(err) + } + paymentPath[0].HopPayload = hopPayload + + ephemeralKey, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + panic(err) + } + + pkt, err := sphinx.NewOnionPacket( + &paymentPath, ephemeralKey, paymentHash, sphinx.BlankPacketFiller) + if err != nil { + panic(err) + } + + var buf bytes.Buffer + err = pkt.Encode(&buf) + if err != nil { + panic(err) + } + + return buf.Bytes() +} + func serializeTx(tx *wire.MsgTx) []byte { var buf bytes.Buffer err := tx.Serialize(&buf) diff --git a/partiallysignedtransaction.go b/partiallysignedtransaction.go index 7266fed..002feb4 100644 --- a/partiallysignedtransaction.go +++ b/partiallysignedtransaction.go @@ -3,6 +3,8 @@ package libwallet import ( "bytes" "encoding/hex" + "errors" + "fmt" "github.com/muun/libwallet/addresses" @@ -11,7 +13,6 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" - "github.com/pkg/errors" ) type SigningExpectations struct { @@ -106,7 +107,7 @@ func NewPartiallySignedTransaction(inputs *InputList, rawTx []byte) (*PartiallyS tx := wire.NewMsgTx(0) err := tx.Deserialize(bytes.NewReader(rawTx)) if err != nil { - return nil, errors.Wrapf(err, "failed to decode tx") + return nil, fmt.Errorf("failed to decode tx: %w", err) } return &PartiallySignedTransaction{tx: tx, inputs: inputs.Inputs()}, nil @@ -128,13 +129,13 @@ func (p *PartiallySignedTransaction) Sign(userKey *HDPrivateKey, muunKey *HDPubl coins, err := p.coins(userKey.Network) if err != nil { - return nil, errors.Wrapf(err, "could not convert input data to coin") + return nil, fmt.Errorf("could not convert input data to coin: %w", err) } for i, coin := range coins { err = coin.SignInput(i, p.tx, userKey, muunKey) if err != nil { - return nil, errors.Wrapf(err, "failed to sign input") + return nil, fmt.Errorf("failed to sign input: %w", err) } } @@ -146,13 +147,13 @@ func (p *PartiallySignedTransaction) FullySign(userKey, muunKey *HDPrivateKey) ( coins, err := p.coins(userKey.Network) if err != nil { - return nil, errors.Wrapf(err, "could not convert input data to coin") + return nil, fmt.Errorf("could not convert input data to coin: %w", err) } for i, coin := range coins { err = coin.FullySignInput(i, p.tx, userKey, muunKey) if err != nil { - return nil, errors.Wrapf(err, "failed to sign input") + return nil, fmt.Errorf("failed to sign input: %w", err) } } @@ -169,11 +170,11 @@ func (p *PartiallySignedTransaction) Verify(expectations *SigningExpectations, u // If we were to receive more than that, we consider it invalid. if expectations.change != nil { if len(p.tx.TxOut) != 2 { - return errors.Errorf("expected destination and change outputs but found %v", len(p.tx.TxOut)) + return fmt.Errorf("expected destination and change outputs but found %v", len(p.tx.TxOut)) } } else { if len(p.tx.TxOut) != 1 { - return errors.Errorf("expected destination output only but found %v", len(p.tx.TxOut)) + return fmt.Errorf("expected destination output only but found %v", len(p.tx.TxOut)) } } @@ -208,12 +209,12 @@ func (p *PartiallySignedTransaction) Verify(expectations *SigningExpectations, u // Fail if not destination output was found in the TX. if toOutput == nil { - return errors.Errorf("destination output is not present") + return errors.New("destination output is not present") } // Verify destination output value matches expected amount if toOutput.Value != expectedAmount { - return errors.Errorf("destination amount is mismatched. found %v expected %v", toOutput.Value, expectedAmount) + return fmt.Errorf("destination amount is mismatched. found %v expected %v", toOutput.Value, expectedAmount) } /* @@ -238,25 +239,25 @@ func (p *PartiallySignedTransaction) Verify(expectations *SigningExpectations, u // Verify change output is spendable by the wallet. if expectedChange != nil { if changeOutput == nil { - return errors.Errorf("Change is not present") + return errors.New("change is not present") } expectedChangeAmount := actualTotal - expectedAmount - expectedFee if changeOutput.Value != expectedChangeAmount { - return errors.Errorf("Change amount is mismatched. found %v expected %v", + return fmt.Errorf("change amount is mismatched. found %v expected %v", changeOutput.Value, expectedChangeAmount) } derivedUserKey, err := userPublicKey.DeriveTo(expectedChange.DerivationPath()) if err != nil { - return errors.Wrapf(err, "failed to derive user key to change path %v", - expectedChange.DerivationPath()) + return fmt.Errorf("failed to derive user key to change path %v: %w", + expectedChange.DerivationPath(), err) } derivedMuunKey, err := muunPublickKey.DeriveTo(expectedChange.DerivationPath()) if err != nil { - return errors.Wrapf(err, "failed to derive muun key to change path %v", - expectedChange.DerivationPath()) + return fmt.Errorf("failed to derive muun key to change path %v: %w", + expectedChange.DerivationPath(), err) } expectedChangeAddress, err := addresses.Create( @@ -267,24 +268,24 @@ func (p *PartiallySignedTransaction) Verify(expectations *SigningExpectations, u network.network, ) if err != nil { - return errors.Wrapf(err, "failed to build the change address with version %v", - expectedChange.Version()) + return fmt.Errorf("failed to build the change address with version %v: %w", + expectedChange.Version(), err) } if expectedChangeAddress.Address() != expectedChange.Address() { - return errors.Errorf("mismatched change address. found %v, expected %v", + return fmt.Errorf("mismatched change address. found %v, expected %v", expectedChange.Address(), expectedChangeAddress.Address()) } actualFee := actualTotal - expectedAmount - expectedChangeAmount if actualFee != expectedFee { - return errors.Errorf("fee mismatched. found %v, expected %v", actualFee, expectedFee) + return fmt.Errorf("fee mismatched. found %v, expected %v", actualFee, expectedFee) } } else { actualFee := actualTotal - expectedAmount if actualFee >= expectedFee+dustThreshold { - return errors.Errorf("change output is too big to be burned as fee") + return errors.New("change output is too big to be burned as fee") } } @@ -301,11 +302,11 @@ func (p *PartiallySignedTransaction) Verify(expectations *SigningExpectations, u func addressToScript(address string, network *Network) ([]byte, error) { parsedAddress, err := btcutil.DecodeAddress(address, network.network) if err != nil { - return nil, errors.Wrapf(err, "failed to parse address %v", address) + return nil, fmt.Errorf("failed to parse address %v: %w", address, err) } script, err := txscript.PayToAddrScript(parsedAddress) if err != nil { - return nil, errors.Wrapf(err, "failed to generate script for address %v", address) + return nil, fmt.Errorf("failed to generate script for address %v: %w", address, err) } return script, nil } @@ -314,7 +315,7 @@ func newTransaction(tx *wire.MsgTx) (*Transaction, error) { var buf bytes.Buffer err := tx.Serialize(&buf) if err != nil { - return nil, errors.Wrapf(err, "failed to encode tx") + return nil, fmt.Errorf("failed to encode tx: %w", err) } return &Transaction{ @@ -426,6 +427,6 @@ func createCoin(input Input, network *Network) (coin, error) { Collect: btcutil.Amount(swap.CollectInSats()), }, nil default: - return nil, errors.Errorf("can't create coin from input version %v", version) + return nil, fmt.Errorf("can't create coin from input version %v", version) } } diff --git a/publickey.go b/publickey.go index 33451ad..f9c4812 100644 --- a/publickey.go +++ b/publickey.go @@ -1,8 +1,9 @@ package libwallet import ( + "fmt" + "github.com/btcsuite/btcd/btcec" - "github.com/pkg/errors" ) type PublicKey struct { @@ -12,7 +13,7 @@ type PublicKey struct { func NewPublicKeyFromBytes(bytes []byte) (*PublicKey, error) { key, err := btcec.ParsePubKey(bytes, btcec.S256()) if err != nil { - return nil, errors.Wrapf(err, "NewPublicKeyFromBytes: failed to parse pub key") + return nil, fmt.Errorf("NewPublicKeyFromBytes: failed to parse pub key: %w", err) } return &PublicKey{key}, nil diff --git a/segwit.go b/segwit.go index b2d6144..6615248 100644 --- a/segwit.go +++ b/segwit.go @@ -2,24 +2,24 @@ package libwallet import ( "crypto/sha256" + "fmt" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" - "github.com/pkg/errors" ) func signNativeSegwitInput(index int, tx *wire.MsgTx, privateKey *HDPrivateKey, witnessScript []byte, amount btcutil.Amount) ([]byte, error) { privKey, err := privateKey.key.ECPrivKey() if err != nil { - return nil, errors.Wrapf(err, "failed to produce EC priv key for signing") + return nil, fmt.Errorf("failed to produce EC priv key for signing: %w", err) } sigHashes := txscript.NewTxSigHashes(tx) sig, err := txscript.RawTxInWitnessSignature(tx, sigHashes, index, int64(amount), witnessScript, txscript.SigHashAll, privKey) if err != nil { - return nil, errors.Wrapf(err, "failed to sign V4 input") + return nil, fmt.Errorf("failed to sign V4 input: %w", err) } return sig, nil @@ -44,20 +44,20 @@ func signNonNativeSegwitInput(index int, tx *wire.MsgTx, privateKey *HDPrivateKe builder.AddData(redeemScript) script, err := builder.Script() if err != nil { - return nil, errors.Wrapf(err, "failed to generate signing script") + return nil, fmt.Errorf("failed to generate signing script: %w", err) } txInput.SignatureScript = script privKey, err := privateKey.key.ECPrivKey() if err != nil { - return nil, errors.Wrapf(err, "failed to produce EC priv key for signing") + return nil, fmt.Errorf("failed to produce EC priv key for signing: %w", err) } sigHashes := txscript.NewTxSigHashes(tx) sig, err := txscript.RawTxInWitnessSignature( tx, sigHashes, index, int64(amount), witnessScript, txscript.SigHashAll, privKey) if err != nil { - return nil, errors.Wrapf(err, "failed to sign V3 input") + return nil, fmt.Errorf("failed to sign V3 input: %w", err) } return sig, nil diff --git a/sphinx/sphinx.go b/sphinx/sphinx.go index f199471..8176dfe 100644 --- a/sphinx/sphinx.go +++ b/sphinx/sphinx.go @@ -44,15 +44,22 @@ func Validate( // Validate payment secret if it exists if payload.MPP != nil { paymentAddr := payload.MPP.PaymentAddr() + amountToForward := payload.ForwardingInfo().AmountToForward + total := payload.MultiPath().TotalMsat() if !bytes.Equal(paymentAddr[:], paymentSecret) { return errors.New("sphinx payment secret does not match") } - if amount != 0 && payload.ForwardingInfo().AmountToForward > amount { + + if amount != 0 && amountToForward > amount { return fmt.Errorf( - "sphinx payment amount does not match (%v != %v)", amount, payload.ForwardingInfo().AmountToForward, + "sphinx payment amount does not match (%v != %v)", amount, amountToForward, ) } + + if amountToForward < total { + return fmt.Errorf("payment is multipart. forwarded amt = %v, total amt = %v", amountToForward, total) + } } return nil } diff --git a/submarineSwapV1.go b/submarineSwapV1.go index 68b7586..3650e32 100644 --- a/submarineSwapV1.go +++ b/submarineSwapV1.go @@ -1,11 +1,13 @@ package libwallet import ( + "errors" + "fmt" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/muun/libwallet/swaps" - "github.com/pkg/errors" ) type coinSubmarineSwapV1 struct { @@ -24,7 +26,7 @@ func (c *coinSubmarineSwapV1) SignInput(index int, tx *wire.MsgTx, userKey *HDPr userKey, err := userKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive user key") + return fmt.Errorf("failed to derive user key: %w", err) } witnessScript, err := swaps.CreateWitnessScriptSubmarineSwapV1( @@ -40,7 +42,7 @@ func (c *coinSubmarineSwapV1) SignInput(index int, tx *wire.MsgTx, userKey *HDPr redeemScript, err := createNonNativeSegwitRedeemScript(witnessScript) if err != nil { - return errors.Wrapf(err, "failed to build reedem script for signing") + return fmt.Errorf("failed to build reedem script for signing: %w", err) } sig, err := signNonNativeSegwitInput( diff --git a/submarineSwapV2.go b/submarineSwapV2.go index c582a71..47fc8d3 100644 --- a/submarineSwapV2.go +++ b/submarineSwapV2.go @@ -1,11 +1,13 @@ package libwallet import ( + "errors" + "fmt" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/muun/libwallet/swaps" - "github.com/pkg/errors" ) type coinSubmarineSwapV2 struct { @@ -26,11 +28,11 @@ func (c *coinSubmarineSwapV2) SignInput(index int, tx *wire.MsgTx, userKey *HDPr userKey, err := userKey.DeriveTo(c.KeyPath) if err != nil { - return errors.Wrapf(err, "failed to derive user key") + return fmt.Errorf("failed to derive user key: %w", err) } if len(c.ServerSignature) == 0 { - return errors.Errorf("Swap server must provide signature") + return errors.New("swap server must provide signature") } witnessScript, err := swaps.CreateWitnessScriptSubmarineSwapV2( diff --git a/swaps/v2.go b/swaps/v2.go index 9c2b673..4c09860 100644 --- a/swaps/v2.go +++ b/swaps/v2.go @@ -11,7 +11,6 @@ import ( "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" "github.com/lightningnetwork/lnd/zpay32" - "github.com/pkg/errors" ) func (swap *SubmarineSwap) validateV2(rawInvoice string, userPublicKey, muunPublicKey *KeyDescriptor, originalExpirationInBlocks int64, network *chaincfg.Params) error { @@ -98,7 +97,7 @@ func (swap *SubmarineSwap) validateV2(rawInvoice string, userPublicKey, muunPubl if len(swap.PreimageInHex) > 0 { preimage, err := hex.DecodeString(swap.PreimageInHex) if err != nil { - return errors.Wrapf(err, "preimagehex is not actually hex 🤔") + return fmt.Errorf("preimageInHex is not valid hex: %w", err) } calculatedPaymentHash := sha256.Sum256(preimage) diff --git a/vendor/modules.txt b/vendor/modules.txt index 8a2c418..9922bf9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -162,6 +162,8 @@ golang.org/x/crypto/ripemd160 golang.org/x/crypto/salsa20/salsa golang.org/x/crypto/scrypt golang.org/x/crypto/ssh/terminal +# golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 +## explicit # golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 golang.org/x/image/ccitt # golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e diff --git a/walletdb/walletdb.go b/walletdb/walletdb.go index c3742a1..ec91c88 100644 --- a/walletdb/walletdb.go +++ b/walletdb/walletdb.go @@ -1,6 +1,7 @@ package walletdb import ( + "errors" "log" "time" @@ -45,7 +46,10 @@ func Open(path string) (*DB, error) { } func migrate(db *gorm.DB) error { - m := gormigrate.New(db, gormigrate.DefaultOptions, []*gormigrate.Migration{ + opts := gormigrate.Options{ + UseTransaction: true, + } + m := gormigrate.New(db, &opts, []*gormigrate.Migration{ { ID: "initial", Migrate: func(tx *gorm.DB) error { @@ -59,7 +63,14 @@ func migrate(db *gorm.DB) error { State string UsedAt *time.Time } - return tx.CreateTable(&Invoice{}).Error + // This guard exists because at some point migrations were run outside a + // transactional context and a user experimented problems with an invoices + // table that was already created but whose migration had not been properly + // recorded. + if !tx.HasTable(&Invoice{}) { + return tx.CreateTable(&Invoice{}).Error + } + return nil }, Rollback: func(tx *gorm.DB) error { return tx.DropTable("invoices").Error @@ -90,6 +101,11 @@ func (d *DB) SaveInvoice(invoice *Invoice) error { func (d *DB) FindFirstUnusedInvoice() (*Invoice, error) { var invoice Invoice if res := d.db.Where(&Invoice{State: InvoiceStateRegistered}).First(&invoice); res.Error != nil { + + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, res.Error } invoice.ShortChanId = invoice.ShortChanId | (1 << 63)