Release 2.0.0

This commit is contained in:
Santiago Lezica
2021-01-29 18:51:08 -03:00
parent 8107c4478b
commit cef49eff22
209 changed files with 70157 additions and 926 deletions

View File

@@ -1,3 +1,4 @@
libwallet/.gitignore# binary
libwallet
.build

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View File

@@ -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)

View File

@@ -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{

View File

@@ -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{

View File

@@ -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)

View File

@@ -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)

View File

@@ -4,8 +4,7 @@ import (
"bytes"
"crypto/aes"
"crypto/cipher"
"github.com/pkg/errors"
"errors"
)
const KeySize = 32

View File

@@ -5,16 +5,33 @@ 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 {
key *btcec.PrivateKey
}
type encryptedPrivateKey struct {
Version uint8
Birthday uint16
EphPublicKey []byte // 33-byte compressed public-key
CipherText []byte // 64-byte encrypted text
Salt []byte // (optional) 8-byte salt
}
type DecryptedPrivateKey struct {
Key *HDPrivateKey
Birthday int
@@ -37,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
@@ -53,43 +70,12 @@ func (k *ChallengePrivateKey) PubKey() *ChallengePublicKey {
}
func (k *ChallengePrivateKey) DecryptKey(encryptedKey string, network *Network) (*DecryptedPrivateKey, error) {
reader := bytes.NewReader(base58.Decode(encryptedKey))
version, err := reader.ReadByte()
decoded, err := decodeEncryptedPrivateKey(encryptedKey)
if err != nil {
return nil, errors.Wrapf(err, "decrypting key")
}
if version != 2 {
return nil, errors.Errorf("decrypting key: found key version %v, expected 2", version)
return nil, err
}
birthdayBytes := make([]byte, 2)
rawPubEph := make([]byte, serializedPublicKeyLength)
ciphertext := make([]byte, 64)
recoveryCodeSalt := make([]byte, 8)
n, err := reader.Read(birthdayBytes)
if err != nil || n != 2 {
return nil, errors.Errorf("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")
}
n, err = reader.Read(ciphertext)
if err != nil || n != 64 {
return nil, errors.Errorf("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")
}
plaintext, err := decryptWithPrivKey(k.key, rawPubEph, ciphertext)
plaintext, err := decryptWithPrivKey(k.key, decoded.EphPublicKey, decoded.CipherText)
if err != nil {
return nil, err
}
@@ -99,11 +85,68 @@ 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{
privKey,
int(birthday),
int(decoded.Birthday),
}, nil
}
func decodeEncryptedPrivateKey(encodedKey string) (*encryptedPrivateKey, error) {
reader := bytes.NewReader(base58.Decode(encodedKey))
version, err := reader.ReadByte()
if err != nil {
return nil, fmt.Errorf("decrypting key: %w", err)
}
if version != 2 {
return nil, fmt.Errorf("decrypting key: found key version %v, expected 2", version)
}
birthdayBytes := make([]byte, 2)
rawPubEph := make([]byte, serializedPublicKeyLength)
ciphertext := make([]byte, 64)
recoveryCodeSalt := make([]byte, 8)
n, err := reader.Read(birthdayBytes)
if err != nil || n != 2 {
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.New("decrypting key: failed to read pubeph")
}
n, err = reader.Read(ciphertext)
if err != nil || n != 64 {
return nil, errors.New("decrypting key: failed to read ciphertext")
}
// 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{
Version: version,
Birthday: birthday,
EphPublicKey: rawPubEph,
CipherText: ciphertext,
Salt: recoveryCodeSalt,
}
return result, nil
}
func shouldHaveSalt(encodedKey string) bool {
return len(encodedKey) > EncodedKeyLengthLegacy // not military-grade logic, but works for now
}

View File

@@ -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)

View File

@@ -1,32 +1,137 @@
package libwallet
import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/muun/libwallet/emergencykit"
)
// EKInput input struct to fill the PDF
type EKInput struct {
FirstEncryptedKey string
FirstFingerprint string
SecondEncryptedKey string
SecondFingerprint string
}
// EKOutput with the html as string and the verification code
type EKOutput struct {
HTML string
VerificationCode string
Metadata string
}
// GenerateEmergencyKitHTML returns the translated html as a string along with the verification code
// GenerateEmergencyKitHTML returns the translated html as a string along with the verification
// code and the kit metadata, represented in an opaque string.
// After calling this method, clients should use their Chromium/WebKit implementations to render
// the HTML into a PDF (better done there), and then come back to call `AddEmergencyKitMetadata`
// and produce the final PDF (better done here).
func GenerateEmergencyKitHTML(ekParams *EKInput, language string) (*EKOutput, error) {
out, err := emergencykit.GenerateHTML(&emergencykit.Input{
moduleInput := &emergencykit.Input{
FirstEncryptedKey: ekParams.FirstEncryptedKey,
FirstFingerprint: ekParams.FirstFingerprint,
SecondEncryptedKey: ekParams.SecondEncryptedKey,
}, language)
if err != nil {
return nil, err
SecondFingerprint: ekParams.SecondFingerprint,
}
// Create the HTML and the verification code:
htmlWithCode, err := emergencykit.GenerateHTML(moduleInput, language)
if err != nil {
return nil, fmt.Errorf("GenerateEkHtml failed to render: %w", err)
}
// Create and serialize the metadata:
metadata, err := createEmergencyKitMetadata(ekParams)
if err != nil {
return nil, fmt.Errorf("GenerateEkHtml failed to create metadata: %w", err)
}
metadataBytes, err := json.Marshal(&metadata)
if err != nil {
return nil, fmt.Errorf("GenerateEkHtml failed to marshal %s: %w", string(metadataBytes), err)
}
output := &EKOutput{
HTML: htmlWithCode.HTML,
VerificationCode: htmlWithCode.VerificationCode,
Metadata: string(metadataBytes),
}
return output, nil
}
// AddEmergencyKitMetadata produces a copy of the PDF file at `srcFile` with embedded metadata,
// writing it into `dstFile`. The provided metadata must be the same opaque string produced by
// `GenerateEmergencyKitHTML`.
func AddEmergencyKitMetadata(metadataText string, srcFile string, dstFile string) error {
// Initialize the MetadataWriter:
metadataWriter := &emergencykit.MetadataWriter{
SrcFile: srcFile,
DstFile: dstFile,
}
// Deserialize the metadata:
var metadata emergencykit.Metadata
err := json.Unmarshal([]byte(metadataText), &metadata)
if err != nil {
return fmt.Errorf("AddEkMetadata failed to unmarshal: %w", err)
}
err = metadataWriter.WriteMetadata(&metadata)
if err != nil {
return fmt.Errorf("AddEkMetadata failed to write metadata: %w", err)
}
return nil
}
func createEmergencyKitMetadata(ekParams *EKInput) (*emergencykit.Metadata, error) {
// NOTE:
// This method would be more naturally placed in the `emergencykit` module, but given the current
// project structure (heavily determined by `gomobile` and the need for top-level bindings) and
// the use of `decodeEncryptedPrivateKey` this isn't possible. Instead, we peek through the layer
// boundary to craft the object here.
// Decode both keys, to extract their inner properties:
firstKey, err := decodeEncryptedPrivateKey(ekParams.FirstEncryptedKey)
if err != nil {
return nil, fmt.Errorf("createEkMetadata failed to decode first key: %w", err)
}
secondKey, err := decodeEncryptedPrivateKey(ekParams.SecondEncryptedKey)
if err != nil {
return nil, fmt.Errorf("createEkMetadata failed to decode second key: %w", err)
}
// Obtain the list of checksumed output descriptors:
descriptors := emergencykit.GetDescriptors(&emergencykit.DescriptorsData{
FirstFingerprint: ekParams.FirstFingerprint,
SecondFingerprint: ekParams.SecondFingerprint,
})
// Create the keys for the key array:
keys := []*emergencykit.MetadataKey{
createEmergencyKitMetadataKey(firstKey),
createEmergencyKitMetadataKey(secondKey),
}
metadata := &emergencykit.Metadata{
Version: 2,
BirthdayBlock: int(secondKey.Birthday),
EncryptedKeys: keys,
OutputDescriptors: descriptors,
}
return metadata, nil
}
func createEmergencyKitMetadataKey(key *encryptedPrivateKey) *emergencykit.MetadataKey {
return &emergencykit.MetadataKey{
DhPubKey: hex.EncodeToString(key.EphPublicKey),
EncryptedPrivKey: hex.EncodeToString(key.CipherText),
Salt: hex.EncodeToString(key.Salt),
}
return &EKOutput{
HTML: out.HTML,
VerificationCode: out.VerificationCode,
}, nil
}

View File

@@ -2,7 +2,6 @@ package emergencykit
type pageData struct {
Css string
Logo string
Content string
}
@@ -11,17 +10,11 @@ type contentData struct {
SecondEncryptedKey string
VerificationCode string
CurrentDate string
Descriptors string
IconHelp string
IconPadlock string
}
const logo = `
<svg class="logo" width="65" height="12" viewBox="0 0 65 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.89661 11.8674V3.24807C2.89661 3.24807 3.72365 2.86307 5.30403 2.86307C6.88441 2.86307 7.58862 3.41307 7.58862 4.96093V11.8674H9.85684V4.96093C9.85684 4.26164 9.85684 3.72736 9.84865 3.31878C10.2089 3.13021 10.9132 2.86307 12.0759 2.86307C13.8774 2.86307 14.5489 3.41307 14.5489 4.96093V11.8674H16.9972V4.96093C16.9972 3.91593 16.7679 2.82378 15.9491 2.03021C15.1221 1.23664 13.9429 0.710205 12.1005 0.710205C10.4464 0.710205 8.98886 1.25235 8.70226 1.37021C7.77696 0.953777 6.47499 0.710205 5.32041 0.710205C3.07675 0.710205 0.456431 1.39378 0.456431 1.39378V11.8674H2.89661Z" fill="#2474CD"/>
<path d="M32.8274 0.710205V11.1838C32.8274 11.1838 30.2309 11.8674 27.7067 11.8674C25.8819 11.8674 24.4268 11.3331 23.6149 10.5474C22.803 9.7538 22.5859 8.66166 22.5859 7.61665V0.710205H24.9895V7.6088C24.9895 9.15666 25.9542 9.70666 27.7227 9.70666C29.4913 9.70666 30.4318 9.32166 30.4318 9.32166V0.710205H32.8274Z" fill="#2474CD"/>
<path d="M48.9016 0.710205V11.1838C48.9016 11.1838 46.3051 11.8674 43.7809 11.8674C41.9561 11.8674 40.501 11.3331 39.6891 10.5474C38.8772 9.7538 38.6602 8.66166 38.6602 7.61665V0.710205H41.0638V7.6088C41.0638 9.15666 42.0284 9.70666 43.797 9.70666C45.5655 9.70666 46.506 9.32166 46.506 9.32166V0.710205H48.9016Z" fill="#2474CD"/>
<path d="M54.7012 11.8674V1.39378C54.7012 1.39378 57.2977 0.710205 59.8219 0.710205C61.6467 0.710205 63.1017 1.24449 63.9137 2.03021C64.7256 2.82378 64.9426 3.91593 64.9426 4.96093V11.8674H62.539V4.96879C62.539 3.42093 61.5744 2.87093 59.8058 2.87093C58.0373 2.87093 57.0967 3.25593 57.0967 3.25593V11.8674H54.7012Z" fill="#2474CD"/>
</svg>
`
const page = `
<!DOCTYPE html>
<html lang="en">
@@ -36,7 +29,6 @@ const page = `
</head>
<body>
{{.Logo}}
{{.Content}}
</body>
</html>
@@ -44,225 +36,322 @@ const page = `
const contentEN = `
<header>
<div class="title">
<h1>Emergency Kit</h1>
<date>Created on {{.CurrentDate}}</date>
</div>
<div class="verification-code">
<h4>Verification code</h4>
<code>{{.VerificationCode}}</code>
</div>
<h1>Emergency Kit</h1>
<h2>Verification <span class="verification-code">#{{.VerificationCode}}</span></p>
</header>
<section>
<h2>About this document</h2>
<p>Here you'll find the encrypted information you need to transfer your money out of your Muun wallet without
requiring collaboration from anyone, including Muun's own software and servers.</p>
<p>This includes all your private keys (securely encrypted with your Recovery Code) and some additional data related
to your wallet.</p>
<p>With this document and your recovery code at hand, you have complete ownership of your money. Nobody else has
all the pieces. This is why Bitcoin was created: to give people full control of the money they rightly own.</p>
</section>
<section>
<h2>Recovering your money</h2>
<p>To move forward with the transfer of funds, we recommend using our
<a href="https://github.com/muun/recovery">open-source Recovery Tool</a>. It's available for the whole world to
download and examine, and it will always be.</p>
<p>We created it to assist you with the process, but nothing stops you from doing it manually if you're so
inclined.</p>
<p>Go to <strong>github.com/muun/recovery</strong> and follow the instructions to easily transfer your money to a Bitcoin
address of your choosing.</p>
</section>
<section>
<h2>Recovery information</h2>
<p>This is what you'll need for the transfer, plus your recovery code. If these random-seeming codes look daunting,
don't worry: the <a href="https://github.com/muun/recovery">recovery tool</a> will take care of everything.</p>
</section>
<section>
<h3>First Encrypted Private Key</h3>
<div class="data">{{.FirstEncryptedKey}}</div>
<h3>Second Encrypted Private Key</h3>
<div class="data">{{.SecondEncryptedKey}}</div>
<h3>Output descriptors</h3>
<div class="data">
sh(wsh(multi(2, <span class="key-placeholder">first key</span>/1'/1'/0/*, <span class="key-placeholder">second key</span>/1'/1'/0/*)))<br>
sh(wsh(multi(2, <span class="key-placeholder">first key</span>/1'/1'/1/*, <span class="key-placeholder">second key</span>/1'/1'/1/*)))<br>
sh(wsh(multi(2, <span class="key-placeholder">first key</span>/1'/1'/2/*/*, <span class="key-placeholder">second key</span>/1'/1'/2/*/*)))<br>
wsh(multi(2, <span class="key-placeholder">first key</span>/1'/1'/0/*, <span class="key-placeholder">second key</span>/1'/1'/0/*))<br>
wsh(multi(2, <span class="key-placeholder">first key</span>/1'/1'/1/*, <span class="key-placeholder">second key</span>/1'/1'/1/*))<br>
wsh(multi(2, <span class="key-placeholder">first key</span>/1'/1'/2/*/*, <span class="key-placeholder">second key</span>/1'/1'/2/*/*))
<div class="backup">
<div class="intro">
{{.IconPadlock}}
<div class="text">
<h1>Encrypted backup</h1>
<h2>It can only be decrypted using your <strong>Recovery Code</strong>.</h2>
</div>
</div>
<div class="keys">
<div class="key">
<h3>First key</h3>
<p>{{.FirstEncryptedKey}}</p>
</div>
<div class="key">
<h3>Second key</h3>
<p>{{.SecondEncryptedKey}}</p>
</div>
<div class="date">
Created on <date>{{.CurrentDate}}</date>
</div>
</div>
</div>
<section class="instructions">
<h1>Instructions</h1>
<p>This emergency procedure will help you recover your funds if you are unable to use Muun on your phone.</p>
<div class="item">
<div class="number-box">
<div class="number">1</div>
</div>
<div class="text-box">
<h3>Find your Recovery Code</h3>
<p>You wrote this code on paper before creating your Emergency Kit. Youll need it later.</p>
</div>
</div>
<div class="item">
<div class="number-box">
<div class="number">2</div>
</div>
<div class="text-box">
<h3>Download the Recovery Tool</h3>
<p>Go to <a href="https://github.com/muun/recovery">github.com/muun/recovery</a> and download the tool on your computer.</p>
</div>
</div>
<div class="item">
<div class="number-box">
<div class="number">3</div>
</div>
<div class="text-box">
<h3>Recover your funds</h3>
<p>Run the Recovery Tool and follow the steps. It will safely transfer your funds to a Bitcoin address that you
choose.</p>
</div>
</div>
</section>
<section class="page-break-before">
<h2>Some questions you might have</h2>
<h3>Can I print this document?</h3>
<p>You can, but we recommend storing it online in a service such as Google Drive, iCloud, OneDrive or Dropbox.
These providers have earned their users' trust by being always available and safeguarding data with strong
security practices. They are also free.</p>
<p>If you decide to print it, be sure to keep it safely away from where you store your recovery code. Remember:
a person with both pieces can take control of your funds.</p>
<h3>What if I lose my emergency kit?</h3>
<p>Don't panic. Your money is not lost. It's all there, in the Bitcoin blockchain, waiting for you. Use our Android
or iOS applications and go to the Security Center to create a new kit.</p>
<h3>What if somebody sees this document?</h3>
<section class="help">
{{.IconHelp}}
<div class="text-box">
<h3>Need help?</h3>
<p>
As long as you keep your recovery code hidden, this document is harmless. All the data it contains is safely
encrypted, and only your recovery code can decrypt it to a usable form.</p>
<p>Still, we recommend that you keep it where only you can see it. If you really fear losing it or want to share it
for some other reason, only do so with people that enjoy your absolute trust.</p>
<h3>Why don't I have a mnemonic phrase?</h3>
<p>If you've been involved with Bitcoin for some time, you've probably seen mnemonics and been told to rely on them.
As of this writing, many wallets still use the technique.</p>
<p>There's nothing inherently wrong with mnemonics, but they have been rendered obsolete. The twelve words are
simply not enough to encode all the information a modern Bitcoin wallet requires to operate, and the problem will
only get worse as technology advances. Already there are improvements taking shape that would make mnemonic
recovery not only harder, but impossible.</p>
<p>For this reason, we decided to guarantee full ownership using a safer, more flexible and future-proof technique.
This way, we'll be able to keep up with technological improvements and continue to provide
state-of-the-art software.</p>
<h3>I have other questions</h3>
<p>We'll be glad to answer them. Contact us at <strong><a href="mailto:support@muun.com" >support@muun.com</a></strong>
to let us know.</p>
Contact us at <a href="mailto:support@muun.com">support@muun.com</a>. Were always there to help.
</p>
</div>
</section>
</body>
`
<section class="advanced page-break-before">
<h1>Advanced information</h1>
<h2>Output descriptors</h2>
<p>These descriptors, combined with your keys, specify how to locate your wallets funds on the Bitcoin blockchain.</p>
{{ if .Descriptors }}
{{.Descriptors}}
{{ else }}
<ul class="descriptors">
<!-- These lines are way too long, but dividing them introduces unwanted spaces -->
<li><span class="f">sh</span>(<span class="f">wsh</span>(<span class="f">multi</span>(2, <span class="fp">first key</span>/1'/1'/0/*, <span class="fp">second key</span>/1'/1'/0/*)))</li>
<li><span class="f">sh</span>(<span class="f">wsh</span>(<span class="f">multi</span>(2, <span class="fp">first key</span>/1'/1'/1/*, <span class="fp">second key</span>/1'/1'/1/*)))</li>
<li><span class="f">sh</span>(<span class="f">wsh</span>(<span class="f">multi</span>(2, <span class="fp">first key</span>/1'/1'/2/*/*, <span class="fp">second key</span>/1'/1'/2/*/*)))</li>
<li><span class="f">wsh</span>(<span class="f">multi</span>(2, <span class="fp">first key</span>/1'/1'/0/*, <span class="fp">second key</span>/1'/1'/0/*))</li>
<li><span class="f">wsh</span>(<span class="f">multi</span>(2, <span class="fp">first key</span>/1'/1'/1/*, <span class="fp">second key</span>/1'/1'/1/*))</li>
<li><span class="f">wsh</span>(<span class="f">multi</span>(2, <span class="fp">first key</span>/1'/1'/2]/*/*, <span class="fp">second key</span>/1'/1'/2/*/*))</li>
</ul>
{{ end }}
<p>
Output descriptors are part of a developing standard for Recovery that Muun intends to support and is helping grow.
Since the standard is in a very early stage, the list above includes some non-standard elements.
</p>
<p>
When descriptors reach a more mature stage, youll be able to take your funds from one wallet to another with
complete independence. Muun believes this freedom is at the core of Bitcoins promise, and is working towards
that goal.
</p>
</section>
`
const contentES = `
<header>
<div class="title">
<h1>Kit de Emergencia</h1>
<date>Creado el {{.CurrentDate}}</date>
</div>
<div class="verification-code">
<h4>Código de Verificación</h4>
<code>{{.VerificationCode}}</code>
</div>
<h1>Kit de Emergencia</h1>
<h2>Verificación <span class="verification-code">#{{.VerificationCode}}</span></p>
</header>
<section>
<h2>Sobre este documento</h2>
<p>Aquí encontrarás la información encriptada que necesitas para transferir tu dinero fuera de tu billetera Muun
sin requerir colaboración de nadie, incluso del software y los servicios de Muun.</p>
<p>Ésto incluye todas tus claves privadas (encriptadas de forma segura con tu Recovery Code) y algo de información
adicional relacionada a tu billetera.</p>
<p>Con éste documento y to Código de Recuperación a mano, tienes posesión total de tu dinero. Nadie más tiene
todas las piezas. Bitcoin fue creado para esto: darle a la gente control total sobre el dinero que les pertenece.</p>
</section>
<section>
<h2>Recuperando tu dinero</h2>
<p>Para proceder con la transferencia de tus fondos, recomendamos usar nuestra
<a href="https://github.com/muun/recovery">Herramienta de Recuperación de código abierto</a>. Está disponible para que
todo el mundo la descargue y la examine, y siempre lo estará.</p>
<p>La creamos para asistirte en el proceso, pero nada te impide hacerlo manualmente si prefieres.</p>
<p>Entra en <strong>github.com/muun/recovery</strong> y sigue las instrucciones para transferir tu dinero a una
dirección de Bitcoin que elijas.</p>
</section>
<section>
<h2>Información de recuperación</h2>
<p>Ésto es lo que necesitas para la transferencia, además de tu Código de Recuperación. Si éstos códigos te parecen
confusos, no te precupes: la <a href="https://github.com/muun/recovery">Herramienta de Recuperación</a> se hará cargo
de todo</p>
</section>
<section>
<h3>Primera Clave Privada Encriptada</h3>
<div class="data">{{.FirstEncryptedKey}}</div>
<h3>Segunda Clave Privada Encriptada</h3>
<div class="data">{{.SecondEncryptedKey}}</div>
<h3>Descriptores de outputs</h3>
<div class="data">
sh(wsh(multi(2, <span class="key-placeholder">primera clave</span>/1'/1'/0/*, <span class="key-placeholder">segunda clave</span>/1'/1'/0/*)))<br>
sh(wsh(multi(2, <span class="key-placeholder">primera clave</span>/1'/1'/1/*, <span class="key-placeholder">segunda clave</span>/1'/1'/1/*)))<br>
sh(wsh(multi(2, <span class="key-placeholder">primera clave</span>/1'/1'/2/*/*, <span class="key-placeholder">segunda clave</span>/1'/1'/2/*/*)))<br>
wsh(multi(2, <span class="key-placeholder">primera clave</span>/1'/1'/0/*, <span class="key-placeholder">segunda clave</span>/1'/1'/0/*))<br>
wsh(multi(2, <span class="key-placeholder">primera clave</span>/1'/1'/1/*, <span class="key-placeholder">segunda clave</span>/1'/1'/1/*))<br>
wsh(multi(2, <span class="key-placeholder">primera clave</span>/1'/1'/2/*/*, <span class="key-placeholder">segunda clave</span>/1'/1'/2/*/*))
<div class="backup">
<div class="intro">
{{.IconPadlock}}
<div class="text">
<h1>Respaldo encriptado</h1>
<h2>Sólo puede ser desencriptado con tu <strong>Código de Recuperación</strong>.</h2>
</div>
</div>
<div class="keys">
<div class="key">
<h3>Primera clave</h3>
<p>{{.FirstEncryptedKey}}</p>
</div>
<div class="key">
<h3>Segunda clave</h3>
<p>{{.SecondEncryptedKey}}</p>
</div>
<div class="date">
Creado el <date>{{.CurrentDate}}</date>
</div>
</div>
</div>
<section class="instructions">
<h1>Instrucciones</h1>
<p>Éste procedimiento de emergencia te ayudará a recuperar tus fondos si no puedes usar Muun en tu teléfono.</p>
<div class="item">
<div class="number-box">
<div class="number">1</div>
</div>
<div class="text-box">
<h3>Encuentra tu Código de Recuperación</h3>
<p>Lo escribiste en papel antes de crear tu Kit de Emergencia. Lo necesitarás después.</p>
</div>
</div>
<div class="item">
<div class="number-box">
<div class="number">2</div>
</div>
<div class="text-box">
<h3>Descarga la Herramienta de Recuperación</h3>
<p>Ingresa en <a href="github.com/muun/recovery">github.com/muun/recovery</a> y descarga la herramienta en tu computadora..</p>
</div>
</div>
<div class="item">
<div class="number-box">
<div class="number">3</div>
</div>
<div class="text-box">
<h3>Recupera tus fondos</h3>
<p>Ejecuta la Herramienta de Recuperación y sigue los pasos. Transferirá tus fondos a una dirección de Bitcoin que elijas.</p>
</div>
</div>
</section>
<section class="page-break-before">
<h2>Algunas preguntas que puedes tener</h2>
<h3>¿Puedo imprimir éste documento?</h3>
<p>Puedes, pero recomendamos almacenarlo online, en algún servicio como Google Drive, iCloud, OneDrive o Dropbox.
Éstos proveedores se han ganado la confianza de sus usuarios por estar siempre disponibles y custodiar su información
con fuertes prácticas de seguridad. También son gratuitos.</p>
<p>Si decides imprimirlos, asegúrate de guardarlos en algún lugar seguro y lejos de tu Código de Recuperación. Recuerda:
una persona con ambas piezas puede tomar control de tus fondos.</p>
<h3>¿Qué pasa si pierdo mi Kit de Emergencia?</h3>
<p>No te preocupes. Tu dinero no está perdido. Está todo ahí, en la blockchain de Bitcoin, esperándote. Usa nuestras
aplicaciones de Android o iOS y crea un nuevo Kit en el Centro de Seguridad.</p>
<h3>¿Qué pasa si alguien ve éste documento?</h3>
<p>Mientras tengas tu Código de Recuperación escondido, éste documento es inofensivo. Todo la información que contiene
está encriptada de forma segura, y sólo tu Código de Recuperación puede desencriptarla para poder usarla.</p>
<p>Aún así, recomendamos que lo guardes donde sólo tú puedes verlo. Si realmente te preocupa perderlo o quieres
compartirlo con alguien por otra razón, sólo hazlo con gente de plena confianza.</p>
<h3>¿Por qué no tengo mi mnemonic?</h3>
<p>Si estás familiarizado con Bitcoin, probablemente hayas visto las mnemonics y aprendido a confiar en ellas.
Al día de hoy, muchas billeteras utilizan esa técnica.</p>
<p>Las mnemonics no tienen ningún problema intrínseco, pero han quedado obsoletas. Las doce palabras no son suficientes para codificar
toda la información que una billetera moderna de Bitcoin necesita para funcionar, y el problema sólo se pondrá peor
a medida que la tecnología avance. Ya hay mejoras encaminadas que harían la recuperación con mnemonics no sólo
difícil, sino imposible.</p>
<p>Por eso decidimos garantizar la posesión completa con un método más seguro, flexible y capaz de evolucionar.
De ésta manera, podremos seguir mejorando nuestra tecnología y continuar modernizando nuestro software.</p>
<h3>Tengo otras preguntas</h3>
<p>Siempre estamos disponibles para contestarlas. Contáctanos a <strong><a href="mailto:support@muun.com" >support@muun.com</a></strong>
y te ayudaremos.</p>
<section class="help">
{{.IconHelp}}
<div class="text-box">
<h3>¿Necesitas ayuda?</h3>
<p>
Contáctanos en <a href="mailto:support@muun.com">support@muun.com</a>. Siempre estamos disponibles para ayudar.
</p>
</div>
</section>
</body>
`
<section class="advanced page-break-before">
<h1>Información Avanzada</h1>
<h2>Output descriptors</h2>
<p>Estos descriptors, combinados con tus claves, indican cómo encontrar los fondos de tu billetera en la blockchain de Bitcoin.</p>
{{ if .Descriptors }}
{{.Descriptors}}
{{ else }}
<ul class="descriptors">
<!-- These lines are way too long, but dividing them introduces unwanted spaces -->
<li><span class="f">sh</span>(<span class="f">wsh</span>(<span class="f">multi</span>(2, <span class="fp">primera clave</span>/1'/1'/0/*, <span class="fp">segunda clave</span>/1'/1'/0/*)))</li>
<li><span class="f">sh</span>(<span class="f">wsh</span>(<span class="f">multi</span>(2, <span class="fp">primera clave</span>/1'/1'/1/*, <span class="fp">segunda clave</span>/1'/1'/1/*)))</li>
<li><span class="f">sh</span>(<span class="f">wsh</span>(<span class="f">multi</span>(2, <span class="fp">primera clave</span>/1'/1'/2/*/*, <span class="fp">segunda clave</span>/1'/1'/2/*/*)))</li>
<li><span class="f">wsh</span>(<span class="f">multi</span>(2, <span class="fp">primera clave</span>/1'/1'/0/*, <span class="fp">segunda clave</span>/1'/1'/0/*))</li>
<li><span class="f">wsh</span>(<span class="f">multi</span>(2, <span class="fp">primera clave</span>/1'/1'/1/*, <span class="fp">segunda clave</span>/1'/1'/1/*))</li>
<li><span class="f">wsh</span>(<span class="f">multi</span>(2, <span class="fp">primera clave</span>/1'/1'/2]/*/*, <span class="fp">segunda clave</span>/1'/1'/2/*/*))</li>
</ul>
{{ end }}
<p>
Los output descriptors son parte de un estándar de recuperación actualmente en desarrollo. Muun tiene la intención
de soportar este estándar y apoyar su crecimiento. Dado que se encuentra en una etapa muy temprana, la siguiente lista
incluye algunos elementos que aún no están estandarizados.
</p>
<p>
Cuando los descriptors lleguen a una etapa más madura, podrás llevar tus fondos de una billetera a la otra con completa
independencia. Muun cree que ésta libertad es central a la promesa de Bitcoin, y está trabajando para que eso suceda.
</p>
</section>
`
const iconHelp = `
<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<g filter="url(#filter0_d)">
<circle cx="36" cy="36" r="28" fill="white"/>
</g>
<path d="M51.9762 41.7833L51.9999 28.1164C52.005 27.3149 51.8507 26.5203 51.5461 25.7789C51.2414 25.0374 50.7924 24.3638 50.2252 23.7972C49.6572 23.2232 48.9802 22.7686 48.2338 22.4599C47.4874 22.1513 46.6869 21.995 45.8791 22.0001H26.1208C24.4981 22.0022 22.9424 22.6473 21.795 23.7938C20.6476 24.9404 20.0021 26.4949 20 28.1164V41.2789C20.0021 42.9004 20.6476 44.4548 21.795 45.6014C22.9424 46.748 24.4981 47.393 26.1208 47.3951H26.5625V51.1547C26.5578 51.7232 26.7257 52.2798 27.0439 52.7512C27.3621 53.2225 27.8158 53.5865 28.3451 53.7951C28.6816 53.9281 29.04 53.9976 29.402 54C29.7741 53.9998 30.1425 53.9251 30.4852 53.7803C30.828 53.6354 31.1382 53.4234 31.3975 53.1567L36.6901 47.3951L48.1895 46.2128L51.9762 41.7833ZM35.8698 45.3774L29.7175 51.4778C29.6594 51.5449 29.5815 51.5917 29.495 51.6116C29.4085 51.6314 29.3179 51.6232 29.2363 51.5882C29.1493 51.5551 29.075 51.4953 29.024 51.4175C28.973 51.3396 28.948 51.2476 28.9524 51.1547V46.2128C28.9524 45.8993 28.8277 45.5986 28.6059 45.3769C28.384 45.1551 28.083 45.0306 27.7693 45.0306H26.1208C25.125 45.0306 24.17 44.6353 23.4659 43.9317C22.7618 43.2281 22.3663 42.2739 22.3663 41.2789V28.1164C22.3663 27.1213 22.7618 26.1671 23.4659 25.4635C24.17 24.7599 25.125 24.3646 26.1208 24.3646H45.8791C46.372 24.3641 46.86 24.4614 47.315 24.6508C47.7699 24.8402 48.1827 25.118 48.5293 25.4681C48.8797 25.8145 49.1577 26.227 49.3472 26.6816C49.5368 27.1362 49.6341 27.6239 49.6336 28.1164V41.2789C49.6336 42.2739 49.238 43.2281 48.5339 43.9317C47.8298 44.6353 46.8749 45.0306 45.8791 45.0306H36.6901C36.3764 45.0309 36.0757 45.1556 35.854 45.3774H35.8698ZM36.6901 47.3951H45.8791C47.4141 47.3926 48.8922 46.8146 50.0211 45.7755C51.1501 44.7364 51.8478 43.3117 51.9762 41.7833L48.1895 46.2128L36.6901 47.3951Z" fill="#2474CD"/>
<path d="M34.708 37.1242C34.612 37.1242 34.528 37.0942 34.456 37.034C34.384 36.9619 34.348 36.8777 34.348 36.7815V36.3666C34.432 35.8615 34.618 35.4105 34.906 35.0136C35.206 34.6168 35.614 34.1658 36.13 33.6607C36.514 33.2758 36.802 32.9631 36.994 32.7226C37.186 32.4701 37.288 32.2175 37.3 31.965C37.336 31.5922 37.21 31.2975 36.922 31.081C36.646 30.8525 36.31 30.7383 35.914 30.7383C34.978 30.7383 34.402 31.1893 34.186 32.0912C34.09 32.3799 33.904 32.5242 33.628 32.5242H31.432C31.3 32.5242 31.192 32.4821 31.108 32.3979C31.036 32.3017 31 32.1814 31 32.0371C31.024 31.3757 31.234 30.7503 31.63 30.161C32.026 29.5597 32.608 29.0727 33.376 28.6998C34.144 28.327 35.062 28.1406 36.13 28.1406C37.222 28.1406 38.11 28.315 38.794 28.6638C39.478 29.0005 39.964 29.4214 40.252 29.9265C40.552 30.4196 40.702 30.9247 40.702 31.4418C40.702 32.0311 40.564 32.5482 40.288 32.9932C40.024 33.4382 39.628 33.9493 39.1 34.5266C38.776 34.8753 38.518 35.17 38.326 35.4105C38.146 35.651 38.008 35.9036 37.912 36.1681C37.876 36.2764 37.834 36.4387 37.786 36.6552C37.69 36.8236 37.606 36.9438 37.534 37.016C37.462 37.0881 37.36 37.1242 37.228 37.1242H34.708ZM34.744 40.9486C34.612 40.9486 34.504 40.9065 34.42 40.8223C34.336 40.7381 34.294 40.6299 34.294 40.4976V38.4411C34.294 38.3088 34.336 38.2006 34.42 38.1164C34.504 38.0322 34.612 37.9901 34.744 37.9901H37.048C37.18 37.9901 37.288 38.0322 37.372 38.1164C37.468 38.2006 37.516 38.3088 37.516 38.4411V40.4976C37.516 40.6299 37.468 40.7381 37.372 40.8223C37.288 40.9065 37.18 40.9486 37.048 40.9486H34.744Z" fill="#182449"/>
</g>
<defs>
<filter id="filter0_d" x="0" y="4" width="72" height="72" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.124943 0 0 0 0 0.228158 0 0 0 0 0.346117 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<clipPath id="clip0">
<rect width="72" height="72" fill="white"/>
</clipPath>
</defs>
</svg>
`
const iconPadlock = `
<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<g filter="url(#filter0_dd)">
<g filter="url(#filter1_i)">
<path d="M48.7367 30.2734H23.2633C21.461 30.2734 20 31.7345 20 33.5367V53.192C20 54.9942 21.461 56.4553 23.2633 56.4553H48.7367C50.539 56.4553 52 54.9942 52 53.192V33.5367C52 31.7345 50.539 30.2734 48.7367 30.2734Z" fill="url(#paint0_linear)"/>
</g>
<path d="M38.9119 41.1786C38.9119 42.7853 37.6095 44.0877 36.0028 44.0877C34.3962 44.0877 33.0938 42.7853 33.0938 41.1786C33.0938 39.572 34.3962 38.2695 36.0028 38.2695C37.6095 38.2695 38.9119 39.572 38.9119 41.1786Z" fill="url(#paint1_radial)"/>
<path d="M34.4106 43.9113C34.4915 43.5876 34.7824 43.3604 35.1161 43.3604H36.8895C37.2233 43.3604 37.5142 43.5876 37.5951 43.9113L38.686 48.275C38.8008 48.734 38.4536 49.1786 37.9805 49.1786H34.0252C33.5521 49.1786 33.2049 48.734 33.3197 48.275L34.4106 43.9113Z" fill="url(#paint2_radial)"/>
<g filter="url(#filter2_i)">
<path d="M25.0906 24.8182V30.2727H29.0906V24.8182C29.0906 22.8788 30.4724 19 35.9997 19C41.5269 19 42.9088 22.8788 42.9088 24.8182V30.2727H46.9088V24.8182C46.9088 21.5455 44.7269 15 35.9997 15C27.2724 15 25.0906 21.5455 25.0906 24.8182Z" fill="#2573F7"/>
<path d="M25.0906 24.8182V30.2727H29.0906V24.8182C29.0906 22.8788 30.4724 19 35.9997 19C41.5269 19 42.9088 22.8788 42.9088 24.8182V30.2727H46.9088V24.8182C46.9088 21.5455 44.7269 15 35.9997 15C27.2724 15 25.0906 21.5455 25.0906 24.8182Z" fill="url(#paint3_linear)"/>
</g>
</g>
</g>
<defs>
<filter id="filter0_dd" x="7.99957" y="8.99978" width="56.0009" height="65.4561" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="6.00022"/>
<feGaussianBlur stdDeviation="6.00022"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.340702 0 0 0 0 0.386926 0 0 0 0 0.529451 0 0 0 0.3 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="1.50005"/>
<feGaussianBlur stdDeviation="1.50005"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
</filter>
<filter id="filter1_i" x="19.5921" y="29.4576" width="32.4079" height="26.9976" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.40791" dy="-0.815819"/>
<feGaussianBlur stdDeviation="0.815819"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.25098 0 0 0 0 0.380392 0 0 0 0 0.552941 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow"/>
</filter>
<filter id="filter2_i" x="24.7156" y="14.625" width="22.1932" height="15.6477" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.375014" dy="-0.375014"/>
<feGaussianBlur stdDeviation="0.375014"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow"/>
</filter>
<linearGradient id="paint0_linear" x1="25.8754" y1="40.3205" x2="64.6733" y2="80.0278" gradientUnits="userSpaceOnUse">
<stop stop-color="#91ACC9"/>
<stop offset="0.561326" stop-color="#3D5F8C"/>
</linearGradient>
<radialGradient id="paint1_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(36.0028 43.7241) rotate(90) scale(5.45455 2.90909)">
<stop stop-color="#0B141D"/>
<stop offset="1" stop-color="#27394D"/>
</radialGradient>
<radialGradient id="paint2_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(36.0028 43.7241) rotate(90) scale(5.45455 2.90909)">
<stop stop-color="#0B141D"/>
<stop offset="1" stop-color="#27394D"/>
</radialGradient>
<linearGradient id="paint3_linear" x1="35.9997" y1="26.0114" x2="35.9997" y2="34.4318" gradientUnits="userSpaceOnUse">
<stop stop-color="#435F7D"/>
<stop offset="1" stop-color="#213953"/>
</linearGradient>
<clipPath id="clip0">
<rect width="72" height="72" fill="white"/>
</clipPath>
</defs>
</svg>
`

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,171 @@
package emergencykit
import (
"fmt"
"strings"
)
type DescriptorsData struct {
FirstFingerprint string
SecondFingerprint string
}
// Output descriptors shown in the PDF do not include legacy descriptors no longer in use. We leave
// the decision of whether to scan them to the Recovery Tool.
var descriptorFormats = []string{
"sh(wsh(multi(2, %s/1'/1'/0/*, %s/1'/1'/0/*)))", // V3 change
"sh(wsh(multi(2, %s/1'/1'/1/*, %s/1'/1'/1/*)))", // V3 external
"wsh(multi(2, %s/1'/1'/0/*, %s/1'/1'/0/*))", // V4 change
"wsh(multi(2, %s/1'/1'/1/*, %s/1'/1'/1/*))", // V4 external
}
// GetDescriptors returns an array of raw output descriptors.
func GetDescriptors(data *DescriptorsData) []string {
var descriptors []string
for _, descriptorFormat := range descriptorFormats {
descriptor := fmt.Sprintf(descriptorFormat, data.FirstFingerprint, data.SecondFingerprint)
checksum := calculateChecksum(descriptor)
descriptors = append(descriptors, descriptor+"#"+checksum)
}
return descriptors
}
// GetDescriptorsHTML returns the HTML for the output descriptor list in the Emergency Kit.
func GetDescriptorsHTML(data *DescriptorsData) string {
descriptors := GetDescriptors(data)
var itemsHTML []string
for _, descriptor := range descriptors {
descriptor, checksum := splitChecksum(descriptor)
html := descriptor
// Replace script type expressions (parenthesis in match prevent replacing the "sh" in "wsh")
html = strings.ReplaceAll(html, "wsh(", renderScriptType("wsh")+"(")
html = strings.ReplaceAll(html, "sh(", renderScriptType("sh")+"(")
html = strings.ReplaceAll(html, "multi(", renderScriptType("multi")+"(")
// Replace fingerprint expressions:
html = strings.ReplaceAll(html, data.FirstFingerprint, renderFingerprint(data.FirstFingerprint))
html = strings.ReplaceAll(html, data.SecondFingerprint, renderFingerprint(data.SecondFingerprint))
// Add checksum and wrap everything:
html += renderChecksum(checksum)
html = renderItem(html)
itemsHTML = append(itemsHTML, html)
}
return renderList(itemsHTML)
}
func renderList(itemsHTML []string) string {
return fmt.Sprintf(`<ul class="descriptors">%s</ul>`, strings.Join(itemsHTML, "\n"))
}
func renderItem(innerHTML string) string {
return fmt.Sprintf(`<li>%s</li>`, innerHTML)
}
func renderScriptType(scriptType string) string {
return fmt.Sprintf(`<span class="f">%s</span>`, scriptType)
}
func renderFingerprint(fingerprint string) string {
return fmt.Sprintf(`<span class="fp">%s</span>`, fingerprint)
}
func renderChecksum(checksum string) string {
return fmt.Sprintf(`#<span class="checksum">%s</span>`, checksum)
}
func splitChecksum(descriptor string) (string, string) {
parts := strings.Split(descriptor, "#")
if len(parts) == 1 {
return parts[0], ""
}
return parts[0], parts[1]
}
// -------------------------------------------------------------------------------------------------
// WARNING:
// Below this point, you may find only fear and confusion.
// I translated the code for computing checksums from the original C++ in the bitcoind source,
// making a few adjustments for language differences. It's a specialized algorithm for the domain of
// output descriptors, and it uses the same primitives as the bech32 encoding.
var inputCharset = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "
var checksumCharset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
func calculateChecksum(desc string) string {
var c uint64 = 1
var cls int = 0
var clscount int = 0
for _, ch := range desc {
pos := strings.IndexRune(inputCharset, ch)
if pos == -1 {
return ""
}
c = polyMod(c, pos&31)
cls = cls*3 + (pos >> 5)
clscount++
if clscount == 3 {
c = polyMod(c, cls)
cls = 0
clscount = 0
}
}
if clscount > 0 {
c = polyMod(c, cls)
}
for i := 0; i < 8; i++ {
c = polyMod(c, 0)
}
c ^= 1
ret := make([]byte, 8)
for i := 0; i < 8; i++ {
ret[i] = checksumCharset[(c>>(5*(7-i)))&31]
}
return string(ret)
}
func polyMod(c uint64, intVal int) uint64 {
val := uint64(intVal)
c0 := c >> 35
c = ((c & 0x7ffffffff) << 5) ^ val
if c0&1 != 0 {
c ^= 0xf5dee51989
}
if c0&2 != 0 {
c ^= 0xa9fdca3312
}
if c0&4 != 0 {
c ^= 0x1bab10e32d
}
if c0&8 != 0 {
c ^= 0x3706b1677a
}
if c0&16 != 0 {
c ^= 0x644d626ffd
}
return c
}

View File

@@ -2,8 +2,9 @@ package emergencykit
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"fmt"
"strconv"
"text/template"
"time"
)
@@ -11,7 +12,9 @@ import (
// Input struct to fill the PDF
type Input struct {
FirstEncryptedKey string
FirstFingerprint string
SecondEncryptedKey string
SecondFingerprint string
}
// Output with the html as string and the verification code
@@ -20,24 +23,57 @@ type Output struct {
VerificationCode string
}
var spanishMonthNames = []string{
"Enero",
"Febrero",
"Marzo",
"Abril",
"Mayo",
"Junio",
"Julio",
"Agosto",
"Septiembre",
"Octubre",
"Noviembre",
"Diciembre",
}
// GenerateHTML returns the translated emergency kit html as a string along with the verification code.
func GenerateHTML(params *Input, lang string) (*Output, error) {
verificationCode := randomCode(6)
verificationCode := generateDeterministicCode(params)
// Render output descriptors:
var descriptors string
if params.hasFingerprints() {
descriptors = GetDescriptorsHTML(&DescriptorsData{
FirstFingerprint: params.FirstFingerprint,
SecondFingerprint: params.SecondFingerprint,
})
}
// Render page body:
content, err := render("EmergencyKitContent", lang, &contentData{
// Externally provided:
FirstEncryptedKey: params.FirstEncryptedKey,
SecondEncryptedKey: params.SecondEncryptedKey,
VerificationCode: verificationCode,
// Careful: do not change these format values. See this doc for more info: https://golang.org/pkg/time/#pkg-constants
CurrentDate: time.Now().Format("2006/01/02"), // Format date to YYYY/MM/DD
// Computed by us:
VerificationCode: verificationCode,
CurrentDate: formatDate(time.Now(), lang),
Descriptors: descriptors,
// Template pieces separated for reuse:
IconHelp: iconHelp,
IconPadlock: iconPadlock,
})
if err != nil {
return nil, fmt.Errorf("failed to render EmergencyKitContent template: %w", err)
}
// Render complete HTML page:
page, err := render("EmergencyKitPage", lang, &pageData{
Css: css,
Logo: logo,
Content: content,
})
if err != nil {
@@ -50,17 +86,40 @@ func GenerateHTML(params *Input, lang string) (*Output, error) {
}, nil
}
func randomCode(length int) string {
result := make([]byte, length)
_, err := rand.Read(result)
if err != nil {
panic(err)
func formatDate(t time.Time, lang string) string {
if lang == "en" {
return t.Format("January 2, 2006")
} else {
// Golang has no i18n facilities, so we do our own formatting.
year, month, day := t.Date()
monthName := spanishMonthNames[month-1]
return fmt.Sprintf("%d de %s, %d", day, monthName, year)
}
charset := "0123456789"
for i := 0; i < length; i++ {
result[i] = charset[int(result[i])%len(charset)]
}
func generateDeterministicCode(params *Input) string {
// NOTE:
// This function creates a stable verification code given the inputs to render the Emergency Kit. For now, the
// implementation relies exclusively on the SecondEncryptedKey, which is the Muun key. This is obviously not ideal,
// since we're both dropping part of the input and introducing the assumption that the Muun key will always be
// rendered second -- but it compensates for a problem with one of our clients that causes the user key serialization
// to be recreated each time the kit is rendered (making this deterministic approach useless).
// Create a deterministic serialization of the input:
inputMaterial := params.SecondEncryptedKey
// Compute a cryptographically secure hash of the material (critical, these are keys):
inputHash := sha256.Sum256([]byte(inputMaterial))
// Extract a verification code from the hash (doesn't matter if we discard bytes):
var code string
for _, b := range inputHash[:6] {
code += strconv.Itoa(int(b) % 10)
}
return string(result)
return code
}
func render(name, language string, data interface{}) (string, error) {
@@ -80,12 +139,18 @@ func getContent(name string, language string) string {
switch name {
case "EmergencyKitPage":
return page
case "EmergencyKitContent":
if language == "es" {
return contentES
}
return contentEN
default:
panic("could not find template with name: " + name)
}
}
func (i *Input) hasFingerprints() bool {
return i.FirstFingerprint != "" && i.SecondFingerprint != ""
}

View File

@@ -0,0 +1,145 @@
package emergencykit
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/pdfcpu/pdfcpu/pkg/api"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
)
// MetadataReader can extract the metadata file from a PDF.
type MetadataReader struct {
SrcFile string
}
// MetadataWriter can add the metadata file to a PDF.
type MetadataWriter struct {
SrcFile string
DstFile string
}
// Metadata holds the machine-readable data for an Emergency Kit.
type Metadata struct {
Version int `json:"version"`
BirthdayBlock int `json:"birthdayBlock"`
EncryptedKeys []*MetadataKey `json:"encryptedKeys"`
OutputDescriptors []string `json:"outputDescriptors"`
}
// MetadataKey holds an entry in the Metadata key array.
type MetadataKey struct {
DhPubKey string `json:"dhPubKey"`
EncryptedPrivKey string `json:"encryptedPrivKey"`
Salt string `json:"salt"`
}
// The name for the embedded metadata file in the PDF document:
const metadataName = "metadata.json"
// Default configuration values copied from pdfcpu source code (some values are irrelevant to us):
var pdfConfig = &pdfcpu.Configuration{
Reader15: true,
DecodeAllStreams: false,
ValidationMode: pdfcpu.ValidationRelaxed,
Eol: pdfcpu.EolLF,
WriteObjectStream: true,
WriteXRefStream: true,
EncryptUsingAES: true,
EncryptKeyLength: 256,
Permissions: pdfcpu.PermissionsNone,
}
// HasMetadata returns whether the metadata is present (and alone) in SrcFile.
func (mr *MetadataReader) HasMetadata() (bool, error) {
fs, err := api.ListAttachmentsFile(mr.SrcFile, pdfConfig)
if err != nil {
return false, fmt.Errorf("HasMetadata failed to list attachments: %w", err)
}
return len(fs) == 1 && fs[0] == metadataName, nil
}
// ReadMetadata returns the deserialized metadata file embedded in the SrcFile PDF.
func (mr *MetadataReader) ReadMetadata() (*Metadata, error) {
// NOTE:
// Due to library constraints, this makes use of a temporary directory in the default system temp
// location, which for the Recovery Tool will always be accessible. If we eventually want to read
// this metadata in mobile clients, we'll need the caller to provide a directory.
// Before we begin, verify that the metadata file is embedded:
hasMetadata, err := mr.HasMetadata()
if err != nil {
return nil, fmt.Errorf("ReadMetadata failed to check for existence: %w", err)
}
if !hasMetadata {
return nil, fmt.Errorf("ReadMetadata didn't find %s (or found more) in this PDF", metadataName)
}
// Create the temporary directory, with a deferred call to clean up:
tmpDir, err := ioutil.TempDir("", "ek-metadata-*")
if err != nil {
return nil, fmt.Errorf("ReadMetadata failed to create a temporary directory")
}
defer os.RemoveAll(tmpDir)
// Extract the embedded attachment from the PDF into that directory:
err = api.ExtractAttachmentsFile(mr.SrcFile, tmpDir, []string{metadataName}, pdfConfig)
if err != nil {
return nil, fmt.Errorf("ReadMetadata failed to extract attachment: %w", err)
}
// Read the contents of the file:
metadataBytes, err := ioutil.ReadFile(filepath.Join(tmpDir, metadataName))
if err != nil {
return nil, fmt.Errorf("ReadMetadata failed to read the extracted file: %w", err)
}
// Deserialize the metadata:
var metadata Metadata
err = json.Unmarshal(metadataBytes, &metadata)
if err != nil {
return nil, fmt.Errorf("ReadMetadata failed to unmarshal %s: %w", string(metadataBytes), err)
}
// Done we are!
return &metadata, nil
}
// WriteMetadata creates a copy of SrcFile with attached JSON metadata into DstFile.
func (mw *MetadataWriter) WriteMetadata(metadata *Metadata) error {
// NOTE:
// Due to library constraints, this makes use of a temporary file placed in the same directory as
// `SrcFile`, which is assumed to be writable. This is a much safer bet than attempting to pick a
// location for temporary files ourselves.
// Decide the location of the temporary file:
srcDir := filepath.Dir(mw.SrcFile)
tmpFile := filepath.Join(srcDir, metadataName)
// Serialize the metadata:
metadataBytes, err := json.Marshal(metadata)
if err != nil {
return fmt.Errorf("WriteMetadata failed to marshal: %w", err)
}
// Write to the temporary file, with a deferred call to clean up:
err = ioutil.WriteFile(tmpFile, metadataBytes, os.FileMode(0600))
if err != nil {
return fmt.Errorf("WriteMetadata failed to write a temporary file: %w", err)
}
defer os.Remove(tmpFile)
// Add the attachment, returning potential errors:
err = api.AddAttachmentsFile(mw.SrcFile, mw.DstFile, []string{tmpFile}, false, pdfConfig)
if err != nil {
return fmt.Errorf("WriteMetadata failed to add attachment file %s: %w", tmpFile, err)
}
return nil
}

View File

@@ -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())

22
vendor/github.com/muun/libwallet/errors.go generated vendored Normal file
View File

@@ -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
}
}

28
vendor/github.com/muun/libwallet/errors/errors.go generated vendored Normal file
View File

@@ -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}
}

View File

@@ -10,10 +10,14 @@ require (
github.com/lightningnetwork/lightning-onion v1.0.1
github.com/lightningnetwork/lnd v0.10.4-beta
github.com/miekg/dns v1.1.29 // indirect
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/mobile v0.0.0-20200720140940-1a48f808d81f // indirect
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
)
// Fork that includes the -cache flag for quicker builds
replace golang.org/x/mobile => github.com/champo/mobile v0.0.0-20201226003606-ef8e5756cda7

View File

@@ -24,7 +24,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.20.0-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.20.1-beta.0.20200513120220-b470eee47728/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
@@ -38,7 +37,6 @@ github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2ut
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
github.com/btcsuite/btcutil/psbt v1.0.2 h1:gCVY3KxdoEVU7Q6TjusPO+GANIwVgr9yTLqM+a6CZr8=
github.com/btcsuite/btcutil/psbt v1.0.2/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ=
github.com/btcsuite/btcwallet v0.10.0/go.mod h1:4TqBEuceheGNdeLNrelliLHJzmXauMM2vtWfuy1pFiM=
github.com/btcsuite/btcwallet v0.11.1-0.20200612012534-48addcd5591a h1:AZ1Mf0gd9mgJqrTTIFUc17ep9EKUbQusVAIzJ6X+x3Q=
github.com/btcsuite/btcwallet v0.11.1-0.20200612012534-48addcd5591a/go.mod h1:9+AH3V5mcTtNXTKe+fe63fDLKGOwQbZqmvOVUef+JFE=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
@@ -48,7 +46,6 @@ github.com/btcsuite/btcwallet/wallet/txrules v1.0.0/go.mod h1:UwQE78yCerZ313EXZw
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0 h1:6DxkcoMnCPY4E9cUDPB5tbuuf40SmmMkSQkoE8vCT+s=
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
github.com/btcsuite/btcwallet/walletdb v1.1.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
github.com/btcsuite/btcwallet/walletdb v1.2.0/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
github.com/btcsuite/btcwallet/walletdb v1.3.1/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
github.com/btcsuite/btcwallet/walletdb v1.3.2/go.mod h1:GZCMPNpUu5KE3ASoVd+k06p/1OW8OwNGCCaNWRto2cQ=
@@ -57,7 +54,6 @@ github.com/btcsuite/btcwallet/walletdb v1.3.3/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPT
github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY=
github.com/btcsuite/btcwallet/wtxmgr v1.2.0 h1:ZUYPsSv8GjF9KK7lboB2OVHF0uYEcHxgrCfFWqPd9NA=
github.com/btcsuite/btcwallet/wtxmgr v1.2.0/go.mod h1:h8hkcKUE3X7lMPzTUoGnNiw5g7VhGrKEW3KpR2r0VnY=
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:QcFA8DZHtuIAdYKCq/BzELOaznRsCvwf4zTPmaYwaig=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=
@@ -72,6 +68,10 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/champo/mobile v0.0.0-20201225234154-3393de95d3bb h1:Doj1b3qkFX5zakU7uJ1lpsER6GNS4R65Zbfrpz2fIWE=
github.com/champo/mobile v0.0.0-20201225234154-3393de95d3bb/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
github.com/champo/mobile v0.0.0-20201226003606-ef8e5756cda7 h1:jbaq2lXHNbmLj9Ab3upCbYSZ/j/TQ6yzDwie/pNyfqA=
github.com/champo/mobile v0.0.0-20201226003606-ef8e5756cda7/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -87,6 +87,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
@@ -132,9 +133,14 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v0.0.0-20170724004829-f2862b476edc/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.8.6 h1:XvND7+MPP7Jp+JpqSZ7naSl5nVZf6k0LbL1V3EKh0zc=
github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hhrutter/lzw v0.0.0-20190827003112-58b82c5a41cc/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk=
github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 h1:1yY/RQWNSBjJe2GDCIYoLmpWVidrooriUr4QS/zaATQ=
github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk=
github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 h1:o1wMw7uTNyA58IlEdDpxIrtFHTgnvYzA8sCQz8luv94=
github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7/go.mod h1:WkUxfS2JUu3qPo6tRld7ISb8HiC0gVSU91kooBMDVok=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc=
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
@@ -145,8 +151,6 @@ github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
github.com/jinzhu/gorm v1.9.15 h1:OdR1qFvtXktlxk73XFYMiYn9ywzTwytqe4QkuMRqc38=
github.com/jinzhu/gorm v1.9.15/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
@@ -174,25 +178,22 @@ github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFF
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lightninglabs/gozmq v0.0.0-20190710231225-cea2a031735d/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
github.com/lightninglabs/neutrino v0.10.0/go.mod h1:C3KhCMk1Mcx3j8v0qRVWM1Ow6rIJSvSPnUAq00ZNAfk=
github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg=
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 h1:j4iZ1XlUAPQmW6oSzMcJGILYsRHNs+4O3Gk+2Ms5Dww=
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0=
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
github.com/lightningnetwork/lightning-onion v0.0.0-20190909101754-850081b08b6a/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
github.com/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1bn6WJpYbvosw/1604=
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
github.com/lightningnetwork/lnd v0.8.0-beta h1:HmmhSRTq48qobqQF8YLqNa8eKU8dDBNbWWpr2VzycJM=
github.com/lightningnetwork/lnd v0.8.0-beta/go.mod h1:nq06y2BDv7vwWeMmwgB7P3pT7/Uj7sGf5FzHISVD6t4=
github.com/lightningnetwork/lnd v0.10.4-beta h1:Af2zOCPePeaU8Tkl8IqtTjr4BP3zYfi+hAtQYcCMM58=
github.com/lightningnetwork/lnd v0.10.4-beta/go.mod h1:4d02pduRVtZwgTJ+EimKJTsEAY0jDwi0SPE9h5aRneM=
github.com/lightningnetwork/lnd/cert v1.0.2 h1:g2rEu+sM2Uyz0bpfuvwri/ks6R/26H5iY1NcGbpDJ+c=
@@ -217,13 +218,16 @@ github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWB
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/muun/libwallet v0.4.0 h1:mqvEA+EpZeyXPOhcm61H8OL3AQxEuvelsm3VqYqIEIY=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pdfcpu/pdfcpu v0.3.8 h1:wdKii186dzmr/aP/fkJl2s9yT3TZcwc1VqgfabNymGI=
github.com/pdfcpu/pdfcpu v0.3.8/go.mod h1:EfJ1EIo3n5+YlGF53DGe1yF1wQLiqK1eqGDN5LuKALs=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -269,23 +273,22 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
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-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/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=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
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/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20200720140940-1a48f808d81f h1:I/h48WbtIgA+7yh90BQGaTm4aoyybl/D5N+N6JIfuCI=
golang.org/x/mobile v0.0.0-20200720140940-1a48f808d81f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
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=
@@ -298,7 +301,6 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -309,7 +311,6 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -323,14 +324,11 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -352,19 +350,16 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
@@ -374,22 +369,20 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso=
gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI=
gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw=
gopkg.in/macaroon-bakery.v2 v2.0.1 h1:0N1TlEdfLP4HXNCg7MQUMp5XwvOoxk+oe9Owr2cpvsc=
gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA=
gopkg.in/macaroon.v2 v2.0.0 h1:LVWycAfeJBUjCIqfR9gqlo7I8vmiXRr51YEOZ1suop8=
gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -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

View File

@@ -1,11 +1,13 @@
package libwallet
import (
"errors"
"fmt"
"strings"
"github.com/muun/libwallet/hdpath"
"github.com/pkg/errors"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
)
@@ -42,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))
@@ -57,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
@@ -98,3 +100,17 @@ func (p *HDPublicKey) Raw() []byte {
return key.SerializeCompressed()
}
// Fingerprint returns the 4-byte fingerprint for this pubkey
func (p *HDPublicKey) Fingerprint() []byte {
key, err := p.key.ECPubKey()
if err != nil {
panic("failed to extract pub key")
}
bytes := key.SerializeCompressed()
hash := btcutil.Hash160(bytes)
return hash[:4]
}

View File

@@ -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 {
@@ -24,6 +24,7 @@ type coinIncomingSwap struct {
SwapServerPublicKey []byte
ExpirationHeight int64
VerifyOutputAmount bool // used only for fulfilling swaps through IncomingSwap
Collect btcutil.Amount
}
func (c *coinIncomingSwap) SignInput(index int, tx *wire.MsgTx, userKey *HDPrivateKey, muunKey *HDPublicKey) error {
@@ -124,13 +125,16 @@ func (c *coinIncomingSwap) SignInput(index int, tx *wire.MsgTx, userKey *HDPriva
// Now check the information we have against the sphinx created by the payer
if len(c.Sphinx) > 0 {
// This incoming swap might be collecting debt, which would be deducted from the outputAmount
// so we add it back up so the amount will match with the sphinx
expectedAmount := outputAmount + lnwire.NewMSatFromSatoshis(c.Collect)
err = sphinx.Validate(
c.Sphinx,
c.PaymentHash256,
secrets.PaymentSecret,
nodeKey,
uint32(c.ExpirationHeight),
outputAmount,
expectedAmount,
c.Network,
)
if err != nil {
@@ -175,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)

View File

@@ -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)
}
}

View File

@@ -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)
@@ -268,6 +272,76 @@ func CreateInvoice(net *Network, userKey *HDPrivateKey, routeHints *RouteHints,
return bech32, nil
}
// ExposePreimage gives the preimage matching a payment hash if we have it
func ExposePreimage(paymentHash []byte) ([]byte, error) {
if len(paymentHash) != 32 {
return nil, fmt.Errorf("ExposePreimage: received invalid hash len %v", len(paymentHash))
}
// Lookup invoice data matching this HTLC using the payment hash
db, err := openDB()
if err != nil {
return nil, err
}
defer db.Close()
secrets, err := db.FindByPaymentHash(paymentHash)
if err != nil {
return nil, fmt.Errorf("could not find invoice data for payment hash: %w", err)
}
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
@@ -282,6 +356,7 @@ type IncomingSwap struct {
HtlcExpiration int64
HtlcBlock []byte // unused
ConfirmationTarget int64 // to validate fee rate, unused for now
CollectInSats int64
}
func (s *IncomingSwap) VerifyAndFulfill(userKey *HDPrivateKey, muunKey *HDPublicKey, net *Network) ([]byte, error) {
@@ -313,6 +388,7 @@ func (s *IncomingSwap) VerifyAndFulfill(userKey *HDPrivateKey, muunKey *HDPublic
SwapServerPublicKey: swapServerPublicKey,
ExpirationHeight: s.HtlcExpiration,
VerifyOutputAmount: true,
Collect: btcutil.Amount(s.CollectInSats),
}
err = coin.SignInput(0, &tx, userKey, muunKey)
if err != nil {

View File

@@ -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 {
@@ -64,6 +65,7 @@ type InputIncomingSwap interface {
PaymentHash256() []byte
SwapServerPublicKey() string
ExpirationHeight() int64
CollectInSats() int64
}
type Input interface {
@@ -105,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
@@ -127,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)
}
}
@@ -145,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)
}
}
@@ -168,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))
}
}
@@ -207,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)
}
/*
@@ -237,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(
@@ -266,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")
}
}
@@ -300,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
}
@@ -313,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{
@@ -422,8 +424,9 @@ func createCoin(input Input, network *Network) (coin, error) {
PaymentHash256: swap.PaymentHash256(),
SwapServerPublicKey: swapServerPublicKey,
ExpirationHeight: swap.ExpirationHeight(),
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)
}
}

View File

@@ -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

View File

@@ -4,6 +4,7 @@ import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"errors"
fmt "fmt"
"strings"
@@ -89,9 +90,14 @@ func ConvertToKey(code, salt string) (*btcec.PrivateKey, error) {
switch version {
case 1:
saltBytes, err := hex.DecodeString(salt)
if err != nil {
return nil, fmt.Errorf("failed to decode salt: %w", err)
}
input, err = scrypt.Key(
[]byte(code),
[]byte(salt),
saltBytes,
kdfIterations,
kdfBlockSize,
kdfParallelizationFactor,

View File

@@ -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

View File

@@ -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
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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)

View File

@@ -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)