Skip to content

Commit

Permalink
add offline create wallet flow, fix transaction submit expiration bug (
Browse files Browse the repository at this point in the history
  • Loading branch information
evq authored Nov 12, 2018
1 parent d6572c5 commit 8d2c5ed
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 63 deletions.
162 changes: 139 additions & 23 deletions bin/vault-create-wallet/main.go
Original file line number Diff line number Diff line change
@@ -1,70 +1,186 @@
package main

import (
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"log"
"os"

"github.com/brave-intl/bat-go/utils/altcurrency"
"github.com/brave-intl/bat-go/utils/formatters"
"github.com/brave-intl/bat-go/utils/httpsignature"
"github.com/brave-intl/bat-go/utils/vaultsigner"
"github.com/brave-intl/bat-go/wallet"
"github.com/brave-intl/bat-go/wallet/provider/uphold"
"github.com/hashicorp/vault/api"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ed25519"
)

var flags = flag.NewFlagSet("", flag.ExitOnError)
var verbose = flags.Bool("v", false, "verbose output")
var offline = flags.Bool("offline", false, "operate in multi-step offline mode")

// State contains the current state of the registration
type State struct {
WalletInfo wallet.Info `json:"walletInfo"`
Registration string `json:"registration"`
}

func main() {
log.SetFlags(0)
log.SetFormatter(&formatters.CliFormatter{})

flag.Usage = func() {
flags.Usage = func() {
log.Printf("Create a new wallet backed by vault.\n\n")
log.Printf("Usage:\n\n")
log.Printf(" %s WALLET_NAME\n\n", os.Args[0])
log.Printf(" If a vault keypair exists with name WALLET_NAME, it will be used.\n")
log.Printf(" Otherwise a new vault keypair with that name will be generated.\n\n")
flags.PrintDefaults()
}
err := flags.Parse(os.Args[1:])
if err != nil {
log.Fatalln(err)
}

if *verbose {
log.SetLevel(log.DebugLevel)
}
flag.Parse()

args := flag.Args()
args := flags.Args()
if len(args) != 1 {
log.Printf("ERROR: Must pass a single argument to name generated wallet / keypair\n\n")
flag.Usage()
flags.Usage()
os.Exit(1)
}

name := args[0]
logFile := name + "-registration.json"

var state State
var enc *json.Encoder

var info wallet.Info
info.Provider = "uphold"
info.ProviderID = ""
{
tmp := altcurrency.BAT
info.AltCurrency = &tmp
if *offline {
f, err := os.OpenFile(logFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
if err != nil {
log.Fatalln(err)
}

dec := json.NewDecoder(f)

for dec.More() {
err := dec.Decode(&state)
if err != nil {
log.Fatalln(err)
}
}

enc = json.NewEncoder(f)
}

client, err := vaultsigner.Connect()
if err != nil {
log.Fatalln(err)
if len(state.WalletInfo.PublicKey) == 0 || len(state.Registration) == 0 {
var info wallet.Info
info.Provider = "uphold"
info.ProviderID = ""
{
tmp := altcurrency.BAT
info.AltCurrency = &tmp
}
state.WalletInfo = info

client, err := vaultsigner.Connect()
if err != nil {
log.Fatalln(err)
}

signer, err := vaultsigner.New(client, name)
if err != nil {
log.Fatalln(err)
}

fmt.Printf("Keypair with public key: %s\n", signer)

state.WalletInfo.PublicKey = signer.String()

wallet := &uphold.Wallet{Info: state.WalletInfo, PrivKey: signer, PubKey: signer}

reg, err := wallet.PrepareRegistration(name)
if err != nil {
log.Fatalln(err)
}
state.Registration = reg

if *offline {
err = enc.Encode(state)
if err != nil {
log.Fatalln(err)
}

fmt.Printf("Success, signed registration for wallet \"%s\"\n", name)
fmt.Printf("Please copy %s to the online machine and re-run.\n", logFile)
os.Exit(1)
}
}

signer, err := vaultsigner.New(client, name)
if len(state.WalletInfo.ProviderID) == 0 {
var publicKey httpsignature.Ed25519PubKey
publicKey, err := hex.DecodeString(state.WalletInfo.PublicKey)
if err != nil {
log.Fatalln(err)
}
wallet := uphold.Wallet{Info: state.WalletInfo, PrivKey: ed25519.PrivateKey{}, PubKey: publicKey}

err = wallet.SubmitRegistration(state.Registration)
if err != nil {
log.Fatalln(err)
}

fmt.Printf("Success, registered new keypair and wallet \"%s\"\n", name)
fmt.Printf("Uphold card ID %s\n", wallet.Info.ProviderID)
state.WalletInfo.ProviderID = wallet.Info.ProviderID

depositAddr, err := wallet.CreateCardAddress("ethereum")
if err != nil {
log.Fatalln(err)
}
fmt.Printf("ETH deposit addr: %s\n", depositAddr)

if *offline {
err = enc.Encode(state)
if err != nil {
log.Fatalln(err)
}

fmt.Printf("Please copy %s to the offline machine and re-run.\n", logFile)
os.Exit(1)
}
}

client, err := vaultsigner.Connect()
if err != nil {
log.Fatalln(err)
}

fmt.Printf("Generated keypair with public key: %s\n", signer)

wallet := &uphold.Wallet{Info: info, PrivKey: signer, PubKey: signer}
err = wallet.Register(name)
mounts, err := client.Sys().ListMounts()
if err != nil {
log.Fatalln(err)
}
if _, ok := mounts["wallets/"]; !ok {
// Mount kv secret backend if not already mounted
if err = client.Sys().Mount("wallets", &api.MountInput{
Type: "kv",
}); err != nil {
log.Fatalln(err)
}
}

fmt.Printf("Success, registered new keypair and wallet \"%s\"\n", name)
fmt.Printf("Uphold card ID %s", wallet.Info.ProviderID)
_, err = client.Logical().Write("wallets/"+name, map[string]interface{}{
"providerId": wallet.Info.ProviderID,
"providerId": state.WalletInfo.ProviderID,
})
if err != nil {
log.Fatalln(err)
}

fmt.Printf("Wallet setup complete!\n")
}
34 changes: 32 additions & 2 deletions settlement/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export VAULT_ADDR=http://127.0.0.1:8200
gpg -d SHARE.GPG | ./vault-unseal
```

## Running settlement
## Bringing up vault

On the offline computer, in one window run:
```
Expand All @@ -29,7 +29,10 @@ In another run:
gpg -d SHARE.GPG | ./vault-unseal
```

You are now ready to transact
## Running settlement

First bring up vault as described above.

```
./vault-sign-settlement -in <SETTLEMENT_REPORT.JSON> <SETTLEMENT_WALLET_CARD_ID>
```
Expand All @@ -56,3 +59,30 @@ allow restoring from errors and to avoid duplicate payouts.

Finally upload the "-finished" output file to eyeshade to account for payout
transactions that were made.

## Creating a new offline wallet

On the offline machine, first bring up vault as described above.

Run vault-create-wallet, this will sign the registration and store it into
a local file:
```
vault-create-wallet -offline name-of-new-wallet
```

Copy the created `name-of-new-wallet-registration.json` file to the online
machine.

Re-run vault-create-wallet, this will submit the pre-signed registration:
```
export UPHOLD_ENVIRONMENT=
export UPHOLD_HTTP_PROXY=
export UPHOLD_ACCESS_TOKEN=
vault-create-wallet -offline name-of-new-wallet
```

Finally copy `name-of-new-wallet-registration.json` back to the offline
machine and run vault-create-wallet to record the provider ID in vault:
```
vault-create-wallet -offline name-of-new-wallet
```
28 changes: 22 additions & 6 deletions settlement/settlement.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,29 @@ func SubmitPreparedTransaction(settlementWallet *uphold.Wallet, settlement *Tran
return nil
}

if len(settlement.ProviderID) > 0 && time.Now().Before(settlement.ValidUntil) {
fmt.Printf("already submitted, skipping submit for channel %s\n", settlement.Channel)
return nil
}

if len(settlement.ProviderID) > 0 {
fmt.Printf("already submitted, but quote has expired for channel %s\n", settlement.Channel)
// first check if the transaction has already been confirmed
upholdInfo, err := settlementWallet.GetTransaction(settlement.ProviderID)
if err == nil {
settlement.Status = upholdInfo.Status
settlement.Currency = upholdInfo.DestCurrency
settlement.Amount = upholdInfo.DestAmount
settlement.TransferFee = upholdInfo.TransferFee
settlement.ExchangeFee = upholdInfo.ExchangeFee

if settlement.IsComplete() {
fmt.Printf("transaction already complete for channel %s\n", settlement.Channel)
return nil
}
} else if wallet.IsNotFound(err) { // unconfirmed transactions appear as "not found"
if time.Now().Before(settlement.ValidUntil) {
return nil
}

fmt.Printf("already submitted, but quote has expired for channel %s\n", settlement.Channel)
} else {
return err
}
}

// post the settlement to uphold but do not confirm it
Expand Down
25 changes: 16 additions & 9 deletions wallet/provider/uphold/httpsigned.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,40 @@ type HTTPSignedRequest struct {
Body string `json:"octets" valid:"json"`
}

// extract an HTTP request from the encapsulated signed request
func (sr *HTTPSignedRequest) extract() (*httpsignature.Signature, *http.Request, error) {
// extract from the encapsulated signed request
// into the provided HTTP request
// NOTE it intentionally does not set the URL
func (sr *HTTPSignedRequest) extract(r *http.Request) (*httpsignature.Signature, error) {
if r == nil {
return nil, errors.New("r was nil")
}

var s httpsignature.Signature
err := s.UnmarshalText([]byte(sr.Headers["signature"]))
if err != nil {
return nil, nil, err
return nil, err
}

var r http.Request
r.Body = ioutil.NopCloser(bytes.NewBufferString(sr.Body))
r.Header = http.Header{}
if r.Header == nil {
r.Header = http.Header{}
}
for k, v := range sr.Headers {
if !httplex.ValidHeaderFieldName(k) {
return nil, nil, errors.New("invalid encapsulated header name")
return nil, errors.New("invalid encapsulated header name")
}
if !httplex.ValidHeaderFieldValue(v) {
return nil, nil, errors.New("invalid encapsulated header value")
return nil, errors.New("invalid encapsulated header value")
}

if k == httpsignature.RequestTarget {
// TODO implement pseudo-header
return nil, nil, fmt.Errorf("%s pseudo-header not implemented", httpsignature.RequestTarget)
return nil, fmt.Errorf("%s pseudo-header not implemented", httpsignature.RequestTarget)
}

r.Header.Set(k, v)
}
return &s, &r, nil
return &s, nil
}

// encapsulate a signed HTTP request
Expand Down
Loading

0 comments on commit 8d2c5ed

Please sign in to comment.