github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/crypto/keys/keybase_test.go (about) 1 package keys 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9 10 "github.com/gnolang/gno/tm2/pkg/crypto" 11 "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" 12 "github.com/gnolang/gno/tm2/pkg/crypto/keys/keyerror" 13 ) 14 15 func TestCreateAccountInvalidMnemonic(t *testing.T) { 16 t.Parallel() 17 18 kb := NewInMemory() 19 _, err := kb.CreateAccount( 20 "some_account", 21 "malarkey pair crucial catch public canyon evil outer stage ten gym tornado", 22 "", "", 0, 1) 23 assert.Error(t, err) 24 assert.Equal(t, "invalid mnemonic", err.Error()) 25 } 26 27 // TestKeyManagement makes sure we can manipulate these keys well 28 func TestKeyManagement(t *testing.T) { 29 t.Parallel() 30 31 // make the storage with reasonable defaults 32 cstore := NewInMemory() 33 34 n1, n2, n3 := "personal", "business", "other" 35 p1, p2 := "1234", "really-secure!@#$" 36 mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake` 37 mn2 := `lecture salt about avocado smooth height escape general arch head barrel clutch dismiss supply doctor project cat truck fruit abuse gorilla symbol portion glare` 38 bip39Passphrase := "" 39 40 // Check empty state 41 l, err := cstore.List() 42 require.Nil(t, err) 43 assert.Empty(t, l) 44 45 // create some keys 46 has, err := cstore.HasByName(n1) 47 require.NoError(t, err) 48 require.False(t, has) 49 i, err := cstore.CreateAccount(n1, mn1, bip39Passphrase, p1, 0, 0) 50 require.NoError(t, err) 51 require.Equal(t, n1, i.GetName()) 52 _, err = cstore.CreateAccount(n2, mn2, bip39Passphrase, p2, 0, 0) 53 require.NoError(t, err) 54 55 // we can get these keys 56 i2, err := cstore.GetByName(n2) 57 require.NoError(t, err) 58 has, err = cstore.HasByName(n3) 59 require.NoError(t, err) 60 require.False(t, has) 61 has, err = cstore.HasByAddress(toAddr(i2)) 62 require.NoError(t, err) 63 require.True(t, has) 64 // Also check with HasByNameOrAddress 65 has, err = cstore.HasByNameOrAddress(crypto.AddressToBech32(toAddr(i2))) 66 require.NoError(t, err) 67 require.True(t, has) 68 addr, err := crypto.AddressFromBech32("g1frtkxv37nq7arvyz5p0mtjqq7hwuvd4dnt892p") 69 require.NoError(t, err) 70 _, err = cstore.GetByAddress(addr) 71 require.NotNil(t, err) 72 require.True(t, keyerror.IsErrKeyNotFound(err)) 73 74 // list shows them in order 75 keyS, err := cstore.List() 76 require.NoError(t, err) 77 require.Equal(t, 2, len(keyS)) 78 // note these are in alphabetical order 79 require.Equal(t, n2, keyS[0].GetName()) 80 require.Equal(t, n1, keyS[1].GetName()) 81 require.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey()) 82 83 // deleting a key removes it 84 err = cstore.Delete("bad name", "foo", false) 85 require.NotNil(t, err) 86 err = cstore.Delete(n1, p1, false) 87 require.NoError(t, err) 88 keyS, err = cstore.List() 89 require.NoError(t, err) 90 require.Equal(t, 1, len(keyS)) 91 has, err = cstore.HasByName(n1) 92 require.NoError(t, err) 93 require.False(t, has) 94 95 // create an offline key 96 o1 := "offline" 97 priv1 := ed25519.GenPrivKey() 98 pub1 := priv1.PubKey() 99 i, err = cstore.CreateOffline(o1, pub1) 100 require.Nil(t, err) 101 require.Equal(t, pub1, i.GetPubKey()) 102 require.Equal(t, o1, i.GetName()) 103 keyS, err = cstore.List() 104 require.NoError(t, err) 105 require.Equal(t, 2, len(keyS)) 106 107 // delete the offline key 108 err = cstore.Delete(o1, "", false) 109 require.NoError(t, err) 110 keyS, err = cstore.List() 111 require.NoError(t, err) 112 require.Equal(t, 1, len(keyS)) 113 114 // addr cache gets nuked - and test skip flag 115 err = cstore.Delete(n2, "", true) 116 require.NoError(t, err) 117 } 118 119 // TestSignVerify does some detailed checks on how we sign and validate 120 // signatures 121 func TestSignVerify(t *testing.T) { 122 t.Parallel() 123 124 cstore := NewInMemory() 125 126 n1, n2, n3 := "some dude", "a dudette", "dude-ish" 127 p1, p2, p3 := "1234", "foobar", "foobar" 128 mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake` 129 mn2 := `lecture salt about avocado smooth height escape general arch head barrel clutch dismiss supply doctor project cat truck fruit abuse gorilla symbol portion glare` 130 bip39Passphrase := "" 131 132 // create two users and get their info 133 i1, err := cstore.CreateAccount(n1, mn1, bip39Passphrase, p1, 0, 0) 134 require.Nil(t, err) 135 136 i2, err := cstore.CreateAccount(n2, mn2, bip39Passphrase, p2, 0, 0) 137 require.Nil(t, err) 138 139 // Import a public key 140 armor, err := cstore.ExportPubKey(n2) 141 require.Nil(t, err) 142 cstore.ImportPubKey(n3, armor) 143 i3, err := cstore.GetByName(n3) 144 require.NoError(t, err) 145 require.Equal(t, i3.GetName(), n3) 146 147 // let's try to sign some messages 148 d1 := []byte("my first message") 149 d2 := []byte("some other important info!") 150 d3 := []byte("feels like I forgot something...") 151 152 // try signing both data with both .. 153 s11, pub1, err := cstore.Sign(n1, p1, d1) 154 require.Nil(t, err) 155 require.Equal(t, i1.GetPubKey(), pub1) 156 157 s12, pub1, err := cstore.Sign(n1, p1, d2) 158 require.Nil(t, err) 159 require.Equal(t, i1.GetPubKey(), pub1) 160 161 s21, pub2, err := cstore.Sign(n2, p2, d1) 162 require.Nil(t, err) 163 require.Equal(t, i2.GetPubKey(), pub2) 164 165 s22, pub2, err := cstore.Sign(n2, p2, d2) 166 require.Nil(t, err) 167 require.Equal(t, i2.GetPubKey(), pub2) 168 169 // let's try to validate and make sure it only works when everything is proper 170 cases := []struct { 171 key crypto.PubKey 172 data []byte 173 sig []byte 174 valid bool 175 }{ 176 // proper matches 177 {i1.GetPubKey(), d1, s11, true}, 178 // change data, pubkey, or signature leads to fail 179 {i1.GetPubKey(), d2, s11, false}, 180 {i2.GetPubKey(), d1, s11, false}, 181 {i1.GetPubKey(), d1, s21, false}, 182 // make sure other successes 183 {i1.GetPubKey(), d2, s12, true}, 184 {i2.GetPubKey(), d1, s21, true}, 185 {i2.GetPubKey(), d2, s22, true}, 186 } 187 188 for i, tc := range cases { 189 valid := tc.key.VerifyBytes(tc.data, tc.sig) 190 require.Equal(t, tc.valid, valid, "%d", i) 191 } 192 193 // Now try to sign data with a secret-less key 194 _, _, err = cstore.Sign(n3, p3, d3) 195 require.NotNil(t, err) 196 } 197 198 func assertPassword(t *testing.T, cstore Keybase, name, pass, badpass string) { 199 t.Helper() 200 201 getNewpass := func() (string, error) { return pass, nil } 202 err := cstore.Update(name, badpass, getNewpass) 203 require.NotNil(t, err) 204 err = cstore.Update(name, pass, getNewpass) 205 require.Nil(t, err, "%+v", err) 206 } 207 208 // TestExportImport tests exporting and importing 209 func TestExportImport(t *testing.T) { 210 t.Parallel() 211 212 // make the storage with reasonable defaults 213 cstore := NewInMemory() 214 215 mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake` 216 bip39Passphrase := "" 217 218 info, err := cstore.CreateAccount("john", mn1, bip39Passphrase, "secretcpw", 0, 0) 219 require.NoError(t, err) 220 require.Equal(t, info.GetName(), "john") 221 222 john, err := cstore.GetByName("john") 223 require.NoError(t, err) 224 require.Equal(t, info.GetName(), "john") 225 johnAddr := info.GetPubKey().Address() 226 227 armor, err := cstore.Export("john") 228 require.NoError(t, err) 229 230 err = cstore.Import("john2", armor) 231 require.NoError(t, err) 232 233 john2, err := cstore.GetByName("john2") 234 require.NoError(t, err) 235 236 require.Equal(t, john.GetPubKey().Address(), johnAddr) 237 require.Equal(t, john.GetName(), "john") 238 require.Equal(t, john, john2) 239 } 240 241 func TestExportImportPubKey(t *testing.T) { 242 t.Parallel() 243 244 // make the storage with reasonable defaults 245 cstore := NewInMemory() 246 247 // CreateAccount a private-public key pair and ensure consistency 248 mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake` 249 bip39Passphrase := "" 250 notPasswd := "n9y25ah7" 251 info, err := cstore.CreateAccount("john", mn1, bip39Passphrase, notPasswd, 0, 0) 252 require.Nil(t, err) 253 require.NotEqual(t, info, "") 254 require.Equal(t, info.GetName(), "john") 255 addr := info.GetPubKey().Address() 256 john, err := cstore.GetByName("john") 257 require.NoError(t, err) 258 require.Equal(t, john.GetName(), "john") 259 require.Equal(t, john.GetPubKey().Address(), addr) 260 261 // Export the public key only 262 armor, err := cstore.ExportPubKey("john") 263 require.NoError(t, err) 264 // Import it under a different name 265 err = cstore.ImportPubKey("john-pubkey-only", armor) 266 require.NoError(t, err) 267 // Ensure consistency 268 john2, err := cstore.GetByName("john-pubkey-only") 269 require.NoError(t, err) 270 // Compare the public keys 271 require.True(t, john.GetPubKey().Equals(john2.GetPubKey())) 272 // Ensure the original key hasn't changed 273 john, err = cstore.GetByName("john") 274 require.NoError(t, err) 275 require.Equal(t, john.GetPubKey().Address(), addr) 276 require.Equal(t, john.GetName(), "john") 277 278 // Ensure keys cannot be overwritten 279 err = cstore.ImportPubKey("john-pubkey-only", armor) 280 require.NotNil(t, err) 281 } 282 283 // TestAdvancedKeyManagement verifies update, import, export functionality 284 func TestAdvancedKeyManagement(t *testing.T) { 285 t.Parallel() 286 287 // make the storage with reasonable defaults 288 cstore := NewInMemory() 289 290 n1, n2 := "old-name", "new name" 291 p1, p2 := "1234", "foobar" 292 mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake` 293 bip39Passphrase := "" 294 295 // make sure key works with initial password 296 _, err := cstore.CreateAccount(n1, mn1, bip39Passphrase, p1, 0, 0) 297 require.Nil(t, err, "%+v", err) 298 assertPassword(t, cstore, n1, p1, p2) 299 300 // update password requires the existing password 301 getNewpass := func() (string, error) { return p2, nil } 302 err = cstore.Update(n1, "jkkgkg", getNewpass) 303 require.NotNil(t, err) 304 assertPassword(t, cstore, n1, p1, p2) 305 306 // then it changes the password when correct 307 err = cstore.Update(n1, p1, getNewpass) 308 require.NoError(t, err) 309 // p2 is now the proper one! 310 assertPassword(t, cstore, n1, p2, p1) 311 312 // exporting requires the proper name and passphrase 313 _, err = cstore.Export(n1 + ".notreal") 314 require.NotNil(t, err) 315 _, err = cstore.Export(" " + n1) 316 require.NotNil(t, err) 317 _, err = cstore.Export(n1 + " ") 318 require.NotNil(t, err) 319 _, err = cstore.Export("") 320 require.NotNil(t, err) 321 exported, err := cstore.Export(n1) 322 require.Nil(t, err, "%+v", err) 323 324 // import succeeds 325 err = cstore.Import(n2, exported) 326 require.NoError(t, err) 327 328 // second import fails 329 err = cstore.Import(n2, exported) 330 require.NotNil(t, err) 331 } 332 333 // TestSeedPhrase verifies restoring from a seed phrase 334 func TestSeedPhrase(t *testing.T) { 335 t.Parallel() 336 337 // make the storage with reasonable defaults 338 cstore := NewInMemory() 339 340 n1 := "lost-key" 341 p1 := "1234" 342 mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake` 343 bip39Passphrase := "" 344 345 // make sure key works with initial password 346 info, err := cstore.CreateAccount(n1, mn1, bip39Passphrase, p1, 0, 0) 347 require.Nil(t, err, "%+v", err) 348 require.Equal(t, n1, info.GetName()) 349 350 // now, let us delete this key 351 err = cstore.Delete(n1, p1, false) 352 require.Nil(t, err, "%+v", err) 353 has, err := cstore.HasByName(n1) 354 require.NoError(t, err) 355 require.False(t, has) 356 } 357 358 func ExampleNew() { 359 // Select the encryption and storage for your cryptostore 360 cstore := NewInMemory() 361 362 mn1 := `lounge napkin all odor tilt dove win inject sleep jazz uncover traffic hint require cargo arm rocket round scan bread report squirrel step lake` 363 mn2 := `lecture salt about avocado smooth height escape general arch head barrel clutch dismiss supply doctor project cat truck fruit abuse gorilla symbol portion glare` 364 mn3 := `jar nest rug lion shallow spring abuse west gravity skin project comic again dirt pelican better galaxy click hold lottery swap solution census own` 365 bip39Passphrase := "" 366 367 // Add keys and see they return in alphabetical order 368 bob, err := cstore.CreateAccount("Bob", mn1, bip39Passphrase, "friend", 0, 0) 369 if err != nil { 370 // this should never happen 371 fmt.Println(err) 372 } else { 373 // return info here just like in List 374 fmt.Println(bob.GetName()) 375 } 376 _, _ = cstore.CreateAccount("Alice", mn2, bip39Passphrase, "secret", 0, 0) 377 _, _ = cstore.CreateAccount("Carl", mn3, bip39Passphrase, "mitm", 0, 0) 378 info, _ := cstore.List() 379 for _, i := range info { 380 fmt.Println(i.GetName()) 381 } 382 383 // We need to use passphrase to generate a signature 384 tx := []byte("deadbeef") 385 sig, pub, err := cstore.Sign("Bob", "friend", tx) 386 if err != nil { 387 fmt.Println("don't accept real passphrase") 388 } 389 390 // and we can validate the signature with publicly available info 391 binfo, _ := cstore.GetByName("Bob") 392 if !binfo.GetPubKey().Equals(bob.GetPubKey()) { 393 fmt.Println("Get and Create return different keys") 394 } 395 396 if pub.Equals(binfo.GetPubKey()) { 397 fmt.Println("signed by Bob") 398 } 399 if !pub.VerifyBytes(tx, sig) { 400 fmt.Println("invalid signature") 401 } 402 403 // Output: 404 // Bob 405 // Alice 406 // Bob 407 // Carl 408 // signed by Bob 409 } 410 411 func toAddr(info Info) crypto.Address { 412 return info.GetPubKey().Address() 413 }