github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/environment/account_key_updater.go (about) 1 package environment 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 7 "github.com/onflow/cadence/runtime" 8 "github.com/onflow/cadence/runtime/common" 9 "github.com/onflow/cadence/runtime/sema" 10 11 fgcrypto "github.com/onflow/crypto" 12 fghash "github.com/onflow/crypto/hash" 13 14 "github.com/onflow/flow-go/fvm/crypto" 15 "github.com/onflow/flow-go/fvm/errors" 16 "github.com/onflow/flow-go/fvm/storage/state" 17 "github.com/onflow/flow-go/fvm/tracing" 18 "github.com/onflow/flow-go/model/flow" 19 "github.com/onflow/flow-go/module/trace" 20 ) 21 22 // NewAccountPublicKey construct an account public key given a runtime 23 // public key. 24 func NewAccountPublicKey(publicKey *runtime.PublicKey, 25 hashAlgo sema.HashAlgorithm, 26 keyIndex int, 27 weight int, 28 ) ( 29 *flow.AccountPublicKey, 30 error, 31 ) { 32 33 var err error 34 signAlgorithm := crypto.RuntimeToCryptoSigningAlgorithm(publicKey.SignAlgo) 35 if signAlgorithm != fgcrypto.ECDSAP256 && 36 signAlgorithm != fgcrypto.ECDSASecp256k1 { 37 38 return nil, fmt.Errorf( 39 "adding account key failed: %w", 40 errors.NewValueErrorf( 41 fmt.Sprintf("%d", publicKey.SignAlgo), 42 "signature algorithm type not supported")) 43 } 44 45 hashAlgorithm := crypto.RuntimeToCryptoHashingAlgorithm(hashAlgo) 46 if hashAlgorithm != fghash.SHA2_256 && 47 hashAlgorithm != fghash.SHA3_256 { 48 49 return nil, fmt.Errorf( 50 "adding account key failed: %w", 51 errors.NewValueErrorf( 52 fmt.Sprintf("%d", hashAlgo), 53 "hashing algorithm type not supported")) 54 } 55 56 decodedPublicKey, err := fgcrypto.DecodePublicKey( 57 signAlgorithm, 58 publicKey.PublicKey) 59 if err != nil { 60 return nil, fmt.Errorf( 61 "adding account key failed: %w", 62 errors.NewValueErrorf( 63 hex.EncodeToString(publicKey.PublicKey), 64 "cannot decode public key: %w", 65 err)) 66 } 67 68 return &flow.AccountPublicKey{ 69 Index: keyIndex, 70 PublicKey: decodedPublicKey, 71 SignAlgo: signAlgorithm, 72 HashAlgo: hashAlgorithm, 73 SeqNumber: 0, 74 Weight: weight, 75 Revoked: false, 76 }, nil 77 } 78 79 // AccountKeyUpdater handles all account keys modification. 80 // 81 // Note that scripts cannot modify account keys, but must expose the API in 82 // compliance with the runtime environment interface. 83 type AccountKeyUpdater interface { 84 85 // AddAccountKey adds a public key to an existing account. 86 // 87 // This function returns an error if the specified account does not exist or 88 // if the key insertion fails. 89 // 90 // Note that the script variant will return OperationNotSupportedError. 91 AddAccountKey( 92 runtimeAddress common.Address, 93 publicKey *runtime.PublicKey, 94 hashAlgo runtime.HashAlgorithm, 95 weight int, 96 ) ( 97 *runtime.AccountKey, 98 error, 99 ) 100 101 // RevokeAccountKey revokes a public key by index from an existing account, 102 // and returns the revoked key. 103 // 104 // This function returns a nil key with no errors, if a key doesn't exist 105 // at the given index. An error is returned if the specified account does 106 // not exist, the provided index is not valid, or if the key revoking 107 // fails. 108 // 109 // Note that the script variant will return OperationNotSupportedError. 110 RevokeAccountKey( 111 runtimeAddress common.Address, 112 keyIndex int, 113 ) ( 114 *runtime.AccountKey, 115 error, 116 ) 117 } 118 119 type ParseRestrictedAccountKeyUpdater struct { 120 txnState state.NestedTransactionPreparer 121 impl AccountKeyUpdater 122 } 123 124 func NewParseRestrictedAccountKeyUpdater( 125 txnState state.NestedTransactionPreparer, 126 impl AccountKeyUpdater, 127 ) ParseRestrictedAccountKeyUpdater { 128 return ParseRestrictedAccountKeyUpdater{ 129 txnState: txnState, 130 impl: impl, 131 } 132 } 133 134 func (updater ParseRestrictedAccountKeyUpdater) AddAccountKey( 135 runtimeAddress common.Address, 136 publicKey *runtime.PublicKey, 137 hashAlgo runtime.HashAlgorithm, 138 weight int, 139 ) ( 140 *runtime.AccountKey, 141 error, 142 ) { 143 return parseRestrict4Arg1Ret( 144 updater.txnState, 145 trace.FVMEnvAddAccountKey, 146 updater.impl.AddAccountKey, 147 runtimeAddress, 148 publicKey, 149 hashAlgo, 150 weight) 151 } 152 153 func (updater ParseRestrictedAccountKeyUpdater) RevokeAccountKey( 154 runtimeAddress common.Address, 155 keyIndex int, 156 ) ( 157 *runtime.AccountKey, 158 error, 159 ) { 160 return parseRestrict2Arg1Ret( 161 updater.txnState, 162 trace.FVMEnvRevokeAccountKey, 163 updater.impl.RevokeAccountKey, 164 runtimeAddress, 165 keyIndex) 166 } 167 168 type NoAccountKeyUpdater struct{} 169 170 func (NoAccountKeyUpdater) AddAccountKey( 171 runtimeAddress common.Address, 172 publicKey *runtime.PublicKey, 173 hashAlgo runtime.HashAlgorithm, 174 weight int, 175 ) ( 176 *runtime.AccountKey, 177 error, 178 ) { 179 return nil, errors.NewOperationNotSupportedError("AddAccountKey") 180 } 181 182 func (NoAccountKeyUpdater) RevokeAccountKey( 183 runtimeAddress common.Address, 184 keyIndex int, 185 ) ( 186 *runtime.AccountKey, 187 error, 188 ) { 189 return nil, errors.NewOperationNotSupportedError("RevokeAccountKey") 190 } 191 192 type accountKeyUpdater struct { 193 tracer tracing.TracerSpan 194 meter Meter 195 196 accounts Accounts 197 txnState state.NestedTransactionPreparer 198 env Environment 199 } 200 201 func NewAccountKeyUpdater( 202 tracer tracing.TracerSpan, 203 meter Meter, 204 accounts Accounts, 205 txnState state.NestedTransactionPreparer, 206 env Environment, 207 ) *accountKeyUpdater { 208 return &accountKeyUpdater{ 209 tracer: tracer, 210 meter: meter, 211 accounts: accounts, 212 txnState: txnState, 213 env: env, 214 } 215 } 216 217 // AddAccountKey adds a public key to an existing account. 218 // 219 // This function returns an error if the specified account does not exist or 220 // if the key insertion fails. 221 func (updater *accountKeyUpdater) addAccountKey( 222 address flow.Address, 223 publicKey *runtime.PublicKey, 224 hashAlgo runtime.HashAlgorithm, 225 weight int, 226 ) ( 227 *runtime.AccountKey, 228 error, 229 ) { 230 ok, err := updater.accounts.Exists(address) 231 if err != nil { 232 return nil, fmt.Errorf("adding account key failed: %w", err) 233 } 234 if !ok { 235 return nil, fmt.Errorf( 236 "adding account key failed: %w", 237 errors.NewAccountNotFoundError(address)) 238 } 239 240 keyIndex, err := updater.accounts.GetPublicKeyCount(address) 241 if err != nil { 242 return nil, fmt.Errorf("adding account key failed: %w", err) 243 } 244 245 accountPublicKey, err := NewAccountPublicKey( 246 publicKey, 247 hashAlgo, 248 int(keyIndex), 249 weight) 250 if err != nil { 251 return nil, fmt.Errorf("adding account key failed: %w", err) 252 } 253 254 err = updater.accounts.AppendPublicKey(address, *accountPublicKey) 255 if err != nil { 256 return nil, fmt.Errorf("adding account key failed: %w", err) 257 } 258 259 return &runtime.AccountKey{ 260 KeyIndex: accountPublicKey.Index, 261 PublicKey: publicKey, 262 HashAlgo: hashAlgo, 263 Weight: accountPublicKey.Weight, 264 IsRevoked: accountPublicKey.Revoked, 265 }, nil 266 } 267 268 // RevokeAccountKey revokes a public key by index from an existing account, 269 // and returns the revoked key. 270 // 271 // This function returns a nil key with no errors, if a key doesn't exist at 272 // the given index. An error is returned if the specified account does not 273 // exist, the provided index is not valid, or if the key revoking fails. 274 // 275 // TODO (ramtin) do we have to return runtime.AccountKey for this method or 276 // can be separated into another method 277 func (updater *accountKeyUpdater) revokeAccountKey( 278 address flow.Address, 279 keyIndex int, 280 ) ( 281 *runtime.AccountKey, 282 error, 283 ) { 284 ok, err := updater.accounts.Exists(address) 285 if err != nil { 286 return nil, fmt.Errorf("revoking account key failed: %w", err) 287 } 288 289 if !ok { 290 return nil, fmt.Errorf( 291 "revoking account key failed: %w", 292 errors.NewAccountNotFoundError(address)) 293 } 294 295 // Don't return an error for invalid key indices 296 if keyIndex < 0 { 297 return nil, nil 298 } 299 300 var publicKey flow.AccountPublicKey 301 publicKey, err = updater.accounts.GetPublicKey( 302 address, 303 uint64(keyIndex)) 304 if err != nil { 305 // If a key is not found at a given index, then return a nil key with 306 // no errors. This is to be inline with the Cadence runtime. Otherwise 307 // Cadence runtime cannot distinguish between a 'key not found error' 308 // vs other internal errors. 309 if errors.IsAccountPublicKeyNotFoundError(err) { 310 return nil, nil 311 } 312 return nil, fmt.Errorf("revoking account key failed: %w", err) 313 } 314 315 // mark this key as revoked 316 publicKey.Revoked = true 317 318 _, err = updater.accounts.SetPublicKey( 319 address, 320 uint64(keyIndex), 321 publicKey) 322 if err != nil { 323 return nil, fmt.Errorf("revoking account key failed: %w", err) 324 } 325 326 // Prepare account key to return 327 signAlgo := crypto.CryptoToRuntimeSigningAlgorithm(publicKey.SignAlgo) 328 if signAlgo == runtime.SignatureAlgorithmUnknown { 329 return nil, fmt.Errorf( 330 "revoking account key failed: %w", 331 errors.NewValueErrorf( 332 publicKey.SignAlgo.String(), 333 "signature algorithm type not found")) 334 } 335 336 hashAlgo := crypto.CryptoToRuntimeHashingAlgorithm(publicKey.HashAlgo) 337 if hashAlgo == runtime.HashAlgorithmUnknown { 338 return nil, fmt.Errorf( 339 "revoking account key failed: %w", 340 errors.NewValueErrorf( 341 publicKey.HashAlgo.String(), 342 "hashing algorithm type not found")) 343 } 344 345 return &runtime.AccountKey{ 346 KeyIndex: publicKey.Index, 347 PublicKey: &runtime.PublicKey{ 348 PublicKey: publicKey.PublicKey.Encode(), 349 SignAlgo: signAlgo, 350 }, 351 HashAlgo: hashAlgo, 352 Weight: publicKey.Weight, 353 IsRevoked: publicKey.Revoked, 354 }, nil 355 } 356 357 func (updater *accountKeyUpdater) AddAccountKey( 358 runtimeAddress common.Address, 359 publicKey *runtime.PublicKey, 360 hashAlgo runtime.HashAlgorithm, 361 weight int, 362 ) ( 363 *runtime.AccountKey, 364 error, 365 ) { 366 defer updater.tracer.StartChildSpan(trace.FVMEnvAddAccountKey).End() 367 368 err := updater.meter.MeterComputation( 369 ComputationKindAddAccountKey, 370 1) 371 if err != nil { 372 return nil, fmt.Errorf("add account key failed: %w", err) 373 } 374 375 accKey, err := updater.addAccountKey( 376 flow.ConvertAddress(runtimeAddress), 377 publicKey, 378 hashAlgo, 379 weight) 380 if err != nil { 381 return nil, fmt.Errorf("add account key failed: %w", err) 382 } 383 384 return accKey, nil 385 } 386 387 func (updater *accountKeyUpdater) RevokeAccountKey( 388 runtimeAddress common.Address, 389 keyIndex int, 390 ) ( 391 *runtime.AccountKey, 392 error, 393 ) { 394 defer updater.tracer.StartChildSpan(trace.FVMEnvRevokeAccountKey).End() 395 396 err := updater.meter.MeterComputation( 397 ComputationKindRevokeAccountKey, 398 1) 399 if err != nil { 400 return nil, fmt.Errorf("revoke account key failed: %w", err) 401 } 402 403 return updater.revokeAccountKey( 404 flow.ConvertAddress(runtimeAddress), 405 keyIndex) 406 }