Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multiple keys for new custodial accounts #235

Merged
merged 3 commits into from
Dec 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,12 @@ Valid log leves are (case-insensitive):
debug
trace

### Multiple keys for custodial accounts

To enable multiple keys for custodial accounts you'll need to set `FLOW_WALLET_DEFAULT_ACCOUNT_KEY_COUNT` to the number of keys each account should have. When a new account is created the auto-generated account key is cloned so that the total number of keys matches the configured value.
latenssi marked this conversation as resolved.
Show resolved Hide resolved

NOTE: Changing `FLOW_WALLET_DEFAULT_ACCOUNT_KEY_COUNT` does not affect _existing_ accounts.

### All possible configuration variables

Refer to [configs/configs.go](configs/configs.go) for details and documentation.
Expand Down
25 changes: 22 additions & 3 deletions accounts/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,19 @@ func (s *Service) createAccount(ctx context.Context) (*Account, string, error) {
return nil, "", err
}

// Public keys for creating the account
publicKeys := []*flow.AccountKey{}

// Create copies based on the configured key count, changing just the index
for i := 0; i < int(s.cfg.DefaultAccountKeyCount); i++ {
clonedAccountKey := *accountKey
clonedAccountKey.Index = i

publicKeys = append(publicKeys, &clonedAccountKey)
}

flowTx := flow_templates.CreateAccount(
[]*flow.AccountKey{accountKey},
publicKeys,
nil,
payer.Address,
)
Expand Down Expand Up @@ -315,8 +326,16 @@ func (s *Service) createAccount(ctx context.Context) (*Account, string, error) {
}
encryptedAccountKey.PublicKey = accountKey.PublicKey.String()

// Store account and key
account.Keys = []keys.Storable{encryptedAccountKey}
// Store account and key(s)
// Looping through accountKeys to get the correct Index values
storableKeys := []keys.Storable{}
for _, pbk := range publicKeys {
clonedEncryptedAccountKey := encryptedAccountKey
clonedEncryptedAccountKey.Index = pbk.Index
storableKeys = append(storableKeys, clonedEncryptedAccountKey)
}

account.Keys = storableKeys
if err := s.store.InsertAccount(account); err != nil {
return nil, "", err
}
Expand Down
12 changes: 12 additions & 0 deletions api-test-scripts/scripts.http
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@emulatorCustodyAccount = 0x0000000000000000

### Really basic test
POST http://localhost:3000/v1/scripts HTTP/1.1
content-type: application/json
Expand Down Expand Up @@ -26,6 +28,16 @@ content-type: application/json
"arguments":[{"type":"Address","value":"{{$dotenv FLOW_WALLET_ADMIN_ADDRESS}}"}]
}


### Get FlowToken balance of custody account (flow-emulator)
POST http://localhost:3000/v1/scripts HTTP/1.1
content-type: application/json

{
"code":"import FungibleToken from 0xee82856bf20e2aa6\nimport FlowToken from 0x0ae53cb6e3f42a79\npub fun main(account: Address): UFix64 {\nlet vaultRef = getAccount(account)\n.getCapability(/public/flowTokenBalance)\n.borrow<&FlowToken.Vault{FungibleToken.Balance}>()\n?? panic(\"Could not borrow Balance reference to the Vault\")\nreturn vaultRef.balance\n}",
"arguments":[{"type":"Address","value":"{{emulatorCustodyAccount}}"}]
}

### Get ExampleNFT balance of admin account (flow-emulator)
POST http://localhost:3000/v1/scripts HTTP/1.1
content-type: application/json
Expand Down
2 changes: 2 additions & 0 deletions configs/configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ type Config struct {
EncryptionKey string `env:"FLOW_WALLET_ENCRYPTION_KEY,notEmpty"`
// Encryption key type, one of: local, aws_kms, google_kms
EncryptionKeyType string `env:"FLOW_WALLET_ENCRYPTION_KEY_TYPE,notEmpty" envDefault:"local"`
// DefaultAccountKeyCount specifies how many times the account key will be duplicated upon account creation, does not affect existing accounts
DefaultAccountKeyCount uint `env:"FLOW_WALLET_DEFAULT_ACCOUNT_KEY_COUNT" envDefault:"1"`
latenssi marked this conversation as resolved.
Show resolved Hide resolved

// -- Database --

Expand Down
83 changes: 82 additions & 1 deletion main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"io/ioutil"
"sort"

"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -321,6 +322,86 @@ func TestAccountServices(t *testing.T) {
t.Fatalf(`expected error to contain "%s" got: "%s"`, expected, job.Error)
}
})

t.Run("sync create with multiple keys", func(t *testing.T) {
cfg2 := getTestConfig(t)
cfg2.DefaultAccountKeyCount = 3

app2 := getTestApp(t, cfg2, true)

_, acc, err := app2.AccountService.Create(context.Background(), true)
fatal(t, err)

if len(acc.Keys) != int(cfg2.DefaultAccountKeyCount) {
t.Fatalf("incorrect number of keys for new account, expected %d, got %d", len(acc.Keys), cfg2.DefaultAccountKeyCount)
}

// Keys should be clones w/ a running Index
indexes := []int{}
for _, key := range acc.Keys {
indexes = append(indexes, key.Index)

// Check that all public keys are identical
if key.PublicKey != acc.Keys[0].PublicKey {
t.Fatalf("expected public keys to be identical")
}
}

sort.Ints(indexes)

expectedIndexes := []int{0, 1, 2}
if !reflect.DeepEqual(expectedIndexes, indexes) {
t.Fatalf("incorrect key indexes, expected %v, got %v", expectedIndexes, indexes)
}
})

t.Run("async create with multiple keys", func(t *testing.T) {
cfg2 := getTestConfig(t)
cfg2.DefaultAccountKeyCount = 3
app2 := getTestApp(t, cfg2, true)

job, _, err := app2.AccountService.Create(context.Background(), false)
fatal(t, err)

if job.State != jobs.Init && job.State != jobs.Accepted && job.State != jobs.Complete {
t.Errorf("expected job status to be %s or %s or %s but got %s",
jobs.Init, jobs.Accepted, jobs.Complete, job.State)
}

for job.State == jobs.Init || job.State == jobs.Accepted {
time.Sleep(10 * time.Millisecond)
}

if job.State != jobs.Complete {
t.Errorf("expected job status to be %s got %s; job.Error: %s", jobs.Complete, job.State, job.Error)
}

acc, err := app2.AccountService.Details(job.Result)
fatal(t, err)

if len(acc.Keys) != int(cfg2.DefaultAccountKeyCount) {
t.Fatalf("incorrect number of keys for new account, expected %d, got %d", len(acc.Keys), cfg2.DefaultAccountKeyCount)
}

// Keys should be clones w/ a running Index
indexes := []int{}
for _, key := range acc.Keys {
indexes = append(indexes, key.Index)

// Check that all public keys are identical
if key.PublicKey != acc.Keys[0].PublicKey {
t.Fatalf("expected public keys to be identical")
}
}

sort.Ints(indexes)

expectedIndexes := []int{0, 1, 2}
if !reflect.DeepEqual(expectedIndexes, indexes) {
t.Fatalf("incorrect key indexes, expected %v, got %v", expectedIndexes, indexes)
}

})
}

func TestAccountHandlers(t *testing.T) {
Expand Down Expand Up @@ -380,7 +461,7 @@ func TestAccountHandlers(t *testing.T) {
{
name: "details unknown address",
method: http.MethodGet,
url: "/0f7025fa05b578e3",
url: "/e34ea67a850e1585",
latenssi marked this conversation as resolved.
Show resolved Hide resolved
expected: "record not found",
status: http.StatusNotFound,
},
Expand Down