Skip to content

Commit

Permalink
Release v0.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mdb1 committed Nov 9, 2020
1 parent 4e9aa7a commit 8107c44
Show file tree
Hide file tree
Showing 1,265 changed files with 440,434 additions and 107,755 deletions.
13 changes: 7 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ module github.com/muun/recovery_tool
go 1.12

require (
github.com/btcsuite/btcd v0.20.1-beta
github.com/btcsuite/btcd v0.21.0-beta
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
github.com/btcsuite/btcwallet v0.10.0
github.com/btcsuite/btcwallet/walletdb v1.1.0
github.com/lightninglabs/neutrino v0.10.0
github.com/muun/libwallet v0.2.0
github.com/btcsuite/btcutil v1.0.2
github.com/btcsuite/btcwallet v0.11.1-0.20200612012534-48addcd5591a
github.com/btcsuite/btcwallet/walletdb v1.3.3
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200
github.com/muun/libwallet v0.5.0
github.com/pkg/errors v0.9.1 // indirect
)

replace github.com/lightninglabs/neutrino => github.com/muun/neutrino v0.0.0-20190914162326-7082af0fa257
179 changes: 177 additions & 2 deletions go.sum

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions keys_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,24 @@ import (
)

func buildExtendedKey(rawKey, recoveryCode string) *libwallet.DecryptedPrivateKey {
recoveryCodeBytes := extractBytes(recoveryCode)
salt := extractSalt(rawKey)

privKey := libwallet.NewChallengePrivateKey(recoveryCodeBytes, salt)
decryptionKey, err := libwallet.RecoveryCodeToKey(recoveryCode, salt)
if err != nil {
log.Fatalf("failed to process recovery code: %v", err)
}

key, err := privKey.DecryptKey(rawKey, libwallet.Mainnet())
walletKey, err := decryptionKey.DecryptKey(rawKey, libwallet.Mainnet())
if err != nil {
log.Fatalf("failed to decrypt key: %v", err)
}

return key
return walletKey
}

func extractSalt(rawKey string) []byte {
func extractSalt(rawKey string) string {
bytes := base58.Decode(rawKey)
return bytes[len(bytes)-8:]
}
saltBytes := bytes[len(bytes)-8:]

func extractBytes(recoveryCode string) []byte {
return []byte(recoveryCode)
return string(saltBytes)
}
241 changes: 241 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package main

import (
"fmt"
"log"
"os"
"strconv"
"strings"

"github.com/btcsuite/btcutil"
)

func main() {
chainService, close, _ := startChainService()
defer close()

printWelcomeMessage()

recoveryCode := readRecoveryCode()

userRawKey := readKey("first encrypted private key", 147)
userKey := buildExtendedKey(userRawKey, recoveryCode)
userKey.Key.Path = "m/1'/1'"

muunRawKey := readKey("second encrypted private key", 147)
muunKey := buildExtendedKey(muunRawKey, recoveryCode)

sweepAddress := readSweepAddress()

fmt.Println("")
fmt.Println("Preparing to scan the blockchain from your wallet creation block")
fmt.Println("Note that only confirmed transactions can be detected")
fmt.Println("\nThis may take a while")

sweeper := Sweeper{
ChainService: chainService,
UserKey: userKey.Key,
MuunKey: muunKey.Key,
Birthday: muunKey.Birthday,
SweepAddress: sweepAddress,
}

utxos := sweeper.GetUTXOs()

fmt.Println("")

if len(utxos) > 0 {
fmt.Printf("The recovery tool has found the following confirmed UTXOs:\n%v", utxos)
} else {
fmt.Printf("No confirmed UTXOs found")
fmt.Println()
return
}
fmt.Println()

txOutputAmount, txWeightInBytes, err := sweeper.GetSweepTxAmountAndWeightInBytes(utxos)
if err != nil {
printError(err)
}

fee := readFee(txOutputAmount, txWeightInBytes)

// Then we re-build the sweep tx with the actual fee
sweepTx, err := sweeper.BuildSweepTx(utxos, fee)
if err != nil {
printError(err)
}

fmt.Println("Transaction ready to be sent")

err = sweeper.BroadcastTx(sweepTx)
if err != nil {
printError(err)
}

fmt.Printf("Transaction sent! You can check the status here: https://blockstream.info/tx/%v", sweepTx.TxHash().String())
fmt.Println("")
fmt.Printf("We appreciate all kinds of feedback. If you have any, send it to [email protected]")
fmt.Println("")
}

func printError(err error) {
log.Printf("The recovery tool failed with the following error: %v", err.Error())
log.Printf("")
log.Printf("You can try again or contact us at [email protected]")
panic(err)
}

func printWelcomeMessage() {
fmt.Println("Welcome to Muun's Recovery Tool")
fmt.Println("")
fmt.Println("You can use this tool to transfer all funds from your Muun account to an")
fmt.Println("address of your choosing.")
fmt.Println("")
fmt.Println("To do this you will need:")
fmt.Println("1. Your Recovery Code, which you wrote down during your security setup")
fmt.Println("2. Your two encrypted private keys, which you exported from your wallet")
fmt.Println("3. A destination bitcoin address where all your funds will be sent")
fmt.Println("")
fmt.Println("If you have any questions, we'll be happy to answer them. Contact us at [email protected]")
fmt.Println("")
}

func readRecoveryCode() string {
fmt.Println("")
fmt.Printf("Enter your Recovery Code")
fmt.Println()
fmt.Println("(it looks like this: 'ABCD-1234-POW2-R561-P120-JK26-12RW-45TT')")
fmt.Print("> ")
var userInput string
fmt.Scan(&userInput)
userInput = strings.TrimSpace(userInput)

finalRC := strings.ToUpper(userInput)

if strings.Count(finalRC, "-") != 7 {
fmt.Printf("Invalid recovery code. Did you add the '-' separator between each 4-characters segment?")
fmt.Println()
fmt.Println("Please, try again")

return readRecoveryCode()
}

if len(finalRC) != 39 {
fmt.Println("Your recovery code must have 39 characters")
fmt.Println("Please, try again")

return readRecoveryCode()
}

return finalRC
}

func readKey(keyType string, characters int) string {
fmt.Println("")
fmt.Printf("Enter your %v", keyType)
fmt.Println()
fmt.Println("(it looks like this: '9xzpc7y6sNtRvh8Fh...')")
fmt.Print("> ")

userInput := scanMultiline(characters)

if len(userInput) != characters {
fmt.Printf("Your %v must have %v characters", keyType, characters)
fmt.Println("")
fmt.Println("Please, try again")

return readKey(keyType, characters)
}

return userInput
}

func readSweepAddress() btcutil.Address {
fmt.Println("")
fmt.Println("Enter your destination bitcoin address")
fmt.Print("> ")
var userInput string
fmt.Scan(&userInput)
userInput = strings.TrimSpace(userInput)

addr, err := btcutil.DecodeAddress(userInput, &chainParams)
if err != nil {
fmt.Println("This is not a valid bitcoin address")
fmt.Println("")
fmt.Println("Please, try again")

return readSweepAddress()
}

return addr
}

func readFee(totalBalance, weight int64) int64 {
fmt.Println("")
fmt.Printf("Enter the fee in satoshis per byte. Tx weight: %v bytes. You can check the status of the mempool here: https://bitcoinfees.earn.com/#fees", weight)
fmt.Println()
fmt.Println("(Example: 5)")
fmt.Print("> ")
var userInput string
fmt.Scan(&userInput)
feeInSatsPerByte, err := strconv.ParseInt(userInput, 10, 64)
if err != nil || feeInSatsPerByte <= 0 {
fmt.Printf("The fee must be a number")
fmt.Println("")
fmt.Println("Please, try again")

return readFee(totalBalance, weight)
}

totalFee := feeInSatsPerByte * weight

if totalBalance-totalFee < 546 {
fmt.Printf("The fee is too high. The amount left must be higher than dust")
fmt.Println("")
fmt.Println("Please, try again")

return readFee(totalBalance, weight)
}

return totalFee
}

func readConfirmation(value, fee int64, address string) {
fmt.Println("")
fmt.Printf("About to send %v satoshis with fee: %v satoshis to %v", value, fee, address)
fmt.Println()
fmt.Println("Confirm? (y/n)")
fmt.Print("> ")
var userInput string
fmt.Scan(&userInput)

if userInput == "y" || userInput == "Y" {
return
}

if userInput == "n" || userInput == "N" {
log.Println()
log.Printf("Recovery tool stopped")
log.Println()
log.Printf("You can try again or contact us at [email protected]")
os.Exit(1)
}

fmt.Println()
fmt.Println("You can only enter 'y' to accept or 'n' to cancel")
readConfirmation(value, fee, address)
}

func scanMultiline(minChars int) string {
var result strings.Builder

for result.Len() < minChars {
var line string
fmt.Scan(&line)

result.WriteString(strings.TrimSpace(line))
}

return result.String()
}
Loading

0 comments on commit 8107c44

Please sign in to comment.