Skip to content
This repository has been archived by the owner on Jun 22, 2023. It is now read-only.

Commit

Permalink
Merge pull request #105 from lidofinance/feat/bip39-seed
Browse files Browse the repository at this point in the history
feat: bip39 seed
  • Loading branch information
zavgorodnii authored Dec 7, 2020
2 parents 78858ac + d9dd09a commit 83d9cad
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 99 deletions.
13 changes: 8 additions & 5 deletions HowTo.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ A laptop that can run Tails (linux distribution) and has a webcam. Preferably it

NB: If you know what you're doing set up an airgapped machine yourself or use one you already have. Tails live dvd/usb is not the best possible setup - it's just good enough in our opinion.

Backup media: Three usb drives at least 1gb in capacity. Preferably from different vendors so that they wouldn't fail all at the same time. These drives will store the secrets that are used in threshold signing ceremonies. You will keep these backup drives until withdrawals are enabled in eth2.
Backup media: paper wallet or another media you will use to backup bip39 word-based seed. You will keep this backup until withdrawals are enabled in eth2.

Plaintext media: 1+ gb usb drive or cd/dvd for non-secrets (executables and the like). If you choose USB drive, you'll be disposing of that particular drive (not backup ones!) by the end of a ceremony.
Plaintext media: 1+ gb usb drive or cd/dvd for non-secrets (executables and the like). If you choose USB drive, you'll be disposing of that particular drive by the end of a ceremony.

1. Make a bootable media for tails using https://tails.boum.org/install/index.en.html instructions. Live dvd is preferabe to a usb stick but usb stick is a valid option. Verification of an image signature per instructions on Tail's site is strongly recommended.
2. Prepare a plaintext media: run a script `sh airapped_folder/setup.sh` to set up a folder with one on your hot node, then copy/burn that folder on the plaintext media. It contains airapped node binary, firefox distribution, qr code reader html file and deploy script to easily copy all that to tails distribution.
2. If your airgapped laptop allows it, switch the wireless hardware off. Boot into Tails, selecting "no network connection" as an additional option on starting (always select this option on an airgapped machine). From this point on to the end of ceremony the machine shouldn't ever connect to the network; if you can afford having it permanently airgapped forever - can by useful in crypto - do it.
3. Use this instruction to make https://tails.boum.org/doc/encryption_and_privacy/encrypted_volumes/index.en.html encrypted volumes on all three backup media, all with strong password. You can use the same password. Make volumes small (<500mb): we don't need a lot of space for data and larger encrypted volumes are slower.
3. If your airgapped laptop allows it, switch the wireless hardware off. Boot into Tails, selecting "no network connection" as an additional option on starting (always select this option on an airgapped machine). From this point on to the end of ceremony the machine shouldn't ever connect to the network; if you can afford having it permanently airgapped forever - can by useful in crypto - do it.
4. Insert a plaintext media and run `sh ./deploy.sh` from there to copy all the needed files to your ephemeral home dir on tails.

Now you've got all paraphernalia set up and can proceed with the guide further.

Expand All @@ -109,7 +109,7 @@ $ ./dc4bc_airgapped --db_path ./stores/dc4bc_john_doe_airgapped_state --password
```
* `--db_path` Specifies the directory in which the Aigapped machibne state will be stored. If the directory that you specified does not exist, the Airgapped machine will generate new keys for you on startup. *N.B.: It is very important not to put your Airgapped machine state to `/tmp` or to occasionally lose it. Please make sure that you keep your Airgapped machine state in a safe place and make a backup.*

Backup it to the encrypted media immediately: if these keys are lost durin the ceremony, the dkg ceremony is. toast; if they are lost after, you won't be able to participate in the signing.
Backup the generated bip39 seed on a paper wallet; if you need to restore it, use the `set_seed` command in the airgapped executable's console.

* `--password_expiration` Specifies the time in which you'll be able to use the Airgapped machine without re-entering your password. The Airgapped machine will ask you to create a new password during the first run. Make sure that the password is not lost.

Expand All @@ -131,8 +131,11 @@ EcVs+nTi4iFERVeBHUPePDmvknBx95co7csKj0sZNuo=
# Inside the airgapped shell:
>>> show_dkg_pubkey
sN7XbnvZCRtg650dVCCpPK/hQ/rMTSlxrdnvzJ75zV4W/Uzk9suvjNPtyRt7PDXLDTGNimn+4X/FcJj2K6vDdgqOrr9BHwMqJXnQykcv3IV0ggIUjpMMgdbQ+0iSseyq
>>> generate_dkg_pubkey_qr
A QR code with DKG public key was saved to: /tmp/dc4bc_qr_dkg_pub_key.gif
```


**N.B.: You can start and stop both the Client node and the Airgapped machine any time you want given that the states are stored safely on your computer. When you restart the Airgapped machine, make sure that you run the `replay_operations_log` command exactly once before performing any actions — that will make the Airgapped machine replay the state and be ready for new actions. Please do not replay the log more than once during one Airgapped session, this might lead to undefined state.**

Now you want to start the DKG procedure. *This action must be done exactly once by only one of the participants. The participants must decide who will send the initial message collectively.*
Expand Down
6 changes: 0 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ build-darwin:
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o dc4bc_airgapped_darwin ./cmd/airgapped/
@echo "Building dc4bc_prysm_compatibility_checker..."
GOOS=darwin GOARCH=amd64 go build -o dc4bc_prysm_compatibility_checker_darwin ./cmd/prysm_compatibility_checker/
@echo "Building Airgapped state cleaner..."
GOOS=darwin GOARCH=amd64 go build -o airgapped_state_cleaner_darwin ./cmd/airgapped_state_cleaner/

build-linux:
@echo "Building dc4bc_d..."
Expand All @@ -33,8 +31,6 @@ build-linux:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dc4bc_airgapped_linux ./cmd/airgapped/
@echo "Building dc4bc_prysm_compatibility_checker..."
GOOS=linux GOARCH=amd64 go build -o dc4bc_prysm_compatibility_checker_linux ./cmd/prysm_compatibility_checker/
@echo "Building Airgapped state cleaner..."
GOOS=linux GOARCH=amd64 go build -o airgapped_state_cleaner_linux ./cmd/airgapped_state_cleaner/

build:
@echo "Building dc4bc_d..."
Expand All @@ -45,7 +41,5 @@ build:
CGO_ENABLED=0 go build -o dc4bc_airgapped ./cmd/airgapped/
@echo "Building dc4bc_prysm_compatibility_checker..."
go build -o dc4bc_prysm_compatibility_checker_linux ./cmd/prysm_compatibility_checker/
@echo "Building Airgapped state cleaner..."
go build -o airgapped_state_cleaner ./cmd/airgapped_state_cleaner/

.PHONY: mocks
24 changes: 17 additions & 7 deletions airgapped/airgapped.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ type Machine struct {

ResultQRFolder string

dkgInstances map[string]*dkg.DKG
dkgInstances map[string]*dkg.DKG
// Used to encrypt local sensitive data, e.g. BLS keyrings.
encryptionKey []byte
pubKey kyber.Point
secKey kyber.Scalar
Expand Down Expand Up @@ -86,23 +87,32 @@ func (am *Machine) SetResultQRFolder(resultQRFolder string) {
am.ResultQRFolder = resultQRFolder
}

// InitKeys load keys public and private keys for DKG from LevelDB. If keys does not exist, creates them.
// InitKeys load keys public and private keys for DKG from LevelDB. If keys do not exist, it creates them.
func (am *Machine) InitKeys() error {
err := am.LoadKeysFromDB()
if err != nil && err != leveldb.ErrNotFound {
return fmt.Errorf("failed to load keys from db: %w", err)
}
// if keys were not generated yet

// If keys were not generated yet.
if err == leveldb.ErrNotFound {
am.secKey = am.baseSuite.Scalar().Pick(am.baseSuite.RandomStream())
am.pubKey = am.baseSuite.Point().Mul(am.secKey, nil)
return am.SaveKeysToDB()
return am.GenerateKeys()
}

return nil
}

func (am *Machine) GenerateKeys() error {
am.secKey = am.baseSuite.Scalar().Pick(am.baseSuite.RandomStream())
am.pubKey = am.baseSuite.Point().Mul(am.secKey, nil)
if err := am.SaveKeysToDB(); err != nil {
return fmt.Errorf("failed to SaveKeysToDB: %w", err)
}

return nil
}

// SetEncryptionKey set a key to encrypt and decrypt a sensitive data
// SetEncryptionKey set a key to encrypt and decrypt sensitive data.
func (am *Machine) SetEncryptionKey(key []byte) {
am.encryptionKey = key
}
Expand Down
38 changes: 34 additions & 4 deletions airgapped/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package airgapped

import (
"crypto/rand"
"crypto/sha512"
"encoding/json"
"errors"
"fmt"
Expand All @@ -11,6 +12,8 @@ import (

client "github.com/lidofinance/dc4bc/client/types"
"github.com/syndtr/goleveldb/leveldb"
"github.com/tyler-smith/go-bip39"
"golang.org/x/crypto/pbkdf2"
)

const (
Expand All @@ -19,25 +22,33 @@ const (
saltDBKey = "salt_key"
baseSeedKey = "base_seed_key"
operationsLogDBKey = "operations_log"
mnemonicSalt = "mnemonic"
)

type RoundOperationLog map[string][]client.Operation

func (am *Machine) loadBaseSeed() error {
seed, err := am.getBaseSeed()
if errors.Is(err, leveldb.ErrNotFound) {
log.Println("Base seed not initialized, generating a new one...")
seed = make([]byte, seedSize)
_, err = rand.Read(seed)
log.Println("Base seed not initialized, making a new one...")
entropy, err := bip39.NewEntropy(256) //maximum
if err != nil {
return fmt.Errorf("failed to rand.Read: %w", err)
return fmt.Errorf("failed to generate bip39 entropy: %w", err)
}

mnemonic, err := bip39.NewMnemonic(entropy)
if err != nil {
return fmt.Errorf("failed to generate new mnemonic form entropy: %w", err)
}

seed = pbkdf2.Key([]byte(mnemonic), []byte(mnemonicSalt), 2048, seedSize, sha512.New)

if err := am.storeBaseSeed(seed); err != nil {
return fmt.Errorf("failed to storeBaseSeed: %w", err)
}

log.Println("Successfully generated a new seed")
log.Println("Write down your mnemonic: ", mnemonic)
} else if err != nil {
return fmt.Errorf("failed to getBaseSeed: %w", err)
}
Expand All @@ -48,6 +59,25 @@ func (am *Machine) loadBaseSeed() error {
return nil
}

func (am *Machine) SetBaseSeed(mnemonic string) error {
_, err := bip39.EntropyFromMnemonic(mnemonic)
if err != nil {
return fmt.Errorf("failed to validate mnemonic: %w", err)
}
seed := pbkdf2.Key([]byte(mnemonic), []byte(mnemonicSalt), 2048, seedSize, sha512.New)

if err := am.storeBaseSeed(seed); err != nil {
return fmt.Errorf("failed to storeBaseSeed: %w", err)
}

am.baseSeed = seed
am.baseSuite = bls12381.NewBLS12381Suite(am.baseSeed)

log.Println("Successfully set a base seed")

return nil
}

func (am *Machine) storeBaseSeed(seed []byte) error {
if err := am.db.Put([]byte(baseSeedKey), seed, nil); err != nil {
return fmt.Errorf("failed to put baseSeed: %w", err)
Expand Down
42 changes: 37 additions & 5 deletions cmd/airgapped/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,11 @@ import (
"syscall"
"time"

"github.com/lidofinance/dc4bc/qr"

"github.com/lidofinance/dc4bc/airgapped"
client "github.com/lidofinance/dc4bc/client/types"
"github.com/lidofinance/dc4bc/qr"
"github.com/syndtr/goleveldb/leveldb"

"golang.org/x/crypto/ssh/terminal"

"github.com/lidofinance/dc4bc/airgapped"
)

func init() {
Expand Down Expand Up @@ -109,6 +106,11 @@ func NewPrompt(machine *airgapped.Machine) (*prompt, error) {
commandHandler: p.generateDKGPubKeyQR,
description: "generates and saves a QR with DKG public key that can be read by the Client node",
})
p.addCommand("set_seed", &promptCommand{
commandHandler: p.setSeedCommand,
description: "resets a global random seed using BIP39 word list. WARNING! Only do that on a fresh database with no operation carried out.",
})

return &p, nil
}

Expand Down Expand Up @@ -304,6 +306,35 @@ func (p *prompt) dropOperationLogCommand() error {
return nil
}

func (p *prompt) setSeedCommand() error {
p.print("> WARNING! this will overwrite your old seed, which might make DKGs you've done with it unusable.\n")
p.print("> Only do this on a fresh db_path. Type 'ok' to continue: ")

ok, err := p.reader.ReadString('\n')
if err != nil {
return fmt.Errorf("failed to read confirmation: %w", err)
}
if strings.Trim(ok, " \n") != "ok" {
return nil
}

p.print("> Enter the BIP39 mnemonic for a random seed: ")
mnemonic, err := p.reader.ReadString('\n')
if err != nil {
return fmt.Errorf("failed to read BIP39 mnemonic: %w", err)
}

if err := p.airgapped.SetBaseSeed(strings.Trim(mnemonic, " \n")); err != nil {
return fmt.Errorf("failed to set base seed: %w", err)
}

if err := p.airgapped.GenerateKeys(); err != nil {
return fmt.Errorf("failed to GenerateKeys: %w", err)
}

return nil
}

func (p *prompt) verifySignCommand() error {
p.print("> Enter the DKGRoundIdentifier: ")
dkgRoundIdentifier, err := p.reader.ReadString('\n')
Expand Down Expand Up @@ -550,6 +581,7 @@ func main() {
}
}()
go p.dropSensitiveDataByTicker(passwordLifeDuration)

if err = p.run(); err != nil {
p.printf("Error occurred: %v", err)
}
Expand Down
69 changes: 0 additions & 69 deletions cmd/airgapped_state_cleaner/main.go

This file was deleted.

5 changes: 3 additions & 2 deletions cmd/dc4bc_cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/lidofinance/dc4bc/fsm/state_machines"
"io/ioutil"
"log"
"net/http"
Expand All @@ -17,6 +16,8 @@ import (
"strings"
"time"

"github.com/lidofinance/dc4bc/fsm/state_machines"

"github.com/lidofinance/dc4bc/fsm/fsm"
"github.com/lidofinance/dc4bc/fsm/state_machines/signature_proposal_fsm"
"github.com/lidofinance/dc4bc/fsm/state_machines/signing_proposal_fsm"
Expand Down Expand Up @@ -686,7 +687,7 @@ func getFSMStatusCommand() *cobra.Command {
}

if len(waiting) > 0 {
fmt.Printf("Waiting for a data from: %s\n", strings.Join(waiting, ", "))
fmt.Printf("Waiting for data from: %s\n", strings.Join(waiting, ", "))
}
if len(confirmed) > 0 {
fmt.Printf("Received a data from: %s\n", strings.Join(confirmed, ", "))
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ require (
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.6.1
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
github.com/tyler-smith/go-bip39 v1.1.0
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c
gopkg.in/matryer/try.v1 v1.0.0-20150601225556-312d2599e12e
lukechampine.com/frand v1.3.0
)
Expand Down
Loading

0 comments on commit 83d9cad

Please sign in to comment.