/
lip-0041.md
619 lines (513 loc) Β· 27.5 KB
/
lip-0041.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
```
LIP: 0041
Title: Introduce Auth module
Author: Alessandro Ricottone <alessandro.ricottone@lightcurve.io>
Ishan Tiwari <ishan.tiwari@lightcurve.io>
Discussions-To: https://research.lisk.com/t/introduce-auth-module
Status: Draft
Type: Standards Track
Created: 2021-07-27
Updated: 2023-01-17
Requires: 0040
```
## Abstract
The Auth module is responsible for handling and verifying nonces and for transaction signature validation, including transactions from multisignature accounts. In this LIP, we specify the properties of the Auth module, along with their serialization and default values. Furthermore, we specify the state transitions logic defined within this module, i.e. the commands, the protocol logic injected during the block lifecycle, and the functions that can be called from other modules or off-chain services.
## Copyright
This LIP is licensed under the [Creative Commons Zero 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/).
## Motivation
In the Lisk protocol, some verification steps are common to all transactions. In particular, to validate a transaction it is necessary to validate the [signatures](https://lisk.com/documentation/lisk-sdk/protocol/accounts.html#keys) and the [nonce](https://lisk.com/documentation/lisk-sdk/protocol/accounts.html#nonce). These validation steps are handled by the authentication (Auth) module. The Auth module supersedes the Keys and Sequence modules.
In this LIP we specify the properties, serialization, and default values of the Auth module, as well as the protocol logic processed during a block lifecycle, the commands, and the functions exposed to other modules and to off-chain services.
## Rationale
Validating a transaction requires reading the state to get the nonce and possibly the multisignature keys from the user account. Since both these checks are necessarily done together, it makes sense to store these values together, to reduce the number of database accesses, rather than in two separate modules.
## Specification
In this section, we specify the substores that are part of the Auth module store, the commands, and the protocol logic called during the block lifecycle. The Auth module has module name `MODULE_NAME_AUTH` (see the [table below](#constants)).
### Constants
We define the following constants:
| Name | Type | Value | Description |
|------|------| ------|-------------|
| `MODULE_NAME_AUTH` | string | "auth" | Name of the Auth module. |
| `SUBSTORE_PREFIX_AUTH` | bytes | 0x0000 | Substore prefix of the auth data substore. |
| `COMMAND_REGISTER_MULTISIGNATURE` | string | "registerMultisignature" | Name of the register multisignature command. |
| `EVENT_NAME_MULTISIGNATURE_REGISTERED` | string | "multisignatureRegistration" | Name of the signature registered event. |
| `EVENT_NAME_INVALID_SIGNATURE` | string | "invalidSignature" | Name of the invalid signature event. |
| `MESSAGE_TAG_MULTISIG_REG` | bytes | "LSK_RMSG_" as ASCII-encoded literal | Message tag for the signatures contained in the register multisignature command (see [LIP 0037](https://github.com/LiskHQ/lips/blob/main/proposals/lip-0037.md#message-tags)). |
| `MESSAGE_TAG_TRANSACTION` | bytes | "LSK_TX_" as ASCII-encoded literal | Message tag for transaction signatures (see [LIP 0037](https://github.com/LiskHQ/lips/blob/main/proposals/lip-0037.md#message-tags)). |
| `MAX_NUMBER_OF_SIGNATURES` | uint32 | 64 | Max number of signatures for a multisignature. |
| `ADDRESS_LENGTH` | uint32 | 20 | Length in bytes of type `Address`. |
| `ED25519_PUBLIC_KEY_LENGTH` | uint32 | 32 | Length in bytes of type `PublicKeyEd25519`. |
| `ED25519_SIGNATURE_LENGTH` | uint32 | 64 | Length in bytes of type `SignatureEd25519`. |
### Type Definition
| Name | Type | Validation | Description |
|------|------|------------|-------------|
| `Address` | bytes | Must be of length `ADDRESS_LENGTH`. | Address of an account. |
| `PublicKeyEd25519` | bytes | Must be of length `ED25519_PUBLIC_KEY_LENGTH`. | Used for Ed25519 public keys. |
| `SignatureEd25519` | bytes | Must be of length `ED25519_SIGNATURE_LENGTH`. | Used for Ed25519 signatures. |
| `AuthAccount` | object | Must follow the `authAccountSchema` schema. | An object representing a auth account. |
### Auth Module Store
The key-value pairs in the module store are organized in the following substore.
#### Auth Data Substore
##### Substore Prefix, Store Key, and Store Value
* The substore prefix is set to `SUBSTORE_PREFIX_AUTH`.
* Store keys are set to `ADDRESS_LENGTH` bytes addresses, representing a user address.
* Store values are set to _auth account_ data structures, holding the properties indicated [below](#properties-and-default-values), serialized using the JSON schema `authAccountSchema`, presented [below](#json-schema).
* Notation: For the rest of this proposal let `authAccount(address)` be an entry in the auth data substore identified by the store key `address`.
##### JSON Schema
```java
authAccountSchema = {
"type": "object",
"required": ["nonce", "numberOfSignatures", "mandatoryKeys", "optionalKeys"],
"properties": {
"nonce": {
"dataType": "uint64",
"fieldNumber": 1
},
"numberOfSignatures": {
"dataType": "uint32",
"fieldNumber": 2
},
"mandatoryKeys": {
"type": "array",
"fieldNumber": 3,
"items": {
"dataType": "bytes",
"length" : ED25519_PUBLIC_KEY_LENGTH
}
},
"optionalKeys": {
"type": "array",
"fieldNumber": 4,
"items": {
"dataType": "bytes",
"length" : ED25519_PUBLIC_KEY_LENGTH
}
}
}
}
```
##### Properties and Default values
In this section, we describe the properties of an auth account and specify their default values.
* <code>[nonce](https://lisk.com/documentation/lisk-sdk/protocol/accounts.html#nonce)</code>: The nonce represents the total number of transactions sent from the account. Each time a new transaction is sent, the nonce is incremented by one. The default value of this property is 0.
* <code>[numberOfSignatures](https://github.com/LiskHQ/lips/blob/main/proposals/lip-0017.md)</code>: The number of private keys that must sign a transaction. This value is greater than 0 if and only if a register multisignature transaction for this account was included. The default value of this property is 0.
* <code>[mandatoryKeys](https://github.com/LiskHQ/lips/blob/main/proposals/lip-0017.md)</code>: An array of public keys in lexicographical order. The corresponding private keys have to necessarily sign the transaction. A valid public key is `ED25519_PUBLIC_KEY_LENGTH` bytes long. The default value of this property is an empty array.
* <code>[optionalKeys](https://github.com/LiskHQ/lips/blob/main/proposals/lip-0017.md)</code>: An array of public keys in lexicographical order. The corresponding private keys can optionally sign the transaction. The number of corresponding private keys that have to sign the transaction equals <code>numberOfSignatures</code> minus the length of <code>mandatoryKeys</code>. A valid public key is `ED25519_PUBLIC_KEY_LENGTH` bytes long. The default value of this property is an empty array.
### Events
#### MultisignatureRegistered
This event has `name = EVENT_NAME_MULTISIGNATURE_REGISTERED`. This event is emitted when a multisignature is registered.
##### Topics
* `address`: The address for which the multisignature has been registered.
##### Data
```java
multisigRegDataSchema = {
"type": "object",
"required": ["numberOfSignatures", "mandatoryKeys", "optionalKeys"],
"properties": {
"numberOfSignatures": {
"dataType": "uint32",
"fieldNumber": 1
},
"mandatoryKeys": {
"type": "array",
"items": {
"dataType": "bytes",
"length" : ED25519_PUBLIC_KEY_LENGTH
},
"fieldNumber": 2
},
"optionalKeys": {
"type": "array",
"items": {
"dataType": "bytes",
"length" : ED25519_PUBLIC_KEY_LENGTH
},
"fieldNumber": 3
}
}
}
```
#### InvalidSignature
This event has `name = EVENT_NAME_INVALID_SIGNATURE`. This event is emitted when the signature validation of a multisignature fails.
##### Topics
* `address`: The address for which the multisignature has been registered.
##### Data
```java
invalidSigDataSchema = {
"type": "object",
"required": ["numberOfSignatures", "mandatoryKeys", "optionalKeys", "failingPublicKey", "failingSignature"],
"properties": {
"numberOfSignatures": {
"dataType": "uint32",
"fieldNumber": 1
},
"mandatoryKeys": {
"type": "array",
"items": {
"dataType": "bytes",
"length" : ED25519_PUBLIC_KEY_LENGTH
},
"fieldNumber": 2
},
"optionalKeys": {
"type": "array",
"items": {
"dataType": "bytes",
"length" : ED25519_PUBLIC_KEY_LENGTH
},
"fieldNumber": 3
},
"failingPublicKey": {
"dataType": "bytes",
"length" : ED25519_PUBLIC_KEY_LENGTH,
"fieldNumber": 4
},
"failingSignature": {
"dataType": "bytes",
"length" : ED25519_SIGNATURE_LENGTH,
"fieldNumber": 5
}
}
}
```
### Commands
#### RegisterMultisignature
This command allows users to register a multisignature for the sender account. These specifications supersede those given in [LIP 0017](https://github.com/LiskHQ/lips/blob/main/proposals/lip-0017.md#multisignature-account-registration). The function `verifyEd25519` is defined in [LIP 0062](https://github.com/LiskHQ/lips/blob/main/proposals/lip-0062.md#specification) and `chainID` is the chain identifier of the chain.
Transactions executing this command have:
* `module = MODULE_NAME_AUTH`,
* `command = COMMAND_REGISTER_MULTISIGNATURE`.
##### Parameters
The `params` property of a multisignature registration transaction must obey the following schema.
```java
multisigRegParamsSchema = {
"type": "object",
"required": ["numberOfSignatures", "mandatoryKeys", "optionalKeys", "signatures"],
"properties": {
"numberOfSignatures": {
"dataType": "uint32",
"fieldNumber": 1
},
"mandatoryKeys": {
"type": "array",
"items": {
"dataType": "bytes",
"length" : ED25519_PUBLIC_KEY_LENGTH
},
"fieldNumber": 2
},
"optionalKeys": {
"type": "array",
"items": {
"dataType": "bytes",
"length" : ED25519_PUBLIC_KEY_LENGTH
},
"fieldNumber": 3
},
"signatures": {
"type": "array",
"items": {
"dataType": "bytes",
"length" : ED25519_SIGNATURE_LENGTH
},
"fieldNumber": 4
}
}
}
```
The schema `multisigRegParamsSchema` contains 4 properties. This schema supersedes the one defined in [LIP 0028](https://github.com/LiskHQ/lips/blob/main/proposals/lip-0028.md#multisignature-registration).
1. `numberOfSignatures`: The number of private keys that must sign a transaction.
2. `mandatoryKeys`: An array of public keys. A valid public key is `ED25519_PUBLIC_KEY_LENGTH` bytes long. Once the account is registered as a multisignature account, every outgoing transaction requires a signature for every public key in `mandatoryKeys`.
3. `optionalKeys`: An array of public keys. A valid public key is `ED25519_PUBLIC_KEY_LENGTH` bytes long. Once the account is registered as a multisignature account, every outgoing transaction requires some signatures for some public keys in `optionalKeys` (the number of needed signatures depends on the `numberOfSignatures` property and may also be zero).
4. `signatures`: An array of signatures, corresponding to the public keys contained in `mandatoryKeys` and `optionalKeys`. All public keys must have a corresponding signature. The signatures corresponding to `mandatoryKeys` are appended first, followed by those corresponding to `optionalKeys`, where the order of signatures is the same as the order of public keys in the respective array. The message that is signed contains the parameters of the transaction, serialized using the `multisigRegMsgSchema` schema given below.
```java
multisigRegMsgSchema = {
"type": "object",
"required": ["address", "nonce", "numberOfSignatures", "mandatoryKeys", "optionalKeys"],
"properties": {
"address": {
"dataType": "bytes",
"length" : ADDRESS_LENGTH
"fieldNumber": 1
},
"nonce": {
"dataType": "uint64",
"fieldNumber": 2
},
"numberOfSignatures": {
"dataType": "uint32",
"fieldNumber": 3
},
"mandatoryKeys": {
"type": "array",
"items": {
"dataType": "bytes",
"length" : ED25519_PUBLIC_KEY_LENGTH
},
"fieldNumber": 4
},
"optionalKeys": {
"type": "array",
"items": {
"dataType": "bytes",
"length" : ED25519_PUBLIC_KEY_LENGTH
},
"fieldNumber": 5
}
}
}
```
##### Verification
```python
def verify(trs: Transaction) -> None:
# Mandatory keys are pairwise distinct and ordered lexicographically. The array can be empty.
if trs.params.mandatoryKeys != sorted(set(trs.params.mandatoryKeys)):
raise Exception('Invalid mandatoryKeys property.')
# Optional keys are pairwise distinct and ordered lexicographically. The array can be empty.
if trs.params.optionalKeys != sorted(set(trs.params.optionalKeys)):
raise Exception('Invalid optionalKeys property.')
# Mandatory and optional keys must be disjoint.
allKeys = trs.params.mandatoryKeys + trs.params.optionalKeys
if len(allKeys) != len(set(allKeys)):
raise Exception('mandatoryKeys and optionalKeys properties are not disjoint arrays.')
# Union of mandatory and optional keys must contain between 1 and MAX_NUMBER_OF_SIGNATURES keys (included).
if len(allKeys) < 1 or len(allKeys) > MAX_NUMBER_OF_SIGNATURES:
raise Exception('Invalid length of mandatoryKeys and optionalKeys properties.')
# Number of signatures must be an integer between 1 and the total number of public keys.
if trs.params.numberOfSignatures < 1 or trs.params.numberOfSignatures > len(allKeys):
raise Exception('Invalid numberOfSignatures property.')
# Number of signatures must not be smaller than the number of mandatory keys.
if trs.params.numberOfSignatures < len(trs.params.mandatoryKeys):
raise Exception('Invalid numberOfSignatures property.')
# Each key must have a corresponding signature.
if len(trs.params.signatures) != len(allKeys):
raise Exception('Invalid length of signatures property.')
```
##### Execution
```python
def execute(trs: Transaction) -> None:
# Verify the signatures.
message = encode(
schema = multisigRegMsgSchema,
object = {
"address": senderAddress,
"nonce": trs.nonce,
"numberOfSignatures": trs.params.numberOfSignatures,
"mandatoryKeys": trs.params.mandatoryKeys,
"optionalKeys": trs.params.optionalKeys
}
)
allKeys = trs.params.mandatoryKeys + trs.params.optionalKeys
for signature, key in zip(trs.params.signatures, allKeys):
if not verifyEd25519(key, MESSAGE_TAG_MULTISIG_REG, chainID, message, signature):
emitPersistentEvent(
module = MODULE_NAME_AUTH,
name = EVENT_NAME_INVALID_SIGNATURE,
data = {
"numberOfSignatures": trs.params.numberOfSignatures,
"mandatoryKeys": trs.params.mandatoryKeys,
"optionalKeys": trs.params.optionalKeys,
"failingPublicKey": key,
"failingSignature": signature
},
topics=[senderAddress]
)
raise Exception(f'Invalid signature for public key {key.hex()}.')
senderAddress = Address derived from trs.senderPublicKey
authAccount(senderAddress).numberOfSignatures = trs.params.numberOfSignatures
authAccount(senderAddress).mandatoryKeys = trs.params.mandatoryKeys
authAccount(senderAddress).optionalKeys = trs.params.optionalKeys
emitEvent(
module = MODULE_NAME_AUTH,
name = EVENT_NAME_MULTISIGNATURE_REGISTERED,
data = {
"numberOfSignatures": trs.params.numberOfSignatures,
"mandatoryKeys": trs.params.mandatoryKeys,
"optionalKeys": trs.params.optionalKeys
},
topics=[senderAddress]
)
```
### Internal Functions
The Auth module has the following internal function.
#### isMultisignatureAccount
This function checks whether the account corresponding to the input address has registered a multisignature.
```python
def isMultisignatureAccount(address: Address) -> bool:
if there is no entry in the auth data substore with storeKey == address:
return False
if authAccount(senderAddress).numberOfSignatures == 0:
return False
return True
```
### Protocol Logic for Other Modules
#### getAuthAccount
This function is used to retrieve information about the auth account corresponding to the input `address`. It returns `authAccount(address)`. If there is no entry corresponding to `address`, it throws an error.
```python
def getAuthAccount(address: Address) -> AuthAccount:
if no entry in the auth data substore exist with storeKey == address:
raise Exception('No auth account found for the input address.')
return authAccount(address)
```
### Endpoints for Off-Chain Services
#### getAuthAccount
This function works exactly as the function `getAuthAccount` defined [above](#getauthaccount).
#### isValidSignature
This function verifies the signatures of a given transaction `trs`, including transactions from multisignature accounts. It returns `true` if the transaction object contains a valid signature, `false` otherwise. This is done using the `verifySignatures(trs)` function defined [below](#transaction-verification).
```python
def isValidSignature(trs: Transaction) -> bool:
try:
verifySignatures(trs)
return True
except:
return False
```
#### isValidNonce
This function verifies the nonce of a given transaction `trs` and returns a boolean value. It returns `true` if the transaction object contains a valid nonce, `false` otherwise. This is done using the `verifyNonce(trs)` function defined [below](#transaction-verification).
```python
def isValidNonce(trs: Transaction) -> bool:
try:
verifyNonce(trs)
return True
except:
return False
```
### Genesis Block Processing
#### Genesis State Initialization
During the genesis state initialization stage, the following steps are executed. If any step fails, the block is discarded and has no further effect.
Let `genesisBlockAssetBytes` be the `data` bytes included in the block assets for the Auth module and let `genesisBlockAssetObject` be the deserialization of `genesisBlockAssetBytes` according to the `genesisAuthStoreSchema` schema, given below.
* For each entry `entry` in `genesisBlockAssetObject.authDataSubstore`, let `address = entry.address` and `authAccount = entry.authAccount` and check the following:
* Each entry has a different `address` and all `address` have length `ADDRESS_LENGTH`.
* If `authAccount.numberOfSignatures` is not 0 (a multisignature account), check that the rules listed in [LIP 0017](https://github.com/LiskHQ/lips/blob/main/proposals/lip-0017.md#multisignature-account-registration) are respected. In particular:
* The keys in the `authAccount.mandatoryKeys` array have length `ED25519_PUBLIC_KEY_LENGTH`, are pairwise distinct and in lexicographical order. The array may be empty.
* The keys in the `authAccount.optionalKeys` array have length `ED25519_PUBLIC_KEY_LENGTH`, are pairwise distinct and in lexicographical order. The array may be empty.
* The arrays `authAccount.mandatoryKeys` and `authAccount.optionalKeys` are disjoint; their union has at least one element and at most `MAX_NUMBER_OF_SIGNATURES` elements.
* `authAccount.numberOfSignatures` is an integer between 1 and the total number of public keys of the account (sum of the length of `authAccount.mandatoryKeys` and `authAccount.optionalKeys`).
* `authAccount.numberOfSignatures` is not smaller than the length of `authAccount.mandatoryKeys`.
* Finally, for each entry `entry` in `genesisBlockAssetObject.authDataSubstore`, create a corresponding entry in the auth data substore with `storeKey = entry.address` and `storeValue = entry.authAccount`.
```java
genesisAuthStoreSchema = {
"type": "object",
"required": ["authDataSubstore"],
"properties": {
"authDataSubstore": {
"type": "array",
"fieldNumber": 1,
"items": {
"type": "object",
"required": ["address", "authAccount"],
"properties": {
"address": {
"dataType": "bytes",
"length": ADDRESS_LENGTH,
"fieldNumber": 1
},
"authAccount": {
"type": "object",
"fieldNumber": 2,
"required": [
"nonce",
"numberOfSignatures",
"mandatoryKeys",
"optionalKeys"
],
"properties": {
"nonce": {
"dataType": "uint64",
"fieldNumber": 1
},
"numberOfSignatures": {
"dataType": "uint32",
"fieldNumber": 2
},
"mandatoryKeys": {
"type": "array",
"fieldNumber": 3,
"items": {
"dataType": "bytes",
"length": ED25519_PUBLIC_KEY_LENGTH
}
},
"optionalKeys": {
"type": "array",
"fieldNumber": 4,
"items": {
"dataType": "bytes",
"length": ED25519_PUBLIC_KEY_LENGTH,
}
}
}
}
}
}
}
}
}
```
#### Genesis State Finalization
The Auth module does not execute any logic during the genesis state finalization.
### Block Processing
#### Transaction Verification
As part of the verification of a transaction `trs`, the `nonce` and `signatures` properties are verified as follows.
```python
def verifyTransaction(trs: Transaction) -> None:
verifyNonce(trs)
verifySignatures(trs)
```
We define the following auxiliary function to verify the nonce of a transaction. Notice that a transaction in the transaction pool with a nonce greater than the account nonce is considered "pending" rather than invalid, since it could become valid in the future.
```python
def verifyNonce(trs: Transaction) -> None:
senderAddress = Address derived from trs.senderPublicKey
if there is no entry in the auth data substore with storeKey == senderAddress:
expectedNonce = 0
else:
expectedNonce = authAccount(senderAddress).nonce
if trs.nonce != expectedNonce:
raise Exception(f'Invalid nonce property. Expected {expectedNonce}, got {trs.nonce}.')
```
We define the following auxiliary function to verify the validity of a signature. These specifications supersede those given in [LIP 0017](https://github.com/LiskHQ/lips/blob/main/proposals/lip-0017.md#signatures-replaces-signature). The function `verifyEd25519` is defined in [LIP 0062](https://github.com/LiskHQ/lips/blob/main/proposals/lip-0062.md#specification) and `chainID` is the chain identifier of the chain.
```python
def verifySignatures(trs: Transaction) -> None:
# Remove signatures from transaction before serialization.
unsignedTrs = trs
unsignedTrs.signatures = []
unsignedTrsBytes = encode(
schema = transactionSchema,
object = unsignedTrs
)
# Check if the account is a multisignature account.
senderAddress = Address derived from trs.senderPublicKey
# Multisignature account case.
if isMultisignatureAccount(senderAddress):
allKeys = authAccount(senderAddress).mandatoryKeys + authAccount(senderAddress).optionalKeys
if len(trs.signatures) != len(allKeys):
raise Exception(f'Transactions from this multisignature account should have exactly {len(allKeys)} signatures. Found {len(trs.signatures)} signatures.')
nonEmptySignatures = [sig for sig in trs.signatures if len(sig) > 0]
if len(nonEmptySignatures) != authAccount(senderAddress).numberOfSignatures:
raise Exception(f'Transaction signatures does not match required number of signatures: {authAccount(senderAddress).numberOfSignatures}')
numberOfMandatoryKeys = len(authAccount(senderAddress).mandatoryKeys)
for idx in range(numberOfMandatoryKeys):
key = authAccount(senderAddress).mandatoryKeys[idx]
if not verifyEd25519(key, MESSAGE_TAG_TRANSACTION, chainID, unsignedTrsBytes, trs.signatures[idx]):
raise Exception(f'Invalid signature for public key {key.hex()}.')
for idx in range(numberOfMandatoryKeys, len(allKeys)):
# Skip empty signature.
if len(trs.signatures[idx]) == 0:
continue
key = authAccount(senderAddress).optionalKeys[idx - numberOfMandatoryKeys]
if not verifyEd25519(key, MESSAGE_TAG_TRANSACTION, chainID, unsignedTrsBytes, trs.signatures[idx]):
raise Exception(f'Invalid signature for public key {key.hex()}.')
# Single signature account case.
else:
if len(trs.signatures) != 1:
raise Exception(f'Transactions from a single signature account should have exactly one signature. Found {len(trs.signatures)} signatures.')
if not verifyEd25519(trs.senderPublicKey, MESSAGE_TAG_TRANSACTION, chainID, unsignedTrsBytes, trs.signatures[0]):
raise Exception(f'Invalid signature for public key {key.hex()}.')
```
#### Before Command Execution
Before the command referenced in a transaction `trs` is executed, an auth account is created if no account exists for the sender address.
```python
def beforeCommandExecution(trs: Transaction) -> None:
senderAddress = Address derived from trs.senderPublicKey
if there is no entry in the auth data substore with storeKey == senderAddress:
create an entry in the auth data substore with storeKey = senderAddress, initialized to default values
authAccount(senderAddress).nonce += 1
```
## Backwards Compatibility
This LIP defines a new storage interface for the Auth module, which in turn will become part of the state tree and will be authenticated by the state root. An existing chain including the Auth module will need to perform a hardfork.